Merge branch 'main' into signing-volume
38
.github/workflows/translations-extract.yml
vendored
@ -1,38 +0,0 @@
|
|||||||
# Extract and compile translations for all PRs.
|
|
||||||
|
|
||||||
name: 'Extract and compile translations'
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
pull_request:
|
|
||||||
branches: ['main']
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
extract_translations:
|
|
||||||
name: Extract and compile translations
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
|
||||||
|
|
||||||
- uses: ./.github/actions/node-install
|
|
||||||
|
|
||||||
- name: Extract and compile translations
|
|
||||||
run: |
|
|
||||||
npm run translate:extract
|
|
||||||
npm run translate:compile
|
|
||||||
|
|
||||||
- name: Check and commit any files created
|
|
||||||
run: |
|
|
||||||
git config --global user.name 'github-actions'
|
|
||||||
git config --global user.email 'github-actions@documenso.com'
|
|
||||||
git add packages/lib/translations
|
|
||||||
git diff --staged --quiet --exit-code || (git commit -m "chore: extract translations" && git push)
|
|
||||||
6
.github/workflows/translations-upload.yml
vendored
@ -25,10 +25,8 @@ jobs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/node-install
|
- uses: ./.github/actions/node-install
|
||||||
|
|
||||||
- name: Extract and compile translations
|
- name: Extract translations
|
||||||
run: |
|
run: npm run translate:extract
|
||||||
npm run translate:extract
|
|
||||||
npm run translate:compile
|
|
||||||
|
|
||||||
- name: Check and commit any files created
|
- name: Check and commit any files created
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@ -13,9 +13,4 @@ node "$MONOREPO_ROOT/scripts/copy-wellknown.cjs"
|
|||||||
git add "$MONOREPO_ROOT/apps/web/public/"
|
git add "$MONOREPO_ROOT/apps/web/public/"
|
||||||
git add "$MONOREPO_ROOT/apps/marketing/public/"
|
git add "$MONOREPO_ROOT/apps/marketing/public/"
|
||||||
|
|
||||||
echo "Extract and compile translations"
|
|
||||||
npm run translate:extract
|
|
||||||
npm run translate:compile
|
|
||||||
git add "$MONOREPO_ROOT/packages/lib/translations/"
|
|
||||||
|
|
||||||
npx lint-staged
|
npx lint-staged
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"index": "Getting Started",
|
||||||
|
"contributing-translations": "Contributing Translations"
|
||||||
|
}
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
title: Contributing Translations
|
||||||
|
description: Learn how to contribute translations to Documenso and become part of our community.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Callout, Steps } from 'nextra/components';
|
||||||
|
|
||||||
|
# Contributing Translations
|
||||||
|
|
||||||
|
We are always open for help with translations! Currently we utilise AI to generate the initial translations for new languages, which are then improved over time by our awesome community.
|
||||||
|
|
||||||
|
If you are looking for development notes on translations, you can find them [here](/developers/local-development/translations).
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
Contributions are made through GitHub Pull Requests, so you will need a GitHub account to
|
||||||
|
contribute.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
We store our translations in PO files, which are located in our GitHub repository [here](https://github.com/documenso/documenso/tree/main/packages/lib/translations).
|
||||||
|
|
||||||
|
The translation files are organized into folders represented by their respective language codes (`en` for English, `de` for German, etc). Each language folder contains three PO files:
|
||||||
|
|
||||||
|
1. `web.po`: Translations for the web application
|
||||||
|
2. `marketing.po`: Translations for the marketing application
|
||||||
|
3. `common.po`: Shared translations between web and marketing
|
||||||
|
|
||||||
|
Each PO file contains translations which look like this:
|
||||||
|
|
||||||
|
```po
|
||||||
|
#: apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx:61
|
||||||
|
msgid "Want to send slick signing links like this one? <0>Check out Documenso.</0>"
|
||||||
|
msgstr "Möchten Sie auffällige Signatur-Links wie diesen senden? <0>Überprüfen Sie Documenso.</0>"
|
||||||
|
```
|
||||||
|
|
||||||
|
- `msgid`: The original text in English (never edit this manually)
|
||||||
|
- `msgstr`: The translated text in the target language
|
||||||
|
|
||||||
|
<Callout type="warning">
|
||||||
|
Notice the `<0>` tags? These represent HTML elements and must remain in both the `msgid` and `msgstr`. Make sure to translate the content between these tags while keeping the tags intact.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
## How to Contribute
|
||||||
|
|
||||||
|
### Updating Existing Translations
|
||||||
|
|
||||||
|
1. Fork the repository.
|
||||||
|
2. Navigate to the appropriate language folder.
|
||||||
|
3. Open the PO file you want to update (web.po, marketing.po, or common.po).
|
||||||
|
4. Make your changes, ensuring you follow the PO file format.
|
||||||
|
5. Commit your changes with a message such as `chore: update German translations`
|
||||||
|
6. Create a Pull Request.
|
||||||
|
|
||||||
|
### Adding a New Language
|
||||||
|
|
||||||
|
If you want to add translations for a language that doesn't exist yet:
|
||||||
|
|
||||||
|
1. Create an issue in our GitHub repository requesting the addition of the new language.
|
||||||
|
2. Wait for our team to review and approve the request.
|
||||||
|
3. Once approved, we will set up the necessary files and kickstart the translations with AI to provide initial coverage.
|
||||||
|
|
||||||
|
## Need Help?
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
If you have any questions, hop into our [Discord](https://documen.so/discord) and ask us directly!
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
Thank you for helping make Documenso more accessible to users around the world!
|
||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Contributing Guide
|
title: Getting started
|
||||||
description: Learn how to contribute to Documenso and become part of our community.
|
description: Learn how to contribute to Documenso and become part of our community.
|
||||||
---
|
---
|
||||||
|
|
||||||
507
apps/documentation/pages/developers/public-api/reference.mdx
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
---
|
||||||
|
title: API Reference
|
||||||
|
description: Reference documentation for the Documenso public API.
|
||||||
|
---
|
||||||
|
|
||||||
|
import { Callout, Steps } from 'nextra/components';
|
||||||
|
|
||||||
|
# API Reference
|
||||||
|
|
||||||
|
The Swagger UI for the API is available at [/api/v1/openapi](https://app.documenso.com/api/v1/openapi). This page provides detailed information about the API endpoints, request and response formats, and authentication requirements.
|
||||||
|
|
||||||
|
## Upload a Document
|
||||||
|
|
||||||
|
Uploading a document to your Documenso account requires a two-step process.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
### Create Document
|
||||||
|
|
||||||
|
First, you need to make a `POST` request to the `/api/v1/documents` endpoint, which takes a JSON payload with the following fields:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "string",
|
||||||
|
"externalId": "string",
|
||||||
|
"recipients": [
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"role": "SIGNER",
|
||||||
|
"signingOrder": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"subject": "string",
|
||||||
|
"message": "string",
|
||||||
|
"timezone": "Etc/UTC",
|
||||||
|
"dateFormat": "yyyy-MM-dd hh:mm a",
|
||||||
|
"redirectUrl": "string",
|
||||||
|
"signingOrder": "PARALLEL"
|
||||||
|
},
|
||||||
|
"authOptions": {
|
||||||
|
"globalAccessAuth": "ACCOUNT",
|
||||||
|
"globalActionAuth": "ACCOUNT"
|
||||||
|
},
|
||||||
|
"formValues": {
|
||||||
|
"additionalProp1": "string",
|
||||||
|
"additionalProp2": "string",
|
||||||
|
"additionalProp3": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `title` _(required)_ - This represents the document's title.
|
||||||
|
- `externalId` - This is an optional field that you can use to store an external identifier for the document. This can be useful for tracking the document in your system.
|
||||||
|
- `recipients` _(required)_ - This is an array of recipient objects. Each recipient object has the following fields:
|
||||||
|
- `name` - The name of the recipient.
|
||||||
|
- `email` - The email address of the recipient.
|
||||||
|
- `role` - The role of the recipient. See the [available roles](/users/signing-documents#roles).
|
||||||
|
- `signingOrder` - The order in which the recipient should sign the document. This is an integer value starting from 0.
|
||||||
|
- `meta` - This object contains additional metadata for the document. It has the following fields:
|
||||||
|
- `subject` - The subject of the email that will be sent to the recipients.
|
||||||
|
- `message` - The message of the email that will be sent to the recipients.
|
||||||
|
- `timezone` - The timezone in which the document should be signed.
|
||||||
|
- `dateFormat` - The date format that should be used in the document.
|
||||||
|
- `redirectUrl` - The URL to which the user should be redirected after signing the document.
|
||||||
|
- `signingOrder` - The signing order for the document. This can be either `SEQUENTIAL` or `PARALLEL`.
|
||||||
|
- `authOptions` - This object contains authentication options for the document. It has the following fields:
|
||||||
|
- `globalAccessAuth` - The authentication level required to access the document. This can be either `ACCOUNT` or `null`.
|
||||||
|
- If the document is set to `ACCOUNT`, all recipients must authenticate with their Documenso account to access it.
|
||||||
|
- The document can be accessed without a Documenso account if it's set to `null`.
|
||||||
|
- `globalActionAuth` - The authentication level required to perform actions on the document. This can be `ACCOUNT`, `PASSKEY`, `TWO_FACTOR_AUTH`, or `null`.
|
||||||
|
- If the document is set to `ACCOUNT`, all recipients must authenticate with their Documenso account to perform actions on the document.
|
||||||
|
- If it's set to `PASSKEY`, all recipients must have the passkey active to perform actions on the document.
|
||||||
|
- If it's set to `TWO_FACTOR_AUTH`, all recipients must have the two-factor authentication active to perform actions on the document.
|
||||||
|
- If it's set to `null`, all the recipients can perform actions on the document without any authentication.
|
||||||
|
- `formValues` - This object contains additional form values for the document. This property only works with native PDF fields and accepts three types: number, text and boolean.
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
The `globalActionAuth` property is only available for Enterprise accounts.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
Here's an example of the JSON payload for uploading a document:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "my-document.pdf",
|
||||||
|
"externalId": "12345",
|
||||||
|
"recipients": [
|
||||||
|
{
|
||||||
|
"name": "Alex Blake",
|
||||||
|
"email": "alexblake@email.com",
|
||||||
|
"role": "SIGNER",
|
||||||
|
"signingOrder": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ash Drew",
|
||||||
|
"email": "ashdrew@email.com",
|
||||||
|
"role": "SIGNER",
|
||||||
|
"signingOrder": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"subject": "Sign the document",
|
||||||
|
"message": "Hey there, please sign this document.",
|
||||||
|
"timezone": "Europe/London",
|
||||||
|
"dateFormat": "Day, Month Year",
|
||||||
|
"redirectUrl": "https://mysite.com/welcome",
|
||||||
|
"signingOrder": "SEQUENTIAL"
|
||||||
|
},
|
||||||
|
"authOptions": {
|
||||||
|
"globalAccessAuth": "ACCOUNT",
|
||||||
|
"globalActionAuth": "PASSKEY"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload to S3
|
||||||
|
|
||||||
|
A successful API call to the `/api/v1/documents` endpoint returns a JSON response containing the upload URL, document ID, and recipient information.
|
||||||
|
|
||||||
|
The upload URL is a pre-signed S3 URL that you can use to upload the document to the Documenso (or your) S3 bucket. You need to make a `PUT` request to this URL to upload the document.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"uploadUrl": "https://<url>/<bucket-name>/<id>/my-document.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=<credentials>&X-Amz-Date=<date>&X-Amz-Expires=3600&X-Amz-Signature=<signature>&X-Amz-SignedHeaders=host&x-id=PutObject",
|
||||||
|
"documentId": 51,
|
||||||
|
"recipients": [
|
||||||
|
{
|
||||||
|
"recipientId": 11,
|
||||||
|
"name": "Alex Blake",
|
||||||
|
"email": "alexblake@email.com",
|
||||||
|
"token": "<unique-signer-token>",
|
||||||
|
"role": "SIGNER",
|
||||||
|
"signingOrder": 1,
|
||||||
|
"signingUrl": "https://app.documenso.com/sign/<unique-signer-token>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipientId": 12,
|
||||||
|
"name": "Ash Drew",
|
||||||
|
"email": "ashdrew@email.com",
|
||||||
|
"token": "<unique-signer-token>",
|
||||||
|
"role": "SIGNER",
|
||||||
|
"signingOrder": 0,
|
||||||
|
"signingUrl": "https://app.documenso.com/sign/<unique-signer-token>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When you make the `PUT` request to the pre-signed URL, you need to include the document file you want to upload. The image below shows how to upload a document to the S3 bucket via Postman.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Here's an example of how to upload a document using cURL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --location --request PUT 'https://<url>/<bucket-name>/<id>/my-document.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=<credentials>&X-Amz-Date=<date>&X-Amz-Expires=3600&X-Amz-Signature=<signature>&X-Amz-SignedHeaders=host&x-id=PutObject' \
|
||||||
|
--form '=@"/Users/my-user/Documents/documenso.pdf"'
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the document is successfully uploaded, you can access it in your Documenso account dashboard. The screenshot below shows the document that was uploaded via the API.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Generate Document From Template
|
||||||
|
|
||||||
|
Documenso allows you to generate documents from templates. This is useful when you have a standard document format you want to reuse.
|
||||||
|
|
||||||
|
The API endpoint for generating a document from a template is `/api/v1/templates/{templateId}/generate-document`, and it takes a JSON payload with the following fields:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "string",
|
||||||
|
"externalId": "string",
|
||||||
|
"recipients": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "string",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"signingOrder": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"subject": "string",
|
||||||
|
"message": "string",
|
||||||
|
"timezone": "string",
|
||||||
|
"dateFormat": "string",
|
||||||
|
"redirectUrl": "string",
|
||||||
|
"signingOrder": "PARALLEL"
|
||||||
|
},
|
||||||
|
"authOptions": {
|
||||||
|
"globalAccessAuth": "ACCOUNT",
|
||||||
|
"globalActionAuth": "ACCOUNT"
|
||||||
|
},
|
||||||
|
"formValues": {
|
||||||
|
"additionalProp1": "string",
|
||||||
|
"additionalProp2": "string",
|
||||||
|
"additionalProp3": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The JSON payload is identical to the payload for uploading a document, so you can read more about the fields in the [Create Document](/developers/public-api/reference#create-document) step. For this API endpoint, the `recipients` property is required.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
### Grab the Template ID
|
||||||
|
|
||||||
|
The first step is to retrieve the template ID from the Documenso dashboard. You can find the template ID in the URL by navigating to the template details page.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In this case, the template ID is "99999".
|
||||||
|
|
||||||
|
### Retrieve the Recipient(s) ID(s)
|
||||||
|
|
||||||
|
Once you have the template ID, the next step involves retrieving the ID(s) of the recipient(s) from the template. You can do this by making a GET request to `/api/v1/templates/{template-id}`.
|
||||||
|
|
||||||
|
A successful response looks as follows:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"externalId": "string",
|
||||||
|
"type": "PUBLIC",
|
||||||
|
"title": "string",
|
||||||
|
"userId": 0,
|
||||||
|
"teamId": 0,
|
||||||
|
"templateDocumentDataId": "string",
|
||||||
|
"createdAt": "2024-10-11T08:46:58.247Z",
|
||||||
|
"updatedAt": "2024-10-11T08:46:58.247Z",
|
||||||
|
"templateMeta": {
|
||||||
|
"id": "string",
|
||||||
|
"subject": "string",
|
||||||
|
"message": "string",
|
||||||
|
"timezone": "string",
|
||||||
|
"dateFormat": "string",
|
||||||
|
"templateId": 0,
|
||||||
|
"redirectUrl": "string",
|
||||||
|
"signingOrder": "PARALLEL"
|
||||||
|
},
|
||||||
|
"directLink": {
|
||||||
|
"token": "string",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"templateDocumentData": {
|
||||||
|
"id": "string",
|
||||||
|
"type": "S3_PATH",
|
||||||
|
"data": "string"
|
||||||
|
},
|
||||||
|
"Field": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"recipientId": 0,
|
||||||
|
"type": "SIGNATURE",
|
||||||
|
"page": 0,
|
||||||
|
"positionX": "string",
|
||||||
|
"positionY": "string",
|
||||||
|
"width": "string",
|
||||||
|
"height": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Recipient": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"email": "user@example.com",
|
||||||
|
"name": "string",
|
||||||
|
"signingOrder": 0,
|
||||||
|
"authOptions": "string",
|
||||||
|
"role": "CC"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll need the recipient(s) ID(s) for the next step.
|
||||||
|
|
||||||
|
### Generate the Document
|
||||||
|
|
||||||
|
To generate a document from the template, you need to make a POST request to the `/api/v1/templates/{template-id}/generate-document` endpoint.
|
||||||
|
|
||||||
|
At the minimum, you must provide the `recipients` array in the JSON payload. Here's an example of the JSON payload:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"recipients": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "Ash Drew",
|
||||||
|
"email": "ashdrew@email.com",
|
||||||
|
"signingOrder": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Filling the `recipients` array with the corresponding recipient for each template placeholder recipient is recommended. For example, if the template has two placeholders, you should provide at least two recipients in the `recipients` array. Otherwise, the document will be sent to inexistent recipients such as `<recipient.1@documenso.com>`. However, the recipients can always be edited via the API or the web app.
|
||||||
|
|
||||||
|
A successful response will contain the document ID and recipient(s) information.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"documentId": 999,
|
||||||
|
"recipients": [
|
||||||
|
{
|
||||||
|
"recipientId": 0,
|
||||||
|
"name": "Ash Drew",
|
||||||
|
"email": "ashdrew@email.com",
|
||||||
|
"token": "<signing-token>",
|
||||||
|
"role": "SIGNER",
|
||||||
|
"signingOrder": null,
|
||||||
|
"signingUrl": "https://app.documenso.com/sign/<signing-token>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can now access the document in your Documenso account dashboard. The screenshot below shows the document that was generated from the template.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Add Fields to Document
|
||||||
|
|
||||||
|
The API allows you to add fields to a document via the `/api/v1/documents/{documentId}/fields` endpoint. This is useful when you want to add fields to a document before sending it to recipients.
|
||||||
|
|
||||||
|
To add fields to a document, you need to make a `POST` request with a JSON payload containing the field(s) information.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"recipientId": 0,
|
||||||
|
"type": "SIGNATURE",
|
||||||
|
"pageNumber": 0,
|
||||||
|
"pageX": 0,
|
||||||
|
"pageY": 0,
|
||||||
|
"pageWidth": 0,
|
||||||
|
"pageHeight": 0,
|
||||||
|
"fieldMeta": {
|
||||||
|
"label": "string",
|
||||||
|
"placeholder": "string",
|
||||||
|
"required": true,
|
||||||
|
"readOnly": true,
|
||||||
|
"type": "text",
|
||||||
|
"text": "string",
|
||||||
|
"characterLimit": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// or
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"recipientId": 0,
|
||||||
|
"type": "SIGNATURE",
|
||||||
|
"pageNumber": 0,
|
||||||
|
"pageX": 0,
|
||||||
|
"pageY": 0,
|
||||||
|
"pageWidth": 0,
|
||||||
|
"pageHeight": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipientId": 0,
|
||||||
|
"type": "TEXT",
|
||||||
|
"pageNumber": 0,
|
||||||
|
"pageX": 0,
|
||||||
|
"pageY": 0,
|
||||||
|
"pageWidth": 0,
|
||||||
|
"pageHeight": 0,
|
||||||
|
"fieldMeta": {
|
||||||
|
"label": "string",
|
||||||
|
"placeholder": "string",
|
||||||
|
"required": true,
|
||||||
|
"readOnly": true,
|
||||||
|
"type": "text",
|
||||||
|
"text": "string",
|
||||||
|
"characterLimit": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="info">This endpoint accepts either one field or an array of fields.</Callout>
|
||||||
|
|
||||||
|
Before adding fields to a document, you need each recipient's ID. If the document already has recipients, you can query the document to retrieve the recipient's details. If the document has no recipients, you need to add a recipient via the UI or API before adding a field.
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
|
||||||
|
### Retrieve the Recipient(s) ID(s)
|
||||||
|
|
||||||
|
Perform a `GET` request to the `/api/v1/documents/{id}` to retrieve the details of a specific document, including the recipient's information.
|
||||||
|
|
||||||
|
An example response would look like this:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 137,
|
||||||
|
"externalId": null,
|
||||||
|
"userId": 3,
|
||||||
|
"teamId": null,
|
||||||
|
"title": "documenso.pdf",
|
||||||
|
"status": "DRAFT",
|
||||||
|
"documentDataId": "<document-data-id>",
|
||||||
|
"createdAt": "2024-10-11T12:29:12.725Z",
|
||||||
|
"updatedAt": "2024-10-11T12:29:12.725Z",
|
||||||
|
"completedAt": null,
|
||||||
|
"recipients": [
|
||||||
|
{
|
||||||
|
"id": 55,
|
||||||
|
"documentId": 137,
|
||||||
|
"email": "ashdrew@email.com",
|
||||||
|
"name": "Ash Drew",
|
||||||
|
"role": "SIGNER",
|
||||||
|
"signingOrder": null,
|
||||||
|
"token": "<signing-token>",
|
||||||
|
"signedAt": null,
|
||||||
|
"readStatus": "NOT_OPENED",
|
||||||
|
"signingStatus": "NOT_SIGNED",
|
||||||
|
"sendStatus": "NOT_SENT",
|
||||||
|
"signingUrl": "https://app.documenso.com/sign/<signing-token>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From this response, you'll only need the recipient ID, which is `55` in this case.
|
||||||
|
|
||||||
|
### (OR) Add a Recipient
|
||||||
|
|
||||||
|
If the document doesn't already have recipient(s), you can add recipient(s) via the API. Make a `POST` request to the `/api/v1/documents/{documentId}/recipients` endpoint with the recipient information. This endpoint takes the following JSON payload:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "string",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"role": "SIGNER",
|
||||||
|
"signingOrder": 0,
|
||||||
|
"authOptions": {
|
||||||
|
"actionAuth": "ACCOUNT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="info">The `authOptions` property is only available for Enterprise accounts.</Callout>
|
||||||
|
|
||||||
|
Here's an example of the JSON payload for adding a recipient:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Ash Drew",
|
||||||
|
"email": "ashdrew@email.com",
|
||||||
|
"role": "SIGNER",
|
||||||
|
"signingOrder": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A successful request will return a JSON response with the newly added recipient. You can now use the recipient ID to add fields to the document.
|
||||||
|
|
||||||
|
### Add Field(s)
|
||||||
|
|
||||||
|
Now you can make a `POST` request to the `/api/v1/documents/{documentId}/fields` endpoint with the field(s) information. Here's an example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"recipientId": 55,
|
||||||
|
"type": "SIGNATURE",
|
||||||
|
"pageNumber": 1,
|
||||||
|
"pageX": 50,
|
||||||
|
"pageY": 20,
|
||||||
|
"pageWidth": 25,
|
||||||
|
"pageHeight": 5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"recipientId": 55,
|
||||||
|
"type": "TEXT",
|
||||||
|
"pageNumber": 1,
|
||||||
|
"pageX": 20,
|
||||||
|
"pageY": 50,
|
||||||
|
"pageWidth": 30,
|
||||||
|
"pageHeight": 7.5,
|
||||||
|
"fieldMeta": {
|
||||||
|
"label": "Address",
|
||||||
|
"placeholder": "32 New York Street, 41241",
|
||||||
|
"required": true,
|
||||||
|
"readOnly": false,
|
||||||
|
"type": "text",
|
||||||
|
"text": "32 New York Street, 41241",
|
||||||
|
"characterLimit": 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
<Callout type="info">
|
||||||
|
The `text` field represents the default value of the field. If the user doesn't provide any other
|
||||||
|
value, this is the value that will be used to sign the field.
|
||||||
|
</Callout>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</Steps>
|
||||||
@ -10,6 +10,7 @@
|
|||||||
"signing-documents": "Signing Documents",
|
"signing-documents": "Signing Documents",
|
||||||
"templates": "Templates",
|
"templates": "Templates",
|
||||||
"direct-links": "Direct Signing Links",
|
"direct-links": "Direct Signing Links",
|
||||||
|
"document-visibility": "Document Visibility",
|
||||||
"-- Legal Overview": {
|
"-- Legal Overview": {
|
||||||
"type": "separator",
|
"type": "separator",
|
||||||
"title": "Legal Overview"
|
"title": "Legal Overview"
|
||||||
|
|||||||
18
apps/documentation/pages/users/document-visibility.mdx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
title: Document Visibility
|
||||||
|
description: Learn how to control the visibility of your team documents.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Team's Document Visibility
|
||||||
|
|
||||||
|
By default, all documents created in a team are visible to all team members. However, you can control the visibility of your documents by changing the document's visibility settings.
|
||||||
|
|
||||||
|
To set the visibility of a document, click on the **Document visibility** dropdown in the document's settings panel.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The document visibility can be set to one of the following options:
|
||||||
|
|
||||||
|
- **Everyone** - The document is visible to all team members.
|
||||||
|
- **Managers and above** - The document is visible to people with the role of Manager or above.
|
||||||
|
- **Admin only** - The document is only visible to the team's admins.
|
||||||
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 65 KiB |
BIN
apps/documentation/public/document-visibility-settings.webp
Normal file
|
After Width: | Height: | Size: 72 KiB |
@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
title: Cal.com Chooses Documenso for DPA and BAA Scalability and Compliance
|
||||||
|
description: Learn how Cal.com scales their Data Processing Agreement (DPA) and Business Associate Agreement (BAA) processes with Documenso’s open source platform as they grow.
|
||||||
|
authorName: 'Timur Ercan'
|
||||||
|
authorImage: '/blog/blog-author-timur.jpeg'
|
||||||
|
authorRole: 'Co-Founder'
|
||||||
|
date: 2024-10-11
|
||||||
|
tags:
|
||||||
|
- Customer Story
|
||||||
|
- Open Startup
|
||||||
|
- Open Source
|
||||||
|
---
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<MdxNextImage
|
||||||
|
src="/blog/cal2.png"
|
||||||
|
width="1260"
|
||||||
|
height="630"
|
||||||
|
alt="Scheduling Infrastructure for Everyone"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<figcaption className="text-center">
|
||||||
|
Scheduling Infrastructure for Everyone.
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
TL;DR: Cal.com uses Documenso’s template direct links to facilitate low-friction compliance paperwork, enhancing scalability and user experience.
|
||||||
|
|
||||||
|
## Cal.com – The Most Public Private Company
|
||||||
|
|
||||||
|
[Cal.com](Cal.com) is an open source company that needs no introduction. Founded in 2021 by Bailey Pumfleet and Peer Richelsen, it quickly evolved from an open source alternative to the widespread but limited scheduling platform Calendly into the internet’s most beloved scheduling solution. Starting with just two founders, Cal.com has grown into a team of 22, facilitating millions of bookings per year for its ever-growing user and customer base.
|
||||||
|
|
||||||
|
Their commitment to transparency is evident as they follow the [open startup movement](https://cal.com/open), opening up not only their source code but also providing insights into their business operations. Their Commercial Open Source Software (COSS) model, combining a company and an open source project, has inspired a whole cohort of startups joining the space—not least of which is Documenso.
|
||||||
|
|
||||||
|
## The Need
|
||||||
|
|
||||||
|
At this point, Cal.com serves customers of all sizes, from single users to large enterprises. To provide the best product for their customers, they are certified for SOC 2, HIPAA, GDPR, and many other compliance regulations. One challenge that comes with this is the increasing number of waivers that need to be signed when onboarding customers. Business Associate Agreements (BAAs) and Data Processing Agreements (DPAs) are two of the more commonly known examples. To get these signed with minimal effort for both sides, they were looking for a solution to handle these at scale.
|
||||||
|
|
||||||
|
> We love open source.
|
||||||
|
|
||||||
|
— Peer Richelsen, Co-Founder, Cal.com
|
||||||
|
|
||||||
|
Being an open source company, they also prefer open source in their vendors—for both the shared philosophy and the higher level of trust. The goal was to integrate signing into the checkout process as seamlessly as possible.
|
||||||
|
|
||||||
|
## The Solution
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<MdxNextImage
|
||||||
|
src="/blog/cal.png"
|
||||||
|
width="1260"
|
||||||
|
height="630"
|
||||||
|
alt="Cal.com direct link template to sign a DPA"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<figcaption className="text-center">
|
||||||
|
Sign a DPA with Cal by clicking a link anytime.
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
Documenso offers exactly this solution through direct link templates, enabling Cal.com to:
|
||||||
|
|
||||||
|
- Provide Immediate Access: Customers can access and sign necessary compliance documents through direct links at any time.
|
||||||
|
- Enhance User Experience: Users are immediately forwarded to onboarding after signing.
|
||||||
|
- Ensure Easy Access: The documents are stored within the company’s team account, allowing easy access for anyone who needs them.
|
||||||
|
|
||||||
|
Direct Link templates can also easily be embedded, using the [Documenso widget](https://documen.so/embedded). Embedding anywhere, pre-Filling the templates and notfiying the compliance team at certain point of the flow are a few of the many option the team now has in continously enhanceing their onboard and compliance UX.
|
||||||
|
|
||||||
|
Read more about our direct link templates here: [Direct Link Signing](https://docs.documenso.com/users/direct-links).
|
||||||
|
|
||||||
|
## The Journey
|
||||||
|
|
||||||
|
Initially, Cal.com’s team approached the new solution with skepticism. As Bailey reflected:
|
||||||
|
|
||||||
|
> We were intrigued but skeptical at first, as we put a lot of thought into compliance and doing things right. Documenso’s documentation and support showcased how their direct link templates could meet our needs while being highly compliant.
|
||||||
|
|
||||||
|
This experience highlights Documenso’s trust philosophy. We strive to be transparent in everything we do and let people judge for themselves. It also shows that we neither want nor get trust by default. Doing things right is a conversation worth having, especially in a space as opaque as digital signatures. It goes without saying that the whole team is hyped to have Cal.com on board and yet another open source company joining the open signing movement 🚀
|
||||||
|
|
||||||
|
If you have any questions or comments, please reach out on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord).
|
||||||
|
|
||||||
|
Thinking about switching to a modern signing platform? Reach out anytime: [https://documen.so/sales](https://documen.so/sales)
|
||||||
|
|
||||||
|
Best from Hamburg\
|
||||||
|
Timur
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
title: 'Customer Story Prisma: 4 Reasons why Prisma chose Documenso for Signatures'
|
||||||
|
description: We are happy to welcome Prisma, another OSS company, as a customer. Read here why they choose us.
|
||||||
|
authorName: 'Timur Ercan'
|
||||||
|
authorImage: '/blog/blog-author-timur.jpeg'
|
||||||
|
authorRole: 'Co-Founder'
|
||||||
|
date: 2024-09-26
|
||||||
|
tags:
|
||||||
|
- Prisma
|
||||||
|
- Customer Story
|
||||||
|
- Open Source
|
||||||
|
---
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<MdxNextImage
|
||||||
|
src="/blog/prisma.png"
|
||||||
|
width="1200"
|
||||||
|
height="675"
|
||||||
|
alt="Primsa Landing Page We simplify database migration, connection pooling, database queries, and readable data models."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<figcaption className="text-center">
|
||||||
|
Prisma uses Documenso for collaborative team signing.
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
> TLDR; Prisma is now using Documenso, and [we added visibility scopes](https://docs.documenso.com/users/document-visibility)
|
||||||
|
|
||||||
|
# Prisma
|
||||||
|
|
||||||
|
Prisma is an open-source company known for its modern OSS ORM (Object-Relational Mapping) tools that simplify database interactions for developers. Their flagship product, Prisma ORM, provides a type-safe way to query databases like PostgreSQL, MySQL, and many more. With the addition of Prisma Studio, an intuitive database management interface, Prisma makes it easier and more efficient for developers to work with databases. With their new additions, Prisma Pulse and Accelerate, you can react to real-time database changes and optimize your queries. And they are completely [open source](https://github.com/prisma/prisma)!
|
||||||
|
|
||||||
|
# We choose Prisma too!
|
||||||
|
|
||||||
|
I discovered Prisma when planning the tech stack for the [first version of Documenso](https://github.com/documenso/documenso/releases/tag/0.9-developer-preview). Prisma has felt natural to use since day one and has been the base of our database architecture ever since. It's great to see them develop and grow with us.
|
||||||
|
|
||||||
|
# Why they choose us
|
||||||
|
|
||||||
|
## 1. Signature Flows
|
||||||
|
|
||||||
|
Documenso signing flows are highly configurable, designed to adapt to the needs of any document signing process. Whether you're working with different roles, varying settings, or specific delivery methods, Documenso offers the flexibility to suit your requirements. You can choose to send documents via email, share a manual link, generate a link through the API, or even use a static direct link for quick access—all while ensuring a smooth signing experience.
|
||||||
|
|
||||||
|
Additionally, you can create templates to streamline and reuse common workflows, saving valuable time. Direct link templates enable users to drive the flow themselves, providing a straightforward path for signing. For a seamless experience, Documenso also allows you to embed the signing process directly into your website, ensuring an uninterrupted, integrated workflow tailored to your needs.
|
||||||
|
|
||||||
|
## 2. Modern UX
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<MdxNextImage
|
||||||
|
src="/blog/dsux.png"
|
||||||
|
width="1200"
|
||||||
|
height="675"
|
||||||
|
alt="A completed document in Documenso, ready to download."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<figcaption className="text-center">
|
||||||
|
We call Documenso's design "Happy Minimalism"
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
We’ve crafted Documenso with a sleek, modern interface that makes it incredibly easy to use. Whether you’re signing documents, managing workflows, or fine-tuning settings, its intuitive design allows you to accomplish tasks quickly and effortlessly. More than just powerful, Documenso is a pleasure to navigate—designed to be accessible to everyone, no matter their level of tech experience.
|
||||||
|
|
||||||
|
## 3. Teams
|
||||||
|
|
||||||
|
### Teamwork Makes the Dream Work
|
||||||
|
|
||||||
|
Documenso makes teamwork a breeze with its team management features. You can easily set up and organize teams, making it simple to share and manage documents and workflows together. This is a lifesaver for larger organizations or teams spread across different departments, ensuring everyone stays in sync and on track. Different visibility scopes ensure private documents stay private and others are shared for easy collaboration.
|
||||||
|
|
||||||
|
### Document Visibility
|
||||||
|
|
||||||
|
Collaboration within a team often demands different levels of access to documents. For instance, the Documenso team at Prisma needed a way to set custom visibility on some documents while keeping others accessible to everyone. To address this need, we introduced role-based visibility scopes. This feature allows teams to manage documents more effectively. They can make certain documents visible only to managers or, in special cases, restricted to admins. This ensures sensitive information stays protected while general documents remain accessible to those who need them.
|
||||||
|
|
||||||
|
Learn more about visibility scopes and [how they can benefit your team here](https://docs.documenso.com/users/document-visibility).
|
||||||
|
|
||||||
|
## 4. OSS!
|
||||||
|
|
||||||
|
As you might know, we are open-source! This means you can peek under the hood, tweak things to your liking, and even contribute to making the platform better. We love the community-driven aspect of open-source, and it aligns perfectly with our goal to keep improving and innovating with input from our users.
|
||||||
|
|
||||||
|
So, whether you're looking to streamline your document workflows or just need a solid, reliable platform, Documenso has got your back. And we're thrilled to serve another OSS company and help make the space more open.
|
||||||
|
|
||||||
|
If you have any questions or comments, please reach out on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord).
|
||||||
|
|
||||||
|
Thinking about switching to a modern signing platform? Reach out anytime: [https://documen.so/sales](https://documen.so/sales)
|
||||||
|
|
||||||
|
Best from Hamburg\
|
||||||
|
Timur
|
||||||
86
apps/marketing/content/blog/go-fork-yourself.mdx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
---
|
||||||
|
title: Go Fork Yourself
|
||||||
|
description: Curious about our take on open-source and code forking? Discover why we see forking not as a threat but as a vital part of the Open Source ecosystem.
|
||||||
|
authorName: 'Timur Ercan'
|
||||||
|
authorImage: '/blog/blog-author-timur.jpeg'
|
||||||
|
authorRole: 'Co-Founder'
|
||||||
|
date: 2024-10-03
|
||||||
|
tags:
|
||||||
|
- Culture
|
||||||
|
- Open Startup
|
||||||
|
- Open Source
|
||||||
|
---
|
||||||
|
|
||||||
|
> TLDR; At Documenso, we see OSS as co-owned by all. Forking—collaborative or not—is part of the open-source spirit.
|
||||||
|
|
||||||
|
## Freedom vs. Ownership
|
||||||
|
|
||||||
|
Recently, there has been a lot of debate on the subject of forks and the usage of OSS IP (Open Source Software Intellectual Property). While I mostly aim to stay out of these controversies (as there is no “winning”), I wanted to take this opportunity to share my views on IP and forking culture here at Documenso. I don’t presume this is the ideal path, but for me, it’s the only path that makes sense.
|
||||||
|
|
||||||
|
What these issues show foremost, in my opinion, is that the concept of Open Source is still evolving. I have heard many say, “Open Source is clearly defined” and that there is no ambiguity anymore. That may be true on the legal side, but there are vast differences in how these rules are interpreted and lived out. Here are a few questions to illustrate the point:
|
||||||
|
|
||||||
|
1. Is it okay to use an open-source project without ever giving back?
|
||||||
|
2. Is it okay to fork (some might say copy) an OSS product and build something on top of it?
|
||||||
|
3. Are we morally obliged to fight those who provide different answers to these questions than we do?
|
||||||
|
|
||||||
|
## Embracing Forks and Collaboration
|
||||||
|
|
||||||
|
Since starting Documenso, I’ve thought a lot about what it actually means to be Open Source for us. So far, it has been about openness in working with everyone, from contributors to customers and sharing our work transparently. For this, we have been richly rewarded with attention and reach. This collaborative give-and-take is what people commonly associate with being Open Source, and it seems ideal.
|
||||||
|
|
||||||
|
Yet, there are the questions mentioned above. And while these may be contentious, my take is straightforward:
|
||||||
|
|
||||||
|
1. Yes.
|
||||||
|
2. Yes.
|
||||||
|
3. No.
|
||||||
|
|
||||||
|
I say this because, to me, the principles of Open Source are rooted in freedom and collaboration. That means allowing others to use, improve, or even compete with what you’ve built without feeling possessive over the code. The beauty of Open Source lies in its openness—its ability to be forked, reused, and adapted by anyone.
|
||||||
|
|
||||||
|
You may answer these questions differently for your own reasons. One thing I’ve found lacking in the discourse is the fact that Open Source is still being treated as socially proprietary. If it’s under an open-source license, you can fork it and try to improve upon the original, and there’s nothing wrong with that. The same is true for closed-source startups. Yet in Open Source, there’s a notion that it’s somehow “dirty,” even though the license explicitly allows it.
|
||||||
|
|
||||||
|
## Forking in Action: Real-World Examples
|
||||||
|
|
||||||
|
When the team behind **Node.js** disagreed with its governance and pace of development, they forked the project to create **io.js**. This wasn’t seen as dirty but as a necessary push for change. In fact, the fork resulted in positive changes—better community governance and faster development—which eventually led to the merge of the two projects under the Node.js Foundation. It shows that forking can be a catalyst for improvement, not just competition.
|
||||||
|
|
||||||
|
## The Misconception of “Exploitative” Usage
|
||||||
|
|
||||||
|
However, sometimes forks don’t merge back but still bring positive change. A good example is **Jenkins**, which was forked from **Hudson** over disagreements in governance after Oracle acquired Sun Microsystems. Jenkins quickly overtook Hudson in terms of community support, development, and innovation. Rather than being seen as a hostile move, the fork enabled Jenkins to become a thriving project, better aligned with the open-source ethos of collaboration and transparency. It emphasizes that forking isn’t inherently exploitative; it can simply be a way to realize a project’s full potential.
|
||||||
|
|
||||||
|
And then there’s **MariaDB**, a fork of **MySQL**. After Oracle acquired MySQL, many in the community feared the project’s open-source nature could be compromised. The fork preserved its spirit, and MariaDB has since grown to become a popular and thriving database. It’s a reminder that sometimes, forking is not just acceptable—it’s necessary to uphold the values and freedoms of open-source software.
|
||||||
|
|
||||||
|
<figure>
|
||||||
|
<MdxNextImage
|
||||||
|
src="/blog/owncode.jpeg"
|
||||||
|
width="1200"
|
||||||
|
height="675"
|
||||||
|
alt="Meme: If everyone owns the code, no one does."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<figcaption className="text-center">
|
||||||
|
Funny Meme to drive the point home.
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
My view is that the code is not “your” code, just as Documenso’s code is not “our” code. It’s been co-owned by the world ever since we published the repo under AGPL V3. That is the whole point. It’s finally not owned by anyone (cue the “everyone/no one” meme). Open Source is for everyone, even competitors. Yet, we are still treating the licenses as extensions of the old, proprietary world and defending perceived injustices based on that model.
|
||||||
|
|
||||||
|
> Side Note: Full compliance with all license and other legal rules is a given here.
|
||||||
|
|
||||||
|
## Documenso’s Approach: Co-Ownership and Community
|
||||||
|
|
||||||
|
So, if you want to fork Documenso and build a business on it, you can. Whether that’s a cool thing to do is another matter. Whether you do a better job than us is also another matter (you won’t). But if you do, I’ll be the first to join. But why not join us from the start since you already have the upside? We exist because we believe this to be the best way forward—not because we force it.
|
||||||
|
|
||||||
|
## The Bigger Picture: Open-Source as Progress
|
||||||
|
|
||||||
|
I’ve also thought a lot about question #3. I understand the impulse to fight anyone who doesn’t appreciate this collaborative approach, but there is no part of this model that backs that up. You are free to “exploit” as long as it’s in a way that adds value. The fallacy is in considering someone else using the OSS part for their business as treason, which it’s not. It’s the whole point.
|
||||||
|
|
||||||
|
While some might say this is theoretical and that reality is different, this is the version of Open Source on which we are building Documenso. The point here is that OSS companies must be resilient to handle forking and competition; without this resilience, an open source driven economy can’t thrive. The focus on freedom and collaboration means being prepared for forks and challenges as part of the growth, not as threats.
|
||||||
|
|
||||||
|
Of course, all of this applies to Documenso, the OSS project, not Documenso Inc., the company, which is very much a privately owned, for-profit entity. However, since the goal is to scale Documenso to the entire world, there is plenty of room to see everyone as co-owners of the Open Source project rather than as competitors. In the end, Open Source is about progress through freedom. If you don’t like how we run things, go fork yourself and hold us accountable. We don’t own this; we just happened to start it.
|
||||||
|
|
||||||
|
> Since this article is open source as well, you are free to fork it and change it here: [https://documen.so/repo](https://documen.so/repo)
|
||||||
|
|
||||||
|
If you have any questions or comments, please reach out on [Twitter / X](https://twitter.com/eltimuro) (DM open) or [Discord](https://documen.so/discord).
|
||||||
|
|
||||||
|
Thinking about switching to a modern signing platform? Reach out anytime: [https://documen.so/sales](https://documen.so/sales)
|
||||||
|
|
||||||
|
Best from Hamburg\
|
||||||
|
Timur
|
||||||
@ -8,6 +8,61 @@ Check out what's new in the latest version and read our thoughts on it. For more
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Documenso v1.7.1: Signing order and document visibility
|
||||||
|
|
||||||
|
We're excited to introduce Documenso v1.7.1, bringing you improved control over your document signing process. Here are the key updates:
|
||||||
|
|
||||||
|
## 🌟 Key New Features
|
||||||
|
|
||||||
|
### 1. Signing Order
|
||||||
|
|
||||||
|
Specify the sequence in which recipients sign your documents. This ensures a structured signing process, particularly useful for complex agreements or hierarchical approvals.
|
||||||
|
|
||||||
|
<video
|
||||||
|
src="/changelog/signing-order-demo.mp4"
|
||||||
|
className="aspect-video w-full"
|
||||||
|
autoPlay
|
||||||
|
loop
|
||||||
|
controls
|
||||||
|
/>
|
||||||
|
|
||||||
|
### 2. Document Visibility
|
||||||
|
|
||||||
|
Manage who can view your documents and when. This feature offers greater privacy and flexibility in your document sharing workflows.
|
||||||
|
|
||||||
|
<video
|
||||||
|
src="/changelog/document-visibility-demo.mp4"
|
||||||
|
className="aspect-video w-full"
|
||||||
|
autoPlay
|
||||||
|
loop
|
||||||
|
controls
|
||||||
|
/>
|
||||||
|
|
||||||
|
## 🔧 Other Improvements
|
||||||
|
|
||||||
|
- Added language switcher for easier language selection
|
||||||
|
- Support for smaller field bounds in documents
|
||||||
|
- Improved select field UX
|
||||||
|
- Enhanced template functionality for advanced fields
|
||||||
|
- Added authOptions to the API
|
||||||
|
- Various UI refinements and bug fixes
|
||||||
|
|
||||||
|
## 💡 Recent Features
|
||||||
|
|
||||||
|
Don't forget about these powerful features from our recent v1.7.0 release:
|
||||||
|
|
||||||
|
- Embedded Signing Experience
|
||||||
|
- Copy and Paste Fields
|
||||||
|
- Customizable Signature Colors
|
||||||
|
|
||||||
|
## 👏 Thank You
|
||||||
|
|
||||||
|
As always, we're grateful for our community's contributions and feedback. Your input continues to shape Documenso into the leading open-source document signing solution.
|
||||||
|
|
||||||
|
We're eager to see how these new features enhance your document workflows. Enjoy exploring Documenso v1.7.1!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Documenso v1.7.0: Embedded Signing, Copy and Paste, and More
|
# Documenso v1.7.0: Embedded Signing, Copy and Paste, and More
|
||||||
|
|
||||||
We're thrilled to announce the release of Documenso v1.7.0, packed with exciting new features and improvements that enhance document signing flexibility, user experience, and global accessibility.
|
We're thrilled to announce the release of Documenso v1.7.0, packed with exciting new features and improvements that enhance document signing flexibility, user experience, and global accessibility.
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/marketing",
|
"name": "@documenso/marketing",
|
||||||
"version": "1.7.1-rc.0",
|
"version": "1.7.2-rc.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3001",
|
"dev": "next dev -p 3001",
|
||||||
"build": "next build",
|
"build": "turbo run translate:extract && turbo run translate:compile && next build",
|
||||||
"start": "next start -p 3001",
|
"start": "next start -p 3001",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"lint:fix": "next lint --fix",
|
"lint:fix": "next lint --fix",
|
||||||
@ -34,7 +34,7 @@
|
|||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"next": "14.2.6",
|
"next": "14.2.6",
|
||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"next-axiom": "^1.1.1",
|
"next-axiom": "^1.5.1",
|
||||||
"next-contentlayer": "^0.3.4",
|
"next-contentlayer": "^0.3.4",
|
||||||
"next-plausible": "^3.10.1",
|
"next-plausible": "^3.10.1",
|
||||||
"perfect-freehand": "^1.2.0",
|
"perfect-freehand": "^1.2.0",
|
||||||
|
|||||||
BIN
apps/marketing/public/blog/cal.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
apps/marketing/public/blog/cal2.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
apps/marketing/public/blog/dsux.png
Normal file
|
After Width: | Height: | Size: 692 KiB |
BIN
apps/marketing/public/blog/owncode.jpeg
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
apps/marketing/public/blog/prisma.png
Normal file
|
After Width: | Height: | Size: 435 KiB |
BIN
apps/marketing/public/changelog/document-visibility-demo.mp4
Normal file
BIN
apps/marketing/public/changelog/signing-order-demo.mp4
Normal file
@ -19,10 +19,10 @@ export const TEAM_MEMBERS = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Ephraim Atta-Duncan',
|
name: 'Ephraim Atta-Duncan',
|
||||||
role: 'Software Engineer - Intern',
|
role: 'Software Engineer - I',
|
||||||
salary: 15_000,
|
salary: 60_000,
|
||||||
location: 'Ghana',
|
location: 'Ghana',
|
||||||
engagement: msg`Part-Time`,
|
engagement: msg`Full-Time`,
|
||||||
joinDate: 'June 6th, 2023',
|
joinDate: 'June 6th, 2023',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -108,14 +108,21 @@ export const Carousel = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedIndex(emblaApi.selectedScrollSnap());
|
const newIndex = emblaApi.selectedScrollSnap();
|
||||||
emblaThumbsApi.scrollTo(emblaApi.selectedScrollSnap());
|
|
||||||
|
setSelectedIndex(newIndex);
|
||||||
|
emblaThumbsApi.scrollTo(newIndex);
|
||||||
|
|
||||||
resetProgress();
|
resetProgress();
|
||||||
|
|
||||||
|
const currentVideo = videoRefs.current[newIndex];
|
||||||
|
if (currentVideo) {
|
||||||
|
currentVideo.currentTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
// moduleResolution: bundler breaks this type
|
// moduleResolution: bundler breaks this type
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
const autoplay = emblaApi.plugins()?.autoplay as unknown as AutoplayType | undefined;
|
const autoplay = emblaApi?.plugins()?.autoplay as unknown as AutoplayType | undefined;
|
||||||
|
|
||||||
if (autoplay) {
|
if (autoplay) {
|
||||||
autoplay.reset();
|
autoplay.reset();
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/web",
|
"name": "@documenso/web",
|
||||||
"version": "1.7.1-rc.0",
|
"version": "1.7.2-rc.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3000",
|
"dev": "next dev -p 3000",
|
||||||
"build": "next build",
|
"build": "turbo run translate:extract && turbo run translate:compile && next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"e2e:prepare": "next build && next start",
|
"e2e:prepare": "next build && next start",
|
||||||
@ -37,7 +37,7 @@
|
|||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"next": "14.2.6",
|
"next": "14.2.6",
|
||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"next-axiom": "^1.1.1",
|
"next-axiom": "^1.5.1",
|
||||||
"next-plausible": "^3.10.1",
|
"next-plausible": "^3.10.1",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
@ -53,7 +53,7 @@
|
|||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"recharts": "^2.7.2",
|
"recharts": "^2.7.2",
|
||||||
"remeda": "^1.27.1",
|
"remeda": "^2.12.1",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-documen
|
|||||||
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||||
|
|
||||||
|
import { DocumentSearch } from '~/components/(dashboard)/document-search/document-search';
|
||||||
import { PeriodSelector } from '~/components/(dashboard)/period-selector/period-selector';
|
import { PeriodSelector } from '~/components/(dashboard)/period-selector/period-selector';
|
||||||
import { isPeriodSelectorValue } from '~/components/(dashboard)/period-selector/types';
|
import { isPeriodSelectorValue } from '~/components/(dashboard)/period-selector/types';
|
||||||
import { DocumentStatus } from '~/components/formatter/document-status';
|
import { DocumentStatus } from '~/components/formatter/document-status';
|
||||||
@ -25,16 +26,17 @@ import { DataTableSenderFilter } from './data-table-sender-filter';
|
|||||||
import { EmptyDocumentState } from './empty-state';
|
import { EmptyDocumentState } from './empty-state';
|
||||||
import { UploadDocument } from './upload-document';
|
import { UploadDocument } from './upload-document';
|
||||||
|
|
||||||
export type DocumentsPageViewProps = {
|
export interface DocumentsPageViewProps {
|
||||||
searchParams?: {
|
searchParams?: {
|
||||||
status?: ExtendedDocumentStatus;
|
status?: ExtendedDocumentStatus;
|
||||||
period?: PeriodSelectorValue;
|
period?: PeriodSelectorValue;
|
||||||
page?: string;
|
page?: string;
|
||||||
perPage?: string;
|
perPage?: string;
|
||||||
senderIds?: string;
|
senderIds?: string;
|
||||||
|
search?: string;
|
||||||
};
|
};
|
||||||
team?: Team & { teamEmail?: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
|
team?: Team & { teamEmail?: TeamEmail | null } & { currentTeamMember?: { role: TeamMemberRole } };
|
||||||
};
|
}
|
||||||
|
|
||||||
export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPageViewProps) => {
|
export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPageViewProps) => {
|
||||||
const { user } = await getRequiredServerComponentSession();
|
const { user } = await getRequiredServerComponentSession();
|
||||||
@ -44,6 +46,7 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
|||||||
const page = Number(searchParams.page) || 1;
|
const page = Number(searchParams.page) || 1;
|
||||||
const perPage = Number(searchParams.perPage) || 20;
|
const perPage = Number(searchParams.perPage) || 20;
|
||||||
const senderIds = parseToIntegerArray(searchParams.senderIds ?? '');
|
const senderIds = parseToIntegerArray(searchParams.senderIds ?? '');
|
||||||
|
const search = searchParams.search || '';
|
||||||
const currentTeam = team
|
const currentTeam = team
|
||||||
? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email }
|
? { id: team.id, url: team.url, teamEmail: team.teamEmail?.email }
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -52,6 +55,7 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
|||||||
const getStatOptions: GetStatsInput = {
|
const getStatOptions: GetStatsInput = {
|
||||||
user,
|
user,
|
||||||
period,
|
period,
|
||||||
|
search,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (team) {
|
if (team) {
|
||||||
@ -79,6 +83,7 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
|||||||
perPage,
|
perPage,
|
||||||
period,
|
period,
|
||||||
senderIds,
|
senderIds,
|
||||||
|
search,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getTabHref = (value: typeof status) => {
|
const getTabHref = (value: typeof status) => {
|
||||||
@ -135,10 +140,7 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
|||||||
<DocumentStatus status={value} />
|
<DocumentStatus status={value} />
|
||||||
|
|
||||||
{value !== ExtendedDocumentStatus.ALL && (
|
{value !== ExtendedDocumentStatus.ALL && (
|
||||||
<span className="ml-1 inline-block opacity-50">
|
<span className="ml-1 inline-block opacity-50">{stats[value]}</span>
|
||||||
{Math.min(stats[value], 99)}
|
|
||||||
{stats[value] > 99 && '+'}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
@ -151,6 +153,9 @@ export const DocumentsPageView = async ({ searchParams = {}, team }: DocumentsPa
|
|||||||
<div className="flex w-48 flex-wrap items-center justify-between gap-x-2 gap-y-4">
|
<div className="flex w-48 flex-wrap items-center justify-between gap-x-2 gap-y-4">
|
||||||
<PeriodSelector />
|
<PeriodSelector />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex w-48 flex-wrap items-center justify-between gap-x-2 gap-y-4">
|
||||||
|
<DocumentSearch initialValue={search} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -204,25 +204,29 @@ export default async function CompletedSigningPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{canSignUp && (
|
<div className="flex flex-col items-center">
|
||||||
<div className={`flex max-w-xl flex-col items-center justify-center p-4 md:p-12`}>
|
{canSignUp && (
|
||||||
<h2 className="mt-8 text-center text-xl font-semibold md:mt-0">
|
<div className="flex max-w-xl flex-col items-center justify-center p-4 md:p-12">
|
||||||
<Trans>Need to sign documents?</Trans>
|
<h2 className="mt-8 text-center text-xl font-semibold md:mt-0">
|
||||||
</h2>
|
<Trans>Need to sign documents?</Trans>
|
||||||
|
</h2>
|
||||||
|
|
||||||
<p className="text-muted-foreground/60 mt-4 max-w-[55ch] text-center leading-normal">
|
<p className="text-muted-foreground/60 mt-4 max-w-[55ch] text-center leading-normal">
|
||||||
<Trans>Create your account and start using state-of-the-art document signing.</Trans>
|
<Trans>
|
||||||
</p>
|
Create your account and start using state-of-the-art document signing.
|
||||||
|
</Trans>
|
||||||
|
</p>
|
||||||
|
|
||||||
<ClaimAccount defaultName={recipientName} defaultEmail={recipient.email} />
|
<ClaimAccount defaultName={recipientName} defaultEmail={recipient.email} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isLoggedIn && (
|
{isLoggedIn && (
|
||||||
<Link href="/documents" className="text-documenso-700 hover:text-documenso-600 mt-36">
|
<Link href="/documents" className="text-documenso-700 hover:text-documenso-600">
|
||||||
<Trans>Go Back Home</Trans>
|
<Trans>Go Back Home</Trans>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PollUntilDocumentCompleted document={document} />
|
<PollUntilDocumentCompleted document={document} />
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import { signOut } from 'next-auth/react';
|
import { signOut } from 'next-auth/react';
|
||||||
|
|
||||||
import { RecipientRole } from '@documenso/prisma/client';
|
import { RecipientRole } from '@documenso/prisma/client';
|
||||||
import { trpc } from '@documenso/trpc/react';
|
|
||||||
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import { DialogFooter } from '@documenso/ui/primitives/dialog';
|
import { DialogFooter } from '@documenso/ui/primitives/dialog';
|
||||||
@ -25,22 +25,19 @@ export const DocumentActionAuthAccount = ({
|
|||||||
}: DocumentActionAuthAccountProps) => {
|
}: DocumentActionAuthAccountProps) => {
|
||||||
const { recipient } = useRequiredDocumentAuthContext();
|
const { recipient } = useRequiredDocumentAuthContext();
|
||||||
|
|
||||||
const [isSigningOut, setIsSigningOut] = useState(false);
|
const router = useRouter();
|
||||||
|
|
||||||
const { mutateAsync: encryptSecondaryData } = trpc.crypto.encryptSecondaryData.useMutation();
|
const [isSigningOut, setIsSigningOut] = useState(false);
|
||||||
|
|
||||||
const handleChangeAccount = async (email: string) => {
|
const handleChangeAccount = async (email: string) => {
|
||||||
try {
|
try {
|
||||||
setIsSigningOut(true);
|
setIsSigningOut(true);
|
||||||
|
|
||||||
const encryptedEmail = await encryptSecondaryData({
|
await signOut({
|
||||||
data: email,
|
redirect: false,
|
||||||
expiresAt: DateTime.now().plus({ days: 1 }).toMillis(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await signOut({
|
router.push(`/signin#email=${email}`);
|
||||||
callbackUrl: `/signin?email=${encodeURIComponent(encryptedEmail)}`,
|
|
||||||
});
|
|
||||||
} catch {
|
} catch {
|
||||||
setIsSigningOut(false);
|
setIsSigningOut(false);
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
import { Trans, msg } from '@lingui/macro';
|
import { Trans, msg } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import { signOut } from 'next-auth/react';
|
import { signOut } from 'next-auth/react';
|
||||||
|
|
||||||
import { trpc } from '@documenso/trpc/react';
|
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||||
|
|
||||||
@ -20,24 +20,19 @@ export const SigningAuthPageView = ({ email, emailHasAccount }: SigningAuthPageV
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [isSigningOut, setIsSigningOut] = useState(false);
|
const router = useRouter();
|
||||||
|
|
||||||
const { mutateAsync: encryptSecondaryData } = trpc.crypto.encryptSecondaryData.useMutation();
|
const [isSigningOut, setIsSigningOut] = useState(false);
|
||||||
|
|
||||||
const handleChangeAccount = async (email: string) => {
|
const handleChangeAccount = async (email: string) => {
|
||||||
try {
|
try {
|
||||||
setIsSigningOut(true);
|
setIsSigningOut(true);
|
||||||
|
|
||||||
const encryptedEmail = await encryptSecondaryData({
|
await signOut({
|
||||||
data: email,
|
redirect: false,
|
||||||
expiresAt: DateTime.now().plus({ days: 1 }).toMillis(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await signOut({
|
router.push(emailHasAccount ? `/signin#email=${email}` : `/signup#email=${email}`);
|
||||||
callbackUrl: emailHasAccount
|
|
||||||
? `/signin?email=${encodeURIComponent(encryptedEmail)}`
|
|
||||||
: `/signup?email=${encodeURIComponent(encryptedEmail)}`,
|
|
||||||
});
|
|
||||||
} catch {
|
} catch {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Something went wrong`),
|
title: _(msg`Something went wrong`),
|
||||||
|
|||||||
@ -128,7 +128,7 @@ export const SigningFieldContainer = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('[container-type:size]', type === 'Checkbox' ? 'group' : '')}>
|
<div className={cn('[container-type:size]', { group: type === 'Checkbox' })}>
|
||||||
<FieldRootContainer field={field}>
|
<FieldRootContainer field={field}>
|
||||||
{!field.inserted && !loading && !readOnlyField && (
|
{!field.inserted && !loading && !readOnlyField && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { redirect } from 'next/navigation';
|
|
||||||
|
|
||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import { env } from 'next-runtime-env';
|
import { env } from 'next-runtime-env';
|
||||||
@ -11,7 +10,6 @@ import {
|
|||||||
IS_OIDC_SSO_ENABLED,
|
IS_OIDC_SSO_ENABLED,
|
||||||
OIDC_PROVIDER_LABEL,
|
OIDC_PROVIDER_LABEL,
|
||||||
} from '@documenso/lib/constants/auth';
|
} from '@documenso/lib/constants/auth';
|
||||||
import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt';
|
|
||||||
|
|
||||||
import { SignInForm } from '~/components/forms/signin';
|
import { SignInForm } from '~/components/forms/signin';
|
||||||
|
|
||||||
@ -19,24 +17,11 @@ export const metadata: Metadata = {
|
|||||||
title: 'Sign In',
|
title: 'Sign In',
|
||||||
};
|
};
|
||||||
|
|
||||||
type SignInPageProps = {
|
export default function SignInPage() {
|
||||||
searchParams: {
|
|
||||||
email?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SignInPage({ searchParams }: SignInPageProps) {
|
|
||||||
setupI18nSSR();
|
setupI18nSSR();
|
||||||
|
|
||||||
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
|
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
|
||||||
|
|
||||||
const rawEmail = typeof searchParams.email === 'string' ? searchParams.email : undefined;
|
|
||||||
const email = rawEmail ? decryptSecondaryData(rawEmail) : null;
|
|
||||||
|
|
||||||
if (!email && rawEmail) {
|
|
||||||
redirect('/signin');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-screen max-w-lg px-4">
|
<div className="w-screen max-w-lg px-4">
|
||||||
<div className="border-border dark:bg-background z-10 rounded-xl border bg-neutral-100 p-6">
|
<div className="border-border dark:bg-background z-10 rounded-xl border bg-neutral-100 p-6">
|
||||||
@ -50,7 +35,6 @@ export default function SignInPage({ searchParams }: SignInPageProps) {
|
|||||||
<hr className="-mx-6 my-4" />
|
<hr className="-mx-6 my-4" />
|
||||||
|
|
||||||
<SignInForm
|
<SignInForm
|
||||||
initialEmail={email || undefined}
|
|
||||||
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
|
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
|
||||||
isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED}
|
isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED}
|
||||||
oidcProviderLabel={OIDC_PROVIDER_LABEL}
|
oidcProviderLabel={OIDC_PROVIDER_LABEL}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { env } from 'next-runtime-env';
|
|||||||
|
|
||||||
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
import { setupI18nSSR } from '@documenso/lib/client-only/providers/i18n.server';
|
||||||
import { IS_GOOGLE_SSO_ENABLED, IS_OIDC_SSO_ENABLED } from '@documenso/lib/constants/auth';
|
import { IS_GOOGLE_SSO_ENABLED, IS_OIDC_SSO_ENABLED } from '@documenso/lib/constants/auth';
|
||||||
import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt';
|
|
||||||
|
|
||||||
import { SignUpFormV2 } from '~/components/forms/v2/signup';
|
import { SignUpFormV2 } from '~/components/forms/v2/signup';
|
||||||
|
|
||||||
@ -13,13 +12,7 @@ export const metadata: Metadata = {
|
|||||||
title: 'Sign Up',
|
title: 'Sign Up',
|
||||||
};
|
};
|
||||||
|
|
||||||
type SignUpPageProps = {
|
export default function SignUpPage() {
|
||||||
searchParams: {
|
|
||||||
email?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function SignUpPage({ searchParams }: SignUpPageProps) {
|
|
||||||
setupI18nSSR();
|
setupI18nSSR();
|
||||||
|
|
||||||
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
|
const NEXT_PUBLIC_DISABLE_SIGNUP = env('NEXT_PUBLIC_DISABLE_SIGNUP');
|
||||||
@ -28,17 +21,9 @@ export default function SignUpPage({ searchParams }: SignUpPageProps) {
|
|||||||
redirect('/signin');
|
redirect('/signin');
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawEmail = typeof searchParams.email === 'string' ? searchParams.email : undefined;
|
|
||||||
const email = rawEmail ? decryptSecondaryData(rawEmail) : null;
|
|
||||||
|
|
||||||
if (!email && rawEmail) {
|
|
||||||
redirect('/signup');
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SignUpFormV2
|
<SignUpFormV2
|
||||||
className="w-screen max-w-screen-2xl px-4 md:px-16 lg:-my-16"
|
className="w-screen max-w-screen-2xl px-4 md:px-16 lg:-my-16"
|
||||||
initialEmail={email || undefined}
|
|
||||||
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
|
isGoogleSSOEnabled={IS_GOOGLE_SSO_ENABLED}
|
||||||
isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED}
|
isOIDCSSOEnabled={IS_OIDC_SSO_ENABLED}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
|
|
||||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
import { RecipientStatusType, getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
||||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||||
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
||||||
import type { DocumentStatus, Recipient } from '@documenso/prisma/client';
|
import type { DocumentStatus, Recipient } from '@documenso/prisma/client';
|
||||||
@ -29,24 +31,26 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
const { _ } = useLingui();
|
const { _ } = useLingui();
|
||||||
|
|
||||||
const waitingRecipients = recipients.filter(
|
const waitingRecipients = recipients.filter(
|
||||||
(recipient) => getRecipientType(recipient) === 'waiting',
|
(recipient) => getRecipientType(recipient) === RecipientStatusType.WAITING,
|
||||||
);
|
);
|
||||||
|
|
||||||
const openedRecipients = recipients.filter(
|
const openedRecipients = recipients.filter(
|
||||||
(recipient) => getRecipientType(recipient) === 'opened',
|
(recipient) => getRecipientType(recipient) === RecipientStatusType.OPENED,
|
||||||
);
|
);
|
||||||
|
|
||||||
const completedRecipients = recipients.filter(
|
const completedRecipients = recipients.filter(
|
||||||
(recipient) => getRecipientType(recipient) === 'completed',
|
(recipient) => getRecipientType(recipient) === RecipientStatusType.COMPLETED,
|
||||||
);
|
);
|
||||||
|
|
||||||
const uncompletedRecipients = recipients.filter(
|
const uncompletedRecipients = recipients.filter(
|
||||||
(recipient) => getRecipientType(recipient) === 'unsigned',
|
(recipient) => getRecipientType(recipient) === RecipientStatusType.UNSIGNED,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sortedRecipients = useMemo(() => recipients.sort((a, b) => a.id - b.id), [recipients]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverHover
|
<PopoverHover
|
||||||
trigger={children || <StackAvatars recipients={recipients} />}
|
trigger={children || <StackAvatars recipients={sortedRecipients} />}
|
||||||
contentProps={{
|
contentProps={{
|
||||||
className: 'flex flex-col gap-y-5 py-2',
|
className: 'flex flex-col gap-y-5 py-2',
|
||||||
side: position,
|
side: position,
|
||||||
@ -65,7 +69,7 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
type={getRecipientType(recipient)}
|
type={getRecipientType(recipient)}
|
||||||
fallbackText={recipientAbbreviation(recipient)}
|
fallbackText={recipientAbbreviation(recipient)}
|
||||||
/>
|
/>
|
||||||
<div className="">
|
<div>
|
||||||
<p className="text-muted-foreground text-sm">{recipient.email}</p>
|
<p className="text-muted-foreground text-sm">{recipient.email}</p>
|
||||||
<p className="text-muted-foreground/70 text-xs">
|
<p className="text-muted-foreground/70 text-xs">
|
||||||
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
|
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
import {
|
||||||
|
getExtraRecipientsType,
|
||||||
|
getRecipientType,
|
||||||
|
} from '@documenso/lib/client-only/recipient-type';
|
||||||
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
||||||
import type { Recipient } from '@documenso/prisma/client';
|
import type { Recipient } from '@documenso/prisma/client';
|
||||||
|
|
||||||
@ -13,20 +16,27 @@ export function StackAvatars({ recipients }: { recipients: Recipient[] }) {
|
|||||||
const remainingItems = recipients.length - itemsToRender.length;
|
const remainingItems = recipients.length - itemsToRender.length;
|
||||||
|
|
||||||
return itemsToRender.map((recipient: Recipient, index: number) => {
|
return itemsToRender.map((recipient: Recipient, index: number) => {
|
||||||
const first = index === 0 ? true : false;
|
const first = index === 0;
|
||||||
|
|
||||||
const lastItemText =
|
if (index === 4 && remainingItems > 0) {
|
||||||
index === itemsToRender.length - 1 && remainingItems > 0
|
return (
|
||||||
? `+${remainingItems + 1}`
|
<StackAvatar
|
||||||
: undefined;
|
key="extra-recipient"
|
||||||
|
first={first}
|
||||||
|
zIndex={String(zIndex - index * 10)}
|
||||||
|
type={getExtraRecipientsType(recipients.slice(4))}
|
||||||
|
fallbackText={`+${remainingItems + 1}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StackAvatar
|
<StackAvatar
|
||||||
key={recipient.id}
|
key={recipient.id}
|
||||||
first={first}
|
first={first}
|
||||||
zIndex={String(zIndex - index * 10)}
|
zIndex={String(zIndex - index * 10)}
|
||||||
type={lastItemText && index === 4 ? 'unsigned' : getRecipientType(recipient)}
|
type={getRecipientType(recipient)}
|
||||||
fallbackText={lastItemText ? lastItemText : recipientAbbreviation(recipient)}
|
fallbackText={recipientAbbreviation(recipient)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
|
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
||||||
|
import { Input } from '@documenso/ui/primitives/input';
|
||||||
|
|
||||||
|
export const DocumentSearch = ({ initialValue = '' }: { initialValue?: string }) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const [searchTerm, setSearchTerm] = useState(initialValue);
|
||||||
|
const debouncedSearchTerm = useDebouncedValue(searchTerm, 500);
|
||||||
|
|
||||||
|
const handleSearch = useCallback(
|
||||||
|
(term: string) => {
|
||||||
|
const params = new URLSearchParams(searchParams?.toString() ?? '');
|
||||||
|
if (term) {
|
||||||
|
params.set('search', term);
|
||||||
|
} else {
|
||||||
|
params.delete('search');
|
||||||
|
}
|
||||||
|
router.push(`?${params.toString()}`);
|
||||||
|
},
|
||||||
|
[router, searchParams],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleSearch(searchTerm);
|
||||||
|
}, [debouncedSearchTerm]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
placeholder="Search documents..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -89,7 +89,7 @@ export const DocumentReadOnlyFields = ({ documentMeta, fields }: DocumentReadOnl
|
|||||||
className="h-full w-full object-contain dark:invert"
|
className="h-full w-full object-contain dark:invert"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p className="font-signature text-muted-foreground text-lg duration-200 sm:text-xl md:text-2xl lg:text-3xl">
|
<p className="font-signature text-muted-foreground text-lg duration-200 sm:text-xl md:text-2xl">
|
||||||
{field.Signature?.typedSignature}
|
{field.Signature?.typedSignature}
|
||||||
</p>
|
</p>
|
||||||
),
|
),
|
||||||
@ -122,7 +122,7 @@ export const DocumentReadOnlyFields = ({ documentMeta, fields }: DocumentReadOnl
|
|||||||
{field.Recipient.signingStatus === SigningStatus.NOT_SIGNED && (
|
{field.Recipient.signingStatus === SigningStatus.NOT_SIGNED && (
|
||||||
<p
|
<p
|
||||||
className={cn('text-muted-foreground text-lg duration-200', {
|
className={cn('text-muted-foreground text-lg duration-200', {
|
||||||
'font-signature sm:text-xl md:text-2xl lg:text-3xl':
|
'font-signature sm:text-xl md:text-2xl':
|
||||||
field.type === FieldType.SIGNATURE ||
|
field.type === FieldType.SIGNATURE ||
|
||||||
field.type === FieldType.FREE_SIGNATURE,
|
field.type === FieldType.FREE_SIGNATURE,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@ -307,6 +307,18 @@ export const SignInForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const hash = window.location.hash.slice(1);
|
||||||
|
|
||||||
|
const params = new URLSearchParams(hash);
|
||||||
|
|
||||||
|
const email = params.get('email');
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
form.setValue('email', email);
|
||||||
|
}
|
||||||
|
}, [form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
@ -203,6 +203,18 @@ export const SignUpFormV2 = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const hash = window.location.hash.slice(1);
|
||||||
|
|
||||||
|
const params = new URLSearchParams(hash);
|
||||||
|
|
||||||
|
const email = params.get('email');
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
form.setValue('email', email);
|
||||||
|
}
|
||||||
|
}, [form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex justify-center gap-x-12', className)}>
|
<div className={cn('flex justify-center gap-x-12', className)}>
|
||||||
<div className="border-border relative hidden flex-1 overflow-hidden rounded-xl border xl:flex">
|
<div className="border-border relative hidden flex-1 overflow-hidden rounded-xl border xl:flex">
|
||||||
|
|||||||
@ -58,6 +58,8 @@ COPY .gitignore .gitignore
|
|||||||
COPY --from=builder /app/out/json/ .
|
COPY --from=builder /app/out/json/ .
|
||||||
COPY --from=builder /app/out/package-lock.json ./package-lock.json
|
COPY --from=builder /app/out/package-lock.json ./package-lock.json
|
||||||
|
|
||||||
|
COPY --from=builder /app/lingui.config.ts ./lingui.config.ts
|
||||||
|
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
# Then copy all the source code (as it changes more often)
|
# Then copy all the source code (as it changes more often)
|
||||||
|
|||||||
106
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"version": "1.7.1-rc.0",
|
"version": "1.7.2-rc.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@documenso/root",
|
"name": "@documenso/root",
|
||||||
"version": "1.7.1-rc.0",
|
"version": "1.7.2-rc.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@ -80,7 +80,7 @@
|
|||||||
},
|
},
|
||||||
"apps/marketing": {
|
"apps/marketing": {
|
||||||
"name": "@documenso/marketing",
|
"name": "@documenso/marketing",
|
||||||
"version": "1.7.1-rc.0",
|
"version": "1.7.2-rc.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/assets": "*",
|
"@documenso/assets": "*",
|
||||||
@ -103,7 +103,7 @@
|
|||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"next": "14.2.6",
|
"next": "14.2.6",
|
||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"next-axiom": "^1.1.1",
|
"next-axiom": "^1.5.1",
|
||||||
"next-contentlayer": "^0.3.4",
|
"next-contentlayer": "^0.3.4",
|
||||||
"next-plausible": "^3.10.1",
|
"next-plausible": "^3.10.1",
|
||||||
"perfect-freehand": "^1.2.0",
|
"perfect-freehand": "^1.2.0",
|
||||||
@ -410,6 +410,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"apps/marketing/node_modules/next-axiom": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-axiom/-/next-axiom-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-sWxIzuJOex48ugMDlXWzvGvDGv5YHZ3w8gLZbUQ/Yml7oy5jcCItJNws9D0qmASirp2e5/BnvHxs44+9CO0GAQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"use-deep-compare": "^1.2.1",
|
||||||
|
"whatwg-fetch": "^3.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": ">=14.0",
|
||||||
|
"react": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"apps/marketing/node_modules/typescript": {
|
"apps/marketing/node_modules/typescript": {
|
||||||
"version": "5.2.2",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||||
@ -424,7 +441,7 @@
|
|||||||
},
|
},
|
||||||
"apps/web": {
|
"apps/web": {
|
||||||
"name": "@documenso/web",
|
"name": "@documenso/web",
|
||||||
"version": "1.7.1-rc.0",
|
"version": "1.7.2-rc.0",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@documenso/api": "*",
|
"@documenso/api": "*",
|
||||||
@ -449,7 +466,7 @@
|
|||||||
"micro": "^10.0.1",
|
"micro": "^10.0.1",
|
||||||
"next": "14.2.6",
|
"next": "14.2.6",
|
||||||
"next-auth": "4.24.5",
|
"next-auth": "4.24.5",
|
||||||
"next-axiom": "^1.1.1",
|
"next-axiom": "^1.5.1",
|
||||||
"next-plausible": "^3.10.1",
|
"next-plausible": "^3.10.1",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
@ -465,7 +482,7 @@
|
|||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
"react-rnd": "^10.4.1",
|
"react-rnd": "^10.4.1",
|
||||||
"recharts": "^2.7.2",
|
"recharts": "^2.7.2",
|
||||||
"remeda": "^1.27.1",
|
"remeda": "^2.12.1",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
@ -493,6 +510,23 @@
|
|||||||
"integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==",
|
"integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"apps/web/node_modules/next-axiom": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-axiom/-/next-axiom-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-sWxIzuJOex48ugMDlXWzvGvDGv5YHZ3w8gLZbUQ/Yml7oy5jcCItJNws9D0qmASirp2e5/BnvHxs44+9CO0GAQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"use-deep-compare": "^1.2.1",
|
||||||
|
"whatwg-fetch": "^3.6.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": ">=14.0",
|
||||||
|
"react": ">=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"apps/web/node_modules/typescript": {
|
"apps/web/node_modules/typescript": {
|
||||||
"version": "5.2.2",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||||
@ -24150,22 +24184,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/next-axiom": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/next-axiom/-/next-axiom-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-0r/TJ+/zetD+uDc7B+2E7WpC86hEtQ1U+DuWYrP/JNmUz+ZdPFbrZgzOSqaZ6TwYbXP56VVlPfYwq1YsKHTHYQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"remeda": "^1.29.0",
|
|
||||||
"whatwg-fetch": "^3.6.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"next": ">=13.4",
|
|
||||||
"react": ">=18.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/next-contentlayer": {
|
"node_modules/next-contentlayer": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/next-contentlayer/-/next-contentlayer-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/next-contentlayer/-/next-contentlayer-0.3.4.tgz",
|
||||||
@ -30130,9 +30148,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/remeda": {
|
"node_modules/remeda": {
|
||||||
"version": "1.29.0",
|
"version": "2.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/remeda/-/remeda-1.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/remeda/-/remeda-2.12.1.tgz",
|
||||||
"integrity": "sha512-M3LQ14KtMdQ1879lj/kKji3zBk158s7Rwg963mEkTfQFMxnKrIEAMxJfo/+0sp/+uGgN/KMVU2MBA4LNjqf8YQ=="
|
"integrity": "sha512-hKFAbxbQe8PMd4+CYO1DYCrCbcZsUSa7e21g7+4co91GBy7BD+Ub6JdaLy76yPOp7PCPTAXRz/9NXtZ9w15jbg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"type-fest": "^4.26.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/remeda/node_modules/type-fest": {
|
||||||
|
"version": "4.26.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz",
|
||||||
|
"integrity": "sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==",
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/remote-git-tags": {
|
"node_modules/remote-git-tags": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
@ -34195,6 +34229,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-deep-compare": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-deep-compare/-/use-deep-compare-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-94iG+dEdEP/Sl3WWde+w9StIunlV8Dgj+vkt5wTwMoFQLaijiEZSXXy8KtcStpmEDtIptRJiNeD4ACTtVvnIKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dequal": "2.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/use-memo-one": {
|
"node_modules/use-memo-one": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz",
|
||||||
@ -36751,7 +36797,7 @@
|
|||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"playwright": "1.43.0",
|
"playwright": "1.43.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"remeda": "^1.27.1",
|
"remeda": "^2.12.1",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
@ -37050,6 +37096,12 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/ui/node_modules/remeda": {
|
||||||
|
"version": "1.61.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/remeda/-/remeda-1.61.0.tgz",
|
||||||
|
"integrity": "sha512-caKfSz9rDeSKBQQnlJnVW3mbVdFgxgGWQKq1XlFokqjf+hQD5gxutLGTTY2A/x24UxVyJe9gH5fAkFI63ULw4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"packages/ui/node_modules/typescript": {
|
"packages/ui/node_modules/typescript": {
|
||||||
"version": "5.2.2",
|
"version": "5.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.7.1-rc.0",
|
"version": "1.7.2-rc.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"build:web": "turbo run build --filter=@documenso/web",
|
"build:web": "turbo run build --filter=@documenso/web",
|
||||||
|
|||||||
@ -302,7 +302,9 @@ export const ApiContractV1Implementation = createNextRoute(ApiContractV1, {
|
|||||||
documentId: document.id,
|
documentId: document.id,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
teamId: team?.id,
|
teamId: team?.id,
|
||||||
data: body.authOptions,
|
data: {
|
||||||
|
...body.authOptions,
|
||||||
|
},
|
||||||
requestMetadata: extractNextApiRequestMetadata(args.req),
|
requestMetadata: extractNextApiRequestMetadata(args.req),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
249
packages/app-tests/e2e/teams/search-documents.spec.ts
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
import { DocumentStatus, TeamMemberRole } from '@documenso/prisma/client';
|
||||||
|
import { seedDocuments, seedTeamDocuments } from '@documenso/prisma/seed/documents';
|
||||||
|
import { seedTeam, seedTeamMember } from '@documenso/prisma/seed/teams';
|
||||||
|
import { seedUser } from '@documenso/prisma/seed/users';
|
||||||
|
|
||||||
|
import { apiSignin, apiSignout } from '../fixtures/authentication';
|
||||||
|
import { checkDocumentTabCount } from '../fixtures/documents';
|
||||||
|
|
||||||
|
test('[TEAMS]: search respects team document visibility', async ({ page }) => {
|
||||||
|
const team = await seedTeam();
|
||||||
|
const adminUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.ADMIN });
|
||||||
|
const managerUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.MANAGER });
|
||||||
|
const memberUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.MEMBER });
|
||||||
|
|
||||||
|
await seedDocuments([
|
||||||
|
{
|
||||||
|
sender: team.owner,
|
||||||
|
recipients: [],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: 'EVERYONE',
|
||||||
|
title: 'Searchable Document for Everyone',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sender: team.owner,
|
||||||
|
recipients: [],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: 'MANAGER_AND_ABOVE',
|
||||||
|
title: 'Searchable Document for Managers',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sender: team.owner,
|
||||||
|
recipients: [],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: 'ADMIN',
|
||||||
|
title: 'Searchable Document for Admins',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
{ user: adminUser, visibleDocs: 3 },
|
||||||
|
{ user: managerUser, visibleDocs: 2 },
|
||||||
|
{ user: memberUser, visibleDocs: 1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const { user, visibleDocs } of testCases) {
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: user.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByPlaceholder('Search documents...').fill('Searchable');
|
||||||
|
await page.waitForURL(/search=Searchable/);
|
||||||
|
|
||||||
|
await checkDocumentTabCount(page, 'All', visibleDocs);
|
||||||
|
|
||||||
|
await apiSignout({ page });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[TEAMS]: search does not reveal documents from other teams', async ({ page }) => {
|
||||||
|
const { team: teamA, teamMember2: teamAMember } = await seedTeamDocuments();
|
||||||
|
const { team: teamB } = await seedTeamDocuments();
|
||||||
|
|
||||||
|
await seedDocuments([
|
||||||
|
{
|
||||||
|
sender: teamA.owner,
|
||||||
|
recipients: [],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: teamA.id,
|
||||||
|
visibility: 'EVERYONE',
|
||||||
|
title: 'Unique Team A Document',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sender: teamB.owner,
|
||||||
|
recipients: [],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: teamB.id,
|
||||||
|
visibility: 'EVERYONE',
|
||||||
|
title: 'Unique Team B Document',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: teamAMember.email,
|
||||||
|
redirectPath: `/t/${teamA.url}/documents`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByPlaceholder('Search documents...').fill('Unique');
|
||||||
|
await page.waitForURL(/search=Unique/);
|
||||||
|
|
||||||
|
await checkDocumentTabCount(page, 'All', 1);
|
||||||
|
await expect(page.getByRole('link', { name: 'Unique Team A Document' })).toBeVisible();
|
||||||
|
await expect(page.getByRole('link', { name: 'Unique Team B Document' })).not.toBeVisible();
|
||||||
|
|
||||||
|
await apiSignout({ page });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[PERSONAL]: search does not reveal team documents in personal account', async ({ page }) => {
|
||||||
|
const { team, teamMember2 } = await seedTeamDocuments();
|
||||||
|
|
||||||
|
await seedDocuments([
|
||||||
|
{
|
||||||
|
sender: teamMember2,
|
||||||
|
recipients: [],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: null,
|
||||||
|
title: 'Personal Unique Document',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sender: team.owner,
|
||||||
|
recipients: [],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: 'EVERYONE',
|
||||||
|
title: 'Team Unique Document',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: teamMember2.email,
|
||||||
|
redirectPath: '/documents',
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByPlaceholder('Search documents...').fill('Unique');
|
||||||
|
await page.waitForURL(/search=Unique/);
|
||||||
|
|
||||||
|
await checkDocumentTabCount(page, 'All', 1);
|
||||||
|
await expect(page.getByRole('link', { name: 'Personal Unique Document' })).toBeVisible();
|
||||||
|
await expect(page.getByRole('link', { name: 'Team Unique Document' })).not.toBeVisible();
|
||||||
|
|
||||||
|
await apiSignout({ page });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[TEAMS]: search respects recipient visibility regardless of team visibility', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const team = await seedTeam();
|
||||||
|
const memberUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.MEMBER });
|
||||||
|
|
||||||
|
await seedDocuments([
|
||||||
|
{
|
||||||
|
sender: team.owner,
|
||||||
|
recipients: [memberUser],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: 'ADMIN',
|
||||||
|
title: 'Admin Document with Member Recipient',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: memberUser.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByPlaceholder('Search documents...').fill('Admin Document');
|
||||||
|
await page.waitForURL(/search=Admin(%20|\+|\s)Document/);
|
||||||
|
|
||||||
|
await checkDocumentTabCount(page, 'All', 1);
|
||||||
|
await expect(
|
||||||
|
page.getByRole('link', { name: 'Admin Document with Member Recipient' }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await apiSignout({ page });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[TEAMS]: search by recipient name respects visibility', async ({ page }) => {
|
||||||
|
const team = await seedTeam();
|
||||||
|
const adminUser = await seedTeamMember({ teamId: team.id, role: TeamMemberRole.ADMIN });
|
||||||
|
const memberUser = await seedTeamMember({
|
||||||
|
teamId: team.id,
|
||||||
|
role: TeamMemberRole.MEMBER,
|
||||||
|
name: 'Team Member',
|
||||||
|
});
|
||||||
|
|
||||||
|
const uniqueRecipient = await seedUser();
|
||||||
|
|
||||||
|
await seedDocuments([
|
||||||
|
{
|
||||||
|
sender: team.owner,
|
||||||
|
recipients: [uniqueRecipient],
|
||||||
|
type: DocumentStatus.COMPLETED,
|
||||||
|
documentOptions: {
|
||||||
|
teamId: team.id,
|
||||||
|
visibility: 'ADMIN',
|
||||||
|
title: 'Admin Document for Unique Recipient',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Admin should see the document when searching by recipient name
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: adminUser.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByPlaceholder('Search documents...').fill('Unique Recipient');
|
||||||
|
await page.waitForURL(/search=Unique(%20|\+|\s)Recipient/);
|
||||||
|
|
||||||
|
await checkDocumentTabCount(page, 'All', 1);
|
||||||
|
await expect(
|
||||||
|
page.getByRole('link', { name: 'Admin Document for Unique Recipient' }),
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
await apiSignout({ page });
|
||||||
|
|
||||||
|
// Member should not see the document when searching by recipient name
|
||||||
|
await apiSignin({
|
||||||
|
page,
|
||||||
|
email: memberUser.email,
|
||||||
|
redirectPath: `/t/${team.url}/documents`,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByPlaceholder('Search documents...').fill('Unique Recipient');
|
||||||
|
await page.waitForURL(/search=Unique(%20|\+|\s)Recipient/);
|
||||||
|
|
||||||
|
await checkDocumentTabCount(page, 'All', 0);
|
||||||
|
await expect(
|
||||||
|
page.getByRole('link', { name: 'Admin Document for Unique Recipient' }),
|
||||||
|
).not.toBeVisible();
|
||||||
|
|
||||||
|
await apiSignout({ page });
|
||||||
|
});
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { equals } from 'remeda';
|
import { isDeepEqual } from 'remeda';
|
||||||
|
|
||||||
import { getLimits } from '../client';
|
import { getLimits } from '../client';
|
||||||
import { FREE_PLAN_LIMITS } from '../constants';
|
import { FREE_PLAN_LIMITS } from '../constants';
|
||||||
@ -42,7 +42,7 @@ export const LimitsProvider = ({
|
|||||||
const newLimits = await getLimits({ teamId });
|
const newLimits = await getLimits({ teamId });
|
||||||
|
|
||||||
setLimits((oldLimits) => {
|
setLimits((oldLimits) => {
|
||||||
if (equals(oldLimits, newLimits)) {
|
if (isDeepEqual(oldLimits, newLimits)) {
|
||||||
return oldLimits;
|
return oldLimits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
78
packages/email/templates/recipient-removed-from-document.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import config from '@documenso/tailwind-config';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Container,
|
||||||
|
Head,
|
||||||
|
Hr,
|
||||||
|
Html,
|
||||||
|
Img,
|
||||||
|
Preview,
|
||||||
|
Section,
|
||||||
|
Tailwind,
|
||||||
|
Text,
|
||||||
|
} from '../components';
|
||||||
|
import type { TemplateDocumentCancelProps } from '../template-components/template-document-cancel';
|
||||||
|
import TemplateDocumentImage from '../template-components/template-document-image';
|
||||||
|
import { TemplateFooter } from '../template-components/template-footer';
|
||||||
|
|
||||||
|
export type DocumentCancelEmailTemplateProps = Partial<TemplateDocumentCancelProps>;
|
||||||
|
|
||||||
|
export const RecipientRemovedFromDocumentTemplate = ({
|
||||||
|
inviterName = 'Lucas Smith',
|
||||||
|
documentName = 'Open Source Pledge.pdf',
|
||||||
|
assetBaseUrl = 'http://localhost:3002',
|
||||||
|
}: DocumentCancelEmailTemplateProps) => {
|
||||||
|
const previewText = `${inviterName} has removed you from the document ${documentName}.`;
|
||||||
|
|
||||||
|
const getAssetUrl = (path: string) => {
|
||||||
|
return new URL(path, assetBaseUrl).toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Html>
|
||||||
|
<Head />
|
||||||
|
<Preview>{previewText}</Preview>
|
||||||
|
<Tailwind
|
||||||
|
config={{
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: config.theme.extend.colors,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Body className="mx-auto my-auto bg-white font-sans">
|
||||||
|
<Section>
|
||||||
|
<Container className="mx-auto mb-2 mt-8 max-w-xl rounded-lg border border-solid border-slate-200 p-4 backdrop-blur-sm">
|
||||||
|
<Section>
|
||||||
|
<Img
|
||||||
|
src={getAssetUrl('/static/logo.png')}
|
||||||
|
alt="Documenso Logo"
|
||||||
|
className="mb-4 h-6"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TemplateDocumentImage className="mt-6" assetBaseUrl={assetBaseUrl} />
|
||||||
|
|
||||||
|
<Section>
|
||||||
|
<Text className="text-primary mx-auto mb-0 max-w-[80%] text-center text-lg font-semibold">
|
||||||
|
{inviterName} has removed you from the document
|
||||||
|
<br />"{documentName}"
|
||||||
|
</Text>
|
||||||
|
</Section>
|
||||||
|
</Section>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
<Hr className="mx-auto mt-12 max-w-xl" />
|
||||||
|
|
||||||
|
<Container className="mx-auto max-w-xl">
|
||||||
|
<TemplateFooter />
|
||||||
|
</Container>
|
||||||
|
</Section>
|
||||||
|
</Body>
|
||||||
|
</Tailwind>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RecipientRemovedFromDocumentTemplate;
|
||||||
@ -1,15 +1,10 @@
|
|||||||
import { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
import { checkboxValidationSigns } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
||||||
|
|
||||||
interface CheckboxFieldMeta {
|
import type { TCheckboxFieldMeta } from '../types/field-meta';
|
||||||
readOnly?: boolean;
|
|
||||||
required?: boolean;
|
|
||||||
validationRule?: string;
|
|
||||||
validationLength?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validateCheckboxField = (
|
export const validateCheckboxField = (
|
||||||
values: string[],
|
values: string[],
|
||||||
fieldMeta: CheckboxFieldMeta,
|
fieldMeta: TCheckboxFieldMeta,
|
||||||
isSigningPage: boolean = false,
|
isSigningPage: boolean = false,
|
||||||
): string[] => {
|
): string[] => {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
interface DropdownFieldMeta {
|
import type { TDropdownFieldMeta as DropdownFieldMeta } from '../types/field-meta';
|
||||||
readOnly?: boolean;
|
|
||||||
required?: boolean;
|
|
||||||
values?: { value: string }[];
|
|
||||||
defaultValue?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validateDropdownField = (
|
export const validateDropdownField = (
|
||||||
value: string | undefined,
|
value: string | undefined,
|
||||||
fieldMeta: DropdownFieldMeta,
|
fieldMeta: DropdownFieldMeta,
|
||||||
isSigningPage: boolean = false,
|
isSigningPage: boolean = false,
|
||||||
|
fontSize?: number,
|
||||||
): string[] => {
|
): string[] => {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
@ -50,5 +46,9 @@ export const validateDropdownField = (
|
|||||||
errors.push('Duplicate values are not allowed');
|
errors.push('Duplicate values are not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fontSize && (fontSize < 8 || fontSize > 96)) {
|
||||||
|
errors.push('Font size must be between 8 and 96.');
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|||||||
19
packages/lib/advanced-fields-validation/validate-fields.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import type {
|
||||||
|
TDateFieldMeta as DateFieldMeta,
|
||||||
|
TEmailFieldMeta as EmailFieldMeta,
|
||||||
|
TInitialsFieldMeta as InitialsFieldMeta,
|
||||||
|
TNameFieldMeta as NameFieldMeta,
|
||||||
|
} from '../types/field-meta';
|
||||||
|
|
||||||
|
export const validateFields = (
|
||||||
|
fieldMeta: DateFieldMeta | EmailFieldMeta | InitialsFieldMeta | NameFieldMeta,
|
||||||
|
): string[] => {
|
||||||
|
const errors = [];
|
||||||
|
const { fontSize } = fieldMeta;
|
||||||
|
|
||||||
|
if (fontSize && (fontSize < 8 || fontSize > 96)) {
|
||||||
|
errors.push('Font size must be between 8 and 96.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
};
|
||||||
@ -1,12 +1,5 @@
|
|||||||
// import { numberFormatValues } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
// import { numberFormatValues } from '@documenso/ui/primitives/document-flow/field-items-advanced-settings/constants';
|
||||||
|
import type { TNumberFieldMeta as NumberFieldMeta } from '../types/field-meta';
|
||||||
interface NumberFieldMeta {
|
|
||||||
minValue?: number;
|
|
||||||
maxValue?: number;
|
|
||||||
readOnly?: boolean;
|
|
||||||
required?: boolean;
|
|
||||||
numberFormat?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validateNumberField = (
|
export const validateNumberField = (
|
||||||
value: string,
|
value: string,
|
||||||
@ -15,7 +8,7 @@ export const validateNumberField = (
|
|||||||
): string[] => {
|
): string[] => {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
const { minValue, maxValue, readOnly, required, numberFormat } = fieldMeta || {};
|
const { minValue, maxValue, readOnly, required, numberFormat, fontSize } = fieldMeta || {};
|
||||||
|
|
||||||
const formatRegex: { [key: string]: RegExp } = {
|
const formatRegex: { [key: string]: RegExp } = {
|
||||||
'123,456,789.00': /^(?:\d{1,3}(?:,\d{3})*|\d+)(?:\.\d{1,2})?$/,
|
'123,456,789.00': /^(?:\d{1,3}(?:,\d{3})*|\d+)(?:\.\d{1,2})?$/,
|
||||||
@ -63,5 +56,9 @@ export const validateNumberField = (
|
|||||||
errors.push('A field cannot be both read-only and required');
|
errors.push('A field cannot be both read-only and required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fontSize && (fontSize < 8 || fontSize > 96)) {
|
||||||
|
errors.push('Font size must be between 8 and 96.');
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
interface RadioFieldMeta {
|
import type { TRadioFieldMeta as RadioFieldMeta } from '../types/field-meta';
|
||||||
readOnly?: boolean;
|
|
||||||
required?: boolean;
|
|
||||||
values?: { checked: boolean; value: string }[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validateRadioField = (
|
export const validateRadioField = (
|
||||||
value: string | undefined,
|
value: string | undefined,
|
||||||
|
|||||||
@ -1,8 +1,4 @@
|
|||||||
interface TextFieldMeta {
|
import type { TTextFieldMeta as TextFieldMeta } from '../types/field-meta';
|
||||||
characterLimit?: number;
|
|
||||||
readOnly?: boolean;
|
|
||||||
required?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const validateTextField = (
|
export const validateTextField = (
|
||||||
value: string,
|
value: string,
|
||||||
@ -11,7 +7,7 @@ export const validateTextField = (
|
|||||||
): string[] => {
|
): string[] => {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
const { characterLimit, readOnly, required } = fieldMeta;
|
const { characterLimit, readOnly, required, fontSize } = fieldMeta;
|
||||||
|
|
||||||
if (required && !value && isSigningPage) {
|
if (required && !value && isSigningPage) {
|
||||||
errors.push('Value is required');
|
errors.push('Value is required');
|
||||||
@ -29,5 +25,9 @@ export const validateTextField = (
|
|||||||
errors.push('A field cannot be both read-only and required');
|
errors.push('A field cannot be both read-only and required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fontSize && (fontSize < 8 || fontSize > 96)) {
|
||||||
|
errors.push('Font size must be between 8 and 96.');
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
|
|
||||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||||
import { Field } from '@documenso/prisma/client';
|
import type { Field } from '@documenso/prisma/client';
|
||||||
|
|
||||||
export const useFieldPageCoords = (field: Field) => {
|
export const useFieldPageCoords = (field: Field) => {
|
||||||
const [coords, setCoords] = useState({
|
const [coords, setCoords] = useState({
|
||||||
|
|||||||
@ -15,9 +15,10 @@ type SupportedLanguages = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
|||||||
async function loadCatalog(lang: SupportedLanguages): Promise<{
|
async function loadCatalog(lang: SupportedLanguages): Promise<{
|
||||||
[k: string]: Messages;
|
[k: string]: Messages;
|
||||||
}> {
|
}> {
|
||||||
const { messages } = await import(
|
const extension = process.env.NODE_ENV === 'development' ? 'po' : 'js';
|
||||||
`../../translations/${lang}/${IS_APP_WEB ? 'web' : 'marketing'}.js`
|
const context = IS_APP_WEB ? 'web' : 'marketing';
|
||||||
);
|
|
||||||
|
const { messages } = await import(`../../translations/${lang}/${context}.${extension}`);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[lang]: messages,
|
[lang]: messages,
|
||||||
|
|||||||
@ -1,12 +1,19 @@
|
|||||||
import type { Recipient } from '@documenso/prisma/client';
|
import type { Recipient } from '@documenso/prisma/client';
|
||||||
import { ReadStatus, RecipientRole, SendStatus, SigningStatus } from '@documenso/prisma/client';
|
import { ReadStatus, RecipientRole, SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
export enum RecipientStatusType {
|
||||||
|
COMPLETED = 'completed',
|
||||||
|
OPENED = 'opened',
|
||||||
|
WAITING = 'waiting',
|
||||||
|
UNSIGNED = 'unsigned',
|
||||||
|
}
|
||||||
|
|
||||||
export const getRecipientType = (recipient: Recipient) => {
|
export const getRecipientType = (recipient: Recipient) => {
|
||||||
if (
|
if (
|
||||||
recipient.role === RecipientRole.CC ||
|
recipient.role === RecipientRole.CC ||
|
||||||
(recipient.sendStatus === SendStatus.SENT && recipient.signingStatus === SigningStatus.SIGNED)
|
(recipient.sendStatus === SendStatus.SENT && recipient.signingStatus === SigningStatus.SIGNED)
|
||||||
) {
|
) {
|
||||||
return 'completed';
|
return RecipientStatusType.COMPLETED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -14,12 +21,33 @@ export const getRecipientType = (recipient: Recipient) => {
|
|||||||
recipient.readStatus === ReadStatus.OPENED &&
|
recipient.readStatus === ReadStatus.OPENED &&
|
||||||
recipient.signingStatus === SigningStatus.NOT_SIGNED
|
recipient.signingStatus === SigningStatus.NOT_SIGNED
|
||||||
) {
|
) {
|
||||||
return 'opened';
|
return RecipientStatusType.OPENED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recipient.sendStatus === 'SENT' && recipient.signingStatus === 'NOT_SIGNED') {
|
if (
|
||||||
return 'waiting';
|
recipient.sendStatus === SendStatus.SENT &&
|
||||||
|
recipient.signingStatus === SigningStatus.NOT_SIGNED
|
||||||
|
) {
|
||||||
|
return RecipientStatusType.WAITING;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'unsigned';
|
return RecipientStatusType.UNSIGNED;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getExtraRecipientsType = (extraRecipients: Recipient[]) => {
|
||||||
|
const types = extraRecipients.map((r) => getRecipientType(r));
|
||||||
|
|
||||||
|
if (types.includes(RecipientStatusType.UNSIGNED)) {
|
||||||
|
return RecipientStatusType.UNSIGNED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.includes(RecipientStatusType.OPENED)) {
|
||||||
|
return RecipientStatusType.OPENED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (types.includes(RecipientStatusType.WAITING)) {
|
||||||
|
return RecipientStatusType.WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RecipientStatusType.COMPLETED;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
export const SUPPORTED_LANGUAGE_CODES = ['de', 'en'] as const;
|
export const SUPPORTED_LANGUAGE_CODES = ['de', 'en', 'fr'] as const;
|
||||||
|
|
||||||
export const ZSupportedLanguageCodeSchema = z.enum(SUPPORTED_LANGUAGE_CODES).catch('en');
|
export const ZSupportedLanguageCodeSchema = z.enum(SUPPORTED_LANGUAGE_CODES).catch('en');
|
||||||
|
|
||||||
@ -38,4 +38,8 @@ export const SUPPORTED_LANGUAGES: Record<string, SupportedLanguage> = {
|
|||||||
full: 'English',
|
full: 'English',
|
||||||
short: 'en',
|
short: 'en',
|
||||||
},
|
},
|
||||||
|
fr: {
|
||||||
|
full: 'French',
|
||||||
|
short: 'fr',
|
||||||
|
},
|
||||||
} satisfies Record<SupportedLanguageCodes, SupportedLanguage>;
|
} satisfies Record<SupportedLanguageCodes, SupportedLanguage>;
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"playwright": "1.43.0",
|
"playwright": "1.43.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
"remeda": "^1.27.1",
|
"remeda": "^2.12.1",
|
||||||
"sharp": "0.32.6",
|
"sharp": "0.32.6",
|
||||||
"stripe": "^12.7.0",
|
"stripe": "^12.7.0",
|
||||||
"ts-pattern": "^5.0.5",
|
"ts-pattern": "^5.0.5",
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export type FindDocumentsOptions = {
|
|||||||
};
|
};
|
||||||
period?: PeriodSelectorValue;
|
period?: PeriodSelectorValue;
|
||||||
senderIds?: number[];
|
senderIds?: number[];
|
||||||
|
search?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const findDocuments = async ({
|
export const findDocuments = async ({
|
||||||
@ -37,6 +38,7 @@ export const findDocuments = async ({
|
|||||||
orderBy,
|
orderBy,
|
||||||
period,
|
period,
|
||||||
senderIds,
|
senderIds,
|
||||||
|
search,
|
||||||
}: FindDocumentsOptions) => {
|
}: FindDocumentsOptions) => {
|
||||||
const { user, team } = await prisma.$transaction(async (tx) => {
|
const { user, team } = await prisma.$transaction(async (tx) => {
|
||||||
const user = await tx.user.findFirstOrThrow({
|
const user = await tx.user.findFirstOrThrow({
|
||||||
@ -92,6 +94,14 @@ export const findDocuments = async ({
|
|||||||
})
|
})
|
||||||
.otherwise(() => undefined);
|
.otherwise(() => undefined);
|
||||||
|
|
||||||
|
const searchFilter: Prisma.DocumentWhereInput = {
|
||||||
|
OR: [
|
||||||
|
{ title: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ Recipient: { some: { name: { contains: search, mode: 'insensitive' } } } },
|
||||||
|
{ Recipient: { some: { email: { contains: search, mode: 'insensitive' } } } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
const visibilityFilters = [
|
const visibilityFilters = [
|
||||||
match(teamMemberRole)
|
match(teamMemberRole)
|
||||||
.with(TeamMemberRole.ADMIN, () => ({
|
.with(TeamMemberRole.ADMIN, () => ({
|
||||||
@ -188,7 +198,7 @@ export const findDocuments = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const whereClause: Prisma.DocumentWhereInput = {
|
const whereClause: Prisma.DocumentWhereInput = {
|
||||||
AND: [{ ...termFilters }, { ...filters }, { ...deletedFilter }],
|
AND: [{ ...termFilters }, { ...filters }, { ...deletedFilter }, { ...searchFilter }],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (period) {
|
if (period) {
|
||||||
|
|||||||
@ -15,9 +15,10 @@ export type GetStatsInput = {
|
|||||||
user: User;
|
user: User;
|
||||||
team?: Omit<GetTeamCountsOption, 'createdAt'>;
|
team?: Omit<GetTeamCountsOption, 'createdAt'>;
|
||||||
period?: PeriodSelectorValue;
|
period?: PeriodSelectorValue;
|
||||||
|
search?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getStats = async ({ user, period, ...options }: GetStatsInput) => {
|
export const getStats = async ({ user, period, search, ...options }: GetStatsInput) => {
|
||||||
let createdAt: Prisma.DocumentWhereInput['createdAt'];
|
let createdAt: Prisma.DocumentWhereInput['createdAt'];
|
||||||
|
|
||||||
if (period) {
|
if (period) {
|
||||||
@ -31,8 +32,14 @@ export const getStats = async ({ user, period, ...options }: GetStatsInput) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [ownerCounts, notSignedCounts, hasSignedCounts] = await (options.team
|
const [ownerCounts, notSignedCounts, hasSignedCounts] = await (options.team
|
||||||
? getTeamCounts({ ...options.team, createdAt, currentUserEmail: user.email, userId: user.id })
|
? getTeamCounts({
|
||||||
: getCounts({ user, createdAt }));
|
...options.team,
|
||||||
|
createdAt,
|
||||||
|
currentUserEmail: user.email,
|
||||||
|
userId: user.id,
|
||||||
|
search,
|
||||||
|
})
|
||||||
|
: getCounts({ user, createdAt, search }));
|
||||||
|
|
||||||
const stats: Record<ExtendedDocumentStatus, number> = {
|
const stats: Record<ExtendedDocumentStatus, number> = {
|
||||||
[ExtendedDocumentStatus.DRAFT]: 0,
|
[ExtendedDocumentStatus.DRAFT]: 0,
|
||||||
@ -72,9 +79,18 @@ export const getStats = async ({ user, period, ...options }: GetStatsInput) => {
|
|||||||
type GetCountsOption = {
|
type GetCountsOption = {
|
||||||
user: User;
|
user: User;
|
||||||
createdAt: Prisma.DocumentWhereInput['createdAt'];
|
createdAt: Prisma.DocumentWhereInput['createdAt'];
|
||||||
|
search?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCounts = async ({ user, createdAt }: GetCountsOption) => {
|
const getCounts = async ({ user, createdAt, search }: GetCountsOption) => {
|
||||||
|
const searchFilter: Prisma.DocumentWhereInput = {
|
||||||
|
OR: [
|
||||||
|
{ title: { contains: search, mode: 'insensitive' } },
|
||||||
|
{ Recipient: { some: { name: { contains: search, mode: 'insensitive' } } } },
|
||||||
|
{ Recipient: { some: { email: { contains: search, mode: 'insensitive' } } } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
// Owner counts.
|
// Owner counts.
|
||||||
prisma.document.groupBy({
|
prisma.document.groupBy({
|
||||||
@ -87,6 +103,7 @@ const getCounts = async ({ user, createdAt }: GetCountsOption) => {
|
|||||||
createdAt,
|
createdAt,
|
||||||
teamId: null,
|
teamId: null,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
AND: [searchFilter],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// Not signed counts.
|
// Not signed counts.
|
||||||
@ -105,6 +122,7 @@ const getCounts = async ({ user, createdAt }: GetCountsOption) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
createdAt,
|
createdAt,
|
||||||
|
AND: [searchFilter],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
// Has signed counts.
|
// Has signed counts.
|
||||||
@ -142,6 +160,7 @@ const getCounts = async ({ user, createdAt }: GetCountsOption) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
AND: [searchFilter],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
@ -155,6 +174,7 @@ type GetTeamCountsOption = {
|
|||||||
userId: number;
|
userId: number;
|
||||||
createdAt: Prisma.DocumentWhereInput['createdAt'];
|
createdAt: Prisma.DocumentWhereInput['createdAt'];
|
||||||
currentTeamMemberRole?: TeamMemberRole;
|
currentTeamMemberRole?: TeamMemberRole;
|
||||||
|
search?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTeamCounts = async (options: GetTeamCountsOption) => {
|
const getTeamCounts = async (options: GetTeamCountsOption) => {
|
||||||
@ -169,6 +189,14 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
|
|||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const searchFilter: Prisma.DocumentWhereInput = {
|
||||||
|
OR: [
|
||||||
|
{ title: { contains: options.search, mode: 'insensitive' } },
|
||||||
|
{ Recipient: { some: { name: { contains: options.search, mode: 'insensitive' } } } },
|
||||||
|
{ Recipient: { some: { email: { contains: options.search, mode: 'insensitive' } } } },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
let ownerCountsWhereInput: Prisma.DocumentWhereInput = {
|
let ownerCountsWhereInput: Prisma.DocumentWhereInput = {
|
||||||
userId: userIdWhereClause,
|
userId: userIdWhereClause,
|
||||||
createdAt,
|
createdAt,
|
||||||
@ -220,6 +248,7 @@ const getTeamCounts = async (options: GetTeamCountsOption) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
...searchFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (teamEmail) {
|
if (teamEmail) {
|
||||||
|
|||||||
@ -93,11 +93,14 @@ export const updateDocumentSettings = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTitleSame = data.title === document.title;
|
const isTitleSame = data.title === undefined || data.title === document.title;
|
||||||
const isExternalIdSame = data.externalId === document.externalId;
|
const isExternalIdSame = data.externalId === undefined || data.externalId === document.externalId;
|
||||||
const isGlobalAccessSame = documentGlobalAccessAuth === newGlobalAccessAuth;
|
const isGlobalAccessSame =
|
||||||
const isGlobalActionSame = documentGlobalActionAuth === newGlobalActionAuth;
|
documentGlobalAccessAuth === undefined || documentGlobalAccessAuth === newGlobalAccessAuth;
|
||||||
const isDocumentVisibilitySame = data.visibility === document.visibility;
|
const isGlobalActionSame =
|
||||||
|
documentGlobalActionAuth === undefined || documentGlobalActionAuth === newGlobalActionAuth;
|
||||||
|
const isDocumentVisibilitySame =
|
||||||
|
data.visibility === undefined || data.visibility === document.visibility;
|
||||||
|
|
||||||
const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
|
const auditLogs: CreateDocumentAuditLogDataResponse[] = [];
|
||||||
|
|
||||||
@ -200,7 +203,7 @@ export const updateDocumentSettings = async ({
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
externalId: data.externalId || null,
|
externalId: data.externalId,
|
||||||
visibility: data.visibility as DocumentVisibility,
|
visibility: data.visibility as DocumentVisibility,
|
||||||
authOptions,
|
authOptions,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { isDeepEqual } from 'remeda';
|
||||||
|
|
||||||
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
import { validateCheckboxField } from '@documenso/lib/advanced-fields-validation/validate-checkbox';
|
||||||
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
import { validateDropdownField } from '@documenso/lib/advanced-fields-validation/validate-dropdown';
|
||||||
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
import { validateNumberField } from '@documenso/lib/advanced-fields-validation/validate-number';
|
||||||
@ -20,22 +22,15 @@ import {
|
|||||||
} from '@documenso/lib/utils/document-audit-logs';
|
} from '@documenso/lib/utils/document-audit-logs';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import type { Field } from '@documenso/prisma/client';
|
import type { Field } from '@documenso/prisma/client';
|
||||||
import { FieldType, SendStatus, SigningStatus } from '@documenso/prisma/client';
|
import { FieldType } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { canRecipientFieldsBeModified } from '../../utils/recipients';
|
||||||
|
|
||||||
export interface SetFieldsForDocumentOptions {
|
export interface SetFieldsForDocumentOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
documentId: number;
|
documentId: number;
|
||||||
fields: {
|
fields: FieldData[];
|
||||||
id?: number | null;
|
|
||||||
type: FieldType;
|
|
||||||
signerEmail: string;
|
|
||||||
pageNumber: number;
|
|
||||||
pageX: number;
|
|
||||||
pageY: number;
|
|
||||||
pageWidth: number;
|
|
||||||
pageHeight: number;
|
|
||||||
fieldMeta?: FieldMeta;
|
|
||||||
}[];
|
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata?: RequestMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +58,9 @@ export const setFieldsForDocument = async ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
Recipient: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await prisma.user.findFirstOrThrow({
|
const user = await prisma.user.findFirstOrThrow({
|
||||||
@ -97,21 +95,36 @@ export const setFieldsForDocument = async ({
|
|||||||
(existingField) => !fields.find((field) => field.id === existingField.id),
|
(existingField) => !fields.find((field) => field.id === existingField.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
const linkedFields = fields
|
const linkedFields = fields.map((field) => {
|
||||||
.map((field) => {
|
const existing = existingFields.find((existingField) => existingField.id === field.id);
|
||||||
const existing = existingFields.find((existingField) => existingField.id === field.id);
|
|
||||||
|
|
||||||
return {
|
const recipient = document.Recipient.find(
|
||||||
...field,
|
(recipient) => recipient.email.toLowerCase() === field.signerEmail.toLowerCase(),
|
||||||
_persisted: existing,
|
);
|
||||||
};
|
|
||||||
})
|
// Each field MUST have a recipient associated with it.
|
||||||
.filter((field) => {
|
if (!recipient) {
|
||||||
return (
|
throw new AppError(AppErrorCode.INVALID_REQUEST, `Recipient not found for field ${field.id}`);
|
||||||
field._persisted?.Recipient?.sendStatus !== SendStatus.SENT &&
|
}
|
||||||
field._persisted?.Recipient?.signingStatus !== SigningStatus.SIGNED
|
|
||||||
|
// Check whether the existing field can be modified.
|
||||||
|
if (
|
||||||
|
existing &&
|
||||||
|
hasFieldBeenChanged(existing, field) &&
|
||||||
|
!canRecipientFieldsBeModified(recipient, existingFields)
|
||||||
|
) {
|
||||||
|
throw new AppError(
|
||||||
|
AppErrorCode.INVALID_REQUEST,
|
||||||
|
'Cannot modify a field where the recipient has already interacted with the document',
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...field,
|
||||||
|
_persisted: existing,
|
||||||
|
_recipient: recipient,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const persistedFields = await prisma.$transaction(async (tx) => {
|
const persistedFields = await prisma.$transaction(async (tx) => {
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
@ -322,3 +335,33 @@ export const setFieldsForDocument = async ({
|
|||||||
|
|
||||||
return [...filteredFields, ...persistedFields];
|
return [...filteredFields, ...persistedFields];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you change this you MUST update the `hasFieldBeenChanged` function.
|
||||||
|
*/
|
||||||
|
type FieldData = {
|
||||||
|
id?: number | null;
|
||||||
|
type: FieldType;
|
||||||
|
signerEmail: string;
|
||||||
|
pageNumber: number;
|
||||||
|
pageX: number;
|
||||||
|
pageY: number;
|
||||||
|
pageWidth: number;
|
||||||
|
pageHeight: number;
|
||||||
|
fieldMeta?: FieldMeta;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasFieldBeenChanged = (field: Field, newFieldData: FieldData) => {
|
||||||
|
const currentFieldMeta = field.fieldMeta || null;
|
||||||
|
const newFieldMeta = newFieldData.fieldMeta || null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
field.type !== newFieldData.type ||
|
||||||
|
field.page !== newFieldData.pageNumber ||
|
||||||
|
field.positionX.toNumber() !== newFieldData.pageX ||
|
||||||
|
field.positionY.toNumber() !== newFieldData.pageY ||
|
||||||
|
field.width.toNumber() !== newFieldData.pageWidth ||
|
||||||
|
field.height.toNumber() !== newFieldData.pageHeight ||
|
||||||
|
!isDeepEqual(currentFieldMeta, newFieldMeta)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -4,5 +4,8 @@ import { cookies } from 'next/headers';
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/require-await
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
export const switchI18NLanguage = async (lang: string) => {
|
export const switchI18NLanguage = async (lang: string) => {
|
||||||
cookies().set('language', lang);
|
// Two year expiry.
|
||||||
|
const maxAge = 60 * 60 * 24 * 365 * 2;
|
||||||
|
|
||||||
|
cookies().set('language', lang, { maxAge });
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
// https://github.com/Hopding/pdf-lib/issues/20#issuecomment-412852821
|
// https://github.com/Hopding/pdf-lib/issues/20#issuecomment-412852821
|
||||||
import fontkit from '@pdf-lib/fontkit';
|
import fontkit from '@pdf-lib/fontkit';
|
||||||
import { PDFDocument, RotationTypes, degrees, radiansToDegrees } from 'pdf-lib';
|
import type { PDFDocument } from 'pdf-lib';
|
||||||
|
import { RotationTypes, degrees, radiansToDegrees } from 'pdf-lib';
|
||||||
import { P, match } from 'ts-pattern';
|
import { P, match } from 'ts-pattern';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -13,7 +14,16 @@ import { FieldType } from '@documenso/prisma/client';
|
|||||||
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
|
import { isSignatureFieldType } from '@documenso/prisma/guards/is-signature-field';
|
||||||
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
|
||||||
|
|
||||||
import { ZCheckboxFieldMeta, ZRadioFieldMeta } from '../../types/field-meta';
|
import {
|
||||||
|
ZCheckboxFieldMeta,
|
||||||
|
ZDateFieldMeta,
|
||||||
|
ZEmailFieldMeta,
|
||||||
|
ZInitialsFieldMeta,
|
||||||
|
ZNameFieldMeta,
|
||||||
|
ZNumberFieldMeta,
|
||||||
|
ZRadioFieldMeta,
|
||||||
|
ZTextFieldMeta,
|
||||||
|
} from '../../types/field-meta';
|
||||||
|
|
||||||
export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignature) => {
|
export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignature) => {
|
||||||
const fontCaveat = await fetch(process.env.FONT_CAVEAT_URI).then(async (res) =>
|
const fontCaveat = await fetch(process.env.FONT_CAVEAT_URI).then(async (res) =>
|
||||||
@ -32,7 +42,6 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
|
|
||||||
const minFontSize = isSignatureField ? MIN_HANDWRITING_FONT_SIZE : MIN_STANDARD_FONT_SIZE;
|
const minFontSize = isSignatureField ? MIN_HANDWRITING_FONT_SIZE : MIN_STANDARD_FONT_SIZE;
|
||||||
const maxFontSize = isSignatureField ? DEFAULT_HANDWRITING_FONT_SIZE : DEFAULT_STANDARD_FONT_SIZE;
|
const maxFontSize = isSignatureField ? DEFAULT_HANDWRITING_FONT_SIZE : DEFAULT_STANDARD_FONT_SIZE;
|
||||||
let fontSize = maxFontSize;
|
|
||||||
|
|
||||||
const page = pages.at(field.page - 1);
|
const page = pages.at(field.page - 1);
|
||||||
|
|
||||||
@ -207,16 +216,33 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.otherwise((field) => {
|
.otherwise((field) => {
|
||||||
|
const fieldMetaParsers = {
|
||||||
|
[FieldType.TEXT]: ZTextFieldMeta,
|
||||||
|
[FieldType.NUMBER]: ZNumberFieldMeta,
|
||||||
|
[FieldType.DATE]: ZDateFieldMeta,
|
||||||
|
[FieldType.EMAIL]: ZEmailFieldMeta,
|
||||||
|
[FieldType.NAME]: ZNameFieldMeta,
|
||||||
|
[FieldType.INITIALS]: ZInitialsFieldMeta,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const Parser = fieldMetaParsers[field.type as keyof typeof fieldMetaParsers];
|
||||||
|
const meta = Parser ? Parser.safeParse(field.fieldMeta) : null;
|
||||||
|
|
||||||
|
const customFontSize = meta?.success && meta.data.fontSize ? meta.data.fontSize : null;
|
||||||
const longestLineInTextForWidth = field.customText
|
const longestLineInTextForWidth = field.customText
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.sort((a, b) => b.length - a.length)[0];
|
.sort((a, b) => b.length - a.length)[0];
|
||||||
|
|
||||||
|
let fontSize = customFontSize || maxFontSize;
|
||||||
let textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
|
let textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
|
||||||
const textHeight = font.heightAtSize(fontSize);
|
const textHeight = font.heightAtSize(fontSize);
|
||||||
|
|
||||||
const scalingFactor = Math.min(fieldWidth / textWidth, fieldHeight / textHeight, 1);
|
if (!customFontSize) {
|
||||||
|
const scalingFactor = Math.min(fieldWidth / textWidth, fieldHeight / textHeight, 1);
|
||||||
|
fontSize = Math.max(Math.min(fontSize * scalingFactor, maxFontSize), minFontSize);
|
||||||
|
}
|
||||||
|
|
||||||
fontSize = Math.max(Math.min(fontSize * scalingFactor, maxFontSize), minFontSize);
|
|
||||||
textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
|
textWidth = font.widthOfTextAtSize(longestLineInTextForWidth, fontSize);
|
||||||
|
|
||||||
let textX = fieldX + (fieldWidth - textWidth) / 2;
|
let textX = fieldX + (fieldWidth - textWidth) / 2;
|
||||||
@ -250,17 +276,6 @@ export const insertFieldInPDF = async (pdf: PDFDocument, field: FieldWithSignatu
|
|||||||
return pdf;
|
return pdf;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const insertFieldInPDFBytes = async (
|
|
||||||
pdf: ArrayBuffer | Uint8Array | string,
|
|
||||||
field: FieldWithSignature,
|
|
||||||
) => {
|
|
||||||
const pdfDoc = await PDFDocument.load(pdf);
|
|
||||||
|
|
||||||
await insertFieldInPDF(pdfDoc, field);
|
|
||||||
|
|
||||||
return await pdfDoc.save();
|
|
||||||
};
|
|
||||||
|
|
||||||
const adjustPositionForRotation = (
|
const adjustPositionForRotation = (
|
||||||
pageWidth: number,
|
pageWidth: number,
|
||||||
pageHeight: number,
|
pageHeight: number,
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export async function insertTextInPDF(
|
|||||||
positionY: number,
|
positionY: number,
|
||||||
page = 0,
|
page = 0,
|
||||||
useHandwritingFont = true,
|
useHandwritingFont = true,
|
||||||
|
customFontSize?: number,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
// Fetch the font file from the public URL.
|
// Fetch the font file from the public URL.
|
||||||
const fontResponse = await fetch(CAVEAT_FONT_PATH());
|
const fontResponse = await fetch(CAVEAT_FONT_PATH());
|
||||||
@ -24,7 +25,7 @@ export async function insertTextInPDF(
|
|||||||
const pages = pdfDoc.getPages();
|
const pages = pdfDoc.getPages();
|
||||||
const pdfPage = pages[page];
|
const pdfPage = pages[page];
|
||||||
|
|
||||||
const textSize = useHandwritingFont ? 50 : 15;
|
const textSize = customFontSize || (useHandwritingFont ? 50 : 15);
|
||||||
const textWidth = font.widthOfTextAtSize(text, textSize);
|
const textWidth = font.widthOfTextAtSize(text, textSize);
|
||||||
const textHeight = font.heightAtSize(textSize);
|
const textHeight = font.heightAtSize(textSize);
|
||||||
const fieldSize = { width: 250, height: 64 };
|
const fieldSize = { width: 250, height: 64 };
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
|
import { createElement } from 'react';
|
||||||
|
|
||||||
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-enterprise';
|
||||||
|
import { mailer } from '@documenso/email/mailer';
|
||||||
|
import { render } from '@documenso/email/render';
|
||||||
|
import RecipientRemovedFromDocumentTemplate from '@documenso/email/templates/recipient-removed-from-document';
|
||||||
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
|
||||||
import {
|
import {
|
||||||
type TRecipientActionAuthTypes,
|
type TRecipientActionAuthTypes,
|
||||||
@ -16,20 +21,16 @@ import type { Recipient } from '@documenso/prisma/client';
|
|||||||
import { RecipientRole } from '@documenso/prisma/client';
|
import { RecipientRole } from '@documenso/prisma/client';
|
||||||
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
||||||
|
import { FROM_ADDRESS, FROM_NAME } from '../../constants/email';
|
||||||
import { AppError, AppErrorCode } from '../../errors/app-error';
|
import { AppError, AppErrorCode } from '../../errors/app-error';
|
||||||
|
import { canRecipientBeModified } from '../../utils/recipients';
|
||||||
|
|
||||||
export interface SetRecipientsForDocumentOptions {
|
export interface SetRecipientsForDocumentOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
teamId?: number;
|
teamId?: number;
|
||||||
documentId: number;
|
documentId: number;
|
||||||
recipients: {
|
recipients: RecipientData[];
|
||||||
id?: number | null;
|
|
||||||
email: string;
|
|
||||||
name: string;
|
|
||||||
role: RecipientRole;
|
|
||||||
signingOrder?: number | null;
|
|
||||||
actionAuth?: TRecipientActionAuthTypes | null;
|
|
||||||
}[];
|
|
||||||
requestMetadata?: RequestMetadata;
|
requestMetadata?: RequestMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +60,9 @@ export const setRecipientsForDocument = async ({
|
|||||||
teamId: null,
|
teamId: null,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
Field: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await prisma.user.findFirstOrThrow({
|
const user = await prisma.user.findFirstOrThrow({
|
||||||
@ -116,25 +120,28 @@ export const setRecipientsForDocument = async ({
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const linkedRecipients = normalizedRecipients
|
const linkedRecipients = normalizedRecipients.map((recipient) => {
|
||||||
.map((recipient) => {
|
const existing = existingRecipients.find(
|
||||||
const existing = existingRecipients.find(
|
(existingRecipient) =>
|
||||||
(existingRecipient) =>
|
existingRecipient.id === recipient.id || existingRecipient.email === recipient.email,
|
||||||
existingRecipient.id === recipient.id || existingRecipient.email === recipient.email,
|
);
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
if (
|
||||||
...recipient,
|
existing &&
|
||||||
_persisted: existing,
|
hasRecipientBeenChanged(existing, recipient) &&
|
||||||
};
|
!canRecipientBeModified(existing, document.Field)
|
||||||
})
|
) {
|
||||||
.filter((recipient) => {
|
throw new AppError(
|
||||||
return (
|
AppErrorCode.INVALID_REQUEST,
|
||||||
recipient._persisted?.role === RecipientRole.CC ||
|
'Cannot modify a recipient who has already interacted with the document',
|
||||||
(recipient._persisted?.sendStatus !== SendStatus.SENT &&
|
|
||||||
recipient._persisted?.signingStatus !== SigningStatus.SIGNED)
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...recipient,
|
||||||
|
_persisted: existing,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const persistedRecipients = await prisma.$transaction(async (tx) => {
|
const persistedRecipients = await prisma.$transaction(async (tx) => {
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
@ -268,6 +275,37 @@ export const setRecipientsForDocument = async ({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Send emails to deleted recipients.
|
||||||
|
await Promise.all(
|
||||||
|
removedRecipients.map(async (recipient) => {
|
||||||
|
if (recipient.sendStatus !== SendStatus.SENT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetBaseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
||||||
|
|
||||||
|
const template = createElement(RecipientRemovedFromDocumentTemplate, {
|
||||||
|
documentName: document.title,
|
||||||
|
inviterName: user.name || undefined,
|
||||||
|
assetBaseUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
await mailer.sendMail({
|
||||||
|
to: {
|
||||||
|
address: recipient.email,
|
||||||
|
name: recipient.name,
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
name: FROM_NAME,
|
||||||
|
address: FROM_ADDRESS,
|
||||||
|
},
|
||||||
|
subject: 'You have been removed from a document',
|
||||||
|
html: render(template),
|
||||||
|
text: render(template, { plainText: true }),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter out recipients that have been removed or have been updated.
|
// Filter out recipients that have been removed or have been updated.
|
||||||
@ -284,3 +322,27 @@ export const setRecipientsForDocument = async ({
|
|||||||
|
|
||||||
return [...filteredRecipients, ...persistedRecipients];
|
return [...filteredRecipients, ...persistedRecipients];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If you change this you MUST update the `hasRecipientBeenChanged` function.
|
||||||
|
*/
|
||||||
|
type RecipientData = {
|
||||||
|
id?: number | null;
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
role: RecipientRole;
|
||||||
|
signingOrder?: number | null;
|
||||||
|
actionAuth?: TRecipientActionAuthTypes | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasRecipientBeenChanged = (recipient: Recipient, newRecipientData: RecipientData) => {
|
||||||
|
const authOptions = ZRecipientAuthOptionsSchema.parse(recipient.authOptions);
|
||||||
|
|
||||||
|
return (
|
||||||
|
recipient.email !== newRecipientData.email ||
|
||||||
|
recipient.name !== newRecipientData.name ||
|
||||||
|
recipient.role !== newRecipientData.role ||
|
||||||
|
recipient.signingOrder !== newRecipientData.signingOrder ||
|
||||||
|
authOptions.actionAuth !== newRecipientData.actionAuth
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { nanoid } from '@documenso/lib/universal/id';
|
import { nanoid } from '@documenso/lib/universal/id';
|
||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import type { DocumentSigningOrder, Field } from '@documenso/prisma/client';
|
|
||||||
import {
|
import {
|
||||||
|
DocumentSigningOrder,
|
||||||
DocumentSource,
|
DocumentSource,
|
||||||
|
type Field,
|
||||||
type Recipient,
|
type Recipient,
|
||||||
RecipientRole,
|
RecipientRole,
|
||||||
SendStatus,
|
SendStatus,
|
||||||
@ -153,7 +154,7 @@ export const createDocumentFromTemplate = async ({
|
|||||||
const document = await tx.document.create({
|
const document = await tx.document.create({
|
||||||
data: {
|
data: {
|
||||||
source: DocumentSource.TEMPLATE,
|
source: DocumentSource.TEMPLATE,
|
||||||
externalId,
|
externalId: externalId || template.externalId,
|
||||||
templateId: template.id,
|
templateId: template.id,
|
||||||
userId,
|
userId,
|
||||||
teamId: template.teamId,
|
teamId: template.teamId,
|
||||||
@ -172,7 +173,9 @@ export const createDocumentFromTemplate = async ({
|
|||||||
dateFormat: override?.dateFormat || template.templateMeta?.dateFormat,
|
dateFormat: override?.dateFormat || template.templateMeta?.dateFormat,
|
||||||
redirectUrl: override?.redirectUrl || template.templateMeta?.redirectUrl,
|
redirectUrl: override?.redirectUrl || template.templateMeta?.redirectUrl,
|
||||||
signingOrder:
|
signingOrder:
|
||||||
override?.signingOrder || template.templateMeta?.signingOrder || undefined,
|
override?.signingOrder ||
|
||||||
|
template.templateMeta?.signingOrder ||
|
||||||
|
DocumentSigningOrder.PARALLEL,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Recipient: {
|
Recipient: {
|
||||||
|
|||||||
@ -100,7 +100,7 @@ export const updateTemplateSettings = async ({
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
title: data.title,
|
title: data.title,
|
||||||
externalId: data.externalId || null,
|
externalId: data.externalId,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
publicDescription: data.publicDescription,
|
publicDescription: data.publicDescription,
|
||||||
publicTitle: data.publicTitle,
|
publicTitle: data.publicTitle,
|
||||||
|
|||||||
4
packages/lib/translations/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# Compiled translations.
|
||||||
|
*.js
|
||||||
@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
"Project-Id-Version: documenso-app\n"
|
"Project-Id-Version: documenso-app\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"PO-Revision-Date: 2024-09-05 06:04\n"
|
"PO-Revision-Date: 2024-10-08 12:05\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: German\n"
|
"Language-Team: German\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
@ -223,6 +223,7 @@ msgstr "Aus dem Blog"
|
|||||||
|
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:9
|
#: apps/marketing/src/app/(marketing)/open/data.ts:9
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:17
|
#: apps/marketing/src/app/(marketing)/open/data.ts:17
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:33
|
#: apps/marketing/src/app/(marketing)/open/data.ts:33
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:41
|
#: apps/marketing/src/app/(marketing)/open/data.ts:41
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:49
|
#: apps/marketing/src/app/(marketing)/open/data.ts:49
|
||||||
@ -385,8 +386,8 @@ msgid "Our self-hosted option is great for small teams and individuals who need
|
|||||||
msgstr "Unsere selbstgehostete Option ist ideal für kleine Teams und Einzelpersonen, die eine einfache Lösung benötigen. Sie können unser docker-basiertes Setup verwenden, um in wenigen Minuten loszulegen. Übernehmen Sie die Kontrolle mit vollständiger Anpassbarkeit und Datenhoheit."
|
msgstr "Unsere selbstgehostete Option ist ideal für kleine Teams und Einzelpersonen, die eine einfache Lösung benötigen. Sie können unser docker-basiertes Setup verwenden, um in wenigen Minuten loszulegen. Übernehmen Sie die Kontrolle mit vollständiger Anpassbarkeit und Datenhoheit."
|
||||||
|
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||||
msgid "Part-Time"
|
#~ msgid "Part-Time"
|
||||||
msgstr "Teilzeit"
|
#~ msgstr "Part-Time"
|
||||||
|
|
||||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||||
msgid "Premium Profile Name"
|
msgid "Premium Profile Name"
|
||||||
@ -431,7 +432,7 @@ msgstr "Sparen Sie $60 oder $120"
|
|||||||
|
|
||||||
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||||
#~ msgid "Search languages..."
|
#~ msgid "Search languages..."
|
||||||
#~ msgstr "Sprachen suchen..."
|
#~ msgstr "Search languages..."
|
||||||
|
|
||||||
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||||
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||||
@ -614,6 +615,6 @@ msgstr "Ja! Documenso wird unter der GNU AGPL V3 Open-Source-Lizenz angeboten. D
|
|||||||
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||||
msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatzbereite gehostete Version nutzen. Die gehostete Version bietet zusätzlichen Support, schmerzfreie Skalierbarkeit und mehr. Frühzeitige Anwender erhalten in diesem Jahr Zugriff auf alle Funktionen, die wir entwickeln, ohne zusätzliche Kosten! Für immer! Ja, das beinhaltet später mehrere Benutzer pro Konto. Wenn Sie Documenso für Ihr Unternehmen möchten, sprechen wir gerne über Ihre Bedürfnisse."
|
msgstr "Sie können Documenso kostenlos selbst hosten oder unsere sofort einsatzbereite gehostete Version nutzen. Die gehostete Version bietet zusätzlichen Support, schmerzfreie Skalierbarkeit und mehr. Frühzeitige Anwender erhalten in diesem Jahr Zugriff auf alle Funktionen, die wir entwickeln, ohne zusätzliche Kosten! Für immer! Ja, das beinhaltet später mehrere Benutzer pro Konto. Wenn Sie Documenso für Ihr Unternehmen möchten, sprechen wir gerne über Ihre Bedürfnisse."
|
||||||
|
|
||||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:265
|
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||||
msgid "Your browser does not support the video tag."
|
msgid "Your browser does not support the video tag."
|
||||||
msgstr "Ihr Browser unterstützt das Video-Tag nicht."
|
msgstr "Ihr Browser unterstützt das Video-Tag nicht."
|
||||||
|
|||||||
@ -68,16 +68,16 @@ msgstr "Add an external ID to the document. This can be used to identify the doc
|
|||||||
msgid "Add an external ID to the template. This can be used to identify in external systems."
|
msgid "Add an external ID to the template. This can be used to identify in external systems."
|
||||||
msgstr "Add an external ID to the template. This can be used to identify in external systems."
|
msgstr "Add an external ID to the template. This can be used to identify in external systems."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:177
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:187
|
||||||
msgid "Add another option"
|
msgid "Add another option"
|
||||||
msgstr "Add another option"
|
msgstr "Add another option"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:230
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:232
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:167
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:167
|
||||||
msgid "Add another value"
|
msgid "Add another value"
|
||||||
msgstr "Add another value"
|
msgstr "Add another value"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:653
|
#: packages/ui/primitives/document-flow/add-signers.tsx:662
|
||||||
msgid "Add myself"
|
msgid "Add myself"
|
||||||
msgstr "Add myself"
|
msgstr "Add myself"
|
||||||
|
|
||||||
@ -89,15 +89,15 @@ msgstr "Add Myself"
|
|||||||
msgid "Add Placeholder Recipient"
|
msgid "Add Placeholder Recipient"
|
||||||
msgstr "Add Placeholder Recipient"
|
msgstr "Add Placeholder Recipient"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:642
|
#: packages/ui/primitives/document-flow/add-signers.tsx:651
|
||||||
msgid "Add Signer"
|
msgid "Add Signer"
|
||||||
msgstr "Add Signer"
|
msgstr "Add Signer"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:70
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:73
|
||||||
msgid "Add text"
|
msgid "Add text"
|
||||||
msgstr "Add text"
|
msgstr "Add text"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:75
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:78
|
||||||
msgid "Add text to the field"
|
msgid "Add text to the field"
|
||||||
msgstr "Add text to the field"
|
msgstr "Add text to the field"
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ msgstr "Admin"
|
|||||||
msgid "Advanced Options"
|
msgid "Advanced Options"
|
||||||
msgstr "Advanced Options"
|
msgstr "Advanced Options"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:522
|
#: packages/ui/primitives/document-flow/add-fields.tsx:565
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:402
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:402
|
||||||
msgid "Advanced settings"
|
msgid "Advanced settings"
|
||||||
msgstr "Advanced settings"
|
msgstr "Advanced settings"
|
||||||
@ -143,15 +143,19 @@ msgstr "Black"
|
|||||||
msgid "Blue"
|
msgid "Blue"
|
||||||
msgstr "Blue"
|
msgstr "Blue"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:287
|
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:356
|
||||||
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:58
|
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:58
|
||||||
msgid "Cancel"
|
msgid "Cancel"
|
||||||
msgstr "Cancel"
|
msgstr "Cancel"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:194
|
#: packages/ui/primitives/document-flow/add-signers.tsx:193
|
||||||
msgid "Cannot remove signer"
|
msgid "Cannot remove signer"
|
||||||
msgstr "Cannot remove signer"
|
msgstr "Cannot remove signer"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:221
|
||||||
|
#~ msgid "Cannot update signer because they have already signed a field"
|
||||||
|
#~ msgstr "Cannot update signer because they have already signed a field"
|
||||||
|
|
||||||
#: packages/lib/constants/recipient-roles.ts:17
|
#: packages/lib/constants/recipient-roles.ts:17
|
||||||
msgid "Cc"
|
msgid "Cc"
|
||||||
msgstr "Cc"
|
msgstr "Cc"
|
||||||
@ -165,16 +169,16 @@ msgstr "CC"
|
|||||||
msgid "CC'd"
|
msgid "CC'd"
|
||||||
msgstr "CC'd"
|
msgstr "CC'd"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:83
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:86
|
||||||
msgid "Character Limit"
|
msgid "Character Limit"
|
||||||
msgstr "Character Limit"
|
msgstr "Character Limit"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:944
|
#: packages/ui/primitives/document-flow/add-fields.tsx:993
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:788
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:788
|
||||||
msgid "Checkbox"
|
msgid "Checkbox"
|
||||||
msgstr "Checkbox"
|
msgstr "Checkbox"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:195
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:197
|
||||||
msgid "Checkbox values"
|
msgid "Checkbox values"
|
||||||
msgstr "Checkbox values"
|
msgstr "Checkbox values"
|
||||||
|
|
||||||
@ -198,7 +202,7 @@ msgstr "Close"
|
|||||||
msgid "Configure Direct Recipient"
|
msgid "Configure Direct Recipient"
|
||||||
msgstr "Configure Direct Recipient"
|
msgstr "Configure Direct Recipient"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:523
|
#: packages/ui/primitives/document-flow/add-fields.tsx:566
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:403
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:403
|
||||||
msgid "Configure the {0} field"
|
msgid "Configure the {0} field"
|
||||||
msgstr "Configure the {0} field"
|
msgstr "Configure the {0} field"
|
||||||
@ -215,7 +219,7 @@ msgstr "Copied to clipboard"
|
|||||||
msgid "Custom Text"
|
msgid "Custom Text"
|
||||||
msgstr "Custom Text"
|
msgstr "Custom Text"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:840
|
#: packages/ui/primitives/document-flow/add-fields.tsx:889
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:684
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:684
|
||||||
msgid "Date"
|
msgid "Date"
|
||||||
msgstr "Date"
|
msgstr "Date"
|
||||||
@ -247,18 +251,18 @@ msgstr "Download"
|
|||||||
msgid "Drag & drop your PDF here."
|
msgid "Drag & drop your PDF here."
|
||||||
msgstr "Drag & drop your PDF here."
|
msgstr "Drag & drop your PDF here."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:970
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1019
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:814
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:814
|
||||||
msgid "Dropdown"
|
msgid "Dropdown"
|
||||||
msgstr "Dropdown"
|
msgstr "Dropdown"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:148
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:158
|
||||||
msgid "Dropdown options"
|
msgid "Dropdown options"
|
||||||
msgstr "Dropdown options"
|
msgstr "Dropdown options"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:788
|
#: packages/ui/primitives/document-flow/add-fields.tsx:837
|
||||||
#: packages/ui/primitives/document-flow/add-signature.tsx:272
|
#: packages/ui/primitives/document-flow/add-signature.tsx:272
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:491
|
#: packages/ui/primitives/document-flow/add-signers.tsx:500
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:632
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:632
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
|
||||||
@ -269,11 +273,15 @@ msgstr "Email"
|
|||||||
msgid "Email Options"
|
msgid "Email Options"
|
||||||
msgstr "Email Options"
|
msgstr "Email Options"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1082
|
||||||
|
msgid "Empty field"
|
||||||
|
msgstr "Empty field"
|
||||||
|
|
||||||
#: packages/lib/constants/template.ts:8
|
#: packages/lib/constants/template.ts:8
|
||||||
msgid "Enable Direct Link Signing"
|
msgid "Enable Direct Link Signing"
|
||||||
msgstr "Enable Direct Link Signing"
|
msgstr "Enable Direct Link Signing"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:392
|
#: packages/ui/primitives/document-flow/add-signers.tsx:401
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362
|
||||||
msgid "Enable signing order"
|
msgid "Enable signing order"
|
||||||
msgstr "Enable signing order"
|
msgstr "Enable signing order"
|
||||||
@ -282,7 +290,7 @@ msgstr "Enable signing order"
|
|||||||
msgid "Enter password"
|
msgid "Enter password"
|
||||||
msgstr "Enter password"
|
msgstr "Enter password"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:216
|
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:257
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Error"
|
msgstr "Error"
|
||||||
|
|
||||||
@ -291,26 +299,44 @@ msgstr "Error"
|
|||||||
msgid "External ID"
|
msgid "External ID"
|
||||||
msgstr "External ID"
|
msgstr "External ID"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:217
|
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
|
||||||
msgid "Failed to save settings."
|
msgid "Failed to save settings."
|
||||||
msgstr "Failed to save settings."
|
msgstr "Failed to save settings."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:90
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:93
|
||||||
msgid "Field character limit"
|
msgid "Field character limit"
|
||||||
msgstr "Field character limit"
|
msgstr "Field character limit"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:107
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:62
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:44
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:44
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx:44
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:130
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:107
|
||||||
|
msgid "Field font size"
|
||||||
|
msgstr "Field font size"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:110
|
||||||
msgid "Field format"
|
msgid "Field format"
|
||||||
msgstr "Field format"
|
msgstr "Field format"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:50
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:53
|
||||||
msgid "Field label"
|
msgid "Field label"
|
||||||
msgstr "Field label"
|
msgstr "Field label"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:62
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:65
|
||||||
msgid "Field placeholder"
|
msgid "Field placeholder"
|
||||||
msgstr "Field placeholder"
|
msgstr "Field placeholder"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx:38
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:124
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:101
|
||||||
|
msgid "Font Size"
|
||||||
|
msgstr "Font Size"
|
||||||
|
|
||||||
#: packages/ui/components/document/document-global-auth-action-select.tsx:64
|
#: packages/ui/components/document/document-global-auth-action-select.tsx:64
|
||||||
msgid "Global recipient action authentication"
|
msgid "Global recipient action authentication"
|
||||||
msgstr "Global recipient action authentication"
|
msgstr "Global recipient action authentication"
|
||||||
@ -348,9 +374,9 @@ msgstr "I am required to receive a copy of this document"
|
|||||||
msgid "Inherit authentication method"
|
msgid "Inherit authentication method"
|
||||||
msgstr "Inherit authentication method"
|
msgstr "Inherit authentication method"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:64
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:67
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:69
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:72
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:45
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:48
|
||||||
msgid "Label"
|
msgid "Label"
|
||||||
msgstr "Label"
|
msgstr "Label"
|
||||||
|
|
||||||
@ -358,7 +384,7 @@ msgstr "Label"
|
|||||||
msgid "Manager"
|
msgid "Manager"
|
||||||
msgstr "Manager"
|
msgstr "Manager"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:168
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:188
|
||||||
msgid "Max"
|
msgid "Max"
|
||||||
msgstr "Max"
|
msgstr "Max"
|
||||||
|
|
||||||
@ -371,14 +397,14 @@ msgstr "Member"
|
|||||||
msgid "Message <0>(Optional)</0>"
|
msgid "Message <0>(Optional)</0>"
|
||||||
msgstr "Message <0>(Optional)</0>"
|
msgstr "Message <0>(Optional)</0>"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:156
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:176
|
||||||
msgid "Min"
|
msgid "Min"
|
||||||
msgstr "Min"
|
msgstr "Min"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:814
|
#: packages/ui/primitives/document-flow/add-fields.tsx:863
|
||||||
#: packages/ui/primitives/document-flow/add-signature.tsx:298
|
#: packages/ui/primitives/document-flow/add-signature.tsx:298
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:526
|
#: packages/ui/primitives/document-flow/add-signers.tsx:535
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:532
|
#: packages/ui/primitives/document-flow/add-signers.tsx:541
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:658
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:658
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504
|
||||||
@ -397,12 +423,12 @@ msgstr "Needs to sign"
|
|||||||
msgid "Needs to view"
|
msgid "Needs to view"
|
||||||
msgstr "Needs to view"
|
msgstr "Needs to view"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:625
|
#: packages/ui/primitives/document-flow/add-fields.tsx:674
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:497
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:497
|
||||||
msgid "No recipient matching this description was found."
|
msgid "No recipient matching this description was found."
|
||||||
msgstr "No recipient matching this description was found."
|
msgstr "No recipient matching this description was found."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:641
|
#: packages/ui/primitives/document-flow/add-fields.tsx:690
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:513
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:513
|
||||||
msgid "No recipients with this role"
|
msgid "No recipients with this role"
|
||||||
msgstr "No recipients with this role"
|
msgstr "No recipients with this role"
|
||||||
@ -427,12 +453,12 @@ msgstr "No signature field found"
|
|||||||
msgid "No value found."
|
msgid "No value found."
|
||||||
msgstr "No value found."
|
msgstr "No value found."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:892
|
#: packages/ui/primitives/document-flow/add-fields.tsx:941
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:736
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:736
|
||||||
msgid "Number"
|
msgid "Number"
|
||||||
msgstr "Number"
|
msgstr "Number"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:100
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:103
|
||||||
msgid "Number format"
|
msgid "Number format"
|
||||||
msgstr "Number format"
|
msgstr "Number format"
|
||||||
|
|
||||||
@ -452,17 +478,17 @@ msgstr "Page {0} of {1}"
|
|||||||
msgid "Password Required"
|
msgid "Password Required"
|
||||||
msgstr "Password Required"
|
msgstr "Password Required"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:154
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:156
|
||||||
msgid "Pick a number"
|
msgid "Pick a number"
|
||||||
msgstr "Pick a number"
|
msgstr "Pick a number"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:76
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:79
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:81
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:84
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:57
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:60
|
||||||
msgid "Placeholder"
|
msgid "Placeholder"
|
||||||
msgstr "Placeholder"
|
msgstr "Placeholder"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:918
|
#: packages/ui/primitives/document-flow/add-fields.tsx:967
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:762
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:762
|
||||||
msgid "Radio"
|
msgid "Radio"
|
||||||
msgstr "Radio"
|
msgstr "Radio"
|
||||||
@ -471,11 +497,11 @@ msgstr "Radio"
|
|||||||
msgid "Radio values"
|
msgid "Radio values"
|
||||||
msgstr "Radio values"
|
msgstr "Radio values"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:184
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:186
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:137
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:147
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:136
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:156
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:122
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:122
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:114
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:133
|
||||||
msgid "Read only"
|
msgid "Read only"
|
||||||
msgstr "Read only"
|
msgstr "Read only"
|
||||||
|
|
||||||
@ -498,15 +524,15 @@ msgstr "Red"
|
|||||||
msgid "Redirect URL"
|
msgid "Redirect URL"
|
||||||
msgstr "Redirect URL"
|
msgstr "Redirect URL"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:1008
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1069
|
||||||
msgid "Remove"
|
msgid "Remove"
|
||||||
msgstr "Remove"
|
msgstr "Remove"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:174
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:176
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:127
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:137
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:126
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:146
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:112
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:112
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:104
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:123
|
||||||
msgid "Required field"
|
msgid "Required field"
|
||||||
msgstr "Required field"
|
msgstr "Required field"
|
||||||
|
|
||||||
@ -514,7 +540,7 @@ msgstr "Required field"
|
|||||||
msgid "Rows per page"
|
msgid "Rows per page"
|
||||||
msgstr "Rows per page"
|
msgstr "Rows per page"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:286
|
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:355
|
||||||
msgid "Save"
|
msgid "Save"
|
||||||
msgstr "Save"
|
msgstr "Save"
|
||||||
|
|
||||||
@ -526,7 +552,7 @@ msgstr "Save Template"
|
|||||||
msgid "Search languages..."
|
msgid "Search languages..."
|
||||||
msgstr "Search languages..."
|
msgstr "Search languages..."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:105
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115
|
||||||
msgid "Select"
|
msgid "Select"
|
||||||
msgstr "Select"
|
msgstr "Select"
|
||||||
|
|
||||||
@ -534,11 +560,11 @@ msgstr "Select"
|
|||||||
msgid "Select an option"
|
msgid "Select an option"
|
||||||
msgstr "Select an option"
|
msgstr "Select an option"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:137
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:139
|
||||||
msgid "Select at least"
|
msgid "Select at least"
|
||||||
msgstr "Select at least"
|
msgstr "Select at least"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:95
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:105
|
||||||
msgid "Select default option"
|
msgid "Select default option"
|
||||||
msgstr "Select default option"
|
msgstr "Select default option"
|
||||||
|
|
||||||
@ -560,7 +586,7 @@ msgstr "Share Signature Card"
|
|||||||
msgid "Share the Link"
|
msgid "Share the Link"
|
||||||
msgstr "Share the Link"
|
msgstr "Share the Link"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:671
|
#: packages/ui/primitives/document-flow/add-signers.tsx:680
|
||||||
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655
|
||||||
msgid "Show advanced settings"
|
msgid "Show advanced settings"
|
||||||
msgstr "Show advanced settings"
|
msgstr "Show advanced settings"
|
||||||
@ -569,7 +595,7 @@ msgstr "Show advanced settings"
|
|||||||
msgid "Sign"
|
msgid "Sign"
|
||||||
msgstr "Sign"
|
msgstr "Sign"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:736
|
#: packages/ui/primitives/document-flow/add-fields.tsx:785
|
||||||
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||||
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:580
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:580
|
||||||
@ -617,7 +643,7 @@ msgstr "Submit"
|
|||||||
msgid "Template title"
|
msgid "Template title"
|
||||||
msgstr "Template title"
|
msgstr "Template title"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:866
|
#: packages/ui/primitives/document-flow/add-fields.tsx:915
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:710
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:710
|
||||||
msgid "Text"
|
msgid "Text"
|
||||||
msgstr "Text"
|
msgstr "Text"
|
||||||
@ -678,7 +704,7 @@ msgstr "The signer's name"
|
|||||||
msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step."
|
msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step."
|
||||||
msgstr "This can be overriden by setting the authentication requirements directly on each recipient in the next step."
|
msgstr "This can be overriden by setting the authentication requirements directly on each recipient in the next step."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:697
|
#: packages/ui/primitives/document-flow/add-fields.tsx:746
|
||||||
msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
|
msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
|
||||||
msgstr "This document has already been sent to this recipient. You can no longer edit this recipient."
|
msgstr "This document has already been sent to this recipient. You can no longer edit this recipient."
|
||||||
|
|
||||||
@ -690,9 +716,17 @@ msgstr "This document is password protected. Please enter the password to view t
|
|||||||
msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
|
msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
|
||||||
msgstr "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
|
msgstr "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-signers.tsx:195
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1050
|
||||||
msgid "This signer has already received the document."
|
msgid "This recipient can no longer be modified as they have signed a field, or completed the document."
|
||||||
msgstr "This signer has already received the document."
|
msgstr "This recipient can no longer be modified as they have signed a field, or completed the document."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:165
|
||||||
|
#~ msgid "This signer has already received the document."
|
||||||
|
#~ msgstr "This signer has already received the document."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:194
|
||||||
|
msgid "This signer has already signed the document."
|
||||||
|
msgstr "This signer has already signed the document."
|
||||||
|
|
||||||
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:48
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:48
|
||||||
msgid "This will override any global settings."
|
msgid "This will override any global settings."
|
||||||
@ -707,7 +741,7 @@ msgstr "Time Zone"
|
|||||||
msgid "Title"
|
msgid "Title"
|
||||||
msgstr "Title"
|
msgstr "Title"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/add-fields.tsx:983
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1033
|
||||||
#: packages/ui/primitives/template-flow/add-template-fields.tsx:828
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:828
|
||||||
msgid "To proceed further, please set at least one value for the {0} field."
|
msgid "To proceed further, please set at least one value for the {0} field."
|
||||||
msgstr "To proceed further, please set at least one value for the {0} field."
|
msgstr "To proceed further, please set at least one value for the {0} field."
|
||||||
@ -728,13 +762,13 @@ msgstr "Upgrade"
|
|||||||
msgid "Upload Template Document"
|
msgid "Upload Template Document"
|
||||||
msgstr "Upload Template Document"
|
msgstr "Upload Template Document"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:130
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:132
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:147
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:167
|
||||||
msgid "Validation"
|
msgid "Validation"
|
||||||
msgstr "Validation"
|
msgstr "Validation"
|
||||||
|
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:88
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:91
|
||||||
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:93
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:96
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Value"
|
msgstr "Value"
|
||||||
|
|
||||||
|
|||||||
@ -218,6 +218,7 @@ msgstr "From the blog"
|
|||||||
|
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:9
|
#: apps/marketing/src/app/(marketing)/open/data.ts:9
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:17
|
#: apps/marketing/src/app/(marketing)/open/data.ts:17
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:33
|
#: apps/marketing/src/app/(marketing)/open/data.ts:33
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:41
|
#: apps/marketing/src/app/(marketing)/open/data.ts:41
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:49
|
#: apps/marketing/src/app/(marketing)/open/data.ts:49
|
||||||
@ -380,8 +381,8 @@ msgid "Our self-hosted option is great for small teams and individuals who need
|
|||||||
msgstr "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
msgstr "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||||
|
|
||||||
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||||
msgid "Part-Time"
|
#~ msgid "Part-Time"
|
||||||
msgstr "Part-Time"
|
#~ msgstr "Part-Time"
|
||||||
|
|
||||||
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||||
msgid "Premium Profile Name"
|
msgid "Premium Profile Name"
|
||||||
@ -609,6 +610,6 @@ msgstr "Yes! Documenso is offered under the GNU AGPL V3 open source license. Thi
|
|||||||
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||||
msgstr "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
msgstr "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||||
|
|
||||||
#: apps/marketing/src/components/(marketing)/carousel.tsx:265
|
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||||
msgid "Your browser does not support the video tag."
|
msgid "Your browser does not support the video tag."
|
||||||
msgstr "Your browser does not support the video tag."
|
msgstr "Your browser does not support the video tag."
|
||||||
|
|||||||
@ -334,7 +334,7 @@ msgstr "All Time"
|
|||||||
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
|
msgid "Allows authenticating using biometrics, password managers, hardware keys, etc."
|
||||||
msgstr "Allows authenticating using biometrics, password managers, hardware keys, etc."
|
msgstr "Allows authenticating using biometrics, password managers, hardware keys, etc."
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:411
|
#: apps/web/src/components/forms/v2/signup.tsx:423
|
||||||
msgid "Already have an account? <0>Sign in instead</0>"
|
msgid "Already have an account? <0>Sign in instead</0>"
|
||||||
msgstr "Already have an account? <0>Sign in instead</0>"
|
msgstr "Already have an account? <0>Sign in instead</0>"
|
||||||
|
|
||||||
@ -573,7 +573,7 @@ msgid "Audit Log"
|
|||||||
msgstr "Audit Log"
|
msgstr "Audit Log"
|
||||||
|
|
||||||
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:41
|
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:41
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:57
|
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:52
|
||||||
msgid "Authentication required"
|
msgid "Authentication required"
|
||||||
msgstr "Authentication required"
|
msgstr "Authentication required"
|
||||||
|
|
||||||
@ -591,7 +591,7 @@ msgstr "Awaiting email confirmation"
|
|||||||
|
|
||||||
#: apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx:369
|
#: apps/web/src/app/(recipient)/d/[token]/sign-direct-template.tsx:369
|
||||||
#: apps/web/src/components/(dashboard)/settings/layout/activity-back.tsx:20
|
#: apps/web/src/components/(dashboard)/settings/layout/activity-back.tsx:20
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:497
|
#: apps/web/src/components/forms/v2/signup.tsx:509
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Back"
|
msgstr "Back"
|
||||||
|
|
||||||
@ -604,7 +604,7 @@ msgid "Background Color"
|
|||||||
msgstr "Background Color"
|
msgstr "Background Color"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:167
|
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:167
|
||||||
#: apps/web/src/components/forms/signin.tsx:473
|
#: apps/web/src/components/forms/signin.tsx:485
|
||||||
msgid "Backup Code"
|
msgid "Backup Code"
|
||||||
msgstr "Backup Code"
|
msgstr "Backup Code"
|
||||||
|
|
||||||
@ -616,7 +616,7 @@ msgstr "Backup codes"
|
|||||||
msgid "Banner Updated"
|
msgid "Banner Updated"
|
||||||
msgstr "Banner Updated"
|
msgstr "Banner Updated"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:460
|
#: apps/web/src/components/forms/v2/signup.tsx:472
|
||||||
msgid "Basic details"
|
msgid "Basic details"
|
||||||
msgstr "Basic details"
|
msgstr "Basic details"
|
||||||
|
|
||||||
@ -657,7 +657,7 @@ msgstr "By enabling 2FA, you will be required to enter a code from your authenti
|
|||||||
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:119
|
#: apps/web/src/app/(dashboard)/templates/move-template-dialog.tsx:119
|
||||||
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:470
|
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:470
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-2fa.tsx:178
|
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-2fa.tsx:178
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-account.tsx:74
|
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-account.tsx:71
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-passkey.tsx:164
|
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-passkey.tsx:164
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-passkey.tsx:189
|
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-passkey.tsx:189
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/form.tsx:150
|
#: apps/web/src/app/(signing)/sign/[token]/form.tsx:150
|
||||||
@ -715,7 +715,7 @@ msgstr "Choose..."
|
|||||||
msgid "Claim account"
|
msgid "Claim account"
|
||||||
msgstr "Claim account"
|
msgstr "Claim account"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:469
|
#: apps/web/src/components/forms/v2/signup.tsx:481
|
||||||
msgid "Claim username"
|
msgid "Claim username"
|
||||||
msgstr "Claim username"
|
msgstr "Claim username"
|
||||||
|
|
||||||
@ -723,7 +723,7 @@ msgstr "Claim username"
|
|||||||
msgid "Claim your profile later"
|
msgid "Claim your profile later"
|
||||||
msgstr "Claim your profile later"
|
msgstr "Claim your profile later"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:267
|
#: apps/web/src/components/forms/v2/signup.tsx:279
|
||||||
msgid "Claim your username now"
|
msgid "Claim your username now"
|
||||||
msgstr "Claim your username now"
|
msgstr "Claim your username now"
|
||||||
|
|
||||||
@ -768,7 +768,7 @@ msgstr "Close"
|
|||||||
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:61
|
#: apps/web/src/app/(signing)/sign/[token]/sign-dialog.tsx:61
|
||||||
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:425
|
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:425
|
||||||
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:304
|
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:304
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:522
|
#: apps/web/src/components/forms/v2/signup.tsx:534
|
||||||
msgid "Complete"
|
msgid "Complete"
|
||||||
msgstr "Complete"
|
msgstr "Complete"
|
||||||
|
|
||||||
@ -784,7 +784,7 @@ msgstr "Complete Signing"
|
|||||||
msgid "Complete Viewing"
|
msgid "Complete Viewing"
|
||||||
msgstr "Complete Viewing"
|
msgstr "Complete Viewing"
|
||||||
|
|
||||||
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:58
|
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:62
|
||||||
#: apps/web/src/components/formatter/document-status.tsx:28
|
#: apps/web/src/components/formatter/document-status.tsx:28
|
||||||
msgid "Completed"
|
msgid "Completed"
|
||||||
msgstr "Completed"
|
msgstr "Completed"
|
||||||
@ -882,7 +882,7 @@ msgstr "Copy token"
|
|||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "Create"
|
msgstr "Create"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:252
|
#: apps/web/src/components/forms/v2/signup.tsx:264
|
||||||
msgid "Create a new account"
|
msgid "Create a new account"
|
||||||
msgstr "Create a new account"
|
msgstr "Create a new account"
|
||||||
|
|
||||||
@ -947,11 +947,11 @@ msgstr "Create webhook"
|
|||||||
msgid "Create Webhook"
|
msgid "Create Webhook"
|
||||||
msgstr "Create Webhook"
|
msgstr "Create Webhook"
|
||||||
|
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:214
|
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:215
|
||||||
msgid "Create your account and start using state-of-the-art document signing."
|
msgid "Create your account and start using state-of-the-art document signing."
|
||||||
msgstr "Create your account and start using state-of-the-art document signing."
|
msgstr "Create your account and start using state-of-the-art document signing."
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:256
|
#: apps/web/src/components/forms/v2/signup.tsx:268
|
||||||
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
|
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
|
||||||
msgstr "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
|
msgstr "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
|
||||||
|
|
||||||
@ -1317,7 +1317,7 @@ msgstr "Document will be permanently deleted"
|
|||||||
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109
|
#: apps/web/src/app/(dashboard)/documents/[id]/edit/document-edit-page-view.tsx:109
|
||||||
#: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16
|
#: apps/web/src/app/(dashboard)/documents/[id]/loading.tsx:16
|
||||||
#: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15
|
#: apps/web/src/app/(dashboard)/documents/[id]/sent/page.tsx:15
|
||||||
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:114
|
#: apps/web/src/app/(dashboard)/documents/documents-page-view.tsx:119
|
||||||
#: apps/web/src/app/(profile)/p/[url]/page.tsx:166
|
#: apps/web/src/app/(profile)/p/[url]/page.tsx:166
|
||||||
#: apps/web/src/app/not-found.tsx:21
|
#: apps/web/src/app/not-found.tsx:21
|
||||||
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:205
|
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:205
|
||||||
@ -1336,7 +1336,7 @@ msgid "Documents Viewed"
|
|||||||
msgstr "Documents Viewed"
|
msgstr "Documents Viewed"
|
||||||
|
|
||||||
#: apps/web/src/app/(unauthenticated)/reset-password/[token]/page.tsx:40
|
#: apps/web/src/app/(unauthenticated)/reset-password/[token]/page.tsx:40
|
||||||
#: apps/web/src/app/(unauthenticated)/signin/page.tsx:61
|
#: apps/web/src/app/(unauthenticated)/signin/page.tsx:45
|
||||||
msgid "Don't have an account? <0>Sign up</0>"
|
msgid "Don't have an account? <0>Sign up</0>"
|
||||||
msgstr "Don't have an account? <0>Sign up</0>"
|
msgstr "Don't have an account? <0>Sign up</0>"
|
||||||
|
|
||||||
@ -1413,7 +1413,7 @@ msgstr "Edit webhook"
|
|||||||
#: apps/web/src/components/(teams)/dialogs/update-team-email-dialog.tsx:153
|
#: apps/web/src/components/(teams)/dialogs/update-team-email-dialog.tsx:153
|
||||||
#: apps/web/src/components/forms/forgot-password.tsx:81
|
#: apps/web/src/components/forms/forgot-password.tsx:81
|
||||||
#: apps/web/src/components/forms/profile.tsx:122
|
#: apps/web/src/components/forms/profile.tsx:122
|
||||||
#: apps/web/src/components/forms/signin.tsx:326
|
#: apps/web/src/components/forms/signin.tsx:338
|
||||||
#: apps/web/src/components/forms/signup.tsx:180
|
#: apps/web/src/components/forms/signup.tsx:180
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "Email"
|
msgstr "Email"
|
||||||
@ -1424,7 +1424,7 @@ msgstr "Email"
|
|||||||
msgid "Email address"
|
msgid "Email address"
|
||||||
msgstr "Email address"
|
msgstr "Email address"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:316
|
#: apps/web/src/components/forms/v2/signup.tsx:328
|
||||||
msgid "Email Address"
|
msgid "Email Address"
|
||||||
msgstr "Email Address"
|
msgstr "Email Address"
|
||||||
|
|
||||||
@ -1585,7 +1585,7 @@ msgid "File cannot be larger than {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB"
|
|||||||
msgstr "File cannot be larger than {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB"
|
msgstr "File cannot be larger than {APP_DOCUMENT_UPLOAD_SIZE_LIMIT}MB"
|
||||||
|
|
||||||
#: apps/web/src/app/(unauthenticated)/forgot-password/page.tsx:21
|
#: apps/web/src/app/(unauthenticated)/forgot-password/page.tsx:21
|
||||||
#: apps/web/src/components/forms/signin.tsx:358
|
#: apps/web/src/components/forms/signin.tsx:370
|
||||||
msgid "Forgot your password?"
|
msgid "Forgot your password?"
|
||||||
msgstr "Forgot your password?"
|
msgstr "Forgot your password?"
|
||||||
|
|
||||||
@ -1594,7 +1594,7 @@ msgstr "Forgot your password?"
|
|||||||
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:361
|
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:361
|
||||||
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:241
|
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:241
|
||||||
#: apps/web/src/components/forms/profile.tsx:110
|
#: apps/web/src/components/forms/profile.tsx:110
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:300
|
#: apps/web/src/components/forms/v2/signup.tsx:312
|
||||||
msgid "Full Name"
|
msgid "Full Name"
|
||||||
msgstr "Full Name"
|
msgstr "Full Name"
|
||||||
|
|
||||||
@ -1621,7 +1621,7 @@ msgstr "Go Back"
|
|||||||
msgid "Go back home"
|
msgid "Go back home"
|
||||||
msgstr "Go back home"
|
msgstr "Go back home"
|
||||||
|
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:223
|
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:226
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx:57
|
#: apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx:57
|
||||||
msgid "Go Back Home"
|
msgid "Go Back Home"
|
||||||
msgstr "Go Back Home"
|
msgstr "Go Back Home"
|
||||||
@ -1869,8 +1869,8 @@ msgid "Loading..."
|
|||||||
msgstr "Loading..."
|
msgstr "Loading..."
|
||||||
|
|
||||||
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:54
|
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:54
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-account.tsx:78
|
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-account.tsx:75
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:72
|
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:67
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "Login"
|
msgstr "Login"
|
||||||
|
|
||||||
@ -2022,7 +2022,7 @@ msgstr "My templates"
|
|||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Name"
|
msgstr "Name"
|
||||||
|
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:210
|
#: apps/web/src/app/(signing)/sign/[token]/complete/page.tsx:211
|
||||||
msgid "Need to sign documents?"
|
msgid "Need to sign documents?"
|
||||||
msgstr "Need to sign documents?"
|
msgstr "Need to sign documents?"
|
||||||
|
|
||||||
@ -2045,7 +2045,7 @@ msgstr "New Template"
|
|||||||
|
|
||||||
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:416
|
#: apps/web/src/app/embed/direct/[[...url]]/client.tsx:416
|
||||||
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:295
|
#: apps/web/src/app/embed/sign/[[...url]]/client.tsx:295
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:509
|
#: apps/web/src/components/forms/v2/signup.tsx:521
|
||||||
msgid "Next"
|
msgid "Next"
|
||||||
msgstr "Next"
|
msgstr "Next"
|
||||||
|
|
||||||
@ -2149,18 +2149,18 @@ msgstr "Once you have scanned the QR code or entered the code manually, enter th
|
|||||||
msgid "Oops! Something went wrong."
|
msgid "Oops! Something went wrong."
|
||||||
msgstr "Oops! Something went wrong."
|
msgstr "Oops! Something went wrong."
|
||||||
|
|
||||||
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:97
|
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:101
|
||||||
msgid "Opened"
|
msgid "Opened"
|
||||||
msgstr "Opened"
|
msgstr "Opened"
|
||||||
|
|
||||||
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:337
|
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:337
|
||||||
#: apps/web/src/components/forms/signup.tsx:243
|
#: apps/web/src/components/forms/signup.tsx:243
|
||||||
#: apps/web/src/components/forms/signup.tsx:267
|
#: apps/web/src/components/forms/signup.tsx:267
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:371
|
#: apps/web/src/components/forms/v2/signup.tsx:383
|
||||||
msgid "Or"
|
msgid "Or"
|
||||||
msgstr "Or"
|
msgstr "Or"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/signin.tsx:378
|
#: apps/web/src/components/forms/signin.tsx:390
|
||||||
msgid "Or continue with"
|
msgid "Or continue with"
|
||||||
msgstr "Or continue with"
|
msgstr "Or continue with"
|
||||||
|
|
||||||
@ -2177,7 +2177,7 @@ msgstr "Owner"
|
|||||||
msgid "Paid"
|
msgid "Paid"
|
||||||
msgstr "Paid"
|
msgstr "Paid"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/signin.tsx:423
|
#: apps/web/src/components/forms/signin.tsx:435
|
||||||
msgid "Passkey"
|
msgid "Passkey"
|
||||||
msgstr "Passkey"
|
msgstr "Passkey"
|
||||||
|
|
||||||
@ -2217,9 +2217,9 @@ msgstr "Passkeys are not supported on this browser"
|
|||||||
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:70
|
#: apps/web/src/components/(dashboard)/common/command-menu.tsx:70
|
||||||
#: apps/web/src/components/forms/password.tsx:123
|
#: apps/web/src/components/forms/password.tsx:123
|
||||||
#: apps/web/src/components/forms/reset-password.tsx:110
|
#: apps/web/src/components/forms/reset-password.tsx:110
|
||||||
#: apps/web/src/components/forms/signin.tsx:344
|
#: apps/web/src/components/forms/signin.tsx:356
|
||||||
#: apps/web/src/components/forms/signup.tsx:196
|
#: apps/web/src/components/forms/signup.tsx:196
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:332
|
#: apps/web/src/components/forms/v2/signup.tsx:344
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr "Password"
|
msgstr "Password"
|
||||||
|
|
||||||
@ -2410,7 +2410,7 @@ msgstr "Public Profile"
|
|||||||
msgid "Public profile URL"
|
msgid "Public profile URL"
|
||||||
msgstr "Public profile URL"
|
msgstr "Public profile URL"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:438
|
#: apps/web/src/components/forms/v2/signup.tsx:450
|
||||||
msgid "Public profile username"
|
msgid "Public profile username"
|
||||||
msgstr "Public profile username"
|
msgstr "Public profile username"
|
||||||
|
|
||||||
@ -2765,17 +2765,17 @@ msgid "Sign field"
|
|||||||
msgstr "Sign field"
|
msgstr "Sign field"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/signup.tsx:212
|
#: apps/web/src/components/forms/signup.tsx:212
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:350
|
#: apps/web/src/components/forms/v2/signup.tsx:362
|
||||||
msgid "Sign Here"
|
msgid "Sign Here"
|
||||||
msgstr "Sign Here"
|
msgstr "Sign Here"
|
||||||
|
|
||||||
#: apps/web/src/app/not-found.tsx:29
|
#: apps/web/src/app/not-found.tsx:29
|
||||||
#: apps/web/src/components/forms/signin.tsx:371
|
#: apps/web/src/components/forms/signin.tsx:383
|
||||||
#: apps/web/src/components/forms/signin.tsx:498
|
#: apps/web/src/components/forms/signin.tsx:510
|
||||||
msgid "Sign In"
|
msgid "Sign In"
|
||||||
msgstr "Sign In"
|
msgstr "Sign In"
|
||||||
|
|
||||||
#: apps/web/src/app/(unauthenticated)/signin/page.tsx:44
|
#: apps/web/src/app/(unauthenticated)/signin/page.tsx:29
|
||||||
msgid "Sign in to your account"
|
msgid "Sign in to your account"
|
||||||
msgstr "Sign in to your account"
|
msgstr "Sign in to your account"
|
||||||
|
|
||||||
@ -2789,7 +2789,7 @@ msgstr "Sign Out"
|
|||||||
msgid "Sign the document to complete the process."
|
msgid "Sign the document to complete the process."
|
||||||
msgstr "Sign the document to complete the process."
|
msgstr "Sign the document to complete the process."
|
||||||
|
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:72
|
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:67
|
||||||
msgid "Sign up"
|
msgid "Sign up"
|
||||||
msgstr "Sign up"
|
msgstr "Sign up"
|
||||||
|
|
||||||
@ -2798,12 +2798,12 @@ msgid "Sign Up"
|
|||||||
msgstr "Sign Up"
|
msgstr "Sign Up"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/signup.tsx:257
|
#: apps/web/src/components/forms/signup.tsx:257
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:389
|
#: apps/web/src/components/forms/v2/signup.tsx:401
|
||||||
msgid "Sign Up with Google"
|
msgid "Sign Up with Google"
|
||||||
msgstr "Sign Up with Google"
|
msgstr "Sign Up with Google"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/signup.tsx:281
|
#: apps/web/src/components/forms/signup.tsx:281
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:405
|
#: apps/web/src/components/forms/v2/signup.tsx:417
|
||||||
msgid "Sign Up with OIDC"
|
msgid "Sign Up with OIDC"
|
||||||
msgstr "Sign Up with OIDC"
|
msgstr "Sign Up with OIDC"
|
||||||
|
|
||||||
@ -2829,8 +2829,8 @@ msgstr "Signatures will appear once the document has been completed"
|
|||||||
msgid "Signed"
|
msgid "Signed"
|
||||||
msgstr "Signed"
|
msgstr "Signed"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/signin.tsx:371
|
#: apps/web/src/components/forms/signin.tsx:383
|
||||||
#: apps/web/src/components/forms/signin.tsx:498
|
#: apps/web/src/components/forms/signin.tsx:510
|
||||||
msgid "Signing in..."
|
msgid "Signing in..."
|
||||||
msgstr "Signing in..."
|
msgstr "Signing in..."
|
||||||
|
|
||||||
@ -2880,7 +2880,7 @@ msgstr "Site Settings"
|
|||||||
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:151
|
#: apps/web/src/app/(dashboard)/templates/template-direct-link-dialog.tsx:151
|
||||||
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:117
|
#: apps/web/src/app/(recipient)/d/[token]/direct-template.tsx:117
|
||||||
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:27
|
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:27
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:43
|
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:38
|
||||||
#: apps/web/src/app/(teams)/t/[teamUrl]/layout-billing-banner.tsx:53
|
#: apps/web/src/app/(teams)/t/[teamUrl]/layout-billing-banner.tsx:53
|
||||||
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:39
|
#: apps/web/src/app/(teams)/t/[teamUrl]/settings/team-email-dropdown.tsx:39
|
||||||
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:61
|
#: apps/web/src/app/(unauthenticated)/verify-email/[token]/page.tsx:61
|
||||||
@ -3446,7 +3446,7 @@ msgstr "To enable two-factor authentication, scan the following QR code using yo
|
|||||||
msgid "To gain access to your account, please confirm your email address by clicking on the confirmation link from your inbox."
|
msgid "To gain access to your account, please confirm your email address by clicking on the confirmation link from your inbox."
|
||||||
msgstr "To gain access to your account, please confirm your email address by clicking on the confirmation link from your inbox."
|
msgstr "To gain access to your account, please confirm your email address by clicking on the confirmation link from your inbox."
|
||||||
|
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-account.tsx:57
|
#: apps/web/src/app/(signing)/sign/[token]/document-action-auth-account.tsx:54
|
||||||
msgid "To mark this document as viewed, you need to be logged in as <0>{0}</0>"
|
msgid "To mark this document as viewed, you need to be logged in as <0>{0}</0>"
|
||||||
msgstr "To mark this document as viewed, you need to be logged in as <0>{0}</0>"
|
msgstr "To mark this document as viewed, you need to be logged in as <0>{0}</0>"
|
||||||
|
|
||||||
@ -3535,7 +3535,7 @@ msgstr "Two factor authentication"
|
|||||||
msgid "Two factor authentication recovery codes are used to access your account in the event that you lose access to your authenticator app."
|
msgid "Two factor authentication recovery codes are used to access your account in the event that you lose access to your authenticator app."
|
||||||
msgstr "Two factor authentication recovery codes are used to access your account in the event that you lose access to your authenticator app."
|
msgstr "Two factor authentication recovery codes are used to access your account in the event that you lose access to your authenticator app."
|
||||||
|
|
||||||
#: apps/web/src/components/forms/signin.tsx:436
|
#: apps/web/src/components/forms/signin.tsx:448
|
||||||
msgid "Two-Factor Authentication"
|
msgid "Two-Factor Authentication"
|
||||||
msgstr "Two-Factor Authentication"
|
msgstr "Two-Factor Authentication"
|
||||||
|
|
||||||
@ -3648,7 +3648,7 @@ msgstr "Unable to sign in"
|
|||||||
msgid "Unauthorized"
|
msgid "Unauthorized"
|
||||||
msgstr "Unauthorized"
|
msgstr "Unauthorized"
|
||||||
|
|
||||||
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:112
|
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:116
|
||||||
msgid "Uncompleted"
|
msgid "Uncompleted"
|
||||||
msgstr "Uncompleted"
|
msgstr "Uncompleted"
|
||||||
|
|
||||||
@ -3745,12 +3745,12 @@ msgid "Uploaded file not an allowed file type"
|
|||||||
msgstr "Uploaded file not an allowed file type"
|
msgstr "Uploaded file not an allowed file type"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:187
|
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:187
|
||||||
#: apps/web/src/components/forms/signin.tsx:493
|
#: apps/web/src/components/forms/signin.tsx:505
|
||||||
msgid "Use Authenticator"
|
msgid "Use Authenticator"
|
||||||
msgstr "Use Authenticator"
|
msgstr "Use Authenticator"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:185
|
#: apps/web/src/components/forms/2fa/disable-authenticator-app-dialog.tsx:185
|
||||||
#: apps/web/src/components/forms/signin.tsx:491
|
#: apps/web/src/components/forms/signin.tsx:503
|
||||||
msgid "Use Backup Code"
|
msgid "Use Backup Code"
|
||||||
msgstr "Use Backup Code"
|
msgstr "Use Backup Code"
|
||||||
|
|
||||||
@ -3766,7 +3766,7 @@ msgstr "User"
|
|||||||
msgid "User ID"
|
msgid "User ID"
|
||||||
msgstr "User ID"
|
msgstr "User ID"
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:222
|
#: apps/web/src/components/forms/v2/signup.tsx:234
|
||||||
msgid "User profiles are here!"
|
msgid "User profiles are here!"
|
||||||
msgstr "User profiles are here!"
|
msgstr "User profiles are here!"
|
||||||
|
|
||||||
@ -3860,7 +3860,7 @@ msgstr "View teams"
|
|||||||
msgid "Viewed"
|
msgid "Viewed"
|
||||||
msgstr "Viewed"
|
msgstr "Viewed"
|
||||||
|
|
||||||
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:82
|
#: apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx:86
|
||||||
msgid "Waiting"
|
msgid "Waiting"
|
||||||
msgstr "Waiting"
|
msgstr "Waiting"
|
||||||
|
|
||||||
@ -4045,7 +4045,7 @@ msgid "We were unable to disable two-factor authentication for your account. Ple
|
|||||||
msgstr "We were unable to disable two-factor authentication for your account. Please ensure that you have entered your password and backup code correctly and try again."
|
msgstr "We were unable to disable two-factor authentication for your account. Please ensure that you have entered your password and backup code correctly and try again."
|
||||||
|
|
||||||
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:28
|
#: apps/web/src/app/(recipient)/d/[token]/signing-auth-page.tsx:28
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:44
|
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:39
|
||||||
msgid "We were unable to log you out at this time."
|
msgid "We were unable to log you out at this time."
|
||||||
msgstr "We were unable to log you out at this time."
|
msgstr "We were unable to log you out at this time."
|
||||||
|
|
||||||
@ -4112,7 +4112,7 @@ msgstr "Webhooks"
|
|||||||
msgid "Weekly"
|
msgid "Weekly"
|
||||||
msgstr "Weekly"
|
msgstr "Weekly"
|
||||||
|
|
||||||
#: apps/web/src/app/(unauthenticated)/signin/page.tsx:48
|
#: apps/web/src/app/(unauthenticated)/signin/page.tsx:33
|
||||||
msgid "Welcome back, we are lucky to have you."
|
msgid "Welcome back, we are lucky to have you."
|
||||||
msgstr "Welcome back, we are lucky to have you."
|
msgstr "Welcome back, we are lucky to have you."
|
||||||
|
|
||||||
@ -4338,7 +4338,7 @@ msgstr "You must have at least one other team member to transfer ownership."
|
|||||||
msgid "You must set a profile URL before enabling your public profile."
|
msgid "You must set a profile URL before enabling your public profile."
|
||||||
msgstr "You must set a profile URL before enabling your public profile."
|
msgstr "You must set a profile URL before enabling your public profile."
|
||||||
|
|
||||||
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:61
|
#: apps/web/src/app/(signing)/sign/[token]/signing-auth-page.tsx:56
|
||||||
msgid "You need to be logged in as <0>{email}</0> to view this page."
|
msgid "You need to be logged in as <0>{email}</0> to view this page."
|
||||||
msgstr "You need to be logged in as <0>{email}</0> to view this page."
|
msgstr "You need to be logged in as <0>{email}</0> to view this page."
|
||||||
|
|
||||||
@ -4350,7 +4350,7 @@ msgstr "You need to be logged in to view this page."
|
|||||||
msgid "You need to setup 2FA to mark this document as viewed."
|
msgid "You need to setup 2FA to mark this document as viewed."
|
||||||
msgstr "You need to setup 2FA to mark this document as viewed."
|
msgstr "You need to setup 2FA to mark this document as viewed."
|
||||||
|
|
||||||
#: apps/web/src/components/forms/v2/signup.tsx:271
|
#: apps/web/src/components/forms/v2/signup.tsx:283
|
||||||
msgid "You will get notified & be able to set up your documenso public profile when we launch the feature."
|
msgid "You will get notified & be able to set up your documenso public profile when we launch the feature."
|
||||||
msgstr "You will get notified & be able to set up your documenso public profile when we launch the feature."
|
msgstr "You will get notified & be able to set up your documenso public profile when we launch the feature."
|
||||||
|
|
||||||
|
|||||||
814
packages/lib/translations/fr/common.po
Normal file
@ -0,0 +1,814 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"POT-Creation-Date: 2024-07-24 13:01+1000\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: @lingui/cli\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"Project-Id-Version: documenso-app\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"PO-Revision-Date: 2024-10-08 12:05\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: French\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Crowdin-Project: documenso-app\n"
|
||||||
|
"X-Crowdin-Project-ID: 694691\n"
|
||||||
|
"X-Crowdin-Language: fr\n"
|
||||||
|
"X-Crowdin-File: common.po\n"
|
||||||
|
"X-Crowdin-File-ID: 4\n"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/data-table-pagination.tsx:30
|
||||||
|
msgid "{0} of {1} row(s) selected."
|
||||||
|
msgstr "{0} sur {1} ligne(s) sélectionnée(s)."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/data-table-pagination.tsx:41
|
||||||
|
msgid "{visibleRows, plural, one {Showing # result.} other {Showing # results.}}"
|
||||||
|
msgstr "{visibleRows, plural, one {Affichage de # résultat.} other {Affichage de # résultats.}}"
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:53
|
||||||
|
msgid "<0>Inherit authentication method</0> - Use the global action signing authentication method configured in the \"General Settings\" step"
|
||||||
|
msgstr "<0>Hériter du méthode d'authentification</0> - Utiliser la méthode d'authentification de signature d'action globale configurée dans l'étape \"Paramètres Générales\""
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-action-select.tsx:95
|
||||||
|
msgid "<0>No restrictions</0> - No authentication required"
|
||||||
|
msgstr "<0>Aucune restriction</0> - Aucune authentification requise"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-access-select.tsx:77
|
||||||
|
msgid "<0>No restrictions</0> - The document can be accessed directly by the URL sent to the recipient"
|
||||||
|
msgstr "<0>Aucune restriction</0> - Le document peut être accédé directement par l'URL envoyée au destinataire"
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:75
|
||||||
|
msgid "<0>None</0> - No authentication required"
|
||||||
|
msgstr "<0>Aucun</0> - Aucune authentification requise"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-action-select.tsx:89
|
||||||
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:69
|
||||||
|
msgid "<0>Require 2FA</0> - The recipient must have an account and 2FA enabled via their settings"
|
||||||
|
msgstr "<0>Exiger 2FA</0> - Le destinataire doit avoir un compte et 2FA activé via ses paramètres"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-access-select.tsx:72
|
||||||
|
msgid "<0>Require account</0> - The recipient must be signed in to view the document"
|
||||||
|
msgstr "<0>Exiger un compte</0> - Le destinataire doit être connecté pour voir le document"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-action-select.tsx:83
|
||||||
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:63
|
||||||
|
msgid "<0>Require passkey</0> - The recipient must have an account and passkey configured via their settings"
|
||||||
|
msgstr "<0>Exiger une clé d'accès</0> - Le destinataire doit avoir un compte et une clé d'accès configurée via ses paramètres"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-dropzone.tsx:69
|
||||||
|
msgid "Add a document"
|
||||||
|
msgstr "Ajouter un document"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:336
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:339
|
||||||
|
msgid "Add a URL to redirect the user to once the document is signed"
|
||||||
|
msgstr "Ajouter une URL pour rediriger l'utilisateur une fois le document signé"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:248
|
||||||
|
msgid "Add an external ID to the document. This can be used to identify the document in external systems."
|
||||||
|
msgstr "Ajouter un ID externe au document. Cela peut être utilisé pour identifier le document dans des systèmes externes."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:256
|
||||||
|
msgid "Add an external ID to the template. This can be used to identify in external systems."
|
||||||
|
msgstr "Ajouter un ID externe au modèle. Cela peut être utilisé pour identifier dans des systèmes externes."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:187
|
||||||
|
msgid "Add another option"
|
||||||
|
msgstr "Ajouter une autre option"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:232
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:167
|
||||||
|
msgid "Add another value"
|
||||||
|
msgstr "Ajouter une autre valeur"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:662
|
||||||
|
msgid "Add myself"
|
||||||
|
msgstr "Ajoutez-moi"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:637
|
||||||
|
msgid "Add Myself"
|
||||||
|
msgstr "Ajoutez-moi"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:623
|
||||||
|
msgid "Add Placeholder Recipient"
|
||||||
|
msgstr "Ajouter un destinataire de substitution"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:651
|
||||||
|
msgid "Add Signer"
|
||||||
|
msgstr "Ajouter un signataire"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:73
|
||||||
|
msgid "Add text"
|
||||||
|
msgstr "Ajouter du texte"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:78
|
||||||
|
msgid "Add text to the field"
|
||||||
|
msgstr "Ajouter du texte au champ"
|
||||||
|
|
||||||
|
#: packages/lib/constants/teams.ts:10
|
||||||
|
msgid "Admin"
|
||||||
|
msgstr "Administrateur"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:230
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:238
|
||||||
|
msgid "Advanced Options"
|
||||||
|
msgstr "Options avancées"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:565
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:402
|
||||||
|
msgid "Advanced settings"
|
||||||
|
msgstr "Paramètres avancés"
|
||||||
|
|
||||||
|
#: packages/lib/constants/template.ts:21
|
||||||
|
msgid "After submission, a document will be automatically generated and added to your documents page. You will also receive a notification via email."
|
||||||
|
msgstr "Après soumission, un document sera automatiquement généré et ajouté à votre page de documents. Vous recevrez également une notification par email."
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:8
|
||||||
|
msgid "Approve"
|
||||||
|
msgstr "Approuver"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:9
|
||||||
|
msgid "Approved"
|
||||||
|
msgstr "Approuvé"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:11
|
||||||
|
msgid "Approver"
|
||||||
|
msgstr "Approuveur"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:10
|
||||||
|
msgid "Approving"
|
||||||
|
msgstr "En attente d'approbation"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:276
|
||||||
|
msgid "Black"
|
||||||
|
msgstr "Noir"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:290
|
||||||
|
msgid "Blue"
|
||||||
|
msgstr "Bleu"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:356
|
||||||
|
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:58
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Annuler"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:193
|
||||||
|
msgid "Cannot remove signer"
|
||||||
|
msgstr "Impossible de retirer le signataire"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:221
|
||||||
|
#~ msgid "Cannot update signer because they have already signed a field"
|
||||||
|
#~ msgstr "Cannot update signer because they have already signed a field"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:17
|
||||||
|
msgid "Cc"
|
||||||
|
msgstr "Cc"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:14
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:16
|
||||||
|
msgid "CC"
|
||||||
|
msgstr "CC"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:15
|
||||||
|
msgid "CC'd"
|
||||||
|
msgstr "CC'd"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:86
|
||||||
|
msgid "Character Limit"
|
||||||
|
msgstr "Limite de caractères"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:993
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:788
|
||||||
|
msgid "Checkbox"
|
||||||
|
msgstr "Case à cocher"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:197
|
||||||
|
msgid "Checkbox values"
|
||||||
|
msgstr "Valeurs de case à cocher"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/data-table.tsx:156
|
||||||
|
msgid "Clear filters"
|
||||||
|
msgstr "Effacer les filtres"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:310
|
||||||
|
msgid "Clear Signature"
|
||||||
|
msgstr "Effacer la signature"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signature.tsx:394
|
||||||
|
msgid "Click to insert field"
|
||||||
|
msgstr "Cliquez pour insérer un champ"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:44
|
||||||
|
msgid "Close"
|
||||||
|
msgstr "Fermer"
|
||||||
|
|
||||||
|
#: packages/lib/constants/template.ts:12
|
||||||
|
msgid "Configure Direct Recipient"
|
||||||
|
msgstr "Configurer le destinataire direct"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:566
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:403
|
||||||
|
msgid "Configure the {0} field"
|
||||||
|
msgstr "Configurer le champ {0}"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/document-flow-root.tsx:141
|
||||||
|
msgid "Continue"
|
||||||
|
msgstr "Continuer"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-share-button.tsx:46
|
||||||
|
msgid "Copied to clipboard"
|
||||||
|
msgstr "Copié dans le presse-papiers"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signature.tsx:360
|
||||||
|
msgid "Custom Text"
|
||||||
|
msgstr "Texte personnalisé"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:889
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:684
|
||||||
|
msgid "Date"
|
||||||
|
msgstr "Date"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:271
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:279
|
||||||
|
msgid "Date Format"
|
||||||
|
msgstr "Format de date"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:570
|
||||||
|
msgid "Direct link receiver"
|
||||||
|
msgstr "Receveur de lien direct"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-access-select.tsx:62
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:174
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:151
|
||||||
|
msgid "Document access"
|
||||||
|
msgstr "Accès au document"
|
||||||
|
|
||||||
|
#: packages/lib/constants/template.ts:20
|
||||||
|
msgid "Document Creation"
|
||||||
|
msgstr "Création de document"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-download-button.tsx:68
|
||||||
|
msgid "Download"
|
||||||
|
msgstr "Télécharger"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-dropzone.tsx:162
|
||||||
|
msgid "Drag & drop your PDF here."
|
||||||
|
msgstr "Faites glisser et déposez votre PDF ici."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1019
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:814
|
||||||
|
msgid "Dropdown"
|
||||||
|
msgstr "Liste déroulante"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:158
|
||||||
|
msgid "Dropdown options"
|
||||||
|
msgstr "Options de liste déroulante"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:837
|
||||||
|
#: packages/ui/primitives/document-flow/add-signature.tsx:272
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:500
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:632
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:463
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:470
|
||||||
|
msgid "Email"
|
||||||
|
msgstr "Email"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:184
|
||||||
|
msgid "Email Options"
|
||||||
|
msgstr "Options d'email"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1082
|
||||||
|
msgid "Empty field"
|
||||||
|
msgstr "Champ vide"
|
||||||
|
|
||||||
|
#: packages/lib/constants/template.ts:8
|
||||||
|
msgid "Enable Direct Link Signing"
|
||||||
|
msgstr "Activer la signature de lien direct"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:401
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:362
|
||||||
|
msgid "Enable signing order"
|
||||||
|
msgstr "Activer l'ordre de signature"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-password-dialog.tsx:84
|
||||||
|
msgid "Enter password"
|
||||||
|
msgstr "Entrez le mot de passe"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:257
|
||||||
|
msgid "Error"
|
||||||
|
msgstr "Erreur"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:241
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:249
|
||||||
|
msgid "External ID"
|
||||||
|
msgstr "ID externe"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:258
|
||||||
|
msgid "Failed to save settings."
|
||||||
|
msgstr "Échec de l'enregistrement des paramètres."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:93
|
||||||
|
msgid "Field character limit"
|
||||||
|
msgstr "Limite de caractères du champ"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:62
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:44
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:44
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx:44
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:130
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:107
|
||||||
|
msgid "Field font size"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:110
|
||||||
|
msgid "Field format"
|
||||||
|
msgstr "Format du champ"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:53
|
||||||
|
msgid "Field label"
|
||||||
|
msgstr "Étiquette du champ"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:65
|
||||||
|
msgid "Field placeholder"
|
||||||
|
msgstr "Espace réservé du champ"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/date-field.tsx:56
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/email-field.tsx:38
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/initials-field.tsx:38
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/name-field.tsx:38
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:124
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:101
|
||||||
|
msgid "Font Size"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-action-select.tsx:64
|
||||||
|
msgid "Global recipient action authentication"
|
||||||
|
msgstr "Authentification d'action de destinataire globale"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/document-flow-root.tsx:142
|
||||||
|
msgid "Go Back"
|
||||||
|
msgstr "Retourner"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:297
|
||||||
|
msgid "Green"
|
||||||
|
msgstr "Vert"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:72
|
||||||
|
msgid "I am a signer of this document"
|
||||||
|
msgstr "Je suis un signataire de ce document"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:75
|
||||||
|
msgid "I am a viewer of this document"
|
||||||
|
msgstr "Je suis un visualiseur de ce document"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:73
|
||||||
|
msgid "I am an approver of this document"
|
||||||
|
msgstr "Je suis un approuveur de ce document"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:74
|
||||||
|
msgid "I am required to receive a copy of this document"
|
||||||
|
msgstr "Je dois recevoir une copie de ce document"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:74
|
||||||
|
#~ msgid "I am required to recieve a copy of this document"
|
||||||
|
#~ msgstr "I am required to recieve a copy of this document"
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:29
|
||||||
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:87
|
||||||
|
msgid "Inherit authentication method"
|
||||||
|
msgstr "Hériter de la méthode d'authentification"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:67
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:72
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:48
|
||||||
|
msgid "Label"
|
||||||
|
msgstr "Étiquette"
|
||||||
|
|
||||||
|
#: packages/lib/constants/teams.ts:11
|
||||||
|
msgid "Manager"
|
||||||
|
msgstr "Gestionnaire"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:188
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "Max"
|
||||||
|
|
||||||
|
#: packages/lib/constants/teams.ts:12
|
||||||
|
msgid "Member"
|
||||||
|
msgstr "Membre"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-subject.tsx:95
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:215
|
||||||
|
msgid "Message <0>(Optional)</0>"
|
||||||
|
msgstr "Message <0>(Optionnel)</0>"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:176
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "Min"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:863
|
||||||
|
#: packages/ui/primitives/document-flow/add-signature.tsx:298
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:535
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:541
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:658
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:498
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:504
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Nom"
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-role-select.tsx:52
|
||||||
|
msgid "Needs to approve"
|
||||||
|
msgstr "Nécessite une approbation"
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-role-select.tsx:31
|
||||||
|
msgid "Needs to sign"
|
||||||
|
msgstr "Nécessite une signature"
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-role-select.tsx:73
|
||||||
|
msgid "Needs to view"
|
||||||
|
msgstr "Nécessite une visualisation"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:674
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:497
|
||||||
|
msgid "No recipient matching this description was found."
|
||||||
|
msgstr "Aucun destinataire correspondant à cette description n'a été trouvé."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:690
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:513
|
||||||
|
msgid "No recipients with this role"
|
||||||
|
msgstr "Aucun destinataire avec ce rôle"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-access-select.tsx:30
|
||||||
|
#: packages/ui/components/document/document-global-auth-access-select.tsx:43
|
||||||
|
#: packages/ui/components/document/document-global-auth-action-select.tsx:31
|
||||||
|
#: packages/ui/components/document/document-global-auth-action-select.tsx:46
|
||||||
|
msgid "No restrictions"
|
||||||
|
msgstr "Aucune restriction"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/data-table.tsx:148
|
||||||
|
msgid "No results found"
|
||||||
|
msgstr "Aucun résultat trouvé"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:30
|
||||||
|
msgid "No signature field found"
|
||||||
|
msgstr "Aucun champ de signature trouvé"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/combobox.tsx:60
|
||||||
|
#: packages/ui/primitives/multi-select-combobox.tsx:153
|
||||||
|
msgid "No value found."
|
||||||
|
msgstr "Aucune valeur trouvée."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:941
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:736
|
||||||
|
msgid "Number"
|
||||||
|
msgstr "Numéro"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:103
|
||||||
|
msgid "Number format"
|
||||||
|
msgstr "Format de numéro"
|
||||||
|
|
||||||
|
#: packages/lib/constants/template.ts:9
|
||||||
|
msgid "Once enabled, you can select any active recipient to be a direct link signing recipient, or create a new one. This recipient type cannot be edited or deleted."
|
||||||
|
msgstr "Une fois activé, vous pouvez sélectionner n'importe quel destinataire actif pour être un destinataire de signature de lien direct ou en créer un nouveau. Ce type de destinataire ne peut pas être modifié ou supprimé."
|
||||||
|
|
||||||
|
#: packages/lib/constants/template.ts:17
|
||||||
|
msgid "Once your template is set up, share the link anywhere you want. The person who opens the link will be able to enter their information in the direct link recipient field and complete any other fields assigned to them."
|
||||||
|
msgstr "Une fois votre modèle configuré, partagez le lien où vous le souhaitez. La personne qui ouvre le lien pourra saisir ses informations dans le champ de destinataire de lien direct et remplir tout autre champ qui lui est attribué."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/data-table-pagination.tsx:77
|
||||||
|
msgid "Page {0} of {1}"
|
||||||
|
msgstr "Page {0} sur {1}"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-password-dialog.tsx:62
|
||||||
|
msgid "Password Required"
|
||||||
|
msgstr "Mot de passe requis"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:156
|
||||||
|
msgid "Pick a number"
|
||||||
|
msgstr "Choisissez un numéro"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:79
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:84
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:60
|
||||||
|
msgid "Placeholder"
|
||||||
|
msgstr "Espace réservé"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:967
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:762
|
||||||
|
msgid "Radio"
|
||||||
|
msgstr "Radio"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:133
|
||||||
|
msgid "Radio values"
|
||||||
|
msgstr "Valeurs radio"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:186
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:147
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:156
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:122
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:133
|
||||||
|
msgid "Read only"
|
||||||
|
msgstr "Lecture seule"
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-role-select.tsx:95
|
||||||
|
msgid "Receives copy"
|
||||||
|
msgstr "Recevoir une copie"
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:39
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:215
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:169
|
||||||
|
msgid "Recipient action authentication"
|
||||||
|
msgstr "Authentification d'action de destinataire"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:283
|
||||||
|
msgid "Red"
|
||||||
|
msgstr "Rouge"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:329
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:332
|
||||||
|
msgid "Redirect URL"
|
||||||
|
msgstr "URL de redirection"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1069
|
||||||
|
msgid "Remove"
|
||||||
|
msgstr "Retirer"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:176
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:137
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:146
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/radio-field.tsx:112
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/text-field.tsx:123
|
||||||
|
msgid "Required field"
|
||||||
|
msgstr "Champ requis"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/data-table-pagination.tsx:55
|
||||||
|
msgid "Rows per page"
|
||||||
|
msgstr "Lignes par page"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-item-advanced-settings.tsx:355
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Sauvegarder"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:848
|
||||||
|
msgid "Save Template"
|
||||||
|
msgstr "Sauvegarder le modèle"
|
||||||
|
|
||||||
|
#: packages/ui/components/common/language-switcher-dialog.tsx:34
|
||||||
|
msgid "Search languages..."
|
||||||
|
msgstr "Rechercher des langues..."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:115
|
||||||
|
msgid "Select"
|
||||||
|
msgstr "Sélectionner"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/combobox.tsx:38
|
||||||
|
msgid "Select an option"
|
||||||
|
msgstr "Sélectionner une option"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:139
|
||||||
|
msgid "Select at least"
|
||||||
|
msgstr "Sélectionnez au moins"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/dropdown-field.tsx:105
|
||||||
|
msgid "Select default option"
|
||||||
|
msgstr "Sélectionner l'option par défaut"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-subject.tsx:124
|
||||||
|
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:34
|
||||||
|
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:64
|
||||||
|
msgid "Send"
|
||||||
|
msgstr "Envoyer"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:41
|
||||||
|
msgid "Send Document"
|
||||||
|
msgstr "Envoyer le document"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-share-button.tsx:135
|
||||||
|
msgid "Share Signature Card"
|
||||||
|
msgstr "Partager la carte de signature"
|
||||||
|
|
||||||
|
#: packages/lib/constants/template.ts:16
|
||||||
|
msgid "Share the Link"
|
||||||
|
msgstr "Partager le lien"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:680
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:655
|
||||||
|
msgid "Show advanced settings"
|
||||||
|
msgstr "Afficher les paramètres avancés"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:20
|
||||||
|
msgid "Sign"
|
||||||
|
msgstr "Signer"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:785
|
||||||
|
#: packages/ui/primitives/document-flow/add-signature.tsx:323
|
||||||
|
#: packages/ui/primitives/document-flow/field-icon.tsx:52
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:580
|
||||||
|
msgid "Signature"
|
||||||
|
msgstr "Signature"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:21
|
||||||
|
msgid "Signed"
|
||||||
|
msgstr "Signé"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:23
|
||||||
|
msgid "Signer"
|
||||||
|
msgstr "Signataire"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:22
|
||||||
|
msgid "Signing"
|
||||||
|
msgstr "Signature en cours"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/missing-signature-field-dialog.tsx:34
|
||||||
|
msgid "Some signers have not been assigned a signature field. Please assign at least 1 signature field to each signer before proceeding."
|
||||||
|
msgstr "Certains signataires n'ont pas été assignés à un champ de signature. Veuillez assigner au moins 1 champ de signature à chaque signataire avant de continuer."
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-share-button.tsx:51
|
||||||
|
msgid "Something went wrong"
|
||||||
|
msgstr "Quelque chose a mal tourné"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/data-table.tsx:136
|
||||||
|
msgid "Something went wrong."
|
||||||
|
msgstr "Quelque chose a mal tourné."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/document-flow-root.tsx:107
|
||||||
|
msgid "Step <0>{step} of {maxStep}</0>"
|
||||||
|
msgstr "Étape <0>{step} sur {maxStep}</0>"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-subject.tsx:78
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:195
|
||||||
|
msgid "Subject <0>(Optional)</0>"
|
||||||
|
msgstr "Objet <0>(Optionnel)</0>"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-password-dialog.tsx:97
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Soumettre"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:134
|
||||||
|
msgid "Template title"
|
||||||
|
msgstr "Titre du modèle"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:915
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:710
|
||||||
|
msgid "Text"
|
||||||
|
msgstr "Texte"
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:44
|
||||||
|
msgid "The authentication required for recipients to sign fields"
|
||||||
|
msgstr "L'authentification requise pour que les destinataires signent des champs"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-action-select.tsx:68
|
||||||
|
msgid "The authentication required for recipients to sign the signature field."
|
||||||
|
msgstr "L'authentification requise pour que les destinataires signent le champ de signature."
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-access-select.tsx:67
|
||||||
|
msgid "The authentication required for recipients to view the document."
|
||||||
|
msgstr "L'authentification requise pour que les destinataires visualisent le document."
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-send-email-message-helper.tsx:31
|
||||||
|
msgid "The document's name"
|
||||||
|
msgstr "Le nom du document"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-password-dialog.tsx:52
|
||||||
|
msgid "The password you have entered is incorrect. Please try again."
|
||||||
|
msgstr "Le mot de passe que vous avez entré est incorrect. Veuillez réessayer."
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-role-select.tsx:103
|
||||||
|
msgid "The recipient is not required to take any action and receives a copy of the document after it is completed."
|
||||||
|
msgstr "Le destinataire n'est pas tenu de prendre des mesures et reçoit une copie du document après son achèvement."
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-role-select.tsx:60
|
||||||
|
msgid "The recipient is required to approve the document for it to be completed."
|
||||||
|
msgstr "Le destinataire doit approuver le document pour qu'il soit complété."
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-role-select.tsx:39
|
||||||
|
msgid "The recipient is required to sign the document for it to be completed."
|
||||||
|
msgstr "Le destinataire doit signer le document pour qu'il soit complété."
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-role-select.tsx:81
|
||||||
|
msgid "The recipient is required to view the document for it to be completed."
|
||||||
|
msgstr "Le destinataire doit visualiser le document pour qu'il soit complété."
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-share-button.tsx:52
|
||||||
|
msgid "The sharing link could not be created at this time. Please try again."
|
||||||
|
msgstr "Le lien de partage n'a pas pu être créé pour le moment. Veuillez réessayer."
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-share-button.tsx:47
|
||||||
|
msgid "The sharing link has been copied to your clipboard."
|
||||||
|
msgstr "Le lien de partage a été copié dans votre presse-papiers."
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-send-email-message-helper.tsx:25
|
||||||
|
msgid "The signer's email"
|
||||||
|
msgstr "L'email du signataire"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-send-email-message-helper.tsx:19
|
||||||
|
msgid "The signer's name"
|
||||||
|
msgstr "Le nom du signataire"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-global-auth-action-select.tsx:72
|
||||||
|
msgid "This can be overriden by setting the authentication requirements directly on each recipient in the next step."
|
||||||
|
msgstr "Cela peut être remplacé par le paramétrage direct des exigences d'authentification pour chaque destinataire à l'étape suivante."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:746
|
||||||
|
msgid "This document has already been sent to this recipient. You can no longer edit this recipient."
|
||||||
|
msgstr "Ce document a déjà été envoyé à ce destinataire. Vous ne pouvez plus modifier ce destinataire."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-password-dialog.tsx:66
|
||||||
|
msgid "This document is password protected. Please enter the password to view the document."
|
||||||
|
msgstr "Ce document est protégé par mot de passe. Veuillez entrer le mot de passe pour visualiser le document."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-placeholder-recipients.tsx:573
|
||||||
|
msgid "This field cannot be modified or deleted. When you share this template's direct link or add it to your public profile, anyone who accesses it can input their name and email, and fill in the fields assigned to them."
|
||||||
|
msgstr "Ce champ ne peut pas être modifié ou supprimé. Lorsque vous partagez le lien direct de ce modèle ou l'ajoutez à votre profil public, toute personne qui y accède peut saisir son nom et son email, et remplir les champs qui lui sont attribués."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1050
|
||||||
|
msgid "This recipient can no longer be modified as they have signed a field, or completed the document."
|
||||||
|
msgstr "Ce destinataire ne peut plus être modifié car il a signé un champ ou complété le document."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:165
|
||||||
|
#~ msgid "This signer has already received the document."
|
||||||
|
#~ msgstr "This signer has already received the document."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-signers.tsx:194
|
||||||
|
msgid "This signer has already signed the document."
|
||||||
|
msgstr "Ce signataire a déjà signé le document."
|
||||||
|
|
||||||
|
#: packages/ui/components/recipient/recipient-action-auth-select.tsx:48
|
||||||
|
msgid "This will override any global settings."
|
||||||
|
msgstr "Cela remplacera tous les paramètres globaux."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:305
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-settings.tsx:309
|
||||||
|
msgid "Time Zone"
|
||||||
|
msgstr "Fuseau horaire"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-settings.tsx:153
|
||||||
|
msgid "Title"
|
||||||
|
msgstr "Titre"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-fields.tsx:1033
|
||||||
|
#: packages/ui/primitives/template-flow/add-template-fields.tsx:828
|
||||||
|
msgid "To proceed further, please set at least one value for the {0} field."
|
||||||
|
msgstr "Pour continuer, veuillez définir au moins une valeur pour le champ {0}."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/add-subject.tsx:124
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Mettre à jour"
|
||||||
|
|
||||||
|
#: packages/lib/constants/template.ts:13
|
||||||
|
msgid "Update the role and add fields as required for the direct recipient. The individual who uses the direct link will sign the document as the direct recipient."
|
||||||
|
msgstr "Mettez à jour le rôle et ajoutez des champs selon les besoins pour le destinataire direct. L'individu qui utilise le lien direct signera le document en tant que destinataire direct."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-dropzone.tsx:168
|
||||||
|
msgid "Upgrade"
|
||||||
|
msgstr "Améliorer"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-dropzone.tsx:70
|
||||||
|
msgid "Upload Template Document"
|
||||||
|
msgstr "Télécharger le document modèle"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/checkbox-field.tsx:132
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:167
|
||||||
|
msgid "Validation"
|
||||||
|
msgstr "Validation"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:91
|
||||||
|
#: packages/ui/primitives/document-flow/field-items-advanced-settings/number-field.tsx:96
|
||||||
|
msgid "Value"
|
||||||
|
msgstr "Valeur"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:26
|
||||||
|
msgid "View"
|
||||||
|
msgstr "Vue"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:27
|
||||||
|
msgid "Viewed"
|
||||||
|
msgstr "Vu"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:29
|
||||||
|
msgid "Viewer"
|
||||||
|
msgstr "Visiteur"
|
||||||
|
|
||||||
|
#: packages/lib/constants/recipient-roles.ts:28
|
||||||
|
msgid "Viewing"
|
||||||
|
msgstr "Visionnage"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/signature-pad/signature-pad.tsx:280
|
||||||
|
#~ msgid "White"
|
||||||
|
#~ msgstr "White"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-flow/send-document-action-dialog.tsx:44
|
||||||
|
msgid "You are about to send this document to the recipients. Are you sure you want to continue?"
|
||||||
|
msgstr "Vous êtes sur le point d'envoyer ce document aux destinataires. Êtes-vous sûr de vouloir continuer ?"
|
||||||
|
|
||||||
|
#: packages/ui/components/document/document-send-email-message-helper.tsx:11
|
||||||
|
msgid "You can use the following variables in your message:"
|
||||||
|
msgstr "Vous pouvez utiliser les variables suivantes dans votre message :"
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-dropzone.tsx:43
|
||||||
|
msgid "You cannot upload documents at this time."
|
||||||
|
msgstr "Vous ne pouvez pas télécharger de documents pour le moment."
|
||||||
|
|
||||||
|
#: packages/ui/primitives/document-dropzone.tsx:69
|
||||||
|
msgid "You have reached your document limit."
|
||||||
|
msgstr "Vous avez atteint votre limite de documents."
|
||||||
1
packages/lib/translations/fr/marketing.js
Normal file
620
packages/lib/translations/fr/marketing.po
Normal file
@ -0,0 +1,620 @@
|
|||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"POT-Creation-Date: 2024-07-24 13:01+1000\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"X-Generator: @lingui/cli\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"Project-Id-Version: documenso-app\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"PO-Revision-Date: 2024-10-08 12:05\n"
|
||||||
|
"Last-Translator: \n"
|
||||||
|
"Language-Team: French\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
"X-Crowdin-Project: documenso-app\n"
|
||||||
|
"X-Crowdin-Project-ID: 694691\n"
|
||||||
|
"X-Crowdin-Language: fr\n"
|
||||||
|
"X-Crowdin-File: marketing.po\n"
|
||||||
|
"X-Crowdin-File-ID: 6\n"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/blog/page.tsx:45
|
||||||
|
msgid "{0}"
|
||||||
|
msgstr "{0}"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:100
|
||||||
|
msgid "5 standard documents per month"
|
||||||
|
msgstr "5 documents standard par mois"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:198
|
||||||
|
msgid "5 Users Included"
|
||||||
|
msgstr "5 utilisateurs inclus"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:34
|
||||||
|
msgid "A 10x better signing experience."
|
||||||
|
msgstr "Une expérience de signature 10 fois meilleure."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:51
|
||||||
|
msgid "Add document"
|
||||||
|
msgstr "Ajouter un document"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:201
|
||||||
|
msgid "Add More Users for {0}"
|
||||||
|
msgstr "Ajouter plus d'utilisateurs pour {0}"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:165
|
||||||
|
msgid "All our metrics, finances, and learnings are public. We believe in transparency and want to share our journey with you. You can read more about why here: <0>Announcing Open Metrics</0>"
|
||||||
|
msgstr "Tous nos indicateurs, finances et apprentissages sont publics. Nous croyons en la transparence et souhaitons partager notre parcours avec vous. Vous pouvez en lire plus sur pourquoi ici : <0>Annonce de Open Metrics</0>"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/funding-raised.tsx:58
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/funding-raised.tsx:65
|
||||||
|
msgid "Amount Raised"
|
||||||
|
msgstr "Montant levé"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:145
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:189
|
||||||
|
msgid "API Access"
|
||||||
|
msgstr "Accès API"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:67
|
||||||
|
msgid "Beautiful."
|
||||||
|
msgstr "Beau."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:69
|
||||||
|
msgid "Because signing should be celebrated. That’s why we care about the smallest detail in our product."
|
||||||
|
msgstr "Parce que la signature doit être célébrée. C'est pourquoi nous nous soucions du moindre détail dans notre produit."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:35
|
||||||
|
#: apps/marketing/src/components/(marketing)/header.tsx:57
|
||||||
|
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:36
|
||||||
|
msgid "Blog"
|
||||||
|
msgstr "Blog"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:64
|
||||||
|
msgid "Build on top."
|
||||||
|
msgstr "Construire dessus."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:163
|
||||||
|
msgid "Can I use Documenso commercially?"
|
||||||
|
msgstr "Puis-je utiliser Documenso commercialement ?"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:42
|
||||||
|
msgid "Careers"
|
||||||
|
msgstr "Carrières"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:36
|
||||||
|
msgid "Changelog"
|
||||||
|
msgstr "Changelog"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:85
|
||||||
|
msgid "Choose a template from the community app store. Or submit your own template for others to use."
|
||||||
|
msgstr "Choisissez un modèle dans la boutique d'applications communautaires. Ou soumettez votre propre modèle pour que d'autres puissent l'utiliser."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:219
|
||||||
|
msgid "Community"
|
||||||
|
msgstr "Communauté"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/monthly-completed-documents-chart.tsx:55
|
||||||
|
msgid "Completed Documents"
|
||||||
|
msgstr "Documents complets"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/monthly-completed-documents-chart.tsx:33
|
||||||
|
msgid "Completed Documents per Month"
|
||||||
|
msgstr "Documents complets par mois"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:65
|
||||||
|
msgid "Connections"
|
||||||
|
msgstr "Connexions"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/enterprise.tsx:35
|
||||||
|
msgid "Contact Us"
|
||||||
|
msgstr "Contactez-nous"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:67
|
||||||
|
msgid "Create connections and automations with Zapier and more to integrate with your favorite tools."
|
||||||
|
msgstr "Créez des connexions et des automatisations avec Zapier et plus encore pour intégrer vos outils préférés."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/call-to-action.tsx:23
|
||||||
|
msgid "Create your account and start using state-of-the-art document signing. Open and beautiful signing is within your grasp."
|
||||||
|
msgstr "Créez votre compte et commencez à utiliser la signature de documents à la pointe de la technologie. Une signature ouverte et belle est à votre portée."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/tooltip.tsx:35
|
||||||
|
msgid "Customers with an Active Subscriptions."
|
||||||
|
msgstr "Clients avec un abonnement actif."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:33
|
||||||
|
msgid "Customise and expand."
|
||||||
|
msgstr "Personnaliser et étendre."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:38
|
||||||
|
msgid "Design"
|
||||||
|
msgstr "Conception"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:44
|
||||||
|
msgid "Designed for every stage of your journey."
|
||||||
|
msgstr "Conçu pour chaque étape de votre parcours."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/carousel.tsx:40
|
||||||
|
msgid "Direct Link"
|
||||||
|
msgstr "Lien Direct"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:181
|
||||||
|
msgid "Documenso is a community effort to create an open and vibrant ecosystem around a tool, everybody is free to use and adapt. By being truly open we want to create trusted infrastructure for the future of the internet."
|
||||||
|
msgstr "Documenso est un effort collectif pour créer un écosystème ouvert et dynamique autour d'un outil, tout le monde est libre de l'utiliser et de l'adapter. En étant vraiment ouvert, nous voulons créer une infrastructure de confiance pour l'avenir d'Internet."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/typefully.tsx:28
|
||||||
|
msgid "Documenso on X"
|
||||||
|
msgstr "Documenso sur X"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/hero.tsx:104
|
||||||
|
msgid "Document signing,<0/>finally open source."
|
||||||
|
msgstr "Signature de documents,<0/> enfin open source."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:33
|
||||||
|
#: apps/marketing/src/components/(marketing)/header.tsx:50
|
||||||
|
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:28
|
||||||
|
msgid "Documentation"
|
||||||
|
msgstr "Documentation"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:110
|
||||||
|
msgid "Easily embed Documenso into your product. Simply copy and paste our react widget into your application."
|
||||||
|
msgstr "Intégrez facilement Documenso dans votre produit. Il vous suffit de copier et coller notre widget React dans votre application."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:42
|
||||||
|
#~ msgid "Easy Sharing (Soon)."
|
||||||
|
#~ msgstr "Easy Sharing (Soon)."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:46
|
||||||
|
msgid "Easy Sharing."
|
||||||
|
msgstr "Partage facile."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:148
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:192
|
||||||
|
msgid "Email and Discord Support"
|
||||||
|
msgstr "Support par e-mail et Discord"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:43
|
||||||
|
msgid "Engagement"
|
||||||
|
msgstr "Engagement"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:64
|
||||||
|
msgid "Enter your details."
|
||||||
|
msgstr "Entrez vos détails."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/enterprise.tsx:16
|
||||||
|
msgid "Enterprise Compliance, License or Technical Needs?"
|
||||||
|
msgstr "Conformité des entreprises, besoins en licence ou techniques ?"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:128
|
||||||
|
msgid "Everything you need for a great signing experience."
|
||||||
|
msgstr "Tout ce dont vous avez besoin pour une excellente expérience de signature."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:45
|
||||||
|
msgid "Fast."
|
||||||
|
msgstr "Rapide."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:36
|
||||||
|
msgid "Faster, smarter and more beautiful."
|
||||||
|
msgstr "Plus rapide, plus intelligent et plus beau."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:210
|
||||||
|
msgid "Finances"
|
||||||
|
msgstr "Finances"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/typefully.tsx:38
|
||||||
|
msgid "Follow us on X"
|
||||||
|
msgstr "Suivez-nous sur X"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:172
|
||||||
|
msgid "For companies looking to scale across multiple teams."
|
||||||
|
msgstr "Pour les entreprises cherchant à se développer au sein de plusieurs équipes."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:85
|
||||||
|
msgid "For small teams and individuals with basic needs."
|
||||||
|
msgstr "Pour les petites équipes et les individus ayant des besoins de base."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:80
|
||||||
|
msgid "Free"
|
||||||
|
msgstr "Gratuit"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/blog/page.tsx:26
|
||||||
|
msgid "From the blog"
|
||||||
|
msgstr "Du blog"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/data.ts:9
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/data.ts:17
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/data.ts:33
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/data.ts:41
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/data.ts:49
|
||||||
|
msgid "Full-Time"
|
||||||
|
msgstr "Temps plein"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:87
|
||||||
|
msgid "Get paid (Soon)."
|
||||||
|
msgstr "Recevez une rémunération (Bientôt)."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/call-to-action.tsx:31
|
||||||
|
msgid "Get started"
|
||||||
|
msgstr "Commencez"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:75
|
||||||
|
msgid "Get Started"
|
||||||
|
msgstr "Commencez"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:47
|
||||||
|
msgid "Get started today."
|
||||||
|
msgstr "Commencez aujourd'hui."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/blog/page.tsx:30
|
||||||
|
msgid "Get the latest news from Documenso, including product updates, team announcements and more!"
|
||||||
|
msgstr "Obtenez les dernières nouvelles de Documenso, y compris les mises à jour de produits, les annonces d'équipe et plus encore !"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:233
|
||||||
|
msgid "GitHub: Total Merged PRs"
|
||||||
|
msgstr "GitHub : Total des PRs fusionnées"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:251
|
||||||
|
msgid "GitHub: Total Open Issues"
|
||||||
|
msgstr "GitHub : Total des problèmes ouverts"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:225
|
||||||
|
msgid "GitHub: Total Stars"
|
||||||
|
msgstr "GitHub : Nombre total d'étoiles"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/salary-bands.tsx:23
|
||||||
|
msgid "Global Salary Bands"
|
||||||
|
msgstr "Bandes de salaire globales"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:261
|
||||||
|
msgid "Growth"
|
||||||
|
msgstr "Croissance"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:134
|
||||||
|
msgid "How can I contribute?"
|
||||||
|
msgstr "Comment puis-je contribuer ?"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:105
|
||||||
|
msgid "How do you handle my data?"
|
||||||
|
msgstr "Comment gérez-vous mes données ?"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:118
|
||||||
|
msgid "Individual"
|
||||||
|
msgstr "Individuel"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:89
|
||||||
|
msgid "Integrated payments with Stripe so you don’t have to worry about getting paid."
|
||||||
|
msgstr "Paiements intégrés avec Stripe afin que vous n'ayez pas à vous soucier d'être payé."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:35
|
||||||
|
msgid "Integrates with all your favourite tools."
|
||||||
|
msgstr "S'intègre à tous vos outils préférés."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:289
|
||||||
|
msgid "Is there more?"
|
||||||
|
msgstr "Y a-t-il plus ?"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:44
|
||||||
|
msgid "It’s up to you. Either clone our repository or rely on our easy to use hosting solution."
|
||||||
|
msgstr "C'est à vous de décider. Soit vous clonez notre dépôt, soit vous vous fiez à notre solution d'hébergement simple à utiliser."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:49
|
||||||
|
msgid "Join Date"
|
||||||
|
msgstr "Date d'adhésion"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/call-to-action.tsx:19
|
||||||
|
msgid "Join the Open Signing Movement"
|
||||||
|
msgstr "Rejoignez le mouvement de signature ouverte"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:46
|
||||||
|
msgid "Location"
|
||||||
|
msgstr "Emplacement"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:66
|
||||||
|
msgid "Make it your own through advanced customization and adjustability."
|
||||||
|
msgstr "Faites-en votre propre grâce à une personnalisation avancée et un ajustement."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:199
|
||||||
|
msgid "Merged PR's"
|
||||||
|
msgstr "PRs fusionnées"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:234
|
||||||
|
msgid "Merged PRs"
|
||||||
|
msgstr "PRs fusionnées"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:40
|
||||||
|
msgid "Monthly"
|
||||||
|
msgstr "Mensuel"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:34
|
||||||
|
msgid "Name"
|
||||||
|
msgstr "Nom"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx:30
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx:43
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/monthly-new-users-chart.tsx:52
|
||||||
|
msgid "New Users"
|
||||||
|
msgstr "Nouveaux utilisateurs"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:106
|
||||||
|
msgid "No credit card required"
|
||||||
|
msgstr "Aucune carte de crédit requise"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/callout.tsx:29
|
||||||
|
#: apps/marketing/src/components/(marketing)/hero.tsx:125
|
||||||
|
msgid "No Credit Card required"
|
||||||
|
msgstr "Aucune carte de crédit requise"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:61
|
||||||
|
msgid "None of these work for you? Try self-hosting!"
|
||||||
|
msgstr "Aucune de ces options ne fonctionne pour vous ? Essayez l'hébergement autonome !"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:194
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:252
|
||||||
|
msgid "Open Issues"
|
||||||
|
msgstr "Problèmes ouverts"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:42
|
||||||
|
msgid "Open Source or Hosted."
|
||||||
|
msgstr "Open Source ou hébergé."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:161
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:37
|
||||||
|
#: apps/marketing/src/components/(marketing)/header.tsx:64
|
||||||
|
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:40
|
||||||
|
msgid "Open Startup"
|
||||||
|
msgstr "Startup ouverte"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:41
|
||||||
|
msgid "OSS Friends"
|
||||||
|
msgstr "Amis OSS"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:91
|
||||||
|
msgid "Our custom templates come with smart rules that can help you save time and energy."
|
||||||
|
msgstr "Nos modèles personnalisés sont dotés de règles intelligentes qui peuvent vous aider à gagner du temps et de l'énergie."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||||
|
msgid "Our Enterprise License is great for large organizations looking to switch to Documenso for all their signing needs. It's available for our cloud offering as well as self-hosted setups and offers a wide range of compliance and Adminstration Features."
|
||||||
|
msgstr "Notre licence entreprise est idéale pour les grandes organisations cherchant à passer à Documenso pour tous leurs besoins de signature. Elle est disponible pour notre offre cloud ainsi que pour des configurations auto-hébergées et propose un large éventail de fonctionnalités de conformité et d'administration."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/enterprise.tsx:20
|
||||||
|
#~ msgid "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||||
|
#~ msgstr "Our Enterprise License is great large organizations looking to switch to Documenso for all their signing needs. It's availible for our cloud offering as well as self-hosted setups and offer a wide range of compliance and Adminstration Features."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:65
|
||||||
|
msgid "Our self-hosted option is great for small teams and individuals who need a simple solution. You can use our docker based setup to get started in minutes. Take control with full customizability and data ownership."
|
||||||
|
msgstr "Notre option auto-hébergée est idéale pour les petites équipes et les individus qui ont besoin d'une solution simple. Vous pouvez utiliser notre configuration basée sur Docker pour commencer en quelques minutes. Prenez le contrôle avec une personnalisation complète et une propriété des données."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/data.ts:25
|
||||||
|
#~ msgid "Part-Time"
|
||||||
|
#~ msgstr "Part-Time"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:151
|
||||||
|
msgid "Premium Profile Name"
|
||||||
|
msgstr "Nom de profil premium"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:40
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:31
|
||||||
|
#: apps/marketing/src/components/(marketing)/header.tsx:42
|
||||||
|
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:24
|
||||||
|
msgid "Pricing"
|
||||||
|
msgstr "Prix"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:43
|
||||||
|
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:53
|
||||||
|
msgid "Privacy"
|
||||||
|
msgstr "Confidentialité"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/carousel.tsx:58
|
||||||
|
msgid "Profile"
|
||||||
|
msgstr "Profil"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:108
|
||||||
|
msgid "React Widget (Soon)."
|
||||||
|
msgstr "Widget React (Bientôt)."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:48
|
||||||
|
msgid "Receive your personal link to share with everyone you care about."
|
||||||
|
msgstr "Recevez votre lien personnel à partager avec tous ceux qui vous tiennent à cœur."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:37
|
||||||
|
msgid "Role"
|
||||||
|
msgstr "Rôle"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/salary-bands.tsx:37
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:40
|
||||||
|
msgid "Salary"
|
||||||
|
msgstr "Salaire"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:62
|
||||||
|
msgid "Save $60 or $120"
|
||||||
|
msgstr "Économisez 60 $ ou 120 $"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/i18n-switcher.tsx:47
|
||||||
|
#~ msgid "Search languages..."
|
||||||
|
#~ msgstr "Search languages..."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:109
|
||||||
|
msgid "Securely. Our data centers are located in Frankfurt (Germany), giving us the best local privacy laws. We are very aware of the sensitive nature of our data and follow best practices to ensure the security and integrity of the data entrusted to us."
|
||||||
|
msgstr "De manière sécurisée. Nos centres de données sont situés à Francfort (Allemagne), ce qui nous permet de bénéficier des meilleures lois locales sur la confidentialité. Nous sommes très conscients de la nature sensible de nos données et suivons les meilleures pratiques pour garantir la sécurité et l'intégrité des données qui nous sont confiées."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/share-connect-paid-widget-bento.tsx:37
|
||||||
|
msgid "Send, connect, receive and embed everywhere."
|
||||||
|
msgstr "Envoyer, connecter, recevoir et intégrer partout."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/salary-bands.tsx:34
|
||||||
|
msgid "Seniority"
|
||||||
|
msgstr "Ancienneté"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:39
|
||||||
|
msgid "Shop"
|
||||||
|
msgstr "Boutique"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:63
|
||||||
|
msgid "Sign"
|
||||||
|
msgstr "Signer"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/header.tsx:72
|
||||||
|
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:61
|
||||||
|
msgid "Sign in"
|
||||||
|
msgstr "Se connecter"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/header.tsx:77
|
||||||
|
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:57
|
||||||
|
msgid "Sign up"
|
||||||
|
msgstr "S'inscrire"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/carousel.tsx:22
|
||||||
|
msgid "Signing Process"
|
||||||
|
msgstr "Processus de signature"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:94
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:136
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:180
|
||||||
|
msgid "Signup Now"
|
||||||
|
msgstr "Inscrivez-vous maintenant"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:89
|
||||||
|
msgid "Smart."
|
||||||
|
msgstr "Intelligent."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/hero.tsx:132
|
||||||
|
msgid "Star on GitHub"
|
||||||
|
msgstr "Étoile sur GitHub"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:226
|
||||||
|
msgid "Stars"
|
||||||
|
msgstr "Étoiles"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:40
|
||||||
|
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:44
|
||||||
|
msgid "Status"
|
||||||
|
msgstr "Statut"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/footer.tsx:34
|
||||||
|
#: apps/marketing/src/components/(marketing)/mobile-navigation.tsx:48
|
||||||
|
msgid "Support"
|
||||||
|
msgstr "Soutien"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/team-members.tsx:26
|
||||||
|
msgid "Team"
|
||||||
|
msgstr "Équipe"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:195
|
||||||
|
msgid "Team Inbox"
|
||||||
|
msgstr "Boîte de réception de l'équipe"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/carousel.tsx:28
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:162
|
||||||
|
msgid "Teams"
|
||||||
|
msgstr "Équipes"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:83
|
||||||
|
msgid "Template Store (Soon)."
|
||||||
|
msgstr "Boutique de modèles (Bientôt)."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:138
|
||||||
|
msgid "That's awesome. You can take a look at the current <0>Issues</0> and join our <1>Discord Community</1> to keep up to date, on what the current priorities are. In any case, we are an open community and welcome all input, technical and non-technical ❤️"
|
||||||
|
msgstr "C'est génial. Vous pouvez consulter les <0>Problèmes</0> actuels et rejoindre notre <1>Communauté Discord</1> pour rester à jour sur ce qui est actuellement prioritaire. Dans tous les cas, nous sommes une communauté ouverte et accueillons toutes les contributions, techniques et non techniques ❤️"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:293
|
||||||
|
msgid "This page is evolving as we learn what makes a great signing company. We'll update it when we have more to share."
|
||||||
|
msgstr "Cette page évolue à mesure que nous apprenons ce qui fait une grande entreprise de signature. Nous la mettrons à jour lorsque nous aurons plus à partager."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/salary-bands.tsx:31
|
||||||
|
msgid "Title"
|
||||||
|
msgstr "Titre"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/total-signed-documents-chart.tsx:30
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/total-signed-documents-chart.tsx:55
|
||||||
|
msgid "Total Completed Documents"
|
||||||
|
msgstr "Documents totalisés complétés"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:267
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/page.tsx:268
|
||||||
|
msgid "Total Customers"
|
||||||
|
msgstr "Total des clients"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/funding-raised.tsx:29
|
||||||
|
msgid "Total Funding Raised"
|
||||||
|
msgstr "Total des fonds levés"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx:30
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx:43
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/monthly-total-users-chart.tsx:52
|
||||||
|
msgid "Total Users"
|
||||||
|
msgstr "Total des utilisateurs"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/open-build-template-bento.tsx:31
|
||||||
|
msgid "Truly your own."
|
||||||
|
msgstr "Vraiment le vôtre."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/callout.tsx:27
|
||||||
|
#: apps/marketing/src/components/(marketing)/hero.tsx:123
|
||||||
|
msgid "Try our Free Plan"
|
||||||
|
msgstr "Essayez notre plan gratuit"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/typefully.tsx:20
|
||||||
|
msgid "Twitter Stats"
|
||||||
|
msgstr "Statistiques Twitter"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:142
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:186
|
||||||
|
msgid "Unlimited Documents per Month"
|
||||||
|
msgstr "Documents illimités par mois"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:103
|
||||||
|
msgid "Up to 10 recipients per document"
|
||||||
|
msgstr "Jusqu'à 10 destinataires par document"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/singleplayer/client.tsx:52
|
||||||
|
msgid "Upload a document and add fields."
|
||||||
|
msgstr "Téléchargez un document et ajoutez des champs."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:123
|
||||||
|
msgid "Using our hosted version is the easiest way to get started, you can simply subscribe and start signing your documents. We take care of the infrastructure, so you can focus on your business. Additionally, when using our hosted version you benefit from our trusted signing certificates which helps you to build trust with your customers."
|
||||||
|
msgstr "Utiliser notre version hébergée est le moyen le plus simple de commencer, vous pouvez simplement vous abonner et commencer à signer vos documents. Nous nous occupons de l'infrastructure, vous pouvez vous concentrer sur votre entreprise. De plus, en utilisant notre version hébergée, vous bénéficiez de nos certificats de signature de confiance, ce qui vous aide à instaurer une relation de confiance avec vos clients."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/open/typefully.tsx:33
|
||||||
|
msgid "View all stats"
|
||||||
|
msgstr "Voir toutes les statistiques"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:195
|
||||||
|
msgid "We are happy to assist you at <0>support@documenso.com</0> or <1>in our Discord-Support-Channel</1> please message either Lucas or Timur to get added to the channel if you are not already a member."
|
||||||
|
msgstr "Nous sommes heureux de vous aider à <0>support@documenso.com</0> ou <1>dans notre canal de support Discord</1>, veuillez envoyer un message à Lucas ou Timur pour être ajouté au canal si vous n'êtes pas déjà membre."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:89
|
||||||
|
msgid "What is the difference between the plans?"
|
||||||
|
msgstr "Quelle est la différence entre les plans ?"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/faster-smarter-beautiful-bento.tsx:47
|
||||||
|
msgid "When it comes to sending or receiving a contract, you can count on lightning-fast speeds."
|
||||||
|
msgstr "En ce qui concerne l'envoi ou la réception d'un contrat, vous pouvez compter sur des vitesses ultrarapides."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:191
|
||||||
|
msgid "Where can I get support?"
|
||||||
|
msgstr "Où puis-je obtenir du support ?"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:177
|
||||||
|
msgid "Why should I prefer Documenso over DocuSign or some other signing tool?"
|
||||||
|
msgstr "Pourquoi devrais-je préférer Documenso à DocuSign ou à un autre outil de signature ?"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:119
|
||||||
|
msgid "Why should I use your hosting service?"
|
||||||
|
msgstr "Pourquoi devrais-je utiliser votre service d'hébergement ?"
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/pricing-table.tsx:60
|
||||||
|
msgid "Yearly"
|
||||||
|
msgstr "Annuel"
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:167
|
||||||
|
msgid "Yes! Documenso is offered under the GNU AGPL V3 open source license. This means you can use it for free and even modify it to fit your needs, as long as you publish your changes under the same license."
|
||||||
|
msgstr "Oui ! Documenso est proposé sous la licence open source GNU AGPL V3. Cela signifie que vous pouvez l'utiliser gratuitement et même le modifier pour répondre à vos besoins, tant que vous publiez vos modifications sous la même licence."
|
||||||
|
|
||||||
|
#: apps/marketing/src/app/(marketing)/pricing/page.tsx:93
|
||||||
|
msgid "You can self-host Documenso for free or use our ready-to-use hosted version. The hosted version comes with additional support, painless scalability and more. Early adopters will get access to all features we build this year, for no additional cost! Forever! Yes, that includes multiple users per account later. If you want Documenso for your enterprise, we are happy to talk about your needs."
|
||||||
|
msgstr "Vous pouvez auto-héberger Documenso gratuitement ou utiliser notre version hébergée prête à l'emploi. La version hébergée est accompagnée d'une assistance supplémentaire, d'une montée en charge sans effort et d'autres avantages. Les premiers adoptants auront accès à toutes les fonctionnalités que nous construirons cette année, sans coût supplémentaire ! Pour toujours ! Oui, cela inclut plusieurs utilisateurs par compte à l'avenir. Si vous souhaitez Documenso pour votre entreprise, nous serons heureux de discuter de vos besoins."
|
||||||
|
|
||||||
|
#: apps/marketing/src/components/(marketing)/carousel.tsx:272
|
||||||
|
msgid "Your browser does not support the video tag."
|
||||||
|
msgstr "Votre navigateur ne prend pas en charge la balise vidéo."
|
||||||
1
packages/lib/translations/fr/web.js
Normal file
4491
packages/lib/translations/fr/web.po
Normal file
@ -1 +0,0 @@
|
|||||||
/*eslint-disable*/module.exports={messages:JSON.parse("{}")};
|
|
||||||
@ -9,10 +9,39 @@ export const ZBaseFieldMeta = z.object({
|
|||||||
|
|
||||||
export type TBaseFieldMeta = z.infer<typeof ZBaseFieldMeta>;
|
export type TBaseFieldMeta = z.infer<typeof ZBaseFieldMeta>;
|
||||||
|
|
||||||
|
export const ZInitialsFieldMeta = z.object({
|
||||||
|
type: z.literal('initials').default('initials'),
|
||||||
|
fontSize: z.number().min(8).max(96).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TInitialsFieldMeta = z.infer<typeof ZInitialsFieldMeta>;
|
||||||
|
|
||||||
|
export const ZNameFieldMeta = z.object({
|
||||||
|
type: z.literal('name').default('name'),
|
||||||
|
fontSize: z.number().min(8).max(96).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TNameFieldMeta = z.infer<typeof ZNameFieldMeta>;
|
||||||
|
|
||||||
|
export const ZEmailFieldMeta = z.object({
|
||||||
|
type: z.literal('email').default('email'),
|
||||||
|
fontSize: z.number().min(8).max(96).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TEmailFieldMeta = z.infer<typeof ZEmailFieldMeta>;
|
||||||
|
|
||||||
|
export const ZDateFieldMeta = z.object({
|
||||||
|
type: z.literal('date').default('date'),
|
||||||
|
fontSize: z.number().min(8).max(96).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TDateFieldMeta = z.infer<typeof ZDateFieldMeta>;
|
||||||
|
|
||||||
export const ZTextFieldMeta = ZBaseFieldMeta.extend({
|
export const ZTextFieldMeta = ZBaseFieldMeta.extend({
|
||||||
type: z.literal('text').default('text'),
|
type: z.literal('text').default('text'),
|
||||||
text: z.string().optional(),
|
text: z.string().optional(),
|
||||||
characterLimit: z.number().optional(),
|
characterLimit: z.number().optional(),
|
||||||
|
fontSize: z.number().min(8).max(96).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TTextFieldMeta = z.infer<typeof ZTextFieldMeta>;
|
export type TTextFieldMeta = z.infer<typeof ZTextFieldMeta>;
|
||||||
@ -23,6 +52,7 @@ export const ZNumberFieldMeta = ZBaseFieldMeta.extend({
|
|||||||
value: z.string().optional(),
|
value: z.string().optional(),
|
||||||
minValue: z.number().optional(),
|
minValue: z.number().optional(),
|
||||||
maxValue: z.number().optional(),
|
maxValue: z.number().optional(),
|
||||||
|
fontSize: z.number().min(8).max(96).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TNumberFieldMeta = z.infer<typeof ZNumberFieldMeta>;
|
export type TNumberFieldMeta = z.infer<typeof ZNumberFieldMeta>;
|
||||||
@ -69,6 +99,10 @@ export type TDropdownFieldMeta = z.infer<typeof ZDropdownFieldMeta>;
|
|||||||
|
|
||||||
export const ZFieldMetaSchema = z
|
export const ZFieldMetaSchema = z
|
||||||
.union([
|
.union([
|
||||||
|
ZBaseFieldMeta.extend(ZInitialsFieldMeta.shape),
|
||||||
|
ZBaseFieldMeta.extend(ZNameFieldMeta.shape),
|
||||||
|
ZBaseFieldMeta.extend(ZEmailFieldMeta.shape),
|
||||||
|
ZBaseFieldMeta.extend(ZDateFieldMeta.shape),
|
||||||
ZTextFieldMeta,
|
ZTextFieldMeta,
|
||||||
ZNumberFieldMeta,
|
ZNumberFieldMeta,
|
||||||
ZRadioFieldMeta,
|
ZRadioFieldMeta,
|
||||||
|
|||||||
@ -362,7 +362,7 @@ export const formatDocumentAuditLogAction = (auditLog: TDocumentAuditLog, userId
|
|||||||
})
|
})
|
||||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT }, ({ data }) => ({
|
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.EMAIL_SENT }, ({ data }) => ({
|
||||||
anonymous: `Email ${data.isResending ? 'resent' : 'sent'}`,
|
anonymous: `Email ${data.isResending ? 'resent' : 'sent'}`,
|
||||||
identified: `${data.isResending ? 'resent' : 'sent'} an email`,
|
identified: `${data.isResending ? 'resent' : 'sent'} an email to ${data.recipientEmail}`,
|
||||||
}))
|
}))
|
||||||
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED }, () => {
|
.with({ type: DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED }, () => {
|
||||||
// Clear the prefix since this should be considered an 'anonymous' event.
|
// Clear the prefix since this should be considered an 'anonymous' event.
|
||||||
|
|||||||
@ -39,3 +39,27 @@ export const validateFieldsInserted = (fields: Field[]): boolean => {
|
|||||||
|
|
||||||
return uninsertedFields.length === 0;
|
return uninsertedFields.length === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const validateFieldsUninserted = (): boolean => {
|
||||||
|
const fieldCardElements = document.getElementsByClassName('react-draggable');
|
||||||
|
|
||||||
|
const errorElements: HTMLElement[] = [];
|
||||||
|
|
||||||
|
Array.from(fieldCardElements).forEach((element) => {
|
||||||
|
const innerDiv = element.querySelector('div');
|
||||||
|
const hasError = innerDiv?.getAttribute('data-error') === 'true';
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
errorElements.push(element as HTMLElement);
|
||||||
|
} else {
|
||||||
|
element.removeAttribute('data-error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errorElements.length > 0) {
|
||||||
|
errorElements[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorElements.length === 0;
|
||||||
|
};
|
||||||
|
|||||||
@ -7,9 +7,10 @@ import type { I18nLocaleData, SupportedLanguageCodes } from '../constants/i18n';
|
|||||||
import { APP_I18N_OPTIONS } from '../constants/i18n';
|
import { APP_I18N_OPTIONS } from '../constants/i18n';
|
||||||
|
|
||||||
export async function dynamicActivate(i18nInstance: I18n, locale: string) {
|
export async function dynamicActivate(i18nInstance: I18n, locale: string) {
|
||||||
const { messages } = await import(
|
const extension = process.env.NODE_ENV === 'development' ? 'po' : 'js';
|
||||||
`../translations/${locale}/${IS_APP_WEB ? 'web' : 'marketing'}.js`
|
const context = IS_APP_WEB ? 'web' : 'marketing';
|
||||||
);
|
|
||||||
|
const { messages } = await import(`../translations/${locale}/${context}.${extension}`);
|
||||||
|
|
||||||
i18nInstance.loadAndActivate({ locale, messages });
|
i18nInstance.loadAndActivate({ locale, messages });
|
||||||
}
|
}
|
||||||
@ -90,8 +91,18 @@ export const extractLocaleData = ({
|
|||||||
lang = 'en';
|
lang = 'en';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter out locales that are not valid.
|
||||||
|
const locales = (langHeader?.locales ?? []).filter((locale) => {
|
||||||
|
try {
|
||||||
|
new Intl.Locale(locale);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lang: lang || APP_I18N_OPTIONS.sourceLang,
|
lang: lang || APP_I18N_OPTIONS.sourceLang,
|
||||||
locales: langHeader.locales,
|
locales,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
33
packages/lib/utils/recipients.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { type Field, type Recipient, RecipientRole, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a recipient can be modified by the document owner.
|
||||||
|
*/
|
||||||
|
export const canRecipientBeModified = (recipient: Recipient, fields: Field[]) => {
|
||||||
|
// Deny if the recipient has already signed the document.
|
||||||
|
if (!recipient || recipient.signingStatus === SigningStatus.SIGNED) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deny if the recipient has inserted any fields.
|
||||||
|
if (fields.some((field) => field.recipientId === recipient.id && field.inserted)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a recipient can have their fields modified by the document owner.
|
||||||
|
*
|
||||||
|
* A recipient can their fields modified if all the conditions are met:
|
||||||
|
* - They are not a Viewer or CCer
|
||||||
|
* - They can be modified (canRecipientBeModified)
|
||||||
|
*/
|
||||||
|
export const canRecipientFieldsBeModified = (recipient: Recipient, fields: Field[]) => {
|
||||||
|
if (!canRecipientBeModified(recipient, fields)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return recipient.role !== RecipientRole.VIEWER && recipient.role !== RecipientRole.CC;
|
||||||
|
};
|
||||||
@ -105,13 +105,15 @@ export const unseedTeam = async (teamUrl: string) => {
|
|||||||
type SeedTeamMemberOptions = {
|
type SeedTeamMemberOptions = {
|
||||||
teamId: number;
|
teamId: number;
|
||||||
role?: TeamMemberRole;
|
role?: TeamMemberRole;
|
||||||
|
name?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const seedTeamMember = async ({
|
export const seedTeamMember = async ({
|
||||||
teamId,
|
teamId,
|
||||||
|
name,
|
||||||
role = TeamMemberRole.ADMIN,
|
role = TeamMemberRole.ADMIN,
|
||||||
}: SeedTeamMemberOptions) => {
|
}: SeedTeamMemberOptions) => {
|
||||||
const user = await seedUser();
|
const user = await seedUser({ name });
|
||||||
|
|
||||||
await prisma.teamMember.create({
|
await prisma.teamMember.create({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@ -21,8 +21,13 @@ export const seedUser = async ({
|
|||||||
password = 'password',
|
password = 'password',
|
||||||
verified = true,
|
verified = true,
|
||||||
}: SeedUserOptions = {}) => {
|
}: SeedUserOptions = {}) => {
|
||||||
if (!name) {
|
let url = name;
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
url = nanoid();
|
||||||
|
} else {
|
||||||
name = nanoid();
|
name = nanoid();
|
||||||
|
url = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!email) {
|
if (!email) {
|
||||||
@ -35,7 +40,7 @@ export const seedUser = async ({
|
|||||||
email,
|
email,
|
||||||
password: hashSync(password),
|
password: hashSync(password),
|
||||||
emailVerified: verified ? new Date() : undefined,
|
emailVerified: verified ? new Date() : undefined,
|
||||||
url: name,
|
url,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,17 +1,8 @@
|
|||||||
import { encryptSecondaryData } from '@documenso/lib/server-only/crypto/encrypt';
|
|
||||||
|
|
||||||
import { procedure, router } from '../trpc';
|
import { procedure, router } from '../trpc';
|
||||||
import { ZEncryptSecondaryDataMutationSchema } from './schema';
|
import { ZEncryptSecondaryDataMutationSchema } from './schema';
|
||||||
|
|
||||||
export const cryptoRouter = router({
|
export const cryptoRouter = router({
|
||||||
encryptSecondaryData: procedure
|
encryptSecondaryData: procedure.input(ZEncryptSecondaryDataMutationSchema).mutation(() => {
|
||||||
.input(ZEncryptSecondaryDataMutationSchema)
|
throw new Error('Public usage of encryptSecondaryData is no longer permitted');
|
||||||
.mutation(({ input }) => {
|
}),
|
||||||
try {
|
|
||||||
return encryptSecondaryData(input);
|
|
||||||
} catch {
|
|
||||||
// Never leak errors for crypto.
|
|
||||||
throw new Error('Failed to encrypt data');
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -101,7 +101,7 @@ export const templateRouter = router({
|
|||||||
.input(ZCreateDocumentFromTemplateMutationSchema)
|
.input(ZCreateDocumentFromTemplateMutationSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
try {
|
try {
|
||||||
const { templateId, teamId } = input;
|
const { templateId, teamId, recipients } = input;
|
||||||
|
|
||||||
const limits = await getServerLimits({ email: ctx.user.email, teamId });
|
const limits = await getServerLimits({ email: ctx.user.email, teamId });
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ export const templateRouter = router({
|
|||||||
templateId,
|
templateId,
|
||||||
teamId,
|
teamId,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
recipients: input.recipients,
|
recipients,
|
||||||
requestMetadata,
|
requestMetadata,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -49,7 +49,7 @@ export function FieldToolTip({ children, color, className = '', field }: FieldTo
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip delayDuration={0} open={!field.inserted}>
|
<Tooltip delayDuration={0} open={!field.inserted || !field.fieldMeta}>
|
||||||
<TooltipTrigger className="absolute inset-0 w-full"></TooltipTrigger>
|
<TooltipTrigger className="absolute inset-0 w-full"></TooltipTrigger>
|
||||||
|
|
||||||
<TooltipContent className={tooltipVariants({ color, className })} sideOffset={2}>
|
<TooltipContent className={tooltipVariants({ color, className })} sideOffset={2}>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|||||||
import { Caveat } from 'next/font/google';
|
import { Caveat } from 'next/font/google';
|
||||||
|
|
||||||
import { Trans, msg } from '@lingui/macro';
|
import { Trans, msg } from '@lingui/macro';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
import {
|
import {
|
||||||
CalendarDays,
|
CalendarDays,
|
||||||
Check,
|
Check,
|
||||||
@ -32,11 +33,18 @@ import {
|
|||||||
ZFieldMetaSchema,
|
ZFieldMetaSchema,
|
||||||
} from '@documenso/lib/types/field-meta';
|
} from '@documenso/lib/types/field-meta';
|
||||||
import { nanoid } from '@documenso/lib/universal/id';
|
import { nanoid } from '@documenso/lib/universal/id';
|
||||||
|
import { validateFieldsUninserted } from '@documenso/lib/utils/fields';
|
||||||
|
import {
|
||||||
|
canRecipientBeModified,
|
||||||
|
canRecipientFieldsBeModified,
|
||||||
|
} from '@documenso/lib/utils/recipients';
|
||||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||||
import { FieldType, RecipientRole, SendStatus } from '@documenso/prisma/client';
|
import { FieldType, RecipientRole, SendStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
|
import { FieldToolTip } from '../../components/field/field-tooltip';
|
||||||
import { getSignerColorStyles, useSignerColors } from '../../lib/signer-colors';
|
import { getSignerColorStyles, useSignerColors } from '../../lib/signer-colors';
|
||||||
import { cn } from '../../lib/utils';
|
import { cn } from '../../lib/utils';
|
||||||
|
import { Alert, AlertDescription } from '../alert';
|
||||||
import { Button } from '../button';
|
import { Button } from '../button';
|
||||||
import { Card, CardContent } from '../card';
|
import { Card, CardContent } from '../card';
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command';
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command';
|
||||||
@ -91,12 +99,6 @@ export type AddFieldsFormProps = {
|
|||||||
teamId?: number;
|
teamId?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
I hate this, but due to TailwindCSS JIT, I couldnn't find a better way to do this for now.
|
|
||||||
|
|
||||||
TODO: Try to find a better way to do this.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const AddFieldsFormPartial = ({
|
export const AddFieldsFormPartial = ({
|
||||||
documentFlow,
|
documentFlow,
|
||||||
hideRecipients = false,
|
hideRecipients = false,
|
||||||
@ -190,8 +192,7 @@ export const AddFieldsFormPartial = ({
|
|||||||
const selectedSignerStyles = useSignerColors(
|
const selectedSignerStyles = useSignerColors(
|
||||||
selectedSignerIndex === -1 ? 0 : selectedSignerIndex,
|
selectedSignerIndex === -1 ? 0 : selectedSignerIndex,
|
||||||
);
|
);
|
||||||
|
const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
|
||||||
const hasSelectedSignerBeenSent = selectedSigner?.sendStatus === SendStatus.SENT;
|
|
||||||
|
|
||||||
const filterFieldsWithEmptyValues = (fields: typeof localFields, fieldType: string) =>
|
const filterFieldsWithEmptyValues = (fields: typeof localFields, fieldType: string) =>
|
||||||
fields
|
fields
|
||||||
@ -225,11 +226,45 @@ export const AddFieldsFormPartial = ({
|
|||||||
const hasErrors =
|
const hasErrors =
|
||||||
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
|
emptyCheckboxFields.length > 0 || emptyRadioFields.length > 0 || emptySelectFields.length > 0;
|
||||||
|
|
||||||
const isFieldsDisabled =
|
const fieldsWithError = useMemo(() => {
|
||||||
!selectedSigner ||
|
const fields = localFields.filter((field) => {
|
||||||
hasSelectedSignerBeenSent ||
|
const hasError =
|
||||||
selectedSigner?.role === RecipientRole.VIEWER ||
|
((field.type === FieldType.CHECKBOX ||
|
||||||
selectedSigner?.role === RecipientRole.CC;
|
field.type === FieldType.RADIO ||
|
||||||
|
field.type === FieldType.DROPDOWN) &&
|
||||||
|
field.fieldMeta === undefined) ||
|
||||||
|
(field.fieldMeta && 'values' in field.fieldMeta && field?.fieldMeta?.values?.length === 0);
|
||||||
|
|
||||||
|
return hasError;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mappedFields = fields.map((field) => ({
|
||||||
|
id: field.nativeId ?? 0,
|
||||||
|
secondaryId: field.formId,
|
||||||
|
documentId: null,
|
||||||
|
templateId: null,
|
||||||
|
recipientId: 0,
|
||||||
|
type: field.type,
|
||||||
|
page: field.pageNumber,
|
||||||
|
positionX: new Prisma.Decimal(field.pageX),
|
||||||
|
positionY: new Prisma.Decimal(field.pageY),
|
||||||
|
width: new Prisma.Decimal(field.pageWidth),
|
||||||
|
height: new Prisma.Decimal(field.pageHeight),
|
||||||
|
customText: '',
|
||||||
|
inserted: true,
|
||||||
|
fieldMeta: field.fieldMeta ?? null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return mappedFields;
|
||||||
|
}, [localFields]);
|
||||||
|
|
||||||
|
const isFieldsDisabled = useMemo(() => {
|
||||||
|
if (!selectedSigner) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !canRecipientFieldsBeModified(selectedSigner, fields);
|
||||||
|
}, [selectedSigner, fields]);
|
||||||
|
|
||||||
const [isFieldWithinBounds, setIsFieldWithinBounds] = useState(false);
|
const [isFieldWithinBounds, setIsFieldWithinBounds] = useState(false);
|
||||||
const [coords, setCoords] = useState({
|
const [coords, setCoords] = useState({
|
||||||
@ -510,6 +545,14 @@ export const AddFieldsFormPartial = ({
|
|||||||
|
|
||||||
if (!everySignerHasSignature) {
|
if (!everySignerHasSignature) {
|
||||||
setIsMissingSignatureDialogVisible(true);
|
setIsMissingSignatureDialogVisible(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setValidateUninsertedFields(true);
|
||||||
|
const isFieldsValid = validateFieldsUninserted();
|
||||||
|
|
||||||
|
if (!isFieldsValid) {
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
void onFormSubmit();
|
void onFormSubmit();
|
||||||
}
|
}
|
||||||
@ -561,6 +604,10 @@ export const AddFieldsFormPartial = ({
|
|||||||
{isDocumentPdfLoaded &&
|
{isDocumentPdfLoaded &&
|
||||||
localFields.map((field, index) => {
|
localFields.map((field, index) => {
|
||||||
const recipientIndex = recipients.findIndex((r) => r.email === field.signerEmail);
|
const recipientIndex = recipients.findIndex((r) => r.email === field.signerEmail);
|
||||||
|
const hasFieldError =
|
||||||
|
emptyCheckboxFields.find((f) => f.formId === field.formId) ||
|
||||||
|
emptyRadioFields.find((f) => f.formId === field.formId) ||
|
||||||
|
emptySelectFields.find((f) => f.formId === field.formId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldItem
|
<FieldItem
|
||||||
@ -568,7 +615,8 @@ export const AddFieldsFormPartial = ({
|
|||||||
recipientIndex={recipientIndex === -1 ? 0 : recipientIndex}
|
recipientIndex={recipientIndex === -1 ? 0 : recipientIndex}
|
||||||
field={field}
|
field={field}
|
||||||
disabled={
|
disabled={
|
||||||
selectedSigner?.email !== field.signerEmail || hasSelectedSignerBeenSent
|
selectedSigner?.email !== field.signerEmail ||
|
||||||
|
!canRecipientBeModified(selectedSigner, fields)
|
||||||
}
|
}
|
||||||
minHeight={fieldBounds.current.height}
|
minHeight={fieldBounds.current.height}
|
||||||
minWidth={fieldBounds.current.width}
|
minWidth={fieldBounds.current.width}
|
||||||
@ -584,6 +632,7 @@ export const AddFieldsFormPartial = ({
|
|||||||
handleAdvancedSettings();
|
handleAdvancedSettings();
|
||||||
}}
|
}}
|
||||||
hideRecipients={hideRecipients}
|
hideRecipients={hideRecipients}
|
||||||
|
hasErrors={!!hasFieldError}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -976,6 +1025,7 @@ export const AddFieldsFormPartial = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DocumentFlowFormContainerContent>
|
</DocumentFlowFormContainerContent>
|
||||||
|
|
||||||
{hasErrors && (
|
{hasErrors && (
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<ul>
|
<ul>
|
||||||
@ -993,13 +1043,24 @@ export const AddFieldsFormPartial = ({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{selectedSigner && !canRecipientFieldsBeModified(selectedSigner, fields) && (
|
||||||
|
<Alert variant="warning">
|
||||||
|
<AlertDescription>
|
||||||
|
<Trans>
|
||||||
|
This recipient can no longer be modified as they have signed a field, or completed
|
||||||
|
the document.
|
||||||
|
</Trans>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
<DocumentFlowFormContainerFooter>
|
<DocumentFlowFormContainerFooter>
|
||||||
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
<DocumentFlowFormContainerStep step={currentStep} maxStep={totalSteps} />
|
||||||
|
|
||||||
<DocumentFlowFormContainerActions
|
<DocumentFlowFormContainerActions
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
disableNextStep={hasErrors}
|
|
||||||
onGoBackClick={() => {
|
onGoBackClick={() => {
|
||||||
previousStep();
|
previousStep();
|
||||||
remove();
|
remove();
|
||||||
@ -1016,6 +1077,11 @@ export const AddFieldsFormPartial = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{validateUninsertedFields && fieldsWithError[0] && (
|
||||||
|
<FieldToolTip key={fieldsWithError[0].id} field={fieldsWithError[0]} color="warning">
|
||||||
|
<Trans>Empty field</Trans>
|
||||||
|
</FieldToolTip>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -95,7 +95,7 @@ export const AddSettingsFormPartial = ({
|
|||||||
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ??
|
TIME_ZONES.find((timezone) => timezone === document.documentMeta?.timezone) ??
|
||||||
DEFAULT_DOCUMENT_TIME_ZONE,
|
DEFAULT_DOCUMENT_TIME_ZONE,
|
||||||
dateFormat:
|
dateFormat:
|
||||||
DATE_FORMATS.find((format) => format.label === document.documentMeta?.dateFormat)
|
DATE_FORMATS.find((format) => format.value === document.documentMeta?.dateFormat)
|
||||||
?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
?.value ?? DEFAULT_DOCUMENT_DATE_FORMAT,
|
||||||
redirectUrl: document.documentMeta?.redirectUrl ?? '',
|
redirectUrl: document.documentMeta?.redirectUrl ?? '',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { prop, sortBy } from 'remeda';
|
|||||||
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
import { useLimits } from '@documenso/ee/server-only/limits/provider/client';
|
||||||
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
import { ZRecipientAuthOptionsSchema } from '@documenso/lib/types/document-auth';
|
||||||
import { nanoid } from '@documenso/lib/universal/id';
|
import { nanoid } from '@documenso/lib/universal/id';
|
||||||
|
import { canRecipientBeModified as utilCanRecipientBeModified } from '@documenso/lib/utils/recipients';
|
||||||
import type { Field, Recipient } from '@documenso/prisma/client';
|
import type { Field, Recipient } from '@documenso/prisma/client';
|
||||||
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@documenso/prisma/client';
|
import { DocumentSigningOrder, RecipientRole, SendStatus } from '@documenso/prisma/client';
|
||||||
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
|
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
|
||||||
@ -159,21 +160,19 @@ export const AddSignersFormPartial = ({
|
|||||||
(recipient) => recipient.sendStatus === SendStatus.SENT,
|
(recipient) => recipient.sendStatus === SendStatus.SENT,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasBeenSentToRecipientId = useCallback(
|
const canRecipientBeModified = (recipientId?: number) => {
|
||||||
(id?: number) => {
|
if (recipientId === undefined) {
|
||||||
if (!id) {
|
return true;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return recipients.some(
|
const recipient = recipients.find((recipient) => recipient.id === recipientId);
|
||||||
(recipient) =>
|
|
||||||
recipient.id === id &&
|
if (!recipient) {
|
||||||
recipient.sendStatus === SendStatus.SENT &&
|
return false;
|
||||||
recipient.role !== RecipientRole.CC,
|
}
|
||||||
);
|
|
||||||
},
|
return utilCanRecipientBeModified(recipient, fields);
|
||||||
[recipients],
|
};
|
||||||
);
|
|
||||||
|
|
||||||
const onAddSigner = () => {
|
const onAddSigner = () => {
|
||||||
appendSigner({
|
appendSigner({
|
||||||
@ -189,10 +188,10 @@ export const AddSignersFormPartial = ({
|
|||||||
const onRemoveSigner = (index: number) => {
|
const onRemoveSigner = (index: number) => {
|
||||||
const signer = signers[index];
|
const signer = signers[index];
|
||||||
|
|
||||||
if (hasBeenSentToRecipientId(signer.nativeId)) {
|
if (!canRecipientBeModified(signer.nativeId)) {
|
||||||
toast({
|
toast({
|
||||||
title: _(msg`Cannot remove signer`),
|
title: _(msg`Cannot remove signer`),
|
||||||
description: _(msg`This signer has already received the document.`),
|
description: _(msg`This signer has already signed the document.`),
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -235,7 +234,7 @@ export const AddSignersFormPartial = ({
|
|||||||
const [reorderedSigner] = items.splice(result.source.index, 1);
|
const [reorderedSigner] = items.splice(result.source.index, 1);
|
||||||
|
|
||||||
let insertIndex = result.destination.index;
|
let insertIndex = result.destination.index;
|
||||||
while (insertIndex < items.length && hasBeenSentToRecipientId(items[insertIndex].nativeId)) {
|
while (insertIndex < items.length && !canRecipientBeModified(items[insertIndex].nativeId)) {
|
||||||
insertIndex++;
|
insertIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,7 +242,7 @@ export const AddSignersFormPartial = ({
|
|||||||
|
|
||||||
const updatedSigners = items.map((item, index) => ({
|
const updatedSigners = items.map((item, index) => ({
|
||||||
...item,
|
...item,
|
||||||
signingOrder: hasBeenSentToRecipientId(item.nativeId) ? item.signingOrder : index + 1,
|
signingOrder: !canRecipientBeModified(item.nativeId) ? item.signingOrder : index + 1,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
updatedSigners.forEach((item, index) => {
|
updatedSigners.forEach((item, index) => {
|
||||||
@ -270,7 +269,7 @@ export const AddSignersFormPartial = ({
|
|||||||
|
|
||||||
await form.trigger('signers');
|
await form.trigger('signers');
|
||||||
},
|
},
|
||||||
[form, hasBeenSentToRecipientId, watchedSigners],
|
[form, canRecipientBeModified, watchedSigners],
|
||||||
);
|
);
|
||||||
|
|
||||||
const triggerDragAndDrop = useCallback(
|
const triggerDragAndDrop = useCallback(
|
||||||
@ -315,9 +314,19 @@ export const AddSignersFormPartial = ({
|
|||||||
if (index === oldIndex) {
|
if (index === oldIndex) {
|
||||||
return { ...signer, signingOrder: newIndex + 1 };
|
return { ...signer, signingOrder: newIndex + 1 };
|
||||||
} else if (index >= newIndex && index < oldIndex) {
|
} else if (index >= newIndex && index < oldIndex) {
|
||||||
return { ...signer, signingOrder: (signer.signingOrder ?? index + 1) + 1 };
|
return {
|
||||||
|
...signer,
|
||||||
|
signingOrder: !canRecipientBeModified(signer.nativeId)
|
||||||
|
? signer.signingOrder
|
||||||
|
: (signer.signingOrder ?? index + 1) + 1,
|
||||||
|
};
|
||||||
} else if (index <= newIndex && index > oldIndex) {
|
} else if (index <= newIndex && index > oldIndex) {
|
||||||
return { ...signer, signingOrder: Math.max(1, (signer.signingOrder ?? index + 1) - 1) };
|
return {
|
||||||
|
...signer,
|
||||||
|
signingOrder: !canRecipientBeModified(signer.nativeId)
|
||||||
|
? signer.signingOrder
|
||||||
|
: Math.max(1, (signer.signingOrder ?? index + 1) - 1),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return signer;
|
return signer;
|
||||||
});
|
});
|
||||||
@ -326,7 +335,7 @@ export const AddSignersFormPartial = ({
|
|||||||
form.setValue(`signers.${index}.signingOrder`, signer.signingOrder);
|
form.setValue(`signers.${index}.signingOrder`, signer.signingOrder);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[form],
|
[form, canRecipientBeModified],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSigningOrderChange = useCallback(
|
const handleSigningOrderChange = useCallback(
|
||||||
@ -417,7 +426,7 @@ export const AddSignersFormPartial = ({
|
|||||||
isDragDisabled={
|
isDragDisabled={
|
||||||
!isSigningOrderSequential ||
|
!isSigningOrderSequential ||
|
||||||
isSubmitting ||
|
isSubmitting ||
|
||||||
hasBeenSentToRecipientId(signer.nativeId) ||
|
!canRecipientBeModified(signer.nativeId) ||
|
||||||
!signer.signingOrder
|
!signer.signingOrder
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -433,7 +442,7 @@ export const AddSignersFormPartial = ({
|
|||||||
>
|
>
|
||||||
<motion.fieldset
|
<motion.fieldset
|
||||||
data-native-id={signer.nativeId}
|
data-native-id={signer.nativeId}
|
||||||
disabled={isSubmitting || hasBeenSentToRecipientId(signer.nativeId)}
|
disabled={isSubmitting || !canRecipientBeModified(signer.nativeId)}
|
||||||
className={cn('grid grid-cols-10 items-end gap-2 pb-2', {
|
className={cn('grid grid-cols-10 items-end gap-2 pb-2', {
|
||||||
'border-b pt-2': showAdvancedSettings,
|
'border-b pt-2': showAdvancedSettings,
|
||||||
'grid-cols-12 pr-3': isSigningOrderSequential,
|
'grid-cols-12 pr-3': isSigningOrderSequential,
|
||||||
@ -466,7 +475,7 @@ export const AddSignersFormPartial = ({
|
|||||||
disabled={
|
disabled={
|
||||||
snapshot.isDragging ||
|
snapshot.isDragging ||
|
||||||
isSubmitting ||
|
isSubmitting ||
|
||||||
hasBeenSentToRecipientId(signer.nativeId)
|
!canRecipientBeModified(signer.nativeId)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -500,7 +509,7 @@ export const AddSignersFormPartial = ({
|
|||||||
disabled={
|
disabled={
|
||||||
snapshot.isDragging ||
|
snapshot.isDragging ||
|
||||||
isSubmitting ||
|
isSubmitting ||
|
||||||
hasBeenSentToRecipientId(signer.nativeId)
|
!canRecipientBeModified(signer.nativeId)
|
||||||
}
|
}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
/>
|
/>
|
||||||
@ -534,7 +543,7 @@ export const AddSignersFormPartial = ({
|
|||||||
disabled={
|
disabled={
|
||||||
snapshot.isDragging ||
|
snapshot.isDragging ||
|
||||||
isSubmitting ||
|
isSubmitting ||
|
||||||
hasBeenSentToRecipientId(signer.nativeId)
|
!canRecipientBeModified(signer.nativeId)
|
||||||
}
|
}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
/>
|
/>
|
||||||
@ -562,7 +571,7 @@ export const AddSignersFormPartial = ({
|
|||||||
disabled={
|
disabled={
|
||||||
snapshot.isDragging ||
|
snapshot.isDragging ||
|
||||||
isSubmitting ||
|
isSubmitting ||
|
||||||
hasBeenSentToRecipientId(signer.nativeId)
|
!canRecipientBeModified(signer.nativeId)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -585,7 +594,7 @@ export const AddSignersFormPartial = ({
|
|||||||
disabled={
|
disabled={
|
||||||
snapshot.isDragging ||
|
snapshot.isDragging ||
|
||||||
isSubmitting ||
|
isSubmitting ||
|
||||||
hasBeenSentToRecipientId(signer.nativeId)
|
!canRecipientBeModified(signer.nativeId)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -601,7 +610,7 @@ export const AddSignersFormPartial = ({
|
|||||||
disabled={
|
disabled={
|
||||||
snapshot.isDragging ||
|
snapshot.isDragging ||
|
||||||
isSubmitting ||
|
isSubmitting ||
|
||||||
hasBeenSentToRecipientId(signer.nativeId) ||
|
!canRecipientBeModified(signer.nativeId) ||
|
||||||
signers.length === 1
|
signers.length === 1
|
||||||
}
|
}
|
||||||
onClick={() => onRemoveSigner(index)}
|
onClick={() => onRemoveSigner(index)}
|
||||||
|
|||||||