Compare commits

...
96 Commits
Author SHA1 Message Date
RaktimaandGitHub e948fdc60b Merge branch 'main' into staging 2026-06-22 09:29:24 +05:30
RaktimaandGitHub 4bdfe9d725 Merge pull request #2192 from OpenSignLabs/updates-27678195317
Merge pull request #2576 from nxglabs/sync-to-public_repo-27677371306
2026-06-17 14:54:02 +05:30
raktima-opensignlabs be0c4a2cbf Merge pull request #2576 from nxglabs/sync-to-public_repo-27677371306
Merge pull request #2575 from nxglabs/staging
2026-06-17 09:08:15 +00:00
b417754a0c fix: handle server.cjs file (#2184)
Co-authored-by: raktima-opensignlabs <raktima-opensignlabs@users.noreply.github.com>
2026-05-26 10:50:21 +05:30
RaktimaandGitHub 6271fedcbf Merge pull request #2183 from OpenSignLabs/updates-26432416990
Merge pull request #2474 from nxglabs/sync-to-public_repo-26403170706
2026-05-26 10:03:53 +05:30
raktima-opensignlabs d3cb8d575c Merge pull request #2474 from nxglabs/sync-to-public_repo-26403170706
Merge pull request #2473 from nxglabs/raktima-patch-main-6
2026-05-26 04:33:08 +00:00
RaktimaandGitHub 1e228c86c3 v2.39.0 (#2180) 2026-05-22 21:03:28 +05:30
RaktimaandGitHub 0c295961c3 Merge pull request #2179 from OpenSignLabs/updates-26234947661
Merge pull request #2467 from nxglabs/sync-to-public_repo-26233821722
2026-05-21 20:46:42 +05:30
raktima-opensignlabs 5fbd1fc6f2 Merge pull request #2467 from nxglabs/sync-to-public_repo-26233821722
Merge pull request #2466 from nxglabs/raktima-main-patch-5
2026-05-21 15:14:25 +00:00
RaktimaandGitHub 3f4007d564 Merge pull request #2177 from OpenSignLabs/updates-26033200443
Merge pull request #2463 from nxglabs/sync-to-public_repo-26032162086
2026-05-18 17:57:04 +05:30
raktima-opensignlabs b49dc6bc6a Merge pull request #2463 from nxglabs/sync-to-public_repo-26032162086
Merge pull request #2462 from nxglabs/staging
2026-05-18 12:22:49 +00:00
RaktimaandGitHub b325095d60 Merge pull request #2175 from OpenSignLabs/updates-25926972891
Merge pull request #2460 from nxglabs/sync-to-public_repo-25861367352
2026-05-18 10:11:45 +05:30
raktima-opensignlabs 56a119a901 Merge pull request #2460 from nxglabs/sync-to-public_repo-25861367352
Merge pull request #2459 from nxglabs/staging
2026-05-15 15:44:29 +00:00
AmolandGitHub b9a309fa36 Merge pull request #2143 from OpenSignLabs/staging
v2.38.0
2026-03-16 17:47:26 +05:30
prafull-opensignlabsandGitHub 197c00dd79 Merge pull request #2142 from OpenSignLabs/updates-23143134478
v2.38.0
2026-03-16 17:46:23 +05:30
prafull-opensignlabs ef81a0409b v2.38.0 2026-03-16 12:14:55 +00:00
AmolandGitHub dd499ba0d0 Merge pull request #2130 from OpenSignLabs/staging 2026-03-04 13:13:57 +05:30
prafull-opensignlabsandGitHub a56b87a702 Merge pull request #2128 from OpenSignLabs/updates-22659538479
v2.37.0
2026-03-04 13:05:13 +05:30
prafull-opensignlabs acb0f3083c Merge pull request 2026-03-04 07:31:19 +00:00
prafull-opensignlabsandGitHub 606eb6a93b Merge pull request #2102 from OpenSignLabs/staging
v2.36.0
2026-02-06 18:21:57 +05:30
prafull-opensignlabsandGitHub 2e2e535b70 Merge pull request #2101 from OpenSignLabs/updates-21751052809
v2.36.0
2026-02-06 18:20:23 +05:30
prafull-opensignlabs 336cfb3482 Delete apps/OpenSignServer/package-lock.json 2026-02-06 12:45:57 +00:00
AmolandGitHub f1278d32a8 Merge pull request #2091 from cozyClassic/feature/kr-trans-0.9 2026-02-05 12:07:19 +05:30
KIM 79d184cf88 feat: Add Korean language support
Add Korean translation file (kr/translation.json)
Update i18n.js to include 'kr' in whitelist
Add Korean option to SelectLanguage component

Manual inspection of full translation.
TODO: check full-text about result of word translation
2026-01-20 13:50:30 +09:00
prafull-opensignlabsandGitHub cd2904b9f0 Merge pull request #2082 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/npm_and_yarn-e7552e82bb
build(deps): bump react-router from 7.11.0 to 7.12.0 in /apps/OpenSign in the npm_and_yarn group across 1 directory
2026-01-09 14:50:50 +05:30
prafull-opensignlabsandGitHub fe8ab56d47 Merge pull request #2084 from OpenSignLabs/staging
fix: handle overlapping of date modal
2026-01-09 14:47:18 +05:30
prafull-opensignlabsandGitHub fce20a165b Merge pull request #2083 from OpenSignLabs/updates-20846982071
fix: handle overlapping of date modal
2026-01-09 14:45:10 +05:30
prafull-opensignlabs 3a737ea1b7 fix: handle overlapping of date modal 2026-01-09 09:13:24 +00:00
dependabot[bot]andGitHub e035592e94 build(deps): bump react-router
Bumps the npm_and_yarn group with 1 update in the /apps/OpenSign directory: [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router).


Updates `react-router` from 7.11.0 to 7.12.0
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.12.0/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 7.12.0
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-09 00:30:55 +00:00
prafull-opensignlabsandGitHub 4c5a3509cd Merge pull request #2081 from OpenSignLabs/staging
refactor: spelling correction
2026-01-07 19:26:30 +05:30
prafull-opensignlabsandGitHub 0891cf55fc Merge pull request #2080 from OpenSignLabs/updates-20783798675
refactor: spelling correction
2026-01-07 19:25:17 +05:30
prafull-opensignlabs 5d328b51c3 refactor: spelling correction 2026-01-07 13:54:39 +00:00
AmolandGitHub f751a97f24 Merge pull request #2077 from OpenSignLabs/staging 2026-01-07 15:16:25 +05:30
prafull-opensignlabsandGitHub 336114f6ec Merge pull request #2076 from OpenSignLabs/updates-20777102041
v2.35.0
2026-01-07 15:12:13 +05:30
prafull-opensignlabs eb8e7b089b v2.35.0 2026-01-07 09:40:36 +00:00
AmolandGitHub e4ecb5dbfd Merge pull request #2060 from OpenSignLabs/staging 2025-12-22 15:29:21 +05:30
prafull-opensignlabsandGitHub bce0e4971e Merge pull request #2059 from OpenSignLabs/updates-20428403989
v2.34.0
2025-12-22 15:27:36 +05:30
prafull-opensignlabs 4fb8aa9a16 Delete apps/OpenSign/package-lock.json 2025-12-22 09:56:25 +00:00
prafull-opensignlabsandGitHub 2297d6fc22 Merge pull request #2051 from OpenSignLabs/staging
v2.33.0
2025-12-08 17:28:27 +05:30
prafull-opensignlabsandGitHub 8a2329dd60 Merge pull request #2050 from OpenSignLabs/updates-20027001996
v2.33.0
2025-12-08 17:16:35 +05:30
prafull-opensignlabs 75a40fbf43 Delete apps/OpenSignServer/package-lock.json 2025-12-08 11:45:29 +00:00
AmolandGitHub 8922b61ecd Merge pull request #2043 from OpenSignLabs/staging
v2.32.0
2025-11-28 18:56:30 +05:30
prafull-opensignlabsandGitHub 334edb3c0c Merge pull request #2042 from OpenSignLabs/updates-19765002150
v2.32.0
2025-11-28 18:54:17 +05:30
prafull-opensignlabs 6465aa7c2c Delete apps/OpenSignServer/package-lock.json 2025-11-28 13:19:46 +00:00
prafull-opensignlabsandGitHub 4593ce7fec Merge pull request #2039 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/npm_and_yarn-42c243e405
build(deps): bump body-parser from 2.2.0 to 2.2.1 in /apps/OpenSignServer in the npm_and_yarn group across 1 directory
2025-11-27 00:09:48 +05:30
dependabot[bot]andGitHub b4ffdf0dc9 build(deps): bump body-parser
Bumps the npm_and_yarn group with 1 update in the /apps/OpenSignServer directory: [body-parser](https://github.com/expressjs/body-parser).


Updates `body-parser` from 2.2.0 to 2.2.1
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-version: 2.2.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-25 18:22:09 +00:00
AmolandGitHub db4b2d58d6 Merge pull request #2030 from OpenSignLabs/staging
fix: remove type parameter from parse.save method
2025-11-20 20:18:58 +05:30
AmolandGitHub 3c8b8835c7 Merge pull request #2029 from OpenSignLabs/build_deps
fix: remove type parameter from parse.save method
2025-11-20 20:17:26 +05:30
prafull-opensignlabs 20752161d3 fix: remove type parameter from parse.save method 2025-11-20 20:08:06 +05:30
AmolandGitHub 7017930709 Merge pull request #2028 from OpenSignLabs/staging
fix: add password validation in change password page
2025-11-20 18:27:50 +05:30
prafull-opensignlabsandGitHub 002b0d5277 Merge pull request #2027 from OpenSignLabs/build_deps
fix: handle password validation in change password page
2025-11-20 17:13:20 +05:30
prafull-opensignlabsandGitHub 47348b990e Merge pull request #1957 from Miracee97/patch-1
Update README.md
2025-11-20 17:09:04 +05:30
prafull-opensignlabs ac86b6ef61 fix: add password validation in change password page 2025-11-20 17:00:39 +05:30
AmolandGitHub 810037e3c8 Merge pull request #2026 from OpenSignLabs/staging
build(deps): update dependencies
2025-11-20 16:24:43 +05:30
prafull-opensignlabsandGitHub 7894a6edb8 Merge pull request #2025 from OpenSignLabs/build_deps
build(deps): update dependencies
2025-11-20 16:21:33 +05:30
prafull-opensignlabs dac8957904 build(deps): update dependencies 2025-11-20 16:19:19 +05:30
AmolandGitHub f8987a57d9 Merge pull request #2024 from OpenSignLabs/staging
v2.31.0
2025-11-20 12:20:55 +05:30
AmolandGitHub ef686aec6a Refactor Docker workflow for multi-arch builds 2025-11-20 12:19:09 +05:30
AmolandGitHub f31155e0b4 Merge pull request #2022 from OpenSignLabs/updates-19528160086
v2.31.0
2025-11-20 12:17:56 +05:30
AmolandGitHub 78e4857d1a Merge pull request #2023 from OpenSignLabs/all-contributors/add-ehowe
docs: add ehowe as a contributor for code
2025-11-20 12:17:29 +05:30
allcontributors[bot]andGitHub c3144366d9 docs: update .all-contributorsrc 2025-11-20 06:47:02 +00:00
allcontributors[bot]andGitHub f00a7c9e50 docs: update README.md 2025-11-20 06:47:01 +00:00
prafull-opensignlabs 803d25e9d1 Fix: Document title changes are not reflected when the document is sent to signers.
Merge pull request #1498 from nxglabs/staging
2025-11-20 06:44:56 +00:00
prafull-opensignlabsandGitHub f3d20391fa Merge pull request #1995 from OpenSignLabs/staging
v2.30.0
2025-10-16 14:24:55 +05:30
AndrewandGitHub 6594ba03a3 Merge pull request #1974 from OpenSignLabs/staging
v2.29.2
2025-10-01 19:39:25 +05:30
AmolandGitHub 9c2a388f66 Merge pull request #1970 from OpenSignLabs/staging
v2.29.1
2025-09-30 17:59:48 +05:30
AndrewandGitHub 8c49e1ac18 Merge pull request #1967 from OpenSignLabs/staging
v2.29.0
2025-09-29 10:56:48 +05:30
RaistlinandGitHub a878ca1a96 Update README.md
updated link to api documentation v1.1
2025-09-23 11:33:29 +02:00
AmolandGitHub e1bf2ed94b Merge pull request #1946 from OpenSignLabs/staging
refactor: remove unnecessary code
2025-09-17 19:11:11 +05:30
AmolandGitHub 797fcbd624 Merge pull request #1944 from OpenSignLabs/staging
build(deps): update dependencies and dark mode ui
2025-09-17 14:38:38 +05:30
AmolandGitHub 847657dc76 Merge pull request #1942 from OpenSignLabs/staging
v2.28.0
2025-09-15 20:31:08 +05:30
prafull-opensignlabsandGitHub 3a361cc21e Merge pull request #1932 from OpenSignLabs/staging
fix: getting error 'user not found' while deleting user
2025-09-08 16:24:29 +05:30
prafull-opensignlabsandGitHub 1496c1b1da Merge pull request #1930 from OpenSignLabs/staging
v2.27.2
2025-09-08 10:38:37 +05:30
prafull-opensignlabsandGitHub d87fa38976 Merge pull request #1836 from OpenSignLabs/staging
v2.26.0
2025-07-07 15:16:19 +05:30
prafull-opensignlabsandGitHub df4630aa31 Merge pull request #1805 from OpenSignLabs/staging
v2.25.0
2025-06-19 17:37:33 +05:30
prafull-opensignlabsandGitHub d8a0d9137e Merge pull request #1790 from OpenSignLabs/staging
v2.24.1
2025-06-06 18:17:54 +05:30
prafull-opensignlabsandGitHub 1b2528ac1d Merge pull request #1765 from OpenSignLabs/staging
v2.23.0
2025-05-30 20:32:35 +05:30
prafull-opensignlabsandGitHub 9b357acbdd Merge pull request #1757 from OpenSignLabs/staging
v2.22.0
2025-05-28 11:27:44 +05:30
prafull-opensignlabsandGitHub ff4fa3a15f v2.21.1
handle undefined placeholders
2025-05-15 16:52:45 +05:30
prafull-opensignlabsandGitHub f104b0ca86 Merge pull request #1736 from OpenSignLabs/staging
v2.21.0
2025-05-15 12:23:07 +05:30
prafull-opensignlabsandGitHub 31affb53f1 Merge pull request #1718 from OpenSignLabs/staging
v2.20.0
2025-05-05 18:31:52 +05:30
prafull-opensignlabsandGitHub 338b76029f Merge pull request #1710 from OpenSignLabs/staging
fix: quick send not working
2025-04-29 12:17:44 +05:30
prafull-opensignlabsandGitHub 646e202b91 Merge pull request #1705 from OpenSignLabs/staging
refactor: remove extra comment from migration
2025-04-27 12:21:49 +05:30
prafull-opensignlabsandGitHub 8a34917343 Merge pull request #1703 from OpenSignLabs/staging
fix: update migration script
2025-04-27 12:03:23 +05:30
prafull-opensignlabsandGitHub 9b8c9403d1 Merge pull request #1701 from OpenSignLabs/staging
v2.19.0
2025-04-25 19:18:06 +05:30
prafull-opensignlabsandGitHub 40198e1bd8 Merge pull request #1693 from OpenSignLabs/staging
fix: correct public URL, normalize emails, migrate user/orgAdmin logic to cloud function
2025-04-22 14:10:35 +05:30
prafull-opensignlabsandGitHub 0e6220e16c Merge pull request #1690 from OpenSignLabs/staging
v2.18.0
2025-04-17 12:07:30 +05:30
prafull-opensignlabsandGitHub fc3a5ad1bf Merge pull request #1682 from OpenSignLabs/staging
v2.17.0
2025-04-11 13:21:32 +05:30
prafull-opensignlabsandGitHub 4919d6c3d3 Merge pull request #1673 from OpenSignLabs/staging
v2.16.0
2025-04-05 11:37:25 +05:30
prafull-opensignlabsandGitHub 91e89bb0e2 Merge pull request #1664 from OpenSignLabs/staging
v2.15.0
2025-03-28 11:38:46 +05:30
prafull-opensignlabsandGitHub f942b2571a Merge pull request #1662 from OpenSignLabs/staging
feat: changes to implement one click deployment
2025-03-24 13:48:24 +05:30
prafull-opensignlabsandGitHub ae2c15660d Merge pull request #1655 from OpenSignLabs/staging
refactor: remove unnecessary code
2025-03-23 13:12:52 +05:30
prafull-opensignlabsandGitHub 721d52d539 Merge pull request #1649 from OpenSignLabs/staging
v2.14.0
2025-03-21 20:10:43 +05:30
prafull-opensignlabsandGitHub b03e27cf70 Merge pull request #1647 from OpenSignLabs/staging
v2.13.0
2025-03-20 17:38:11 +05:30
prafull-opensignlabsandGitHub 4eae44c9b4 Merge pull request #1604 from OpenSignLabs/staging
v2.13.1
2025-02-21 13:02:46 +05:30
prafull-opensignlabsandGitHub 79234441db Merge pull request #1600 from OpenSignLabs/staging
v2.13.0
2025-02-21 12:47:30 +05:30
300 changed files with 48925 additions and 19915 deletions
+9
View File
@@ -241,6 +241,15 @@
"contributions": [
"translation"
]
},
{
"login": "ehowe",
"name": "Eugene Howe",
"avatar_url": "https://avatars.githubusercontent.com/u/756984?v=4",
"profile": "https://github.com/ehowe",
"contributions": [
"code"
]
}
]
}
+24 -12
View File
@@ -21,34 +21,46 @@ jobs:
dockerfile: apps/OpenSign/Dockerhubfile
- image: opensign/opensignserver
dockerfile: apps/OpenSignServer/Dockerhubfile
steps:
-
name: Checkout
- name: Checkout
uses: actions/checkout@v3
-
name: Login to Docker Hub
# 👇 Enable emulation so we can build for arm64 on amd64 runners
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
with:
platforms: linux/amd64,linux/arm64
# 👇 Enable Buildx for multi-arch builds
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Extract metadata (tags, labels) for Docker
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ matrix.image }}
-
name: Debug - List files opensign
- name: Debug - List files opensign
run: ls -R apps/OpenSign/ # Adjust the path as needed
-
name: Debug - List files opensignserver
- name: Debug - List files opensignserver
run: ls -R apps/OpenSignServer/ # Adjust the path as needed
-
name: Build and push
- name: Build and push (multi-arch)
uses: docker/build-push-action@v4
with:
context: .
file: ${{ matrix.dockerfile }}
push: true
# 👇 Build for both AMD64 & ARM64
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
+2 -1
View File
@@ -60,7 +60,7 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
- **PDF Template Creation:** OpenSign™ allows you to create and store PDF document templates for repeated use thereby saving you a lot of time & collect e-signatures seamlessly.
- **OpenSign™ Drive:** It is a centralised secure vault for your digital documents that makes storing, signing, organizing, sharing & archieving your docs a breeze.
- **Audit Trails & completion certificate:** Being a security focused solution, OpenSign™ makes it a top priority to save detailed logs for tracking document activities along with time-stamps, IP addresses, email IDs & phone numbers. A completion certificate is generated as soon as document is completed which contains all the document related logs for added safety.
- **API Support:** OpenSign™ API allows seamless integration into existing systems and software. You can generate an API key from the app and refer the [official API docs](https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1) to start integrating it in your existing applications.
- **API Support:** OpenSign™ API allows seamless integration into existing systems and software. You can generate an API key from the app and refer the [official API docs](https://docs.opensignlabs.com/docs/API-docs/v1.1) to start integrating it in your existing applications.
- **Integrations:** The open source document signing experience becomes even more seamless because of integrations with various Cloud storage systems, CRMs & enterprise platforms. We also have a Zapier integration that allows you to integrate it with virtually any application.
<img alt="Login page" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/8016edb6-7d6e-4185-ab72-82d2c4a16032" height='200' >
<img alt="Dash_board" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/6e7b96f7-3154-4c25-bc1e-caf33925cc47" height='200'>
@@ -165,6 +165,7 @@ We would like to thank all our contributors and users for their support and feed
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daniel-mutwiri"><img src="https://avatars.githubusercontent.com/u/8936960?v=4?s=100" width="100px;" alt="Daniel Mutwiri"/><br /><sub><b>Daniel Mutwiri</b></sub></a><br /><a href="#code-daniel-mutwiri" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/1024mb"><img src="https://avatars.githubusercontent.com/u/9301204?v=4?s=100" width="100px;" alt="1024mb"/><br /><sub><b>1024mb</b></sub></a><br /><a href="#translation-1024mb" title="Translation">🌍</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ehowe"><img src="https://avatars.githubusercontent.com/u/756984?v=4?s=100" width="100px;" alt="Eugene Howe"/><br /><sub><b>Eugene Howe</b></sub></a><br /><a href="#code-ehowe" title="Code">💻</a></td>
</tr>
</tbody>
</table>
@@ -21,9 +21,7 @@ const DisabledButtonExamples = () => {
{/* Option C: Conditional styling */}
<button
className={`op-btn op-btn-primary ${
isDisabled ? "op-btn-vscode-disabled" : ""
}`}
className={`op-btn op-btn-primary ${isDisabled ? "op-btn-vscode-disabled" : ""}`}
disabled={isDisabled}
>
Conditional Button
+17449
View File
File diff suppressed because it is too large Load Diff
+80 -53
View File
@@ -1,67 +1,88 @@
{
"name": "open_sign",
"version": "2.30.0",
"version": "2.37.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@formkit/auto-animate": "^0.9.0",
"@imgly/background-removal": "^1.7.0",
"@lottiefiles/dotlottie-react": "^0.16.2",
"@lottiefiles/dotlottie-react": "^0.19.0",
"@mui/icons-material": "^5.15.10",
"@mui/material": "^5.15.10",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.2.1",
"@reduxjs/toolkit": "^2.8.2",
"axios": "^1.12.2",
"@radix-ui/themes": "^3.3.0",
"@reactour/tour": "^3.8.0",
"@reduxjs/toolkit": "^2.11.2",
"@usewaypoint/block-avatar": "^0.0.3",
"@usewaypoint/block-button": "^0.0.3",
"@usewaypoint/block-columns-container": "^0.0.3",
"@usewaypoint/block-container": "^0.0.2",
"@usewaypoint/block-divider": "^0.0.4",
"@usewaypoint/block-heading": "^0.0.3",
"@usewaypoint/block-html": "^0.0.3",
"@usewaypoint/block-image": "^0.0.5",
"@usewaypoint/block-spacer": "^0.0.3",
"@usewaypoint/block-text": "^0.0.7",
"@usewaypoint/document-core": "^0.0.6",
"@usewaypoint/email-builder": "^0.0.9",
"axios": "^1.16.0",
"date-fns-tz": "^3.2.0",
"dompurify": "^3.4.1",
"file-saver": "^2.0.5",
"i18next": "^25.4.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"highlight.js": "^11.11.1",
"i18next": "^26.0.8",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^3.0.6",
"jszip": "^3.10.1",
"juice": "^11.1.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"parse": "^6.1.1",
"parse": "^8.1.0",
"pdf-lib": "^1.17.1",
"pkijs": "^3.0.8",
"pkijs": "^3.4.0",
"prettier": "^3.8.3",
"print-js": "^1.6.0",
"prismjs": "^1.30.0",
"process": "^0.11.10",
"quill-html-edit-button": "^3.0.0",
"radix-ui": "^1.4.3",
"react": "^18.3.1",
"react": "^19.2.5",
"react-bootstrap": "^2.10.10",
"react-colorful": "^5.6.1",
"react-confetti": "^6.4.0",
"react-datepicker": "^8.7.0",
"react-datepicker": "^9.1.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^9.0.0",
"react-dnd-touch-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-dom": "^19.2.5",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.7.4",
"react-konva": "^18.2.12",
"react-pdf": "^9.2.1",
"react-quill-new": "^3.6.0",
"react-i18next": "^17.0.6",
"react-konva": "^19.2.3",
"react-pdf": "^10.4.1",
"react-quill-new": "^3.8.3",
"react-redux": "^9.2.0",
"react-rnd": "^10.5.2",
"react-router": "^7.9.3",
"react-rnd": "^10.5.3",
"react-router": "^7.14.2",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.10.2",
"react-signature-canvas": "^1.1.0-alpha.2",
"react-timezone-select": "^3.2.8",
"react-tooltip": "^5.29.1",
"reactour": "^1.19.4",
"react-timezone-select": "^3.3.3",
"react-to-print": "^3.3.0",
"react-tooltip": "^5.30.1",
"redux": "^5.0.1",
"regex-parser": "^2.3.1",
"serve": "^14.2.5",
"styled-components": "^5.3.11",
"web-vitals": "^5.1.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
"web-vitals": "^5.2.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"zod": "^3.22.4",
"zustand": "^4.5.1"
},
"scripts": {
"build": "npm run version && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
"start-dev": "vite",
"dev": "vite",
"preview": "vite preview",
"start": "serve -s build",
"start": "node server.cjs",
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
"build-win": "npm run version-win && vite build",
@@ -94,36 +115,42 @@
}
},
"devDependencies": {
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.28.3",
"@babel/preset-react": "^7.27.1",
"@babel/runtime-corejs2": "^7.28.3",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@babel/core": "^7.29.0",
"@babel/preset-env": "^7.29.2",
"@babel/preset-react": "^7.28.5",
"@babel/runtime-corejs2": "^7.29.2",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.1.13",
"@vitejs/plugin-react": "^4.7.0",
"@vitest/ui": "^3.2.4",
"autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0",
"@types/react": "^19.2.14",
"@vitejs/plugin-react": "^6.0.1",
"@vitest/ui": "^4.1.5",
"autoprefixer": "^10.5.0",
"babel-loader": "^9.2.1",
"commitizen": "^4.3.1",
"concurrently": "^9.2.1",
"css-loader": "^7.1.2",
"concurrently": "^10.0.3",
"css-loader": "^7.1.4",
"daisyui": "^4.12.24",
"eslint": "^9.37.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint": "^9.39.4",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-react": "^7.37.5",
"jsdom": "^27.0.0",
"lint-staged": "^16.1.6",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"jsdom": "^29.0.2",
"lint-staged": "^16.4.0",
"postcss": "^8.5.12",
"pretty-quick": "^4.2.2",
"rollup-plugin-node-polyfills": "^0.2.1",
"tailwindcss": "^3.4.17",
"vite": "^7.1.4",
"vite-plugin-svgr": "^4.5.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
"tailwindcss": "^3.4.19",
"tmp": "^0.2.7",
"vite": ">=8.0.16",
"vite-plugin-svgr": "^5.2.0",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.1.5"
},
"overrides": {
"tmp": "$tmp",
"react": "$react",
"react-dom": "$react-dom",
"form-data": ">=4.0.6"
},
"engines": {
"node": "18 || 20 || 22"
+348 -34
View File
@@ -71,10 +71,11 @@
"login-page": "Anmeldeseite",
"or": "ODER",
"signup-page": "Registrierungsseite",
"password-match-length": "Neues Passwort und Bestätigungspasswort sollten übereinstimmen",
"password-length": "Das Passwort sollte 8 Zeichen lang sein",
"password-case": "Das Passwort sollte einen Großbuchstaben, einen Kleinbuchstaben und eine Zahl enthalten",
"password-special-char": "Das Passwort sollte ein Sonderzeichen enthalten",
"agreee": "Ich stimme den",
"agree": "Ich stimme den",
"term": "Nutzungsbedingungen zu",
"subscribe-to-opensign": "Abonnieren Sie den OpenSign-Newsletter",
"register": "Registrieren",
@@ -120,7 +121,8 @@
"Teams": "Teams",
"Team-update-successfully": "Team erfolgreich aktualisiert.",
"Users": "Benutzer"
}
},
"Global SMTP": "Globales SMTP"
},
"dashboard-card": {
"Need your Signature": "Benötigt Ihre Unterschrift",
@@ -166,7 +168,10 @@
"Save as template": "Als Vorlage speichern",
"Fix & resend": "Korrigieren und erneut senden",
"Kiosk Mode": "Kiosk Modus",
"Reset password": "Passwort zurücksetzen"
"Reset password": "Passwort zurücksetzen",
"View formdata": "Formulardaten ansehen",
"Download all formdata": "Alle Formulardaten herunterladen",
"API credits": "API-Credits"
},
"report-heading": {
"Sr.No": "Nr.",
@@ -201,7 +206,8 @@
"Redirect url": "Weiterleitungs-URL",
"Created Date": "Erstellungsdatum",
"Updated Date": "Aktualisierungsdatum",
"Expiry Date": "Ablaufdatum"
"Expiry Date": "Ablaufdatum",
"Sent Date": "Sendedatum"
},
"report-help": {
"Draft Documents": "Dies sind Dokumente, die Sie begonnen, aber noch nicht zum Versenden fertiggestellt haben.",
@@ -218,7 +224,7 @@
"New Template": "Neue Vorlage",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "Datei ausgewählt",
"files-selected": "Datei(en) ausgewählt",
"template-title": "Vorlagentitel",
"document-title": "Dokumenttitel",
"title": "Titel",
@@ -231,11 +237,12 @@
"p3": "Wenn Sie diese Option auswählen, werden die Signaturlinks gleichzeitig an alle Unterzeichner gesendet. Jeder Unterzeichner kann das Dokument nach Belieben unterzeichnen, unabhängig davon, ob andere Unterzeichner ihre Unterschriften abgeschlossen haben. Diese Methode ist schneller, erzwingt jedoch keine Reihenfolge unter den Teilnehmern.",
"p4": "Wählen Sie die Option, die am besten zu den Anforderungen Ihrer Dokumentenverarbeitung passt."
},
"strict-order": "Strikte Reihenfolge erzwingen",
"strict-order-help": "Wenn aktiviert, kann kein Unterzeichner unterschreiben oder genehmigen, bis alle vorherigen Unterzeichner/Genehmiger ihre Aktion abgeschlossen haben.",
"no": "Nein",
"auto-reminder": "Automatische Erinnerung",
"remind-once": "Einmal erinnern alle (Tage)",
"next": "Weiter",
"select-folder": "Ordner auswählen",
"OpenSign-drive": "{{appName}} Drive",
"drive-document-status": {
"Completed": "Abgeschlossen",
@@ -255,6 +262,7 @@
"signers": "Unterzeichner",
"signers-help": "Beginnen Sie mit der Eingabe eines Kontaktnamens, um vorgeschlagene Unterzeichner aus Ihren gespeicherten Kontakten zu sehen, oder fügen Sie neue hinzu. Ordnen Sie die Reihenfolge der Unterzeichner, indem Sie sie in der gewünschten Reihenfolge hinzufügen. Verwenden Sie die '+'-Schaltfläche, um Unterzeichner hinzuzufügen, und 'x', um sie zu entfernen. Jeder Unterzeichner erhält eine E-Mail-Aufforderung, das Dokument in der aufgelisteten Reihenfolge zu unterzeichnen.",
"bcc-help": "Beginnen Sie mit der Eingabe des Namens eines Kontakts, um Vorschläge aus Ihren gespeicherten Kontakten anzuzeigen oder neue hinzuzufügen. Verwenden Sie die Schaltfläche '+', um einen Benutzer hinzuzufügen, und das 'x', um ihn zu entfernen. Die E-Mail des ausgewählten Benutzers wird als Bcc (Blindkopie) hinzugefügt. Jeder Benutzer erhält eine E-Mail-Benachrichtigung, sobald das Dokument fertig ist.",
"cc-help": "Beginnen Sie mit der Eingabe des Namens eines Kontakts, um Vorschläge aus Ihren gespeicherten Kontakten zu sehen, oder fügen Sie neue hinzu. Verwenden Sie die Schaltfläche „+“, um einen Benutzer hinzuzufügen, und „x“, um ihn zu entfernen. Die E-Mail des ausgewählten Benutzers wird als CC (Kopie) hinzugefügt. Jeder Benutzer erhält eine E-Mail-Benachrichtigung, sobald das Dokument abgeschlossen ist.",
"add-signer": "Unterzeichner hinzufügen",
"contact-not-found": "Kontakt nicht gefunden",
"add-yourself": "Sich selbst hinzufügen",
@@ -264,7 +272,7 @@
"signature": "Unterschrift",
"upload-image": "Bild hochladen",
"clear": "Löschen",
"upload-signature/Image": "Bitte Unterschrift/Bild hochladen",
"upload-signature/Image": "Bitte Unterschrift/Bild hochladen/Stempel",
"initials": "Initialen",
"API": "API",
"api-token": "API-Token",
@@ -276,13 +284,15 @@
"yes": "Ja",
"copied": "Kopiert",
"something-went-wrong-mssg": "Etwas ist schiefgelaufen, Das Aktualisieren dieser Seite kann das Problem lösen.",
"attachments-docx-upload-failed": "Derzeit liegt ein Problem vor. Bitte versuchen Sie es erneut.",
"attachments-docx-upload-limit": "Das Upload-Limit für große DOCX-Dateien ist erreicht. Bitte warten Sie 4 Minuten und versuchen Sie es erneut.",
"token-generated": "Token erfolgreich generiert.",
"webhook": "Webhook",
"update-webhook": "Webhook aktualisieren",
"add-webhook": "Webhook hinzufügen",
"webhook-added": "Webhook erfolgreich hinzugefügt.",
"team-disabled": "Team deaktiviert",
"Team enabled": "Team aktiviert",
"team-enabled": "Team aktiviert",
"are-you-sure": "Sind Sie sicher, dass Sie",
"disable": "deaktivieren",
"enable": "aktivieren",
@@ -290,7 +300,7 @@
"edit-team": "Team bearbeiten",
"name-of-team": "Name des Teams",
"prev": "Zurück",
"no-data-avaliable": "Keine Daten verfügbar",
"no-data-available": "Keine Daten verfügbar",
"add-team": "Team hinzufügen",
"page-not-found": "Seite nicht gefunden",
"users-from-teams": "Benutzer aus Teams",
@@ -301,12 +311,37 @@
"delete-user": "Benutzer löschen",
"delete": "Löschen",
"add-user": "Benutzer hinzufügen",
"password-generateed": "Das Passwort wird nur einmal generiert; stellen Sie sicher, dass Sie es kopieren.",
"password-generated": "Das Passwort wird nur einmal generiert; stellen Sie sicher, dass Sie es kopieren.",
"Team status": "Teamstatus",
"user-deactivated": "Benutzer deaktiviert.",
"user-activated": "Benutzer aktiviert.",
"public": "Öffentlich",
"download": "Herunterladen",
"sign-offline": "Offline unterzeichnen",
"sign-offline-instructions": "Laden Sie das Dokument herunter, unterzeichnen Sie es mit einem externen Tool und laden Sie dann die unterschriebene Kopie hier hoch. Der Dokumenteigentümer wird Ihre Einreichung prüfen und entweder genehmigen oder ablehnen.",
"sign-offline-step-1": "Das unsignierte PDF herunterladen.",
"sign-offline-step-2": "Das PDF mit Ihrem bevorzugten Tool unterzeichnen.",
"sign-offline-step-3": "Das unterschriebene PDF zur Genehmigung hochladen.",
"sign-offline-upload-hint": "Das unterschriebene PDF zur Einreichung zur Genehmigung auswählen.",
"sign-offline-submitted-title": "Zur Genehmigung eingereicht",
"sign-offline-submitted-body": "Der Dokumenteigentümer wurde per E-Mail benachrichtigt, Ihre offline unterschriebene Kopie zu prüfen.",
"offline-already-submitted-title": "Dokument offline eingereicht",
"offline-already-submitted-body": "Sie haben dieses Dokument bereits zur Offline-Signaturprüfung eingereicht. Sobald es genehmigt oder abgelehnt wurde, werden Sie benachrichtigt.",
"offline-action-approve-title": "Offline-Signatur genehmigen",
"offline-action-approve-confirm": "Genehmigen",
"offline-action-reject-title": "Offline-Signatur ablehnen",
"offline-action-reject-hint": "Optional können Sie einen Grund angeben. Der Unterzeichner wird per E-Mail benachrichtigt.",
"offline-action-reject-reason-placeholder": "Grund (optional)",
"offline-action-reject-confirm": "Ablehnung bestätigen",
"offline-action-review-title": "Offline-Signatur prüfen",
"offline-action-review-hint": "Sehen Sie sich das vom Unterzeichner hochgeladene signierte PDF an, und genehmigen oder lehnen Sie es dann ab. Keine Aktion wird durchgeführt, bis Sie bestätigen.",
"offline-action-submitted-at": "Eingereicht",
"offline-action-no-preview": "Für diese Einreichung ist keine Vorschau verfügbar.",
"offline-action-open-in-new-tab": "PDF in neuem Tab öffnen",
"offline-action-invalid-link": "Dieser Prüflink ist ungültig oder abgelaufen.",
"offline-action-success": "Aktion abgeschlossen.",
"reject": "Ablehnen",
"document": "Dokument",
"view": "Ansehen",
"make-template-public": "Vorlage öffentlich machen",
"make-template-private": "Vorlage privat machen",
@@ -331,7 +366,7 @@
"quick-send-alert-1": "Alle Rollen in diesem Dokument sind derzeit mit Kontakten verknüpft. Um Kopien dieser Vorlage schnell an mehrere Unterzeichner zu senden, stellen Sie sicher, dass mindestens eine Rolle keinem Kontakt zugeordnet ist.",
"quick-send-alert-2": "Bitte stellen Sie sicher, dass für alle Empfänger mindestens ein Signatur-Widget hinzugefügt wurde.",
"quick-send-alert-3": "Bitte fügen Sie diesem template mindestens eine Rolle hinzu.",
"quick-send-alert-4": "Das Limit für Schnellsendungen wurde erreicht.",
"quick-send-alert-4": "{{flow}} unterstützt bis zu {{count}} Unterzeichner pro Batch. Teilen Sie Ihre Liste auf und versuchen Sie es erneut.",
"copy-link": "Link kopieren",
"copy": "Kopieren",
"revoke-document": "Dokument widerrufen",
@@ -387,7 +422,11 @@
"radio button": "Radiobutton",
"image": "Bild",
"email": "E-Mail",
"number": "Zahl"
"number": "Zahl",
"draw": "Zeichnen",
"attachments": "Anhänge",
"attachments-add": "Klicken, um Anhänge hochzuladen",
"attachments-count": "{{count}} Anhang/Anhänge"
},
"fields": "Felder",
"recipients": "Empfänger",
@@ -399,6 +438,14 @@
"certificate": "Zertifikat",
"decline": "Ablehnen",
"finish": "Fertigstellen",
"approve": "Genehmigen",
"approve-mssg": " Sind Sie sicher, dass Sie das Dokument genehmigen möchten?",
"signer-role-label": "Empfängerrolle",
"signer-role-signer": "Unterzeichner",
"signer-role-viewer": "Betrachter",
"signer-role-approver": "Genehmiger",
"signer-role-hint": "Unterzeichner muss unterschreiben. Genehmiger genehmigt ohne Unterschrift. Betrachter kann nur ansehen.",
"signer-role-upgrade-msg": "Die Rollen Betrachter und Genehmiger sind nur in den kostenpflichtigen Tarifen verfügbar. Aktualisieren Sie Ihren Tarif, um diese Rollen zuzuweisen.",
"done": "Fertig",
"mail": "E-Mail",
"sign-now": "Jetzt unterzeichnen",
@@ -431,16 +478,18 @@
"reset-password-alert-1": "Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.",
"reset-password-alert-2": "Bitte richten Sie den E-Mail-Adapter ein.",
"reset-password-alert-3": "Setzen Sie Ihr Passwort zurück",
"faild-animation": "Animation konnte nicht geladen werden",
"failed-animation": "Animation konnte nicht geladen werden",
"apply": "Anwenden",
"select-columns": "Spalten auswählen",
"copy-type": {
"All pages": "Alle Seiten",
"All pages but last": "Alle Seiten außer der letzten",
"All pages but first": "Alle Seiten außer der ersten",
"Next to current widget": "Neben dem aktuellen Widget"
"Next to current widget": "Neben dem aktuellen Widget",
"Page range": "Seitenbereich"
},
"options": "Optionen",
"minimun-check": "Minimale Anzahl",
"minimum-check": "Minimale Anzahl",
"maximum-check": "Maximale Anzahl",
"cell-count": "Zellzahl",
"default-value": "Standardwert",
@@ -454,6 +503,19 @@
"formula-error-braces": "Jede öffnende {{ muss eine passende }} haben.",
"formula-error-operators": "Verwenden Sie bei Operatoren oder Klammern mindestens zwei Werte.",
"formula-error-leading-operator": "Formeln dürfen nicht mit Operatoren beginnen.",
"formula-error-trailing-operator": "Formeln dürfen nicht mit einem Operator enden.",
"formula-error-adjacent-placeholders": "Zwei Platzhalter dürfen nicht ohne einen Operator dazwischen direkt nebeneinander stehen.",
"formula-error-continuous-operators": "Zwei Operatoren dürfen nicht direkt hintereinander verwendet werden.",
"formula-error-empty-parentheses": "Klammern dürfen nicht leer sein.",
"formula-error-invalid-parentheses-content": "Klammern dürfen nicht mit einem Operator beginnen oder enden.",
"formula-error-before-open-parenthesis": "Fehlender Operator vor '('. Fügen Sie vor der öffnenden Klammer einen Operator hinzu.",
"formula-error-after-close-parenthesis": "Fehlender Operator nach ')'. Fügen Sie nach der schließenden Klammer einen Operator wie '+' oder '*' hinzu.",
"formula-error-between-parentheses": "Fehlender Operator zwischen ')' und '('. Fügen Sie zwischen den beiden Gruppen einen Operator wie '*' hinzu.",
"formula-error-division-by-zero": "Division durch Null ist nicht zulässig. Ersetzen Sie '0' durch eine gültige, von Null verschiedene Zahl oder Variable.",
"formula-error-invalid-number-format": "Ungültiges Zahlenformat gefunden. Zahlen müssen in einem gültigen Format wie '1' oder '3.14' vorliegen, nicht '.5', '1.' oder '1.2.3'.",
"formula-error-unclosed-parenthesis": "Nicht geschlossene '(' gefunden. Jede öffnende Klammer '(' muss eine passende schließende Klammer ')' haben.",
"formula-error-unexpected-closing-parenthesis": "Unerwartete schließende Klammer ')' gefunden, ohne passende öffnende Klammer '('.",
"formula-error-empty-placeholder": "Leerer Platzhalter '{{}}' ist nicht zulässig. Geben Sie einen Variablennamen innerhalb der Klammern an.",
"decimal-places": "Dezimalstellen",
"number-of-decimal-places": "Anzahl der Dezimalstellen",
"decimal-places-helper": "Auf 0 setzen für ganze Zahlen",
@@ -510,18 +572,19 @@
"placeholder-alert-4": "Sie haben erfolgreich E-Mails an alle Empfänger gesendet!",
"placeholder-mail-alert": "Sie haben erfolgreich eine E-Mail an {{name}} gesendet. Die nachfolgenden Unterzeichner erhalten eine E-Mail, sobald {{name}} das Dokument unterzeichnet.",
"placeholder-mail-alert-you": "Nachfolgende Unterzeichner erhalten E-Mails, sobald Sie das Dokument unterschreiben.",
"placeholder-alert-5": "Möchten Sie die Dokumente jetzt unterschreiben?",
"placeholder-alert-5": "Sie können jetzt unterschreiben oder es an Ihre E-Mail senden, um es später abzuschließen.",
"placeholder-alert-6": "Bitte richten Sie den E-Mail-Adapter ein, um E-Mails zu senden!",
"placeholder-alert-7": "Bitte wählen Sie einen Unterzeichner aus, um Platzhalter hinzuzufügen!",
"email-subject": "E-Mail-Betreff",
"email-body": "E-Mail-Inhalt",
"email-placeholder": "E-Mail-Text eingeben",
"reset-to-default": "Auf Standard zurücksetzen",
"cutomize-email": "E-Mail anpassen",
"customize-email": "E-Mail anpassen",
"upgrade-to-customize-email": "Upgrade, um E-Mails anzupassen",
"sign-url": "Signatur-URL",
"mails-sent": "E-Mails gesendet",
"mail-not-delivered": "E-Mail nicht zugestellt",
"email-not-verified-head": "E-Mail nicht verifiziert",
"document-alert": "Dokument-Warnung",
"owner-subscription-expired": "Das Abonnement des Besitzers ist abgelaufen.",
"subscription-expired": "Abonnement abgelaufen",
@@ -582,7 +645,7 @@
"provide-your-details": "Bitte geben Sie Ihre Daten an",
"provide-email": "Bitte E-Mail angeben.",
"additional-info": "Zusätzliche Informationen",
"signature-saved-alert": "Unterschrift erfolgreich gespeichert.",
"signature-saved-alert": "erfolgreich gespeichert.",
"saved-successfully": "Erfolgreich gespeichert.",
"fill-field": "Bitte dieses Feld ausfüllen",
"error-template": "Fehler: Vorlage nicht gefunden!",
@@ -597,6 +660,9 @@
"document-signed-alert-7": "Dieses Dokument ist nicht mehr verfügbar.",
"document-signed-alert-8": "Das Dokument wurde bereits zum Unterzeichnen versendet.",
"yet-to-sign": "Noch zu unterzeichnen",
"viewers": "Betrachter",
"viewer-no-widgets": "Betrachter erhalten eine schreibgeschützte Kopie des Dokuments und können keine Widgets zugewiesen bekommen.",
"viewer-confirm-widget-removal": "Wenn Sie diesen Empfänger auf Betrachter umstellen, werden alle ihm bereits zugewiesenen Widgets entfernt. Fortfahren?",
"validation-alert-1": "Die Eingabe erfüllt nicht die Kriterien des regulären Ausdrucks.",
"account-already-exists": "Konto existiert bereits!",
"choose-password": "Passwort wählen",
@@ -619,7 +685,7 @@
"date": "Datum",
"report-not-found": "Bericht nicht gefunden",
"please-wait": "Bitte warten",
"select-foler": "Ordner auswählen",
"select-folder": "Ordner auswählen",
"generated-on": "Erstellt am",
"text-field": "Textfeld",
"font-size": "Schriftgröße",
@@ -678,13 +744,13 @@
"public-template": "Dieses Video zeigt Ihnen, wie Sie Ihr personalisiertes öffentliches Profil einrichten, z. B. 'https://opensign.me/Ihr-Benutzername'. Sie lernen auch, wie Sie Ihren Slogan anpassen und Ihre Vorlagen für öffentliche Signaturen verfügbar machen.",
"allowModify-widgets": "Sie können jedes dieser Felder per Drag & Drop in das Dokument ziehen, zusätzlich zu den Feldern, die der Dokumentersteller bereits für Sie festgelegt hat."
},
"enter-email-plaholder": "E-Mail-Adresse hinzufügen und Enter drücken",
"enter-email-placeholder": "E-Mail-Adresse hinzufügen und Enter drücken",
"success-email-alert": "E-Mail erfolgreich gesendet!",
"expired-doc-title": "Abgelaufenes Dokument",
"expired-on-mssg": "Dieses Dokument ist am {{expiredDate}} abgelaufen und nicht mehr verfügbar zum Unterzeichnen.",
"signature-validate-alert": "Bitte bestätigen Sie, dass Sie mindestens {{minRequiredCount}} Kontrollkästchen ausgewählt haben.",
"signature-validate-alert-2": "Stellen Sie sicher, dass dieses Feld korrekt ausgefüllt ist und alle Anforderungen erfüllt.",
"remaing-users": "Verbleibende Benutzer",
"remaining-users": "Verbleibende Benutzer",
"Role": "Rolle",
"additional-users": "Bitte kaufen Sie zusätzliche Benutzer hinzu.",
"Quantity-of-users": "Anzahl der Benutzer",
@@ -698,7 +764,7 @@
"select-date-format": "Wählen Sie ein Datumsformat",
"quantity-of-credits": "Anzahl der Premium-Credits",
"remaining-credits": "Verfügbare Premium-Credits:",
"remaining-credits-help": "Verwenden Sie Premium-Credits für API-Dokumentensignaturen, Massenversand oder die Einbettung der OpenSign-Integration auf Ihrer Website. Sie haben {{allowedcredits}} enthaltene Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig.",
"remaining-credits-help": "Verwenden Sie Premium-Credits für die API-Dokumentensignatur, den Massenversand oder die Einbettung der OpenSign-Integration auf Ihrer Website. Sie haben noch {{allowedcredits}} inkludierte Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig. Wenn Sie einen Teams- oder Enterprise-Plan nutzen, können diese Credits von allen Benutzern in Ihrem Unternehmen gemeinsam verwendet werden, sofern der Kontoadministrator für Ihr Konto kein Limit festgelegt hat.",
"additional-credits": "Bitte kaufen Sie Premium-Credits hinzu",
"quota-err-quicksend": "Kontingent erreicht, Sie haben nicht genügend Credits.",
"buy-credits": "Premium-Credits kaufen",
@@ -706,7 +772,7 @@
"rotate-left": "Nach links drehen",
"rotate-alert-mssg": "Alle Widgets auf dieser Seite gehen verloren. Sind Sie sicher, dass Sie fortfahren möchten?",
"templateid": "Vorlagen-ID",
"bulk-send-subcription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um den Massenversand zu nutzen.",
"bulk-send-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um den Massenversand zu nutzen.",
"generate-test-token": "Test-Token generieren",
"regenerate-test-token": "Test-Token neu generieren",
"help-test-token": "Dieses Token kann verwendet werden, um die APIs unter https://sandbox.opensignlabs.com/api/v1 zu testen und unbegrenzt Dokumente zu signieren. Beachten Sie, dass die Sandbox-API Ihre Dokumente mit selbstsignierten Zertifikaten signiert, die möglicherweise von Adobe nicht als gültig erkannt werden. Nach Abschluss Ihrer Tests können Sie auf einen unserer kostenpflichtigen Pläne upgraden, um ein Produktionstoken zu generieren.",
@@ -726,8 +792,9 @@
"copied-code": "KOPIERT",
"Installation": "Installation",
"Usage": "Nutzung",
"insufficient-credits": "Nicht genügend Signatur-Credits",
"insufficient-credits-mssg": "Der Besitzer dieses Dokuments verfügt derzeit nicht über die notwendigen OpenSign-Credits, damit Sie unterzeichnen können. Bitte wenden Sie sich an den Besitzer, wenn Sie weitere Informationen benötigen.",
"insufficient-credits": "Unzureichende Signaturguthaben. Bitte kontaktieren Sie {{ownerEmail}}",
"insufficient-credits-title": "Nicht genügend Signatur-Credits",
"insufficient-credits-mssg": "Der Besitzer dieses Dokuments verfügt derzeit nicht über die notwendigen {{appName}}-Credits, damit Sie unterzeichnen können. Bitte wenden Sie sich an den Besitzer, wenn Sie weitere Informationen benötigen.",
"angular-npm-mssg-1": "Um OpenSign in Ihr Angular-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
"quota-mail-info-head": "Monatliches E-Mail-Limit",
"quota-mail-info": "Um die Servicequalität aufrechtzuerhalten und Spam zu verhindern, erlaubt OpenSign bis zu 15 E-Mails pro Monat im kostenlosen Plan. Upgraden Sie jetzt für unbegrenzten E-Mail-Versand.",
@@ -746,6 +813,13 @@
"p3": "Wenn Sie diese Option deaktivieren, können Benutzer das Dokument direkt ohne zusätzliche Schritte unterzeichnen.",
"p4": "Bitte wählen Sie die Option, die am besten zu Ihren Anforderungen für die Dokumentenunterzeichnung passt."
},
"allow-offline-sign": "Offline-Signatur zulassen",
"allow-offline-sign-help": {
"p1": "Legen Sie fest, ob Unterzeichner eine Kopie dieses Dokuments einreichen dürfen, die mit einem externen Werkzeug signiert wurde, damit der Eigentümer sie prüfen kann.",
"p2": "Unterzeichner sehen die Option 'Offline unterzeichnen' und können eine extern signierte PDF zur Genehmigung hochladen.",
"p3": "Unterzeichner können nur in der App signieren. Die Option 'Offline unterzeichnen' wird ausgeblendet.",
"p4": "Diese Einstellung verwendet standardmäßig Ihre Kontoeinstellung und ist in kostenpflichtigen Tarifen verfügbar."
},
"advanced-options": "Erweiterte Optionen",
"hide-advanced-options": "Erweiterte Optionen ausblenden",
"download-files": "Dateien herunterladen",
@@ -808,6 +882,7 @@
"delete-folder-alert-1": "Löschen nicht möglich: Der Ordner muss leer sein.",
"defaultSign-alert": "{{widgetsType}} Widgets sind nicht für die automatische Signatur verfügbar.",
"embed-heading": "{{appName}} Vorlagen einbetten",
"other-apps-by-opensign": "Andere Apps vom OpenSign-Team",
"public-template-edit-title": "Öffentliche Vorlage bearbeiten",
"public-template-edit-mssg": "Eine öffentliche Vorlage können Sie nicht direkt bearbeiten. Um Änderungen vorzunehmen, wird die Vorlage auf privat gesetzt und Sie müssen sie anschließend wieder öffentlich machen. Möchten Sie fortfahren?",
"agree-p1": "Ich bestätige, dass ich die ",
@@ -859,7 +934,6 @@
"js-snippet-msg-1": "Die TemplateId erhalten Sie von der",
"js-snippet-msg-2": "Vorlagen verwalten",
"js-snippet-msg-3": "Seite.",
"agrrement-alert": "Um fortzufahren, müssen Sie der Offenlegung über elektronische Aufzeichnungen und Unterschriften zustimmen.",
"webhook-already-exists": "Die URL existiert bereits! Bitte versuchen Sie eine andere.",
"webhook-must-be-secure": "Die Webhook-URL muss sicher sein und https:// verwenden.",
"provide-test-token": "Bitte generieren Sie einen API-Test-Token.",
@@ -879,6 +953,7 @@
"p3": "Durch Auswahl dieser Option wird verhindert, dass Unterzeichner während des Unterzeichnungsprozesses Ergänzungen zum Dokument vornehmen."
},
"signature-setting": "Signatur-Einstellungen",
"rotation": "Drehung",
"activation-key": "Aktivierungsschlüssel",
"paid-selfhost-help": "Bitte kontaktieren Sie {{appName}} für weitere Informationen unter ",
"remaining-credits-selfhost-help": "Verwenden Sie Premium-Credits für API-Dokumentensignaturen, den Massenversand und die {{appName}} Integration auf Ihrer Website. Sie haben {{allowedcredits}} enthaltene Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig.",
@@ -907,6 +982,8 @@
"visit-below-link": "Besuchen Sie den untenstehenden Link, um mehr zu erfahren -",
"storage-help": "Durch die Aktivierung von BYOC können Sie Ihren eigenen S3-Speicher verbinden, sodass Ihre Dateien vollständig unter Ihrer Kontrolle bleiben und keine externen Kopien gespeichert werden. Wenn Ihnen Datenautonomie wichtig ist, erwägen Sie ein Upgrade auf Teams, um diese Funktion freizuschalten.",
"daily-quota-reached": "Sie haben Ihr tägliches Kontingent erreicht. Für Unterstützung kontaktieren Sie bitte quotas@opensignlabs.com.",
"email-not-verified-send": "Bitte verifizieren Sie Ihre E-Mail-Adresse, bevor Sie E-Mails senden. Gehen Sie zu Ihren <1>Profileinstellungen</1>, um die E-Mail-Verifizierung abzuschließen.",
"email-not-verified-send-plain": "Bitte verifizieren Sie Ihre E-Mail-Adresse, bevor Sie E-Mails senden. Gehen Sie zu Ihren Profileinstellungen, um die E-Mail-Verifizierung abzuschließen.",
"enabled-signature-type": "Aktivierte Signaturtypen",
"enabled-signature-type-help": "Die Einstellung 'Aktivierte Signaturtypen' bestimmt, welche Signaturoptionen in Ihrer Organisation verfügbar sind. Wenn Sie beispielsweise die Option 'Zeichnen' deaktivieren, wird sie den Mitgliedern Ihrer Organisation im Signatur-Widget nicht angezeigt, während die anderen drei Optionen weiterhin zugänglich bleiben.",
"indexing-public-profile": "Erlaube die Indexierung des öffentlichen Profils durch Suchmaschinen",
@@ -934,6 +1011,7 @@
"please-provide-templateid": "Bitte geben Sie templateid an",
"this-template-is-not-public": "Dieses template ist nicht öffentlich",
"invalid-templateid": "Ungültige templateid",
"kiosk-no-active-role": "Diese Vorlage hat keine für die Kiosk-Signatur verfügbaren Unterzeichner- oder Genehmigerrollen.",
"contact-billing-at-opensign": "Um weitere Plätze hinzuzufügen, kontaktieren Sie bitte OpenSign™ unter <1>billing@opensignlabs.com</1> für Unterstützung.",
"title-length-alert": "Der Titel darf höchstens 250 Zeichen lang sein.",
"note-length-alert": "Die Notiz darf höchstens 200 Zeichen lang sein.",
@@ -1023,7 +1101,7 @@
},
"signer-already-present": "Unterzeichner bereits vorhanden",
"kiosk-sign": "Kiosk-Unterschrift",
"dont-have-access-to-template": "Das template wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
"do-not-have-access-to-template": "Das template wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
"kiosk-info": "Kiosk Modus ermöglicht es Ihnen, persönliche Unterschriften schnell und effizient zu erfassen. Ideal für Messen, Veranstaltungen oder Laufkundschaft, bei denen alle Unterzeichner physisch anwesend sind. ",
"learn-more": "Mehr erfahren",
"finish-mssg": "Sind Sie sicher, dass Sie das Dokument abschließen möchten?",
@@ -1079,8 +1157,6 @@
"search-contacts": "Kontakte durchsuchen…",
"edit-draft": "Entwurf bearbeiten",
"add-role-alert": "Bitte fügen Sie mindestens eine Rolle hinzu",
"invalid-email-found": "Ungültige E-Mail gefunden: {{email}}",
"duplicate-email-found": "Doppelte E-Mail gefunden: {{email}}",
"vertical": "Vertikal",
"horizontal": "Horizontal",
"billing": "Abrechnung",
@@ -1094,7 +1170,7 @@
"access-denied": "Zugriff verweigert",
"upgrade": "Upgrade",
"do-not-access-app": "Sie haben keinen Zugriff auf diese Anwendung.",
"dont-have-access": "Sie haben keinen Zugriff.",
"do-not-have-access": "Sie haben keinen Zugriff.",
"valid-email-alert": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
"otp-not-validate": "OTP ist ungültig.",
"domain-not-allowed": "Diese Domain ist nicht erlaubt",
@@ -1111,10 +1187,12 @@
"branding-help": "Branding ermöglicht White-Labeling für Ihre App",
"custom-sub-domain": "Benutzerdefinierte Subdomain",
"app-name": "App Name",
"provide-domain-name": "Geben Sie Ihren Domainnamen an",
"provide-domain-name": "Es sind nur Subdomains erlaubt (z. B. app.deinedomain.com). Primär-/Root-Domains wie deinedomain.com werden nicht unterstützt.",
"provide-app-name-help": "Geben Sie einen eindeutigen App-Namen an. Er wird als Ihr Markenname in der Anwendung sowie in allen E-Mails angezeigt, überall dort, wo das Branding angewendet wird.",
"upload-app-logo": "Laden Sie ein Logo im .png-, .jpg- oder .svg-Format hoch. Empfohlene Größe: {{size}} Pixel (Seitenverhältnis 5:2) für beste Anzeigequalität.",
"upload-app-favicon": "Laden Sie ein quadratisches Favicon-Bild (.ico, .png oder .svg) hoch. Empfohlene Größe: {{size}} Pixel für beste Browserkompatibilität.",
"provide-app-name": "Geben Sie Ihren App-Namen an",
"logo": "Logo",
"upload-app-logo": "Laden Sie Ihr App-Logo hoch",
"prefill-unfilled-widget": "Die folgenden Pflichtfelder dürfen nicht leer sein: {{emptyWidget}}. Bitte füllen Sie diese aus, um fortzufahren.",
"Dashboard": "Armaturenbrett",
"Analytics": "Analytik",
@@ -1237,6 +1315,7 @@
},
"read-only-date-error": "Das schreibgeschützte Datums-Widget muss ein Standarddatum haben.",
"set-date": "Datum festlegen",
"default-date": "Standarddatum",
"set-today": "Unterzeichnungsdatum",
"enter-name": "Name eingeben",
"enter-email": "E-Mail eingeben",
@@ -1256,5 +1335,240 @@
"Please provide required parameters.": "Bitte geben Sie die erforderlichen Parameter an.",
"reset-password": "Benutzerpasswort zurücksetzen",
"enter-strong-password": "Geben Sie ein sicheres Passwort ein oder generieren Sie eines automatisch.",
"autogenerate": "Automatisch generieren"
}
"autogenerate": "Automatisch generieren",
"extend": "Verlängern",
"console-general-help": "Um einzelnen Benutzern zu ermöglichen, ihre eigenen E-Mail-Vorlagen anzupassen, navigieren Sie zu",
"console-mail-help": "Um unternehmensweite E-Mail-Vorlagen zu aktualisieren, gehen Sie zu",
"admin-setting-disabled": "Diese Einstellung ist deaktiviert. Bitte wenden Sie sich für Unterstützung an den Administrator.",
"company-email-settings": "Unternehmens-E-Mail-Einstellungen",
"allow-email-template-individually": "E-Mail-Vorlagenanpassung für Benutzer individuell erlauben ",
"user-email-template-general-help": "Wenn diese Option deaktiviert ist, können Benutzer ihre E-Mail-Vorlagen nicht mehr anpassen.",
"disable-individual-user-smtp": "individuelle benutzer-smtp-einstellungen deaktivieren",
"enable-individual-user-smtp": "individuelle benutzer-smtp-einstellungen aktivieren",
"user-smtp-general-help": "Wenn deaktiviert, können Benutzer ihre eigenen SMTP-Einstellungen nicht konfigurieren. Alle E-Mails verwenden dann die globale SMTP-Konfiguration.",
"global-smtp-settings": "Globale SMTP-Einstellungen",
"global-smtp-settings-help-p1": "Diese Einstellungen gelten global für Ihr gesamtes {{appName}}-Konto. Wenn ein Benutzer keine eigenen E-Mail-Zustellungs­einstellungen konfiguriert hat, verwendet das System automatisch die hier definierten globalen SMTP-Einstellungen.",
"global-smtp-settings-help-p2": "Um eine bessere Zustellbarkeit aller Signaturanfragen sicherzustellen, können Sie Ihren bevorzugten E-Mail-Anbieter verbinden. Wählen Sie eine der folgenden Optionen:",
"global-connect-to-gmail-help": "Verwenden Sie ein Gmail-Konto, um Signaturanfragen direkt aus dem Posteingang Ihrer Organisation zu senden. Dies verbessert die Zustellrate, Reputation und Zuverlässigkeit.",
"global-connect-to-smtp-help": "Verwenden Sie Ihren eigenen SMTP-Server, um E-Mails über Ihre Domain zu senden. Dies bietet volle Kontrolle über Ihre E-Mail-Infrastruktur, verbessert die Zustellbarkeit und sorgt für konsistentes Branding.",
"global-smtp-settings-help-p3": "Wenn keine individuellen Benutzereinstellungen konfiguriert sind, werden automatisch diese globalen Einstellungen angewendet.",
"hide-text-with-asterisks": "Text mit Sternchen ausblenden",
"hide-text-with-asterisks-help": "Der Text im unterzeichneten Dokument wird ausgeblendet, bleibt jedoch für den Dokumentabsender (Eigentümer) über sein {{appName}}-Konto zugänglich.",
"document-id": "Dokument-ID",
"sent-date": "Sendedatum",
"status": "Status",
"signer-form-data-for": "Formulardaten für Unterzeichner: {{signerName}} <{{signerEmail}}>",
"widget-name": "Widget-Name",
"widget-responses": "Widget-Antworten",
"formdata-download-note": "Hinweis: Signaturen, Stempel, Initialen und Bilder werden nicht exportiert. Daten von Offline-Unterzeichnern sind nicht verfügbar.",
"export-all-formdata-error": "Sie können die Formulardaten nicht exportieren, da die Vorlage doppelte Widget-Namen enthält. Bitte erstellen Sie eine neue Vorlage, um Formulardaten exportieren zu können.",
"export-formdata-error": "Sie können die Formulardaten nicht exportieren, da das Dokument doppelte Widget-Namen enthält. Bitte erstellen Sie ein neues Dokument ohne doppelte Widget-Namen, um Formulardaten exportieren zu können.",
"duplicate-widget-name-error": "Dieser Widget-Name ist bereits einem anderen Unterzeichner zugewiesen. Bitte wählen Sie einen anderen Namen.",
"duplicate-template-widget-error": "Die Vorlage kann nicht dupliziert werden, weil einige Widget-Namen in verschiedenen Rollen doppelt vorkommen. Benennen Sie die doppelten Widgets um und versuchen Sie es erneut.",
"credit-alert": "Kreditwarnung",
"document-form-data": "Dokumenten-Formulardaten",
"view-formdata-paid-title-error": "Die Funktion 'Formulardaten anzeigen' ermöglicht es Ihnen, die von Unterzeichnern ausgefüllten Widget-Daten auszudrucken und herunterzuladen.",
"view-formdata-paid-error": "Hinweis: Die Funktion 'Formulardaten anzeigen' ist nur für Dokumente verfügbar, die ab Version v2.32.0 erstellt wurden und keine doppelten Widget-Namen enthalten.",
"download-all-formdata-paid-title-error": "Die Funktion 'Alle Formulardaten herunterladen' ermöglicht es Ihnen, die von Unterzeichnern ausgefüllten Widget-Daten für alle aus der Vorlage erstellten Dokumente herunterzuladen.",
"download-all-formdata-paid-error": "Hinweis: Die Funktion 'Alle Formulardaten herunterladen' ist nur für Vorlagen verfügbar, die ab Version v2.32.0 erstellt wurden und keine doppelten Widget-Namen enthalten.",
"api-token-not-found": "API-Token nicht gefunden.",
"sub-head-api-credits": "Legen Sie die maximale Anzahl von API-Credits fest, die dieser Benutzer verbrauchen darf.",
"consumed-credits": "Verbrauchte Credits",
"allowed-api-credits": "Zulässige API-Credits",
"allowed-api-credits-help": "Die zulässigen Credits sollten größer oder gleich den verbrauchten Credits sein.",
"Enter-allowed-api-credits": "Zulässige API-Credits eingeben",
"login-to-sandbox": "Im Sandbox-Modus anmelden",
"your-credits-usage": "Ihr Kreditverbrauch",
"remaining": "verbleibend",
"used": "verwendet",
"your-credits-usage-help": "Dies ist die Anzahl der Credits, die mit Ihrem API-Schlüssel verbraucht wurden.",
"api-credits-updated": "API-Credits wurden erfolgreich zugewiesen.",
"min-date": "Mindestdatum",
"max-date": "Höchstdatum",
"invalid-min-date": "Ungültiges Mindestdatum.",
"min-less-max": "Das Mindestdatum muss kleiner oder gleich dem Höchstdatum sein.",
"invalid-max-date": "Ungültiges Höchstdatum.",
"max-less-min": "Das Höchstdatum muss größer oder gleich dem Mindestdatum sein.",
"date-range": "Datumsbereich",
"date-range-help": "Legen Sie ein Mindest- und/oder Höchstdatum fest, um die Auswahl einzuschränken. Das gewählte Datum muss innerhalb dieses Bereichs liegen.",
"option-disabled-by-owner": "Der Dokumentersteller hat diese Option deaktiviert",
"pen-colors": "Stiftfarben",
"pen-colors-help": "Wählen Sie aus, welche Stiftfarben für Signatur- und Initialen-Widgets verfügbar sind. Aktivieren Sie ein Kontrollkästchen, um eine Farbe anzuzeigen, und deaktivieren Sie es, um sie auszublenden.",
"security-key": "Sicherheitsschlüssel",
"remove-limit": "Limit entfernen",
"removed-api-credits": "Das API-Credit-Limit wurde entfernt.",
"enable-authentication": "Authentifizierung aktivieren",
"working": "In Bearbeitung",
"test-security-key": "Sicherheitsschlüssel testen",
"generate": "Generieren",
"regenerate": "Neu generieren",
"signing-date": "Signierdatum",
"date-widget": "Datums-Widget",
"mail-failed": "Beim Senden der E-Mail ist etwas schiefgelaufen. Bitte versuchen Sie es erneut.",
"mail-status-head": "Nächster Schritt: Ihr Dokument unterschreiben?",
"send-to-email": "Per E-Mail senden",
"date-pref-help-sub-title": "Mit den Datums-Widget-Einstellungen können Sie die Standardkonfiguration für das Datums-Widget festlegen. Diese Standards werden automatisch angewendet, wenn Sie ein neues Datums-Widget zu einem Dokument oder einer Vorlage hinzufügen. Sie können jedes Datums-Widget jederzeit individuell anpassen, indem Sie auf das Zahnradsymbol klicken.",
"date-pref-help-format": "Wählen Sie das standardmäßige Datumsformat für Datums-Widgets.",
"date-pref-help-default-date": "Legen Sie einen Standard-Datumswert für Datums-Widgets fest.",
"date-pref-help-signing-date": "Wenn aktiviert, verwendet das Datums-Widget automatisch das Datum, an dem der Unterzeichner das Dokument signiert.",
"date-pref-help-read-only": "Wenn aktiviert, ist das Datums-Widget für den Unterzeichner schreibgeschützt.",
"upload-csv": "CSV hochladen",
"file-selected": "Datei ausgewählt",
"bulk-send-csv-helper": "Verwenden Sie Header für Unterzeichnerrollen (Rollen-E-Mail/Name/Telefon) und Vorabfüllwerte (Vorabfüllung: Feldname). Erforderliche Vorabfüllfelder müssen ausgefüllt sein.",
"bulk-send-csv-helper-note": "Hinweis: Für Vorabfüllfelder vom Typ Datum in der CSV muss der Wert in folgendem Format angegeben werden: {{value}} (Beispiel: {{example}}).",
"bulk-send-csv-summary": "{{total}} Zeilen aus der CSV importiert.",
"bulk-send-csv-missing-headers": "Erforderliche CSV-Header fehlen. Rollen: {{roles}}.",
"bulk-send-no-records": "Bitte fügen Sie vor dem Senden mindestens einen Empfänger hinzu.",
"csv-only-support": "Es werden nur CSV-Dateien unterstützt.",
"csv-no-records": "Keine Datensätze in der CSV gefunden.",
"select-at-least-option": "Bitte wählen Sie mindestens eine Option aus.",
"draw-required": "Bitte zeichnen Sie in das Feld.",
"enter-smtp-username": "SMTP-Benutzernamen eingeben",
"enter-webhook-url": "Webhook-URL eingeben",
"enter-user-name": "Benutzernamen eingeben",
"enter-tagline": "Slogan eingeben",
"enter-password-or-click-autogenerate": "Passwort eingeben oder auf „Automatisch generieren“ klicken",
"enter-daily-email-quota": "Tägliches E-Mail-Kontingent eingeben",
"enter-custom-regular-expression": "Benutzerdefinierten regulären Ausdruck eingeben",
"enter-value": "{{value}} eingeben",
"are-you-sure-want-to-send-document": "Sind Sie sicher, dass Sie das Dokument senden möchten?",
"favicon": "Favicon",
"delete-two-factor-authentication": "Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung löschen möchten?",
"paste-html-here": "HTML hier einfügen",
"open-email-builder": "Sie können eine E-Mail-Vorlage mit der <1>E-Mail-Builder</1>-Plattform erstellen und den HTML-Code kopieren.",
"contact-saved": "Kontakt erfolgreich gespeichert.",
"remove-limit-of-user": "Sind Sie sicher, dass Sie das Limit dieses Benutzers aufheben möchten? Der Benutzer kann dann alle in Ihrer Organisation verfügbaren Premium-Credits nutzen, die unter allen Benutzern geteilt werden.",
"current-expiry-date": "Aktuelles Ablaufdatum",
"reminder-error": "„Einmal alle (Tage) erinnern“ darf die „Zeit zur Fertigstellung (Tage)“ nicht überschreiten.",
"showing-to-of-records": "Zeige {{record}} bis {{recordPerPage}} von {{totalRows}} Einträgen",
"records-per-page": "Einträge pro Seite",
"email-not-found-in-row": "E-Mail in Zeile {{row}} nicht gefunden.",
"invalid-email-found-in-row": "Ungültige E-Mail in Zeile {{row}} gefunden: {{email}}",
"duplicate-email-found-in-row": "Doppelte E-Mail in Zeile {{row}} gefunden: {{email}}",
"prefill": "Vorabfüllung",
"recipients-&-data": "Empfänger & Daten",
"response": "Antwort",
"prefill-tab-help": "Dies sind die gemeinsamen Vorlagenfelder, die auf alle Dokumente in diesem Massenversand angewendet werden.",
"recipient-tab-help-p1": "Im nächsten Schritt werden Sie:",
"recipient-tab-help-p2": "Eine CSV mit Empfängerdetails hochladen oder",
"recipient-tab-help-p3": "Empfängerinformationen manuell eingeben",
"recipient-tab-help-p4": "Jeder Empfänger erhält sein eigenes Dokument, das aus dieser Vorlage generiert wird.",
"response-tab-help": "Überprüfen Sie Ihre Antworten.",
"summary": "Zusammenfassung",
"prefill-fields": "Vorabfüllfelder",
"documents": "Dokumente",
"message": "Nachricht",
"formula-help-p1": "Erfassen Sie numerische Eingaben und führen Sie dynamische Berechnungen mit Formeln durch.",
"example": "Beispiel",
"formula-help-p2": "Um eine Summe in number-3 anzuzeigen, verwenden Sie {{number-1}} + {{number-2}}.",
"formula-help-p3": "Um einen Wert in number-4 zu berechnen, verwenden Sie ({{number-1}} * {{number-2}}) / {{number-3}}.",
"formula-help-p4": "Jedes Zahlen-Widget muss einen eindeutigen Namen haben (z. B. number-1), da Formeln auf diesen Bezeichnern basieren.",
"note-consume-premium-credits": "Hinweis: Diese Funktion verbraucht Premium-Credits.",
"note-consume-premium-credits-on-document-send": "Hinweis: Diese Funktion verbraucht Premium-Credits. Sie werden jedes Mal verbraucht, wenn ein Dokument versendet wird unabhängig davon, ob es unterschrieben wird oder nicht.",
"webhook-authentication-help": "{{appName}} sichert jeden Webhook, indem eine HMAC-SHA256-Signatur mit Ihrem Webhook-Sicherheitsschlüssel und dem unveränderten Request-Payload generiert wird. Diese Signatur wird im Header x-webhook-signature gesendet. Ihr Server muss dieselbe Signatur mit dem empfangenen Payload und Ihrem geheimen Schlüssel generieren. Stimmen beide Signaturen überein, ist die Webhook-Anfrage authentisch und wurde nicht verändert.",
"invalid-widget-found-in-row": "Ungültiger Wert in Zeile {{row}} für Widget {{widget}} erkannt.",
"all-signer-status": "Alle Unterzeichner-Status",
"viewed": "Angesehen",
"signed": "Unterzeichnet",
"switch-to-advanced": "Zum erweiterten Modus wechseln",
"switch-to-basic": "Zum Basis-Modus wechseln",
"use-name-as-sender": "Profilnamen als Absender verwenden",
"use-name-as-sender-help": "Wenn aktiviert, verwendet {{appName}} Ihren Profilnamen als Absender für Signaturanfragen und Abschluss-E-Mails.",
"acro-fields-detected": "Formularfelder erkannt",
"acro-fields-detected-msg": "Es wurden {{count}} Formularfeld(er) im hochgeladenen PDF erkannt. Möchten Sie die entsprechenden Widgets automatisch einfügen?",
"documents-count": "Anzahl der Dokumente",
"templates-count": "Anzahl der Vorlagen",
"emails-sent": "E-Mails gesendet",
"storage-used": "Speicher verwendet",
"docs-count-team-help": "Gesamtzahl der von allen Benutzern in Ihren Organisationen erstellten Dokumente.",
"docs-count-help": "Gesamtzahl der von Ihnen erstellten Dokumente.",
"templates-count-team-help": "Gesamtzahl der von allen Benutzern in Ihren Organisationen erstellten Vorlagen.",
"templates-count-help": "Gesamtzahl der von Ihnen erstellten Vorlagen.",
"emails-sent-team-help": "Gesamtzahl der in Ihren Organisationen versendeten E-Mail-Benachrichtigungen.",
"emails-sent-help": "Gesamtzahl der von Ihnen versendeten E-Mail-Benachrichtigungen.",
"storage-used-team-help": "Gesamter Speicherplatzverbrauch aller Benutzer in Ihren Organisationen.",
"storage-used-help": "Gesamter Speicherplatzverbrauch durch Sie.",
"cloud": "Cloud",
"on-premises": "On-Premises",
"plan-name-free": "OPENSIGN™ FREE",
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
"plan-name-teams": "OPENSIGN™ TEAMS",
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
"plan-subtitle-free": "Kostenlose unbegrenzte E-Signaturen, für immer.",
"plan-subtitle-professional": "Exklusiver Zugang zu erweiterten Funktionen.",
"plan-subtitle-scalable": "Skalierbare Funktionen mit Priority-Support.",
"subscribe": "Abonnieren",
"contact-us": "Kontaktiere uns",
"benefit-unlimited-digital-signatures": "Unbegrenzte digitale Signaturen",
"benefit-sign-docs-yourself": "Dokumente selbst unterschreiben",
"benefit-request-signatures": "Signaturen von anderen anfordern",
"benefit-unlimited-templates": "Unbegrenzte Vorlagen",
"benefit-14-field-types": "14 Feldtypen",
"benefit-automatic-e-signatures": "Automatische E-Signaturen",
"benefit-completion-certificates": "Abschlusszertifikate",
"benefit-send-in-order": "In Reihenfolge senden",
"benefit-organize-docs": "Dokumente in OpenSign™ Drive organisieren",
"benefit-document-templates": "Dokumentvorlagen",
"benefit-import-from-dropbox": "Import aus Dropbox",
"benefit-contact-book": "Kontaktbuch",
"benefit-document-expiry": "Unterstützung für Dokumentablauf",
"benefit-decline-document": "Unterstützung für Dokumentablehnung",
"benefit-email-notifications": "E-Mail-Benachrichtigungen",
"benefit-public-profiles": "Öffentliche Profile",
"benefit-and-much-more": "Und vieles mehr",
"benefit-everything-free": "Alles in OpenSign™ Free",
"benefit-field-validations": "Feldvalidierungen",
"benefit-regex-validations": "Reguläre-Ausdrucks-Validierungen",
"benefit-webhooks": "Webhooks",
"benefit-zapier": "Zapier-Integration",
"benefit-api-access": "API-Zugang",
"benefit-api-signatures-100": "bis zu 100 API-Signaturen",
"benefit-api-signatures-240": "bis zu 240 API-Signaturen",
"benefit-custom-email-templates": "Benutzerdefinierte E-Mail-Vorlagen",
"benefit-custom-smtp": "Eigenes Gmail- oder SMTP-Konto für den E-Mail-Versand verbinden",
"benefit-auto-reminders": "Automatische Erinnerungen",
"benefit-bulk-send-100": "Massenversand (bis zu 100 Dokumente)",
"benefit-bulk-send-240": "Massenversand (bis zu 240 Dokumente)",
"benefit-premium-profiles": "Premium öffentliche Profilbenutzernamen",
"benefit-email-verification": "E-Mail-basierte Verifizierung zur Bestätigung der Unterzeichneridentität erzwingen",
"benefit-embedded-signing": "Eingebettetes Signieren",
"benefit-everything-professional": "Alles in OpenSign™ Professional",
"benefit-teams-organizations": "Teams und Organisationen",
"benefit-share-templates-teams": "Vorlagen mit Teams teilen",
"benefit-share-templates-individuals": "Vorlagen mit Einzelpersonen teilen",
"benefit-byoc": "BYOC - Dokumente in Ihrem eigenen Cloud-Speicher aufbewahren",
"benefit-doc-id-removal": "DocumentId-Entfernung aus signierten Dokumenten",
"benefit-request-payments": "Zahlungen anfordern (demnächst)",
"benefit-mobile-app": "Mobile App (demnächst)",
"benefit-api-signatures-500": "bis zu 500 API-Signaturen",
"benefit-bulk-send-500": "Massenversand (bis zu 500 Dokumente)",
"benefit-all-features": "Alle Funktionen",
"benefit-custom-domain": "Benutzerdefinierte Domain",
"benefit-custom-branding": "Benutzerdefiniertes Branding",
"benefit-uptime-sla": "Uptime SLA",
"benefit-sso-support": "SSO-Unterstützung",
"benefit-priority-support": "Prioritätssupport",
"benefit-3-users-1500-credits": "Enthält 3 Benutzer und 1500 Premium-Credits",
"benefit-public-links": "Öffentliche Links",
"benefit-kiosk-mode": "Kiosk-Modus",
"benefit-bulk-send-1500": "Massenversand (bis zu 1500 Dokumente)",
"benefit-connect-gmail-smtp": "Eigenes Gmail- oder SMTP-Konto verbinden",
"benefit-analytics": "Analysen",
"benefit-bulksend-api-pro-month-help": "Dieser Plan enthält 100 Premium-Credits pro Monat, mit denen Sie Dokumente per API versenden, öffentliche Signierlinks teilen, Massen-Signaturanfragen ausführen, den Kiosk-Modus für Vor-Ort-Unterschriften nutzen oder Signaturen aus direkt auf Ihrer Website eingebetteten Dokumenten sammeln können. Jedes Mal, wenn Sie ein Dokument initiieren oder versenden, wird ein Credit verwendet — und dieses einzelne Dokument kann bis zu 10 Unterzeichner enthalten.",
"benefit-bulksend-api-pro-year-help": "Dieser Plan enthält 240 Premium-Credits pro Jahr, mit denen Sie Dokumente per API versenden, öffentliche Signierlinks teilen, Massen-Signaturanfragen ausführen, den Kiosk-Modus für Vor-Ort-Unterschriften nutzen oder Signaturen aus direkt auf Ihrer Website eingebetteten Dokumenten sammeln können. Jedes Mal, wenn Sie ein Dokument initiieren oder versenden, wird ein Credit verwendet — und dieses einzelne Dokument kann bis zu 10 Unterzeichner enthalten.",
"benefit-bulksend-api-team-month-help": "Dieser Plan enthält 100 Premium-Credits pro Monat, mit denen Sie Dokumente per API versenden, öffentliche Signierlinks teilen, Massen-Signaturanfragen ausführen, den Kiosk-Modus für Vor-Ort-Unterschriften nutzen oder Signaturen aus direkt auf Ihrer Website eingebetteten Dokumenten sammeln können. Jedes Mal, wenn Sie ein Dokument initiieren oder versenden, wird ein Credit verwendet — und dieses einzelne Dokument kann bis zu 10 Unterzeichner enthalten.",
"benefit-bulksend-api-team-year-help": "Dieser Plan enthält 500 Premium-Credits pro Jahr, mit denen Sie Dokumente per API versenden, öffentliche Signierlinks teilen, Massen-Signaturanfragen ausführen, den Kiosk-Modus für Vor-Ort-Unterschriften nutzen oder Signaturen aus direkt auf Ihrer Website eingebetteten Dokumenten sammeln können. Jedes Mal, wenn Sie ein Dokument initiieren oder versenden, wird ein Credit verwendet — und dieses einzelne Dokument kann bis zu 10 Unterzeichner enthalten.",
"failed-pdf-for-prefill-processing": "PDF für die Vorbefüllung konnte nicht geladen werden.",
"attach-all-role-to-signer": "Bitte weisen Sie dem Unterzeichner alle Rollen zu.",
"upgrade-to-edit": "Zum Bearbeiten upgraden",
"hide-live-chat-for-signers": "Live-Chat für Unterzeichner ausblenden",
"hide-live-chat-for-signers-help": "Wenn aktiviert, werden Live-Chat-Widgets für Unterzeichner auf Signierseiten ausgeblendet.",
"attachments-creator-hint": "Unterzeichner können Dateien hochladen, die am Ende des Dokuments angehängt werden.",
"attachments-drop-hint": "Dateien hier ablegen oder klicken, um auszuwählen",
"attachments-limits": "PDF, DOCX, PNG, JPG. Max. {{maxFiles}} Dateien, je {{maxSizeMB}} MB.",
"attachments-invalid-type": "Dateityp wird nicht unterstützt: {{name}}",
"attachments-max-files": "Maximal {{maxFiles}} Anhänge erlaubt.",
"attachments-remove-confirm": "Das Entfernen löscht nur die Metadaten. Bereits angehängte Seiten bleiben im Dokument. Fortfahren?",
"attachments-docx-not-supported": "Derzeit tritt ein Problem beim Hochladen von DOCX-Dateien auf. Bitte versuchen Sie, das Dokument in einem anderen unterstützten Format hochzuladen, oder versuchen Sie es später erneut."
}
+355 -41
View File
@@ -68,13 +68,14 @@
"loading": "Loading...",
"of": "of",
"sign-SSO": "Sign in with SSO",
"login-page": "Login P+age",
"login-page": "Login Page",
"or": "OR",
"signup-page": "Signup page",
"password-match-length": "New password and confirmation password should match",
"password-length": "Password should be 8 characters long",
"password-case": "Password should contain uppercase letter, lowercase letter, digit",
"password-special-char": " Password should contain special character",
"agreee": "I agree to the",
"agree": "I agree to the",
"term": "Terms of Service",
"subscribe-to-opensign": "Subscribe to OpenSign newsletter",
"register": "Register",
@@ -120,7 +121,8 @@
"Teams": "Teams",
"Team-update-successfully": "Team Updated successfully.",
"Users": "Users"
}
},
"Global SMTP": "Global SMTP"
},
"dashboard-card": {
"Need your Signature": "Need your signature",
@@ -166,7 +168,10 @@
"Save as template": "Save as template",
"Fix & resend": "Fix & Resend",
"Kiosk Mode": "Kiosk Mode",
"Reset password": "Reset password"
"Reset password": "Reset password",
"View formdata": "View formdata",
"Download all formdata": "Download all formdata",
"API credits": "API credits"
},
"report-heading": {
"Sr.No": "Sr.No",
@@ -201,7 +206,8 @@
"Redirect url": "Redirect url",
"Created Date": "Created Date",
"Updated Date": "Updated Date",
"Expiry Date": "Expiry Date"
"Expiry Date": "Expiry Date",
"Sent Date": "Sent Date"
},
"report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -218,7 +224,7 @@
"New Template": "New template",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "file(s) selected",
"files-selected": "file(s) selected",
"template-title": "Template title",
"document-title": "Document title",
"title": "Title",
@@ -231,11 +237,12 @@
"p3": "Selecting this option will send the signing links to all signers simultaneously. Every signer can sign the document at their convenience, regardless of whether other signers have completed their signatures. This method is faster but does not enforce any signing order among the participants.",
"p4": "Select the option that best suits the needs of your document processing."
},
"strict-order": "Enforce strict order",
"strict-order-help": "When enabled, no signer can sign or approve until every previous signer/approver has completed their action.",
"no": "No",
"auto-reminder": "Auto reminder",
"remind-once": " Remind once in every (Days)",
"next": "Next",
"select-folder": "Select folder",
"OpenSign-drive": "{{appName}} Drive",
"drive-document-status": {
"Completed": "Completed",
@@ -255,6 +262,7 @@
"signers": "Signers",
"signers-help": "Begin typing a contact's name to see suggested signers from your saved contacts or add new ones. Arrange the signing order by adding signers in the desired sequence. Use the '+' button to include signers and the 'x' to remove them. Each signer will receive an email prompt to sign the document in the order listed.",
"bcc-help": "Start typing a contact's name to see suggestions from your saved contacts or add new ones. Use the '+' button to add a user and the 'x' to remove them. The selected user's email will be added as a Bcc (blind carbon copy). Each user will receive an email notification once the document is completed.",
"cc-help": "Start typing a contact's name to see suggestions from your saved contacts or add new ones. Use the '+' button to add a user and the 'x' to remove them. The selected user's email will be added as a cc (carbon copy). Each user will receive an email notification once the document is completed.",
"add-signer": "Add Signer",
"contact-not-found": "Contact not found",
"add-yourself": "Add yourself",
@@ -264,7 +272,7 @@
"signature": "Signature",
"upload-image": " Upload image",
"clear": "Clear",
"upload-signature/Image": "Please upload signature/Image",
"upload-signature/Image": "Please upload signature/Image/stamp",
"initials": "Initials",
"API": "API",
"api-token": "API token",
@@ -282,7 +290,7 @@
"add-webhook": "Add webhook",
"webhook-added": "Webhook added successfully.",
"team-disabled": "Team disabled",
"Team enabled": "Team enabled",
"team-enabled": "Team enabled",
"are-you-sure": "Are you sure you want to",
"disable": "disable",
"enable": "enable",
@@ -290,7 +298,7 @@
"edit-team": "Edit team",
"name-of-team": "Name of team",
"prev": "Prev",
"no-data-avaliable": "No data available",
"no-data-available": "No data available",
"add-team": "Add team",
"page-not-found": "Page not found",
"users-from-teams": "users from Teams",
@@ -301,12 +309,37 @@
"delete-user": "Delete user",
"delete": "Delete",
"add-user": "Add user",
"password-generateed": "Password will only be generated once; make sure to copy it.",
"password-generated": "Password will only be generated once; make sure to copy it.",
"Team status": "Team status",
"user-deactivated": "User deactivated.",
"user-activated": "User activated.",
"public": "Public",
"download": "Download",
"sign-offline": "Sign Offline",
"sign-offline-instructions": "Download the document, sign it using any external tool, then upload the signed copy here. The document owner will review your submission and either approve or reject it.",
"sign-offline-step-1": "Download the unsigned PDF.",
"sign-offline-step-2": "Sign the PDF using your preferred tool.",
"sign-offline-step-3": "Upload the signed PDF for owner approval.",
"sign-offline-upload-hint": "Select the signed PDF to submit for approval.",
"sign-offline-submitted-title": "Submitted for approval",
"sign-offline-submitted-body": "The document owner has been notified by email to review your offline-signed copy.",
"offline-already-submitted-title": "Document submitted offline",
"offline-already-submitted-body": "You have already submitted this document for offline signature review. The document owner will be notified once it is approved or rejected.",
"offline-action-approve-title": "Approve offline signature",
"offline-action-approve-confirm": "Approve",
"offline-action-reject-title": "Reject offline signature",
"offline-action-reject-hint": "Optionally provide a reason. The signer will be notified by email.",
"offline-action-reject-reason-placeholder": "Reason (optional)",
"offline-action-reject-confirm": "Confirm rejection",
"offline-action-review-title": "Review offline signature",
"offline-action-review-hint": "Preview the signed PDF uploaded by the signer, then approve or reject it. No action is taken until you confirm.",
"offline-action-submitted-at": "Submitted",
"offline-action-no-preview": "No preview available for this submission.",
"offline-action-open-in-new-tab": "Open PDF in new tab",
"offline-action-invalid-link": "This review link is invalid or has expired.",
"offline-action-success": "Action completed.",
"reject": "Reject",
"document": "Document",
"view": "View",
"make-template-public": "Make template public",
"make-template-private": "Make template private",
@@ -331,7 +364,7 @@
"quick-send-alert-1": "All roles in this document are currently linked to contacts. To quick send copies of this template to multiple signers, please ensure that at least one role is not linked to any contact.",
"quick-send-alert-2": "Please ensure there's at least one signature widget added for all recipients.",
"quick-send-alert-3": "Please add at least one role to this template.",
"quick-send-alert-4": "Quick send reached limit.",
"quick-send-alert-4": "{{flow}} supports up to {{count}} signers per batch. Split your list and try again.",
"copy-link": "Copy link",
"copy": "Copy",
"revoke-document": "Revoke document",
@@ -342,7 +375,7 @@
"body": "Body",
"add-contact": "Add contact",
"edit-contact": "Edit contact",
"add-signer-alert": "Contact already exist! Please select it from 'Signers' dropdown",
"add-signer-alert": "Contact already exists! Please select it from 'Signers' dropdown",
"record-delete-alert": "Record deleted successfully!",
"record-revoke-alert": "Record revoked successfully!",
"mail-sent-alert": "Mail sent successfully.",
@@ -387,7 +420,11 @@
"radio button": "radio button",
"image": "image",
"email": "email",
"number": "number"
"number": "number",
"draw": "draw",
"attachments": "attachments",
"attachments-add": "Click to add attachments",
"attachments-count": "{{count}} attachment(s)"
},
"fields": "Fields",
"recipients": "Recipients",
@@ -399,6 +436,14 @@
"certificate": "Certificate",
"decline": "Decline",
"finish": "Finish",
"approve": "Approve",
"approve-mssg": " Are you sure you want to approve the document ?",
"signer-role-label": "Recipient role",
"signer-role-signer": "Signer",
"signer-role-viewer": "Viewer",
"signer-role-approver": "Approver",
"signer-role-hint": "Signer must sign. Approver approves without signing. Viewer only views.",
"signer-role-upgrade-msg": "Viewer and Approver roles are available on paid plans only. Upgrade your plan to assign these roles to recipients.",
"done": "Done",
"mail": "Mail",
"sign-now": "Sign now",
@@ -431,17 +476,18 @@
"reset-password-alert-1": "Reset password link has been sent to your email id",
"reset-password-alert-2": "Please setup email adapter ",
"reset-password-alert-3": "Reset Your Password",
"faild-animation": "Failed to load animation",
"failed-animation": "Failed to load animation",
"apply": "Apply",
"select-columns": "Select columns",
"copy-type": {
"All pages": "All pages",
"All pages but last": "All pages but last",
"All pages but first": "All pages but first",
"Next to current widget": "Next to current widget"
"Next to current widget": "Next to current widget",
"Page range": "Page range"
},
"options": "Options",
"minimun-check": "Minimun check",
"minimum-check": "Minimum check",
"maximum-check": "Maximum check",
"cell-count": "Cell count",
"default-value": "Default value",
@@ -455,6 +501,19 @@
"formula-error-braces": "Each opening {{ must have a matching }}.",
"formula-error-operators": "Add at least two values when using operators or parentheses.",
"formula-error-leading-operator": "Formulas cannot start with operators.",
"formula-error-trailing-operator": "Formulas cannot end with an operator.",
"formula-error-adjacent-placeholders": "Two placeholders cannot be written together without an operator between them.",
"formula-error-continuous-operators": "Two operators cannot be used together.",
"formula-error-empty-parentheses": "Parentheses cannot be empty.",
"formula-error-invalid-parentheses-content": "Parentheses cannot start or end with an operator.",
"formula-error-before-open-parenthesis": "Missing operator before '('. Add an operator before the opening parenthesis.",
"formula-error-after-close-parenthesis": "Missing operator after ')'. Add an operator like '+' or '*' after the closing parenthesis.",
"formula-error-between-parentheses": "Missing operator between ')' and '('. Add an operator like '*' between the two groups.",
"formula-error-division-by-zero": "Division by zero is not allowed. Replace '0' with a valid non-zero number or variable.",
"formula-error-invalid-number-format": "Invalid number format found. Numbers must be in a valid format like '1', '3.14', not '.5', '1.', or '1.2.3'.",
"formula-error-unclosed-parenthesis": "Unclosed '(' found. Every opening '(' must have a matching closing ')'.",
"formula-error-unexpected-closing-parenthesis": "Unexpected closing parenthesis ')' found without a matching opening '('.",
"formula-error-empty-placeholder": "Empty placeholder '{{}}' is not allowed. Provide a variable name inside the braces.",
"decimal-places": "Decimal Places",
"number-of-decimal-places": "Number of decimal places",
"decimal-places-helper": "Set to 0 for whole numbers",
@@ -511,18 +570,19 @@
"placeholder-alert-4": "You have successfully sent mails to all recipients!",
"placeholder-mail-alert": "You have successfully sent email to {{name}}. Subsequent signers will get email(s) once {{name}} signs the document",
"placeholder-mail-alert-you": "Subsequent signers will get email(s) once you sign the document.",
"placeholder-alert-5": "Do you want to sign the document right now?",
"placeholder-alert-5": "You can sign now or send it to your email to complete later.",
"placeholder-alert-6": "Please setup mail adapter to send mail!",
"placeholder-alert-7": "Please select signer for add placeholder!",
"email-subject": "email subject",
"email-body": "email body",
"email-placeholder": "add body of email",
"reset-to-default": "Reset to default",
"cutomize-email": "Cutomize email",
"customize-email": "Customize email",
"upgrade-to-customize-email": "Upgrade to customize Email",
"sign-url": "Sign url",
"mails-sent": "Mails Sent",
"mail-not-delivered": "Mail not delivered",
"email-not-verified-head": "Email not verified",
"document-alert": "Document alert",
"owner-subscription-expired": "Owner's subscription has expired.",
"subscription-expired": "Subscription Expired",
@@ -535,12 +595,12 @@
"sign here": "Sign here",
"guest-email-alert": "verification code is sent to your email",
"get-otp-alert": "you will get a verification code via Email",
"user-already-exist": "User already exist.",
"user-already-exist": "User already exists.",
"server-error": "Internal server error !",
"fill-required-details!": "Please fill required details!",
"sign-up-form": "Sign up form",
"sign-up": "Sign up",
"folder-already-exist!": "folder already exist!",
"folder-already-exist!": "folder already exists!",
"please-select-position!": "please select position!",
"default-sign-alert": "Are you sure you want to auto-sign all requested {{widgetsType}} widgets?",
"Your-Signature": "Your signature",
@@ -549,7 +609,9 @@
"pdf-download": "PDF download",
"generate-certificate-alert": " Your completion certificate is being generated. Please wait momentarily. If the download doesn't start shortly, click the button again.",
"generate-certificate-err": "We are currently experiencing an issue with certificate generation. Please try again later.",
"folder-already-exist": "Folder already exist!",
"attachments-docx-upload-failed": "We are currently facing a problem. Please try again.",
"attachments-docx-size-limit": "Upload limit reached for large files. Please wait until {{time}} before retrying.",
"folder-already-exist": "Folder already exists!",
"folder-created-successfully": "Folder created successfully!",
"fill-folder-name": "Please fill folder name",
"already-exists-this-username": "User already exists with this username!",
@@ -560,7 +622,7 @@
"add-admin": "Add admin",
"opensign-setup": "{{appName}} setup",
"join-discord": "Join our discord server",
"admin-already-exist": "Admin already exist.",
"admin-already-exist": "Admin already exists.",
"password-update-alert-1": "Password updated successfully.",
"password-update-alert-2": "Your current password is missing or incorrect.",
"password-update-alert-3": "Your current password is missing or incorrect.",
@@ -583,7 +645,7 @@
"provide-your-details": "Provide your details",
"provide-email": "Please provide email.",
"additional-info": "Additional info",
"signature-saved-alert": "Signature saved successfully.",
"signature-saved-alert": "Saved successfully.",
"saved-successfully": "Saved successfully.",
"fill-field": "Please fill out this field",
"error-template": "Error: Template not found!",
@@ -598,6 +660,9 @@
"document-signed-alert-7": "This Document is no longer available.",
"document-signed-alert-8": "The document has already been dispatched for signing.",
"yet-to-sign": "Yet to sign",
"viewers": "Viewers",
"viewer-no-widgets": "Viewers receive a read-only copy of the document and cannot have widgets assigned.",
"viewer-confirm-widget-removal": "Switching this recipient to Viewer will remove all widgets already assigned to them. Continue?",
"validation-alert-1": "The input does not meet the criteria set by the regular expression.",
"account-already-exists": "Account already exists!",
"choose-password": "Choose password",
@@ -620,7 +685,7 @@
"date": "Date",
"report-not-found": "Report not found",
"please-wait": "please wait",
"select-foler": "Select folder",
"select-folder": "Select folder",
"generated-on": "Generated on",
"text-field": "Text field",
"font-size": "Font size",
@@ -679,13 +744,13 @@
"public-template": "This video demonstrates how to set up your personalized public profile, such as 'https://opensign.me/your-username'. You'll also learn how to customize your tagline and make your templates available for public signing.",
"allowModify-widgets": "You can drag and drop any of these fields onto the document, in addition to the fields already designated for you by the document creator."
},
"enter-email-plaholder": "Add an email address and hit enter",
"enter-email-placeholder": "Add an email address and hit enter",
"success-email-alert": "Email sent successfully!",
"expired-doc-title": "Expired document",
"expired-on-mssg": "This document expired on {{expiredDate}} and is no longer available to sign.",
"signature-validate-alert": "Please confirm that you have selected at least {{minRequiredCount}} checkboxes.",
"signature-validate-alert-2": "Ensure this field is accurately filled and meets all requirements.",
"remaing-users": "Remaining users",
"remaining-users": "Remaining users",
"Role": "Role",
"additional-users": "Please purchase add-on users.",
"Quantity-of-users": "Quantity of users",
@@ -694,12 +759,12 @@
"Proceed": "Proceed",
"Select": "Select",
"Add-seats": "Add seats",
"user-name-exist": "user name already exist",
"user-name-exist": "user name already exists",
"format": "format",
"select-date-format": "Select a date format",
"quantity-of-credits": "Quantity of premium credits",
"remaining-credits": "Premium credits available:",
"remaining-credits-help": "Use premium credits for API document signing, bulk sending, or embedding OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
"remaining-credits-help": "Use premium credits for API document signing, bulk sending, or embedding OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining. If you are on Teams or enterprise plan these are the credits that could be shared across all users in your company unless there is a limit set by the account admin for your account.",
"additional-credits": "Please purchase premium credits",
"quota-err-quicksend": "Quota reached, you don't have enough credits.",
"buy-credits": "Buy premium credits",
@@ -707,7 +772,7 @@
"rotate-left": "Rotate left",
"rotate-alert-mssg": "All widgets on this page will be lost. Are you sure you want to proceed?",
"templateid": "Template-Id",
"bulk-send-subcription-alert": "Please upgrade to Professional or Team plan to use bulk send.",
"bulk-send-subscription-alert": "Please upgrade to Professional or Team plan to use bulk send.",
"generate-test-token": "Generate test token",
"regenerate-test-token": "Regenerate test token",
"help-test-token": "This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once you've completed your testing, you can upgrade to one of our paid plans to generate a production token.",
@@ -727,8 +792,9 @@
"copied-code": "COPIED",
"Installation": "Installation",
"Usage": "Usage",
"insufficient-credits": "Insufficient signing credits",
"insufficient-credits-mssg": "The owner of this document currently lacks the necessary OpenSign credits for you to sign. Please reach out to the owner if you require further details.",
"insufficient-credits": "Insufficient signing credits. Please contact {{ownerEmail}}",
"insufficient-credits-title": "Insufficient signing credits",
"insufficient-credits-mssg": "The owner of this document currently lacks the necessary {{appName}} credits for you to sign. Please reach out to the owner if you require further details.",
"angular-npm-mssg-1": "To integrate OpenSign into your Angular project, simply run the following command:",
"quota-mail-info-head": "Monthly Email Limit",
"quota-mail-info": "To maintain service quality and prevent spam, OpenSign allows up to 15 emails per month on the free plan. Upgrade now for unlimited email sending.",
@@ -747,6 +813,13 @@
"p3": "Selecting this option will disable OTP verification, allowing users to sign the document directly without additional steps.",
"p4": "Please choose the option that best suits your document signing requirements."
},
"allow-offline-sign": "Allow offline signing",
"allow-offline-sign-help": {
"p1": "Choose whether signers can submit a copy of this document signed using an external tool for owner review.",
"p2": "Signers will see a Sign Offline option and can upload an externally signed PDF for the document owner to approve.",
"p3": "Signers can only sign within the app. The Sign Offline option will be hidden.",
"p4": "This setting defaults to your account preference under Preferences and is available on paid plans."
},
"advanced-options": "Advanced options",
"hide-advanced-options": "Hide advanced options",
"download-files": "Download files",
@@ -809,6 +882,7 @@
"delete-folder-alert-1": "Unable to delete: The folder must be empty.",
"defaultSign-alert": "{{widgetsType}} widgets are not available for auto sign.",
"embed-heading": "Embed {{appName}} Templates",
"other-apps-by-opensign": "Other apps by OpenSign team",
"public-template-edit-title": "Edit Public Template",
"public-template-edit-mssg": "You cannot edit a public template directly. To make changes, the template will be set to private, and you'll need to make it public again afterward. Do you want to continue?",
"agree-p1": "I confirm that I have read and understood the ",
@@ -860,7 +934,6 @@
"js-snippet-msg-1": "You can get the TemplateId from the",
"js-snippet-msg-2": "Manage Templates",
"js-snippet-msg-3": "page.",
"agrrement-alert": "To proceed, you must consent to the electronic record and signature disclosure.",
"webhook-already-exists": "The URL already exists! Please try a different one.",
"webhook-must-be-secure": "The webhook URL must be secure and use https://",
"provide-test-token": "Please generate api test token",
@@ -880,6 +953,7 @@
"p3": "Selecting this option restricts signers from making any additions to the document during the signing process."
},
"signature-setting": "Signature settings",
"rotation": "Rotation",
"activation-key": "Activation key",
"paid-selfhost-help": "Please contact to {{appName}} for more info at ",
"remaining-credits-selfhost-help": "Use premium credits for API document signing, bulk sending {{appName}} integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
@@ -908,6 +982,8 @@
"visit-below-link": "Visit below link to know more -",
"storage-help": "Enabling BYOC lets you connect your own S3 storage so your files remain entirely under your control—no external copies retained. If data autonomy matters to you, consider upgrading to Teams to unlock this feature.",
"daily-quota-reached": "You've reached your daily quota. For assistance, please contact quotas@opensignlabs.com.",
"email-not-verified-send": "Please verify your email address before sending emails. Go to your <1>profile settings</1> to complete email verification.",
"email-not-verified-send-plain": "Please verify your email address before sending emails. Go to your profile settings to complete email verification.",
"enabled-signature-type": "Enabled Signature Types",
"enabled-signature-type-help": "The 'Enabled Signature Types' setting determines which signature options are available across your organization. For example, if you disable the 'Draw' option, members of your organization will not see it in the signature widget, while the other three options will remain accessible.",
"indexing-public-profile": "Allow indexing of public profile by search engines",
@@ -935,6 +1011,7 @@
"please-provide-templateid": "Please provide templateid",
"this-template-is-not-public": "This template is not public",
"invalid-templateid": "Invalid templateid",
"kiosk-no-active-role": "This template has no signer or approver roles available for kiosk signing.",
"contact-billing-at-opensign": "To add more seats, please contact OpenSign™ at <1>billing@opensignlabs.com</1> for assistance",
"title-length-alert": "Title must be at most 250 characters long.",
"note-length-alert": "Note must be at most 200 characters long.",
@@ -1024,7 +1101,7 @@
},
"signer-already-present": "Signer already present",
"kiosk-sign": "Kiosk Sign",
"dont-have-access-to-template": "The template has been deleted or you don't have access. Please contact the sender.",
"do-not-have-access-to-template": "The template has been deleted or you don't have access. Please contact the sender.",
"kiosk-info": "Kiosk Mode lets you collect in-person signatures quickly and efficiently. Ideal for trade shows, events, or walk-in scenarios where all signers are physically present. ",
"learn-more": "Learn more",
"finish-mssg": " Are you sure you want to finish the document ?",
@@ -1080,8 +1157,6 @@
"search-contacts": "Search contacts…",
"add-role-alert": "Please add at least one role",
"edit-draft": "Edit draft",
"invalid-email-found": "Invalid email found: {{email}}",
"duplicate-email-found": "Duplicate email found: {{email}}",
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Billing",
@@ -1095,7 +1170,7 @@
"access-denied": "Access denied",
"upgrade": "Upgrade",
"do-not-access-app": "You don't have access to this application.",
"dont-have-access": "You don't have access.",
"do-not-have-access": "You don't have access.",
"valid-email-alert": "Please enter a valid email address.",
"otp-not-validate": "OTP is not valid.",
"domain-not-allowed": "This domain is not allowed",
@@ -1112,10 +1187,12 @@
"branding-help": "Branding provides white labelling to your app",
"custom-sub-domain": "Custom sub-domain",
"app-name": "App Name",
"provide-domain-name": "provide your domain name",
"provide-domain-name": "Only subdomains are allowed (e.g., app.yourdomain.com). Primary/root domains like yourdomain.com are not supported.",
"provide-app-name-help": "Provide a unique app name. It will be displayed as your brand name within the application and in all emails wherever the branding is applied.",
"upload-app-logo": "Upload a logo in .png, .jpg, or .svg format. Recommended size: {{size}} pixels (5:2 aspect ratio) for best display quality.",
"upload-app-favicon": "Upload a square favicon image (.ico, .png, or .svg). Recommended size is {{size}} pixels for best browser compatibility.",
"provide-app-name": "provide your app name",
"logo": "Logo",
"upload-app-logo": "upload your app logo",
"prefill-unfilled-widget": "The following required field(s) cannot be left empty: {{emptyWidget}} Please fill them out to proceed.",
"Dashboard": "Dashboard",
"Analytics": "Analytics",
@@ -1238,6 +1315,7 @@
},
"read-only-date-error": "Read-only date widget must have a default date.",
"set-date": "Set Date",
"default-date": "Default Date",
"set-today": "Signing Date",
"enter-name": "Enter name",
"enter-email": "Enter email",
@@ -1257,5 +1335,241 @@
"Please provide required parameters.": "Please provide required parameters.",
"reset-password": "Reset User Password",
"enter-strong-password": "Enter a strong password or auto-generate one.",
"autogenerate": "Autogenerate"
}
"autogenerate": "Autogenerate",
"extend": "Extend",
"console-general-help": "To let individual users customize their own email templates, navigate to",
"console-mail-help": "To update company-wide email templates, go to",
"admin-setting-disabled": "This setting is disabled. Please contact admin for assistance.",
"company-email-settings": "Company email settings",
"allow-email-template-individually": "Allow email template customization for users individually ",
"user-email-template-general-help": "Disabling this will prevent users from customizing their email templates.",
"disable-individual-user-smtp": "Disable individual user smtp settings",
"enable-individual-user-smtp": "Enable individual user smtp settings",
"user-smtp-general-help": "When disabled, users will not be able to configure their own SMTP settings. All emails will use the global SMTP configuration instead.",
"hide-live-chat-for-signers": "Hide live chat widget for signers",
"hide-live-chat-for-signers-help": "When enabled, the live chat widget will be hidden on all signing pages. A window variable 'opensign_hide_chat' is set to true, which can be used as a GTM trigger condition to prevent loading the widget for signers.",
"global-smtp-settings": "Global SMTP Settings",
"global-smtp-settings-help-p1": "These settings apply globally across your entire {{appName}} account. If an individual user has not configured their own email delivery settings, the system will automatically use the Global SMTP Settings defined here.",
"global-smtp-settings-help-p2": "To ensure better inbox deliverability for all signature request emails, you can connect your preferred email provider. Choose one of the following options:",
"global-connect-to-gmail-help": "Use a Gmail account to send signature request emails directly from your organization's inbox. This improves delivery rates, reputation, and overall reliability.",
"global-connect-to-smtp-help": "Use your own SMTP server to send emails through your domain. This provides full control over your email infrastructure, enhances deliverability, and maintains consistent branding.",
"global-smtp-settings-help-p3": "If no individual user-level settings are configured, these global settings will be applied automatically.",
"hide-text-with-asterisks": "Hide text with asterisks",
"hide-text-with-asterisks-help": "The text on the signed document will be concealed from view but remain accessible to the document sender (owner) from their {{appName}} account.",
"document-id": "Document ID",
"sent-date": "Sent Date",
"status": "Status",
"signer-form-data-for": "Signer Form data for: {{signerName}} <{{signerEmail}}>",
"widget-name": "Widget Name",
"widget-responses": "Widget Responses",
"formdata-download-note": "Note: Signatures, stamps, initials and images wont be exported. Offline signers data will not be available.",
"export-all-formdata-error": "You can/t export form data because the template contains duplicate widget names. Please create a fresh template to be able to export form data.",
"export-formdata-error": "You can't export form data because the document contains duplicate widget names. Please create a fresh document without duplicate widget names to be able to export form data.",
"duplicate-widget-name-error": "This widget name is already assigned to another signer. Please choose a different name.",
"duplicate-template-widget-error": "Unable to duplicate the template because some widget names are duplicated across different roles. Rename the duplicate widgets and try again.",
"credit-alert": "Credit Alert",
"document-form-data": "Document Form Data",
"view-formdata-paid-title-error": "The View Form Data feature lets you print and download the widget data filled in by signers while signing.",
"view-formdata-paid-error": "Note: 'View Formdata' feature is only available for documents created on or after v2.32.0 that do not contain duplicate widget names.",
"download-all-formdata-paid-title-error": "The Download All Form Data feature lets you download widget data filled in by signers for all documents created from the template.",
"download-all-formdata-paid-error": "Note: 'Download All Formdata' feature is only available for templates created on or after v2.32.0 that do not contain duplicate widget names.",
"api-token-not-found": "API token not found.",
"sub-head-api-credits": " Define the maximum number of API credits this user is allowed to consume.",
"consumed-credits": "Consumed credits",
"allowed-api-credits": "Allowed API credits",
"allowed-api-credits-help": "The allowed credits should be greater than or equal to the consumed credits.",
"Enter-allowed-api-credits": "Enter allowed API credits",
"login-to-sandbox": "Login to sandbox",
"your-credits-usage": "Your credits usage",
"remaining": "remaining",
"used": "used",
"your-credits-usage-help": "This is the number of credits consumed with your API key.",
"api-credits-updated": "API credits have been allocated successfully.",
"min-date": "Min Date",
"max-date": "Max Date",
"invalid-min-date": "Invalid min date.",
"min-less-max": "Min date must be less than or equal to max date.",
"invalid-max-date": "Invalid max date.",
"max-less-min": "Max date must be greater than or equal to min date.",
"date-range": "Date range",
"date-range-help": "Set Min and/or Max to restrict selection. Chosen date must fall within the range.",
"option-disabled-by-owner": "Document creator has disabled this option",
"pen-colors": "Pen colors",
"pen-colors-help": "Choose which pen colors are available for Signature and Initials widgets. Check to show a color, uncheck to hide it.",
"security-key": "Security key",
"remove-limit": "Remove limit",
"removed-api-credits": "The API credit limit has been removed.",
"enable-authentication": "Enable authentication",
"working": "Working",
"test-security-key": "Test security key",
"generate": "Generate",
"regenerate": "Regenerate",
"signing-date": "Signing Date",
"date-widget": "Date Widget",
"mail-failed": "Something went wrong while sending the email. Please try again.",
"mail-status-head": "Next step: Sign your document ?",
"send-to-email": "Send to Email",
"date-pref-help-sub-title": "Date Widget Preferences let you define the default settings for the Date widget. These defaults are automatically applied whenever you add a new Date widget to a document or template. You can still customize any individual Date widget at any time by clicking the gear icon.",
"date-pref-help-format": "Choose the default date format used for Date widgets.",
"date-pref-help-default-date": "Set a default date value for Date widgets.",
"date-pref-help-signing-date": "When enabled, the Date widget automatically uses the date the signer signs the document.",
"date-pref-help-read-only": "When enabled, the Date widget is read-only for the signer.",
"upload-csv": "Upload CSV",
"file-selected": "file selected",
"bulk-send-csv-helper": "Use headers for signer roles (Role Email/Name/Phone) and prefill values (Prefill: Field Name). Required prefill fields must be completed.",
"bulk-send-csv-helper-note": "Note: For Date-type prefill fields in the CSV, the value must be provided in this format: {{value}} (example: {{example}}).",
"bulk-send-csv-summary": "Imported {{total}} rows from CSV.",
"bulk-send-csv-missing-headers": "Missing required CSV headers. Roles: {{roles}}.",
"bulk-send-no-records": "Please add at least one recipient before sending.",
"csv-only-support": "Only CSV files are supported.",
"csv-no-records": "No records found in the CSV.",
"select-at-least-option": "Please select at least one option.",
"draw-required": "Please draw in the box.",
"enter-smtp-username": "Enter your SMTP username",
"enter-webhook-url": "Enter webhook url",
"enter-user-name": "Enter user name",
"enter-tagline": "Enter tagline",
"enter-password-or-click-autogenerate": "Enter password or click Autogenerate",
"enter-daily-email-quota": "Enter daily email quota",
"enter-custom-regular-expression": "Enter custom regular expression",
"enter-value": "Enter {{value}}",
"are-you-sure-want-to-send-document": "Are you sure want to send document?",
"favicon": "Favicon",
"delete-two-factor-authentication": "Are you sure you want to delete two-factor authentication?",
"paste-html-here": "Paste HTML Here",
"open-email-builder": "You can create email template using <1>email builder</1> platform and copy html code.",
"contact-saved": "Contact saved successfully.",
"remove-limit-of-user": "Are you sure you want to remove this user's limit? They will be able to consume all your organization's available premium credits that are shared across all users.",
"current-expiry-date": "Current expiry date",
"reminder-error": "Remind once in every (Days) must not exceed Time to complete (Days)",
"showing-to-of-records": "Showing {{record}} to {{recordPerPage}} of {{totalRows}} records",
"records-per-page": "Records per page",
"email-not-found-in-row": "Email not found in row {{row}}",
"invalid-email-found-in-row": "Invalid email found in row {{row}}: {{email}}",
"duplicate-email-found-in-row": "Duplicate email found in row {{row}}: {{email}}",
"prefill": "Prefill",
"recipients-&-data": "Recipients & Data",
"response": "Response",
"prefill-tab-help": "These are the common template fields that will apply to all documents in this bulk send.",
"recipient-tab-help-p1": "In this next step, you'll",
"recipient-tab-help-p2": "Upload a CSV with recipient details or",
"recipient-tab-help-p3": "Manually enter recipient information",
"recipient-tab-help-p4": "Each recipient will receive their own document generated from this template.",
"response-tab-help": "Review your responses.",
"summary": "Summary",
"prefill-fields": "Prefill fields",
"documents": "Documents",
"message": "Message",
"formula-help-p1": "Collect numeric input and perform dynamic calculations using formulas.",
"example": "Example",
"formula-help-p2": "To display a total in number-3, use {{number-1}} + {{number-2}}.",
"formula-help-p3": "To calculate a value in number-4, use ({{number-1}} * {{number-2}}) / {{number-3}}.",
"formula-help-p4": "Each number widget must have a unique name (e.g., number-1), as formulas rely on these identifiers.",
"note-consume-premium-credits": "Note: This feature consumes premium credits.",
"note-consume-premium-credits-on-document-send": "Note: This feature consumes premium credits. It is consumed any time a document is sent out, regardless of whether it is signed or not.",
"webhook-authentication-help": "{{appName}} secures every webhook by generating an HMAC-SHA256 signature using your webhook security key and the raw request payload. This signature is sent in the x-webhook-signature header. Your server must generate the same signature using the received payload and your secret key. If both signatures match, the webhook request is authentic and has not been modified.",
"invalid-widget-found-in-row": "Invalid value detected in row {{row}} for widget {{widget}}.",
"all-signer-status": "All signer status",
"viewed": "Viewed",
"signed": "Signed",
"switch-to-advanced": "Switch to advanced",
"switch-to-basic": "Switch to basic",
"use-name-as-sender": "Use profile name as sender",
"use-name-as-sender-help": "When enabled, {{appName}} uses your profile name as sender for signature request and completion mail.",
"acro-fields-detected": "Form Fields Detected",
"acro-fields-detected-msg": "We detected {{count}} form field(s) in the uploaded PDF. Would you like to automatically insert corresponding widgets?",
"documents-count": "Documents count",
"templates-count": "Templates count",
"emails-sent": "Emails sent",
"storage-used": "Storage used",
"docs-count-team-help": "Total number of documents created by all users in your organizations.",
"docs-count-help": "Total number of documents created by you.",
"templates-count-team-help": "Total number of templates created by all users in your organizations.",
"templates-count-help": "Total number of templates created by you.",
"emails-sent-team-help": "Total number of email notifications sent across your organizations.",
"emails-sent-help": "Total number of email notifications sent by you.",
"storage-used-team-help": "Total storage space consumed by all users across your organizations.",
"storage-used-help": "Total storage space consumed by you.",
"cloud": "Cloud",
"on-premises": "On-premises",
"plan-name-free": "OPENSIGN™ FREE",
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
"plan-name-teams": "OPENSIGN™ TEAMS",
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
"plan-subtitle-free": "Free Unlimited E-signatures, Forever.",
"plan-subtitle-professional": "Exclusive Access to advanced features.",
"plan-subtitle-scalable": "Scalable Features with priority support.",
"subscribe": "Subscribe",
"contact-us": "Contact us",
"benefit-unlimited-digital-signatures": "Unlimited digital signatures",
"benefit-sign-docs-yourself": "Sign documents yourself",
"benefit-request-signatures": "Request signatures from others",
"benefit-unlimited-templates": "Unlimited templates",
"benefit-14-field-types": "14 field types",
"benefit-automatic-e-signatures": "Automatic e-signatures",
"benefit-completion-certificates": "Completion certificates",
"benefit-send-in-order": "Send in order",
"benefit-organize-docs": "Organize docs in OpenSign™ Drive",
"benefit-document-templates": "Document templates",
"benefit-import-from-dropbox": "Import from Dropbox",
"benefit-contact-book": "Contact book",
"benefit-document-expiry": "Document expiry support",
"benefit-decline-document": "Decline document support",
"benefit-email-notifications": "Email notifications",
"benefit-public-profiles": "Public profiles",
"benefit-and-much-more": "And much more",
"benefit-everything-free": "Everything in OpenSign™ free",
"benefit-field-validations": "Field validations",
"benefit-regex-validations": "Regular expression validations",
"benefit-webhooks": "Webhooks",
"benefit-zapier": "Zapier integration",
"benefit-api-access": "API Access",
"benefit-api-signatures-100": "upto 100 API signatures",
"benefit-api-signatures-240": "upto 240 API signatures",
"benefit-custom-email-templates": "Custom email templates",
"benefit-custom-smtp": "Connect your own Gmail or SMTP account for sending emails",
"benefit-auto-reminders": "Auto reminders",
"benefit-bulk-send-100": "Bulk send (upto 100 docs)",
"benefit-bulk-send-240": "Bulk send (upto 240 docs)",
"benefit-premium-profiles": "Premium Public profile usernames",
"benefit-email-verification": "Enforce email-based verification to confirm signer identity",
"benefit-embedded-signing": "Embedded signing",
"benefit-everything-professional": "Everything in OpenSign™ professional",
"benefit-teams-organizations": "Teams and Organizations",
"benefit-share-templates-teams": "Share Templates with teams",
"benefit-share-templates-individuals": "Share Templates with individuals",
"benefit-byoc": "BYOC - Store your documents in your own cloud storage",
"benefit-doc-id-removal": "DocumentId removal from signed docs",
"benefit-request-payments": "Request Payments (coming soon)",
"benefit-mobile-app": "Mobile app (coming soon)",
"benefit-api-signatures-500": "upto 500 API signatures",
"benefit-bulk-send-500": "Bulk send (upto 500 docs)",
"benefit-all-features": "All features",
"benefit-custom-domain": "Custom domain",
"benefit-custom-branding": "Custom branding",
"benefit-uptime-sla": "Uptime SLA",
"benefit-sso-support": "SSO support",
"benefit-priority-support": "Priority support",
"benefit-3-users-1500-credits": "Includes 3 users and 1500 Premium Credit",
"benefit-public-links": "Public links",
"benefit-kiosk-mode": "Kiosk Mode",
"benefit-bulk-send-1500": "Bulk send (upto 1500 docs)",
"benefit-connect-gmail-smtp": "Connect your own Gmail or SMTP",
"benefit-analytics": "Analytics",
"benefit-bulksend-api-pro-month-help": "This plan comes with 100 premium credits per month that let you send documents via API, share public signing links, run bulk signature requests, use Kiosk mode for in-person signing, or collect signatures from documents embedded directly on your website. Each time you initiate or send a document, one credit is used — and that single document can include up to 10 signers.",
"benefit-bulksend-api-pro-year-help": "This plan comes with 240 premium credits per year that let you send documents via API, share public signing links, run bulk signature requests, use Kiosk mode for in-person signing, or collect signatures from documents embedded directly on your website. Each time you initiate or send a document, one credit is used — and that single document can include up to 10 signers.",
"benefit-bulksend-api-team-month-help": "This plan comes with 100 premium credits per month that let you send documents via API, share public signing links, run bulk signature requests, use Kiosk mode for in-person signing, or collect signatures from documents embedded directly on your website. Each time you initiate or send a document, one credit is used — and that single document can include up to 10 signers.",
"benefit-bulksend-api-team-year-help": "This plan comes with 500 premium credits per year that let you send documents via API, share public signing links, run bulk signature requests, use Kiosk mode for in-person signing, or collect signatures from documents embedded directly on your website. Each time you initiate or send a document, one credit is used — and that single document can include up to 10 signers.",
"failed-pdf-for-prefill-processing": "Failed to load PDF for prefill processing.",
"attach-all-role-to-signer": "Please attach all roles to the signer.",
"upgrade-to-edit": "Upgrade to edit",
"attachments-creator-hint": "Signers can upload files that will be appended to the end of the document.",
"attachments-drop-hint": "Drop files here or click to choose",
"attachments-limits": "PDF, DOCX, PNG, JPG. Up to {{maxFiles}} files, {{maxSizeMB}} MB each.",
"attachments-invalid-type": "Unsupported file type: {{name}}",
"attachments-max-files": "A maximum of {{maxFiles}} attachments are allowed.",
"attachments-remove-confirm": "Removing only deletes the metadata. Pages already appended will remain in the document. Continue?",
"attachement-note":"Note: This widget allows signers to upload a file during the signing process. The uploaded file will be appended as additional pages at the end of the existing PDF",
"attachments-docx-not-supported":"We are currently experiencing an issue with DOCX file uploads. Please try uploading the document in a different supported format, or try again later."
}
+348 -34
View File
@@ -71,10 +71,11 @@
"login-page": "Página de inicio de sesión",
"or": "O",
"signup-page": "Página de registro",
"password-match-length": "La nueva contraseña y la confirmación deben coincidir",
"password-length": "La contraseña debe tener 8 caracteres",
"password-case": "La contraseña debe incluir mayúsculas, minúsculas y números",
"password-special-char": " La contraseña debe incluir caracteres especiales",
"agreee": "Estoy de acuerdo con los",
"agree": "Estoy de acuerdo con los",
"term": "Términos de Servicio",
"subscribe-to-opensign": "Suscribirse al boletín de OpenSign",
"register": "Registrarse",
@@ -120,7 +121,8 @@
"Teams": "Equipos",
"Team-update-successfully": "Equipo actualizado correctamente.",
"Users": "Usuarios"
}
},
"Global SMTP": "SMTP Global"
},
"dashboard-card": {
"Need your Signature": "Necesitan tu firma",
@@ -166,7 +168,10 @@
"Save as template": "Guardar como plantilla",
"Fix & resend": "Corregir y reenviar",
"Kiosk Mode": "Modo Kiosco",
"Reset password": "Restablecer contraseña"
"Reset password": "Restablecer contraseña",
"View formdata": "Ver datos del formulario",
"Download all formdata": "Descargar todos los datos del formulario",
"API credits": "Créditos de API"
},
"report-heading": {
"Sr.No": "Nº",
@@ -201,7 +206,8 @@
"Redirect url": "URL de redirección",
"Created Date": "Fecha de creación",
"Updated Date": "Fecha de actualización",
"Expiry Date": "Fecha de vencimiento"
"Expiry Date": "Fecha de vencimiento",
"Sent Date": "Fecha de envío"
},
"report-help": {
"Draft Documents": "Estos son documentos que has iniciado pero no has finalizado para su envío.",
@@ -218,7 +224,7 @@
"New Template": "Nueva plantilla",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "archivo seleccionado",
"files-selected": "archivo(s) seleccionado(s)",
"template-title": "Título de la plantilla",
"document-title": "Título del documento",
"title": "Título",
@@ -231,11 +237,12 @@
"p3": "Seleccionando esta opción se enviarán los enlaces para la firma a todos los firmantes simultáneamente. Cada firmante puede firmar el documento cuando le convenga, independientemente de si otros firmantes han completado sus firmas. Este método es más rápido, pero no impone ningún orden de firma entre los participantes.",
"p4": "Selecciona la opción que mejor se adapte a las necesidades de tu tramitación de documentos."
},
"strict-order": "Forzar orden estricto",
"strict-order-help": "Cuando está habilitado, ningún firmante puede firmar o aprobar hasta que todos los firmantes/aprobadores anteriores hayan completado su acción.",
"no": "No",
"auto-reminder": "Recordatorio automático",
"remind-once": " Recordar una vez cada (Días)",
"next": "Siguiente",
"select-folder": "Seleccionar carpeta",
"OpenSign-drive": "{{appName}} Drive",
"drive-document-status": {
"Completed": "Completado",
@@ -255,6 +262,7 @@
"signers": "Firmantes",
"signers-help": "Empieza a escribir el nombre de un contacto para ver los firmantes sugeridos entre tus contactos guardados o añade otros nuevos. Organiza el orden de firma añadiendo firmantes en la secuencia deseada. Utiliza el botón «+» para añadir nuevos firmantes y la «X» para quitar todos. Cada firmante recibirá una solicitud por correo electrónico para que firme el documento según el orden indicado.",
"bcc-help": "Empieza a escribir el nombre de un contacto para ver sugerencias de tus contactos guardados o agregar nuevos. Usa el botón '+' para añadir un usuario y el botón 'x' para eliminarlo. El correo electrónico del usuario seleccionado se añadirá como Bcc (copia oculta). Cada usuario recibirá una notificación por correo electrónico una vez que el documento esté completado.",
"cc-help": "Empieza a escribir el nombre de un contacto para ver sugerencias de tus contactos guardados o añadir nuevos. Usa el botón '+' para agregar un usuario y la 'x' para eliminarlo. El correo del usuario seleccionado se añadirá como CC (copia). Cada usuario recibirá una notificación por correo una vez que el documento se complete.",
"add-signer": "Añadir firmante",
"contact-not-found": "Contacto no encontrado",
"add-yourself": "Añadirte a ti mismo",
@@ -264,7 +272,7 @@
"signature": "Firma",
"upload-image": " Subir imagen",
"clear": "Limpiar",
"upload-signature/Image": "Por favor, sube la firma/imagen",
"upload-signature/Image": "Por favor, sube la firma/imagen/estampilla",
"initials": "Iniciales",
"API": "API",
"api-token": "Token API",
@@ -276,13 +284,15 @@
"yes": "Sí",
"copied": "Copiado",
"something-went-wrong-mssg": "Un problème est survenu, Actualiser cette page peut résoudre le problème.",
"attachments-docx-upload-failed": "Actualmente tenemos un problema. Inténtalo de nuevo.",
"attachments-docx-upload-limit": "Se alcanzó el límite de carga para archivos DOCX grandes. Espera 4 minutos antes de intentarlo de nuevo.",
"token-generated": "Token generado exitosamente.",
"webhook": "Webhook",
"update-webhook": "Actualizar webhook",
"add-webhook": "Agregar webhook",
"webhook-added": "Webhook agregado exitosamente.",
"team-disabled": "Equipo deshabilitado",
"Team enabled": "Equipo habilitado",
"team-enabled": "Equipo habilitado",
"are-you-sure": "¿En definitiva quieres ",
"disable": "deshabilitar",
"enable": "habilitar",
@@ -290,7 +300,7 @@
"edit-team": "Editar equipo",
"name-of-team": "Nombre del equipo",
"prev": "Anterior",
"no-data-avaliable": "No hay datos disponibles",
"no-data-available": "No hay datos disponibles",
"add-team": "Agregar equipo",
"page-not-found": "Página no encontrada",
"users-from-teams": "usuarios de equipos",
@@ -301,12 +311,37 @@
"delete-user": "Eliminar usuario",
"delete": "Eliminar",
"add-user": "Agregar usuario",
"password-generateed": "La contraseña solo será generada una vez; asegúrate de copiarla.",
"password-generated": "La contraseña solo será generada una vez; asegúrate de copiarla.",
"Team status": "Estado del equipo",
"user-deactivated": "Usuario desactivado.",
"user-activated": "Usuario activado.",
"public": "Público",
"download": "Descargar",
"sign-offline": "Firmar sin conexión",
"sign-offline-instructions": "Descargue el documento, fírmelo con una herramienta externa y luego suba la copia firmada aquí. El propietario del documento revisará su envío y lo aprobará o rechazará.",
"sign-offline-step-1": "Descargar el PDF sin firmar.",
"sign-offline-step-2": "Firmar el PDF con su herramienta preferida.",
"sign-offline-step-3": "Subir el PDF firmado para aprobación del propietario.",
"sign-offline-upload-hint": "Seleccione el PDF firmado para enviarlo para su aprobación.",
"sign-offline-submitted-title": "Enviado para aprobación",
"sign-offline-submitted-body": "El propietario del documento ha sido notificado por correo electrónico para revisar su copia firmada sin conexión.",
"offline-already-submitted-title": "Documento enviado sin conexión",
"offline-already-submitted-body": "Ya ha enviado este documento para la revisión de la firma sin conexión. Se le notificará una vez que sea aprobado o rechazado.",
"offline-action-approve-title": "Aprobar firma sin conexión",
"offline-action-approve-confirm": "Aprobar",
"offline-action-reject-title": "Rechazar firma sin conexión",
"offline-action-reject-hint": "Opcionalmente, proporcione un motivo. El firmante será notificado por correo electrónico.",
"offline-action-reject-reason-placeholder": "Motivo (opcional)",
"offline-action-reject-confirm": "Confirmar rechazo",
"offline-action-review-title": "Revisar firma sin conexión",
"offline-action-review-hint": "Previsualice el PDF firmado subido por el firmante y luego apruébelo o recházelo. No se realiza ninguna acción hasta que confirme.",
"offline-action-submitted-at": "Enviado",
"offline-action-no-preview": "No hay vista previa disponible para este envío.",
"offline-action-open-in-new-tab": "Abrir PDF en nueva pestaña",
"offline-action-invalid-link": "Este enlace de revisión no es válido o ha expirado.",
"offline-action-success": "Acción completada.",
"reject": "Rechazar",
"document": "Documento",
"view": "Ver",
"make-template-public": "Convertir la plantilla en pública",
"make-template-private": "Convertir la plantilla en privada",
@@ -331,7 +366,7 @@
"quick-send-alert-1": "Todos los roles de este documento están actualmente vinculados a contactos. Para enviar rápidamente copias de esta plantilla a varios firmantes, por favor, asegúrate de que al menos un rol no esté vinculado a ningún contacto.",
"quick-send-alert-2": "Por favor, asegúrate de que hay al menos un widget de firma añadido para cada destinatario.",
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
"quick-send-alert-4": "El envío rápido ha alcanzado el límite.",
"quick-send-alert-4": "{{flow}} admite hasta {{count}} firmantes por lote. Divida su lista e inténtelo nuevamente.",
"copy-link": "Copiar enlace",
"copy": "Copiar",
"revoke-document": "Revocar documento",
@@ -387,7 +422,11 @@
"radio button": "botón de radio",
"image": "imagen",
"email": "correo",
"number": "número"
"number": "número",
"draw": "Dibujar",
"attachments": "adjuntos",
"attachments-add": "Clic para subir archivos adjuntos",
"attachments-count": "{{count}} adjunto(s)"
},
"fields": "Campos",
"recipients": "Destinatarios",
@@ -399,6 +438,14 @@
"certificate": "Certificado",
"decline": "Rechazar",
"finish": "Finalizar",
"approve": "Aprobar",
"approve-mssg": " ¿Está seguro de que desea aprobar el documento?",
"signer-role-label": "Rol del destinatario",
"signer-role-signer": "Firmante",
"signer-role-viewer": "Visualizador",
"signer-role-approver": "Aprobador",
"signer-role-hint": "El firmante debe firmar. El aprobador aprueba sin firmar. El visualizador solo visualiza.",
"signer-role-upgrade-msg": "Los roles de Visualizador y Aprobador solo están disponibles en los planes de pago. Actualice su plan para asignar estos roles a los destinatarios.",
"done": "Hecho",
"mail": "Correo",
"sign-now": "Firmar ahora",
@@ -431,17 +478,18 @@
"reset-password-alert-1": "El enlace para restablecer la contraseña ha sido enviado a tu correo electrónico",
"reset-password-alert-2": "Por favor, configura un adaptador de correo ",
"reset-password-alert-3": "Restablece tu contraseña",
"faild-animation": "Error al cargar la animación",
"failed-animation": "Error al cargar la animación",
"apply": "Aplicar",
"select-columns": "Seleccionar columnas",
"copy-type": {
"All pages": "Todas las páginas",
"All pages but last": "Todas las páginas menos la última",
"All pages but first": "Todas las páginas menos la primera",
"Next to current widget": "Junto al widget actual"
"Next to current widget": "Junto al widget actual",
"Page range": "Rango de páginas"
},
"options": "Opciones",
"minimun-check": "Chequeo mínimo",
"minimum-check": "Chequeo mínimo",
"maximum-check": "Chequeo máximo",
"cell-count": "recuento de células",
"default-value": "Valor por defecto",
@@ -455,6 +503,19 @@
"formula-error-braces": "Cada {{ de apertura debe tener una }} correspondiente.",
"formula-error-operators": "Cuando uses operadores o paréntesis, agrega al menos dos valores.",
"formula-error-leading-operator": "Las fórmulas no pueden comenzar con operadores.",
"formula-error-trailing-operator": "Las fórmulas no pueden terminar con un operador.",
"formula-error-adjacent-placeholders": "No se pueden escribir dos marcadores de posición seguidos sin un operador entre ellos.",
"formula-error-continuous-operators": "No se pueden usar dos operadores seguidos.",
"formula-error-empty-parentheses": "Los paréntesis no pueden estar vacíos.",
"formula-error-invalid-parentheses-content": "Los paréntesis no pueden empezar ni terminar con un operador.",
"formula-error-before-open-parenthesis": "Falta un operador antes de '('. Añada un operador antes del paréntesis de apertura.",
"formula-error-after-close-parenthesis": "Falta un operador después de ')'. Añada un operador como '+' o '*' después del paréntesis de cierre.",
"formula-error-between-parentheses": "Falta un operador entre ')' y '('. Añada un operador como '*' entre los dos grupos.",
"formula-error-division-by-zero": "No se permite la división entre cero. Reemplace '0' por un número o variable válido distinto de cero.",
"formula-error-invalid-number-format": "Se encontró un formato de número no válido. Los números deben estar en un formato válido como '1' o '3.14', no '.5', '1.' ni '1.2.3'.",
"formula-error-unclosed-parenthesis": "Se encontró un '(' sin cerrar. Cada paréntesis de apertura '(' debe tener un paréntesis de cierre ')' correspondiente.",
"formula-error-unexpected-closing-parenthesis": "Se encontró un paréntesis de cierre ')' inesperado sin un paréntesis de apertura '(' correspondiente.",
"formula-error-empty-placeholder": "No se permite un marcador de posición vacío '{{}}'. Proporcione un nombre de variable dentro de las llaves.",
"decimal-places": "Decimales",
"number-of-decimal-places": "Número de decimales",
"decimal-places-helper": "Establecer en 0 para números enteros",
@@ -511,18 +572,19 @@
"placeholder-alert-4": "¡Has enviado exitosamente correos a todos los destinatarios!",
"placeholder-mail-alert": "Has enviado un correo electrónico con éxito a {{name}}. Los siguientes firmantes recibirán un correo electrónico una vez que {{name}} firme el documento.",
"placeholder-mail-alert-you": "Los firmantes siguientes recibirán un correo electrónico una vez que usted firme el documento.",
"placeholder-alert-5": "¿Quieres firmar documentos ahora mismo?",
"placeholder-alert-5": "Puede firmar ahora o enviarlo a su correo electrónico para completarlo más tarde.",
"placeholder-alert-6": "¡Por favor, configura el adaptador de correo para enviar correos!",
"placeholder-alert-7": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
"email-subject": "asunto del correo",
"email-body": "cuerpo del correo",
"email-placeholder": "agregar cuerpo del correo",
"reset-to-default": "Restablecer a por defecto",
"cutomize-email": "Personalizar correo",
"customize-email": "Personalizar correo",
"upgrade-to-customize-email": "Mejora para personalizar el correo",
"sign-url": "URL de firma",
"mails-sent": "Correos enviados",
"mail-not-delivered": "correo no entregado",
"email-not-verified-head": "Correo no verificado",
"document-alert": "Alerta de documento",
"owner-subscription-expired": "La suscripción del propietario ha expirado.",
"owner-doesnt-have-paid-plan": "El propietario no tiene un plan de pago.",
@@ -583,7 +645,7 @@
"provide-your-details": "Proporciona tus detalles",
"provide-email": "Por favor, proporciona un correo.",
"additional-info": "Información adicional",
"signature-saved-alert": "Firma guardada exitosamente.",
"signature-saved-alert": "guardada exitosamente.",
"saved-successfully": "Guardado con éxito.",
"fill-field": "Por favor, rellena este campo",
"error-template": "Error: ¡Plantilla no encontrada!",
@@ -598,6 +660,9 @@
"document-signed-alert-7": "Este documento ya no está disponible.",
"document-signed-alert-8": "Este documento ya ha sido enviado para ser firmado.",
"yet-to-sign": "Todavía por firmar",
"viewers": "Visualizadores",
"viewer-no-widgets": "Los visualizadores reciben una copia de solo lectura del documento y no pueden tener widgets asignados.",
"viewer-confirm-widget-removal": "Al cambiar este destinatario a Visualizador se eliminarán todos los widgets ya asignados. ¿Continuar?",
"validation-alert-1": "Los datos ingresados no cumplen con los criterios establecidos por la expresión regular.",
"account-already-exists": "¡La cuenta ya existe!",
"choose-password": "Elegir contraseña",
@@ -620,7 +685,7 @@
"date": "Fecha",
"report-not-found": "Reporte no encontrado",
"please-wait": "por favor, espera",
"select-foler": "Seleccionar carpeta",
"select-folder": "Seleccionar carpeta",
"generated-on": "Generado el",
"text-field": "Campo de texto",
"font-size": "Tamaño de la fuente",
@@ -679,13 +744,13 @@
"public-template": "Este video muestra cómo puedes configurar tu perfil público personalizado, como «https://opensign.me/tu-nombre-de-usuario». También aprenderás cómo personalizar tu eslogan y cómo hacer tus plantillas disponibles para su firmado público.",
"allowModify-widgets": "Puede arrastrar y soltar cualquiera de estos campos en el documento, además de los campos ya designados para usted por el creador del documento"
},
"enter-email-plaholder": "Agrega una dirección de correo y presiona «Enter»",
"enter-email-placeholder": "Agrega una dirección de correo y presiona «Enter»",
"success-email-alert": "¡Correo enviado exitosamente!",
"expired-doc-title": "Documento expirado",
"expired-on-mssg": "Este documento expiro el {{expiredDate}} y ya no está disponible para ser firmado.",
"signature-validate-alert": "Por favor, confirma que has seleccionado al menos {{minRequiredCount}} casillas.",
"signature-validate-alert-2": "Asegúrate de que este campo esté correctamente rellenado y que cumpla con todos los requisitos.",
"remaing-users": "Usuarios restantes",
"remaining-users": "Usuarios restantes",
"Role": "Rol",
"additional-users": "Por favor, compra el complemento Usuarios.",
"Quantity-of-users": "Cantidad de usuarios",
@@ -699,7 +764,7 @@
"select-date-format": "Selecciona un formato de fecha",
"quantity-of-credits": "Cantidad de créditos premium",
"remaining-credits": "Créditos premium disponibles:",
"remaining-credits-help": "Usa créditos premium para la API para la firma de documentos, envío en masa, o para incorporar la integración de OpenSign en tu página web. Te quedan {{allowedcredits}} créditos incluidos y {{addoncredits}} créditos comprados adicionalmente.",
"remaining-credits-help": "Utilice créditos premium para la firma de documentos mediante API, el envío masivo o para integrar OpenSign en su sitio web. Le quedan {{allowedcredits}} créditos incluidos y {{addoncredits}} créditos adicionales comprados. Si está en un plan Teams o Enterprise, estos créditos pueden compartirse entre todos los usuarios de su empresa, a menos que el administrador de la cuenta haya establecido un límite para su cuenta.",
"additional-credits": "Por favor, compra créditos premium",
"quota-err-quicksend": "Cuota alcanzada, no tienes créditos suficientes.",
"buy-credits": "Comprar créditos premium",
@@ -707,7 +772,7 @@
"rotate-left": "Rotar a la izquierda",
"rotate-alert-mssg": "Todos los widgets de esta página se perderán. ¿En definitiva quieres proceder?",
"templateid": "ID de plantilla",
"bulk-send-subcription-alert": "Por favor, mejora al plan a Professional o Team para usar envío en masa.",
"bulk-send-subscription-alert": "Por favor, mejora al plan a Professional o Team para usar envío en masa.",
"generate-test-token": "Generar token de prueba",
"regenerate-test-token": "Regenerar token de prueba",
"help-test-token": "Este token puede ser usado para probar las API en el punto de acceso «https://sandbox.opensignlabs.com/api/v1», permitiéndote realizar firmas de documentos ilimitadas. Por favor, ten en cuenta que el API de pruebas firmará tus documentos con certificados autofirmados, que podrían no ser reconocidos por Adobe como válidos. Una vez hayas completado tus pruebas, puedes mejorar a uno de nuestros planes de pago para generar un token de producción.",
@@ -727,8 +792,9 @@
"copied-code": "COPIADO",
"Installation": "Instalación",
"Usage": "Uso",
"insufficient-credits": "Créditos de firma insuficientes",
"insufficient-credits-mssg": "El propietario de este documento actualmente carece de los créditos OpenSign necesarios para que puedas firmarlo. Por favor, contacta al propietario si necesitas más detalles.",
"insufficient-credits": "Créditos de firma insuficientes. Por favor contacte con {{ownerEmail}}",
"insufficient-credits-title": "Créditos de firma insuficientes",
"insufficient-credits-mssg": "El propietario de este documento actualmente carece de los créditos {{appName}} necesarios para que puedas firmarlo. Por favor, contacta al propietario si necesitas más detalles.",
"angular-npm-mssg-1": "Para integrar OpenSign a tu proyecto Angular, simplemente ejecuta los siguientes comandos:",
"quota-mail-info-head": "Límite mensual de correos electrónicos",
"quota-mail-info": "Para mantener la calidad del servicio y prevenir el spam, OpenSign permite hasta 15 correos electrónicos por mes en el plan gratuito. Mejora tu plan ahora para envíos ilimitados de correos electrónicos.",
@@ -747,6 +813,13 @@
"p3": "Seleccionar esta opción deshabilitará la verificación con OTP, lo cual permitirá a los usuarios firmar el documento directamente sin pasos adicionales.",
"p4": "Por favor, selecciona la opción que mejor se adapte a tus requisitos para la firma de documentos."
},
"allow-offline-sign": "Permitir firma sin conexión",
"allow-offline-sign-help": {
"p1": "Decide si los firmantes pueden enviar una copia de este documento firmada con una herramienta externa para que el propietario la revise.",
"p2": "Los firmantes verán una opción Firmar sin conexión y podrán subir un PDF firmado externamente para su aprobación.",
"p3": "Los firmantes solo podrán firmar dentro de la aplicación. La opción Firmar sin conexión se ocultará.",
"p4": "Esta configuración usa por defecto tu preferencia de cuenta y está disponible en planes de pago."
},
"advanced-options": "Opciones avanzadas",
"hide-advanced-options": "Ocultar las opciones avanzadas",
"download-files": "Descargar archivos",
@@ -809,6 +882,7 @@
"delete-folder-alert-1": "No se puede eliminar: la carpeta debe estar vacía",
"defaultSign-alert": "Los widgets {{widgetsType}} no están disponibles para la firma automática",
"embed-heading": "Incrustar plantillas de {{appName}}",
"other-apps-by-opensign": "Otras aplicaciones del equipo de OpenSign",
"public-template-edit-title": "Editar plantilla pública",
"public-template-edit-mssg": "No puede editar una plantilla pública directamente. Para realizar cambios, la plantilla se configurará como privada y luego tendrás que volver a hacerla pública. ¿Quieres continuar?",
"agree-p1": "Confirmo que he leído y comprendido el ",
@@ -860,7 +934,6 @@
"js-snippet-msg-1": "Puede obtener el TemplateId en la página Administrar plantillas",
"js-snippet-msg-2": "Administrar plantillas",
"js-snippet-msg-3": "página.",
"agrrement-alert": "Para continuar, debe consentir la divulgación de registros y firmas electrónicas.",
"webhook-already-exists": "¡La URL ya existe! Pruebe con uno diferente.",
"webhook-must-be-secure": "La URL del webhook debe ser segura y utilizar https://",
"provide-test-token": "Por favor genere un token de prueba de API",
@@ -874,6 +947,7 @@
"p3": "Seleccionar esta opción restringe a los firmantes de realizar cualquier adición al documento durante el proceso de firma."
},
"signature-setting": "Configuración de firmas",
"rotation": "Rotación",
"signyour-self-button": "Utilice este formulario para crear plantillas reutilizables para sus documentos.",
"requestsign-button": "Utilice esta opción para solicitar firmas de otras personas y de usted mismo en conjunto.",
"signyour-self-description": "Utilice este formulario para firmar el documento usted mismo sin agregar otros.",
@@ -908,6 +982,8 @@
"visit-below-link": "Visita el siguiente enlace para saber más -",
"storage-help": "Habilitar BYOC te permite conectar tu propio almacenamiento S3 para que tus archivos permanezcan completamente bajo tu control, sin copias externas retenidas. Si la autonomía de los datos es importante para ti, considera actualizar a Teams para desbloquear esta función.",
"daily-quota-reached": "Ha alcanzado su cuota diaria. Para obtener ayuda, comuníquese con quotas@opensignlabs.com.",
"email-not-verified-send": "Por favor, verifica tu dirección de correo antes de enviar correos. Ve a tu <1>configuración de perfil</1> para completar la verificación del correo.",
"email-not-verified-send-plain": "Por favor, verifica tu dirección de correo antes de enviar correos. Ve a la configuración de tu perfil para completar la verificación del correo.",
"enabled-signature-type": "Tipos de firma habilitados",
"enabled-signature-type-help": "La configuración de 'Tipos de firma habilitados' determina qué opciones de firma están disponibles en su organización. Por ejemplo, si desactiva la opción 'Dibujar', los miembros de su organización no la verán en el widget de firma, mientras que las otras tres opciones seguirán siendo accesibles.",
"indexing-public-profile": "Permitir la indexación del perfil público por los motores de búsqueda",
@@ -935,6 +1011,7 @@
"please-provide-templateid": "Por favor, proporcione templateid",
"this-template-is-not-public": "Esta template no es pública",
"invalid-templateid": "templateid no válida",
"kiosk-no-active-role": "Esta plantilla no tiene roles de firmante o aprobador disponibles para la firma en modo kiosko.",
"contact-billing-at-opensign": "Para agregar más asientos, comuníquese con OpenSign™ a <1>billing@opensignlabs.com</1> para obtener ayuda.",
"title-length-alert": "El título debe tener como máximo 250 caracteres.",
"note-length-alert": "La nota debe tener como máximo 200 caracteres.",
@@ -1024,7 +1101,7 @@
},
"signer-already-present": "Firmante ya presente",
"kiosk-sign": "Firma en quiosco",
"dont-have-access-to-template": "El template ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
"do-not-have-access-to-template": "El template ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
"kiosk-info": "El Modo Kiosco le permite recopilar firmas en persona de forma rápida y eficiente. Ideal para ferias, eventos o situaciones con personas que firman en el lugar. ",
"learn-more": "Más información",
"finish-mssg": "¿Está seguro de que desea finalizar el documento?",
@@ -1078,8 +1155,6 @@
"search-templates": "Buscar plantillas…",
"search-documents": "Buscar documentos…",
"search-contacts": "Buscar contactos…",
"invalid-email-found": "Correo electrónico no válido encontrado: {{email}}",
"duplicate-email-found": "Correo electrónico duplicado encontrado: {{email}}",
"vertical": "Vertical",
"add-role-alert": "Por favor, agregue al menos un rol",
"edit-draft": "Editar borrador",
@@ -1095,7 +1170,7 @@
"access-denied": "Acceso denegado",
"upgrade": "Actualizar",
"do-not-access-app": "No tienes acceso a esta aplicación.",
"dont-have-access": "No tienes acceso.",
"do-not-have-access": "No tienes acceso.",
"valid-email-alert": "Por favor ingresa una dirección de correo electrónico válida.",
"otp-not-validate": "OTP no es válido.",
"domain-not-allowed": "Este dominio no está permitido",
@@ -1112,10 +1187,12 @@
"branding-help": "El branding permite el white labelling de su aplicación",
"custom-sub-domain": "Subdominio personalizado",
"app-name": "Nombre de la aplicación",
"provide-domain-name": "Proporcione su nombre de dominio",
"provide-domain-name": "Solo se permiten subdominios (p. ej., app.tudominio.com). No se admiten dominios principales/raíz como tudominio.com.",
"provide-app-name-help": "Indique un nombre único para la aplicación. Se mostrará como el nombre de su marca dentro de la aplicación y en todos los correos electrónicos donde se aplique la marca.",
"upload-app-logo": "Sube un logotipo en formato .png, .jpg o .svg. Tamaño recomendado: {{size}} píxeles (relación de aspecto 5:2) para la mejor calidad de visualización.",
"upload-app-favicon": "Sube una imagen cuadrada de favicon (.ico, .png o .svg). El tamaño recomendado es {{size}} píxeles para la mejor compatibilidad con el navegador.",
"provide-app-name": "Proporcione el nombre de su aplicación",
"logo": "Logo",
"upload-app-logo": "Suba el logotipo de su aplicación",
"prefill-unfilled-widget": "Los siguientes campos obligatorios no pueden quedar vacíos: {{emptyWidget}}. Por favor, complételos para continuar.",
"Dashboard": "Panel",
"Analytics": "Analítica",
@@ -1238,6 +1315,7 @@
},
"read-only-date-error": "El widget de fecha de solo lectura debe tener una fecha predeterminada.",
"set-date": "Establecer fecha",
"default-date": "Fecha predeterminada",
"set-today": "Fecha de firma",
"enter-name": "Ingrese nombre",
"enter-email": "Ingrese correo electrónico",
@@ -1257,5 +1335,241 @@
"Please provide required parameters.": "Por favor, proporcione los parámetros requeridos.",
"reset-password": "Restablecer contraseña de usuario",
"enter-strong-password": "Ingrese una contraseña segura o genere una automáticamente.",
"autogenerate": "Generar automáticamente"
}
"autogenerate": "Generar automáticamente",
"extend": "Extender",
"console-general-help": "Para permitir que los usuarios individuales personalicen sus propias plantillas de correo electrónico, vaya a",
"console-mail-help": "Para actualizar las plantillas de correo electrónico de toda la empresa, vaya a",
"admin-setting-disabled": "Esta configuración está deshabilitada. Comuníquese con el administrador para obtener ayuda.",
"company-email-settings": "Configuración de correo electrónico de la empresa",
"allow-email-template-individually": "Permitir la personalización individual de plantillas de correo electrónico para los usuarios ",
"user-email-template-general-help": "Si se desactiva, los usuarios no podrán personalizar sus plantillas de correo electrónico.",
"disable-individual-user-smtp": "desactivar la configuración smtp individual del usuario",
"enable-individual-user-smtp": "activar la configuración smtp individual del usuario",
"user-smtp-general-help": "Cuando está desactivado, los usuarios no podrán configurar su propio SMTP. Todos los correos utilizarán la configuración SMTP global.",
"global-smtp-settings": "Configuración SMTP Global",
"global-smtp-settings-help-p1": "Estas configuraciones se aplican globalmente en toda su cuenta de {{appName}}. Si un usuario no ha configurado sus propios ajustes de entrega de correo, el sistema utilizará automáticamente la configuración SMTP global definida aquí.",
"global-smtp-settings-help-p2": "Para garantizar una mejor entregabilidad de todos los correos de solicitud de firma, puede conectar su proveedor de correo preferido. Elija una de las siguientes opciones:",
"global-connect-to-gmail-help": "Use una cuenta de Gmail para enviar correos de solicitud de firma directamente desde la bandeja de entrada de su organización. Esto mejora la entregabilidad, la reputación y la fiabilidad.",
"global-connect-to-smtp-help": "Use su propio servidor SMTP para enviar correos a través de su dominio. Esto proporciona control total sobre su infraestructura de correo, mejora la entregabilidad y mantiene la coherencia de marca.",
"global-smtp-settings-help-p3": "Si no se configuran ajustes a nivel de usuario, se aplicarán automáticamente estos ajustes globales.",
"hide-text-with-asterisks": "Ocultar texto con asteriscos",
"hide-text-with-asterisks-help": "El texto en el documento firmado se ocultará, pero seguirá siendo accesible para el remitente (propietario) desde su cuenta de {{appName}}.",
"document-id": "ID del documento",
"sent-date": "Fecha de envío",
"status": "Estado",
"signer-form-data-for": "Datos del formulario para el firmante: {{signerName}} <{{signerEmail}}>",
"widget-name": "Nombre del widget",
"widget-responses": "Respuestas del widget",
"formdata-download-note": "Nota: Las firmas, sellos, iniciales e imágenes no se exportarán. Los datos de los firmantes sin conexión no estarán disponibles.",
"export-all-formdata-error": "No puede exportar los datos del formulario porque la plantilla contiene nombres de widgets duplicados. Cree una nueva plantilla para poder exportar los datos.",
"export-formdata-error": "No puede exportar los datos del formulario porque el documento contiene nombres de widgets duplicados. Cree un documento nuevo sin nombres duplicados para poder exportar los datos.",
"duplicate-widget-name-error": "Este nombre de widget ya está asignado a otro firmante. Por favor elija un nombre diferente.",
"duplicate-template-widget-error": "No se puede duplicar la plantilla porque algunos nombres de widgets están duplicados en diferentes roles. Cambie el nombre de los widgets duplicados e inténtelo de nuevo.",
"credit-alert": "Alerta de crédito",
"document-form-data": "Datos del formulario del documento",
"view-formdata-paid-title-error": "La función 'Ver datos del formulario' le permite imprimir y descargar los datos de los widgets completados por los firmantes durante la firma.",
"view-formdata-paid-error": "Nota: La función 'Ver datos del formulario' solo está disponible para documentos creados en o después de la versión v2.32.0 que no contengan nombres de widgets duplicados.",
"download-all-formdata-paid-title-error": "La función 'Descargar todos los datos del formulario' le permite descargar los datos de los widgets completados por los firmantes para todos los documentos creados a partir de la plantilla.",
"download-all-formdata-paid-error": "Nota: La función 'Descargar todos los datos del formulario' solo está disponible para plantillas creadas en o después de la versión v2.32.0 que no contengan nombres de widgets duplicados.",
"api-token-not-found": "Token de API no encontrado.",
"sub-head-api-credits": "Defina la cantidad máxima de créditos API que este usuario puede consumir.",
"consumed-credits": "Créditos consumidos",
"allowed-api-credits": "Créditos API permitidos",
"allowed-api-credits-help": "Los créditos permitidos deben ser mayores o iguales a los créditos consumidos.",
"Enter-allowed-api-credits": "Ingrese los créditos API permitidos",
"login-to-sandbox": "Iniciar sesión en sandbox",
"your-credits-usage": "Uso de sus créditos",
"remaining": "restantes",
"used": "usados",
"your-credits-usage-help": "Esta es la cantidad de créditos consumidos con su clave API.",
"api-credits-updated": "Los créditos de API se han asignado correctamente.",
"min-date": "Fecha mínima",
"max-date": "Fecha máxima",
"invalid-min-date": "Fecha mínima no válida.",
"min-less-max": "La fecha mínima debe ser menor o igual a la fecha máxima.",
"invalid-max-date": "Fecha máxima no válida.",
"max-less-min": "La fecha máxima debe ser mayor o igual a la fecha mínima.",
"date-range": "Rango de fechas",
"date-range-help": "Establezca una fecha mínima y/o máxima para restringir la selección. La fecha elegida debe estar dentro del rango.",
"option-disabled-by-owner": "El creador del documento ha deshabilitado esta opción",
"pen-colors": "Colores del bolígrafo",
"pen-colors-help": "Elija qué colores de bolígrafo están disponibles para los widgets de Firma e Iniciales. Marque una casilla para mostrar un color y desmárquela para ocultarlo.",
"security-key": "Clave de seguridad",
"remove-limit": "Eliminar límite",
"removed-api-credits": "El límite de créditos API ha sido eliminado.",
"enable-authentication": "Habilitar autenticación",
"working": "En funcionamiento",
"test-security-key": "Probar clave de seguridad",
"generate": "Generar",
"regenerate": "Regenerar",
"signing-date": "Fecha de firma",
"date-widget": "Widget de fecha",
"mail-failed": "Algo salió mal al enviar el correo electrónico. Por favor, inténtelo de nuevo.",
"mail-status-head": "Siguiente paso: ¿Firmar su documento?",
"send-to-email": "Enviar por correo electrónico",
"date-pref-help-sub-title": "Las preferencias del widget de fecha le permiten definir la configuración predeterminada del widget de fecha. Estos valores se aplican automáticamente cada vez que añade un nuevo widget de fecha a un documento o plantilla. Aún puede personalizar cualquier widget de fecha individual en cualquier momento haciendo clic en el icono de engranaje.",
"date-pref-help-format": "Elija el formato de fecha predeterminado utilizado para los widgets de fecha.",
"date-pref-help-default-date": "Establezca un valor de fecha predeterminado para los widgets de fecha.",
"date-pref-help-signing-date": "Cuando está habilitado, el widget de fecha utiliza automáticamente la fecha en que el firmante firma el documento.",
"date-pref-help-read-only": "Cuando está habilitado, el widget de fecha es de solo lectura para el firmante.",
"upload-csv": "Subir CSV",
"file-selected": "archivo seleccionado",
"bulk-send-csv-helper": "Use encabezados para los roles de firmantes (Correo/Nombre/Teléfono del rol) y valores de prellenado (Prellenado: Nombre del campo). Los campos de prellenado obligatorios deben completarse.",
"bulk-send-csv-helper-note": "Nota: Para los campos de prellenado de tipo fecha en el CSV, el valor debe proporcionarse en el siguiente formato: {{value}} (ejemplo: {{example}}).",
"bulk-send-csv-summary": "Se importaron {{total}} filas del CSV.",
"bulk-send-csv-missing-headers": "Faltan encabezados obligatorios del CSV. Roles: {{roles}}.",
"bulk-send-no-records": "Agregue al menos un destinatario antes de enviar.",
"csv-only-support": "Solo se admiten archivos CSV.",
"csv-no-records": "No se encontraron registros en el CSV.",
"select-at-least-option": "Por favor, seleccione al menos una opción.",
"draw-required": "Por favor, dibuje en el campo.",
"enter-smtp-username": "Ingrese su nombre de usuario SMTP",
"enter-webhook-url": "Ingrese la URL del webhook",
"enter-user-name": "Ingrese el nombre de usuario",
"enter-tagline": "Ingrese el eslogan",
"enter-password-or-click-autogenerate": "Ingrese la contraseña o haga clic en Autogenerar",
"enter-daily-email-quota": "Ingrese la cuota diaria de correo electrónico",
"enter-custom-regular-expression": "Ingrese una expresión regular personalizada",
"enter-value": "Ingrese {{value}}",
"are-you-sure-want-to-send-document": "¿Está seguro de que desea enviar el documento?",
"favicon": "Favicon",
"delete-two-factor-authentication": "¿Está seguro de que desea eliminar la autenticación de dos factores?",
"paste-html-here": "Pegar HTML aquí",
"open-email-builder": "Puede crear una plantilla de correo electrónico utilizando la plataforma <1>email builder</1> y copiar el código HTML.",
"contact-saved": "Contacto guardado correctamente.",
"remove-limit-of-user": "¿Está seguro de que desea eliminar el límite de este usuario? Podrá consumir todos los créditos premium disponibles de su organización que se comparten entre todos los usuarios.",
"current-expiry-date": "Fecha de vencimiento actual",
"reminder-error": "“Recordar una vez cada (días)” no debe exceder el “Tiempo para completar (días)”.",
"showing-to-of-records": "Mostrando {{record}} a {{recordPerPage}} de {{totalRows}} registros",
"records-per-page": "Registros por página",
"email-not-found-in-row": "No se encontró correo electrónico en la fila {{row}}.",
"invalid-email-found-in-row": "Correo electrónico no válido encontrado en la fila {{row}}: {{email}}",
"duplicate-email-found-in-row": "Correo electrónico duplicado encontrado en la fila {{row}}: {{email}}",
"prefill": "Prellenado",
"recipients-&-data": "Destinatarios y datos",
"response": "Respuesta",
"prefill-tab-help": "Estos son los campos comunes de la plantilla que se aplicarán a todos los documentos en este envío masivo.",
"recipient-tab-help-p1": "En este siguiente paso, usted:",
"recipient-tab-help-p2": "Subirá un CSV con los detalles de los destinatarios o",
"recipient-tab-help-p3": "Ingresará manualmente la información de los destinatarios",
"recipient-tab-help-p4": "Cada destinatario recibirá su propio documento generado a partir de esta plantilla.",
"response-tab-help": "Revise sus respuestas.",
"summary": "Resumen",
"prefill-fields": "Campos de prellenado",
"documents": "Documentos",
"message": "Mensaje",
"formula-help-p1": "Recopile entradas numéricas y realice cálculos dinámicos usando fórmulas.",
"example": "Ejemplo",
"formula-help-p2": "Para mostrar un total en number-3, use {{number-1}} + {{number-2}}.",
"formula-help-p3": "Para calcular un valor en number-4, use ({{number-1}} * {{number-2}}) / {{number-3}}.",
"formula-help-p4": "Cada widget numérico debe tener un nombre único (por ejemplo, number-1), ya que las fórmulas dependen de estos identificadores.",
"note-consume-premium-credits": "Nota: Esta función consume créditos premium.",
"note-consume-premium-credits-on-document-send": "Nota: Esta función consume créditos premium. Se consumen cada vez que se envía un documento, independientemente de si se firma o no.",
"webhook-authentication-help": "{{appName}} protege cada webhook generando una firma HMAC-SHA256 utilizando su clave de seguridad del webhook y el payload original de la solicitud. Esta firma se envía en el encabezado x-webhook-signature. Su servidor debe generar la misma firma utilizando el payload recibido y su clave secreta. Si ambas firmas coinciden, la solicitud del webhook es auténtica y no ha sido modificada.",
"invalid-widget-found-in-row": "Se detectó un valor no válido en la fila {{row}} para el widget {{widget}}.",
"all-signer-status": "Estado de todos los firmantes",
"viewed": "Visto",
"signed": "Firmado",
"switch-to-advanced": "Cambiar a avanzado",
"switch-to-basic": "Cambiar a básico",
"use-name-as-sender": "Usar el nombre del perfil como remitente",
"use-name-as-sender-help": "Cuando está activado, {{appName}} usa el nombre de tu perfil como remitente para las solicitudes de firma y el correo de finalización.",
"acro-fields-detected": "Campos de formulario detectados",
"acro-fields-detected-msg": "Se detectaron {{count}} campo(s) de formulario en el PDF cargado. ¿Desea insertar automáticamente los widgets correspondientes?",
"documents-count": "Cantidad de documentos",
"templates-count": "Cantidad de plantillas",
"emails-sent": "Correos enviados",
"storage-used": "Almacenamiento usado",
"docs-count-team-help": "Número total de documentos creados por todos los usuarios en tus organizaciones.",
"docs-count-help": "Número total de documentos creados por ti.",
"templates-count-team-help": "Número total de plantillas creadas por todos los usuarios en tus organizaciones.",
"templates-count-help": "Número total de plantillas creadas por ti.",
"emails-sent-team-help": "Número total de notificaciones por correo enviadas en tus organizaciones.",
"emails-sent-help": "Número total de notificaciones por correo enviadas por ti.",
"storage-used-team-help": "Espacio total de almacenamiento consumido por todos los usuarios en tus organizaciones.",
"storage-used-help": "Espacio total de almacenamiento consumido por ti.",
"cloud": "Nube",
"on-premises": "En las instalaciones",
"plan-name-free": "OPENSIGN™ FREE",
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
"plan-name-teams": "OPENSIGN™ TEAMS",
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
"plan-subtitle-free": "Firmas electrónicas ilimitadas gratuitas, para siempre.",
"plan-subtitle-professional": "Acceso exclusivo a funciones avanzadas.",
"plan-subtitle-scalable": "Funciones escalables con soporte prioritario.",
"subscribe": "Suscribirse",
"contact-us": "Contáctenos",
"benefit-unlimited-digital-signatures": "Firmas digitales ilimitadas",
"benefit-sign-docs-yourself": "Firmar documentos usted mismo",
"benefit-request-signatures": "Solicitar firmas de otros",
"benefit-unlimited-templates": "Plantillas ilimitadas",
"benefit-14-field-types": "14 tipos de campo",
"benefit-automatic-e-signatures": "Firmas electrónicas automáticas",
"benefit-completion-certificates": "Certificados de finalización",
"benefit-send-in-order": "Enviar en orden",
"benefit-organize-docs": "Organizar documentos en OpenSign™ Drive",
"benefit-document-templates": "Plantillas de documentos",
"benefit-import-from-dropbox": "Importar desde Dropbox",
"benefit-contact-book": "Libreta de contactos",
"benefit-document-expiry": "Compatibilidad con vencimiento de documentos",
"benefit-decline-document": "Compatibilidad con rechazo de documentos",
"benefit-email-notifications": "Notificaciones por correo electrónico",
"benefit-public-profiles": "Perfiles públicos",
"benefit-and-much-more": "Y mucho más",
"benefit-everything-free": "Todo en OpenSign™ Free",
"benefit-field-validations": "Validaciones de campo",
"benefit-regex-validations": "Validaciones de expresiones regulares",
"benefit-webhooks": "Webhooks",
"benefit-zapier": "Integración con Zapier",
"benefit-api-access": "Acceso a API",
"benefit-api-signatures-100": "hasta 100 firmas de API",
"benefit-api-signatures-240": "hasta 240 firmas de API",
"benefit-custom-email-templates": "Plantillas de correo electrónico personalizadas",
"benefit-custom-smtp": "Conecte su propia cuenta de Gmail o SMTP para enviar correos",
"benefit-auto-reminders": "Recordatorios automáticos",
"benefit-bulk-send-100": "Envío masivo (hasta 100 documentos)",
"benefit-bulk-send-240": "Envío masivo (hasta 240 documentos)",
"benefit-premium-profiles": "Nombres de usuario de perfil público premium",
"benefit-email-verification": "Aplicar verificación basada en correo electrónico para confirmar la identidad del firmante",
"benefit-embedded-signing": "Firma incrustada",
"benefit-everything-professional": "Todo en OpenSign™ Professional",
"benefit-teams-organizations": "Equipos y Organizaciones",
"benefit-share-templates-teams": "Compartir plantillas con equipos",
"benefit-share-templates-individuals": "Compartir plantillas con individuos",
"benefit-byoc": "BYOC - Almacena tus documentos en tu propio almacenamiento en la nube",
"benefit-doc-id-removal": "Eliminación de DocumentId de documentos firmados",
"benefit-request-payments": "Solicitar pagos (próximamente)",
"benefit-mobile-app": "Aplicación móvil (próximamente)",
"benefit-api-signatures-500": "hasta 500 firmas de API",
"benefit-bulk-send-500": "Envío masivo (hasta 500 documentos)",
"benefit-all-features": "Todas las funciones",
"benefit-custom-domain": "Dominio personalizado",
"benefit-custom-branding": "Marca personalizada",
"benefit-uptime-sla": "SLA de tiempo de actividad",
"benefit-sso-support": "Soporte SSO",
"benefit-priority-support": "Soporte prioritario",
"benefit-3-users-1500-credits": "Incluye 3 usuarios y 1500 créditos premium",
"benefit-public-links": "Enlaces públicos",
"benefit-kiosk-mode": "Modo quiosco",
"benefit-bulk-send-1500": "Envío masivo (hasta 1500 documentos)",
"benefit-connect-gmail-smtp": "Conecte su propio Gmail o SMTP",
"benefit-analytics": "Análisis",
"benefit-bulksend-api-pro-month-help": "Este plan incluye 100 créditos premium al mes que te permiten enviar documentos mediante la API, compartir enlaces públicos de firma, ejecutar solicitudes de firma masiva, usar el modo Kiosco para firmas presenciales o recopilar firmas de documentos incrustados directamente en tu sitio web. Cada vez que inicias o envías un documento, se usa un crédito — y ese mismo documento puede incluir hasta 10 firmantes.",
"benefit-bulksend-api-pro-year-help": "Este plan incluye 240 créditos premium al año que te permiten enviar documentos mediante la API, compartir enlaces públicos de firma, ejecutar solicitudes de firma masiva, usar el modo Kiosco para firmas presenciales o recopilar firmas de documentos incrustados directamente en tu sitio web. Cada vez que inicias o envías un documento, se usa un crédito — y ese mismo documento puede incluir hasta 10 firmantes.",
"benefit-bulksend-api-team-month-help": "Este plan incluye 100 créditos premium al mes que te permiten enviar documentos mediante la API, compartir enlaces públicos de firma, ejecutar solicitudes de firma masiva, usar el modo Kiosco para firmas presenciales o recopilar firmas de documentos incrustados directamente en tu sitio web. Cada vez que inicias o envías un documento, se usa un crédito — y ese mismo documento puede incluir hasta 10 firmantes.",
"benefit-bulksend-api-team-year-help": "Este plan incluye 500 créditos premium al año que te permiten enviar documentos mediante la API, compartir enlaces públicos de firma, ejecutar solicitudes de firma masiva, usar el modo Kiosco para firmas presenciales o recopilar firmas de documentos incrustados directamente en tu sitio web. Cada vez que inicias o envías un documento, se usa un crédito — y ese mismo documento puede incluir hasta 10 firmantes.",
"failed-pdf-for-prefill-processing": "No se pudo cargar el PDF para el procesamiento de prellenado.",
"attach-all-role-to-signer": "Por favor, asigna todos los roles al firmante.",
"upgrade-to-edit": "Actualiza para editar",
"hide-live-chat-for-signers": "Ocultar chat en vivo para firmantes",
"hide-live-chat-for-signers-help": "Cuando está activado, los widgets de chat en vivo se ocultarán para los firmantes en las páginas de firma.",
"attachments-creator-hint": "Los firmantes pueden subir archivos que se añadirán al final del documento.",
"attachments-drop-hint": "Arrastra archivos aquí o haz clic para seleccionar",
"attachments-limits": "PDF, DOCX, PNG, JPG. Hasta {{maxFiles}} archivos, {{maxSizeMB}} MB cada uno.",
"attachments-invalid-type": "Tipo de archivo no admitido: {{name}}",
"attachments-max-files": "Se permiten hasta {{maxFiles}} adjuntos.",
"attachments-remove-confirm": "Eliminar solo borra los metadatos. Las páginas ya añadidas permanecerán en el documento. ¿Continuar?",
"attachement-note": "Nota: Este widget permite a los firmantes cargar un archivo durante el proceso de firma. El archivo cargado se añadirá como páginas adicionales al final del PDF existente.",
"attachments-docx-not-supported": "Actualmente estamos experimentando un problema con las cargas de archivos DOCX. Intente cargar el documento en otro formato compatible o vuelva a intentarlo más tarde."
}
+350 -34
View File
@@ -71,10 +71,11 @@
"login-page": "Page de connexion",
"or": "OU",
"signup-page": "Page d'inscription",
"password-match-length": "Le nouveau mot de passe et la confirmation doivent correspondre",
"password-length": "Le mot de passe doit être de plus de 8 caractères",
"password-case": "Le mot de passe doit contenir une lettre majuscule, minuscule et un chiffre ",
"password-special-char": "Le mot de passe doit contenir un caractère spécial",
"agreee": "Je suis d'accord avec le",
"agree": "Je suis d'accord avec le",
"term": "Conditions d'utilisation",
"subscribe-to-opensign": "Abonnez-vous à la newsletter OpenSign",
"register": "S'enregistrer ",
@@ -120,7 +121,8 @@
"Teams": "Équipe",
"Team-update-successfully": "Équipe mise à jour avec succès.",
"Users": "Utilisateurs"
}
},
"Global SMTP": "SMTP Global"
},
"dashboard-card": {
"Need your Signature": "Besoin de votre signature",
@@ -174,7 +176,8 @@
"Redirect url": "URL de redirection",
"Created Date": "Date de création",
"Updated Date": "Date de mise à jour",
"Expiry Date": "Date d'expiration"
"Expiry Date": "Date d'expiration",
"Sent Date": "Date d'envoi"
},
"btnLabel": {
"sign": "Signer",
@@ -201,7 +204,10 @@
"Save as template": "Enregistrer comme modèle",
"Fix & resend": "Corriger et renvoyer",
"Kiosk Mode": "Mode Kiosque",
"Reset password": "Réinitialiser le mot de passe"
"Reset password": "Réinitialiser le mot de passe",
"View formdata": "Voir les données du formulaire",
"Download all formdata": "Télécharger toutes les données du formulaire",
"API credits": "Crédits API"
},
"report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -218,7 +224,7 @@
"New Template": "Nouveau modèle",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "fichier sélectionné",
"files-selected": "fichier(s) sélectionné(s)",
"template-title": "Titre du modèle",
"document-title": "Titre du document",
"title": "Titre",
@@ -231,11 +237,12 @@
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais n'impose aucun ordre de signature entre les participants.",
"p4": "Sélectionnez l'option qui correspond le mieux aux besoins de votre traitement de documents."
},
"strict-order": "Imposer l'ordre strict",
"strict-order-help": "Lorsqu'il est activé, aucun signataire ne peut signer ou approuver tant que tous les signataires/approbateurs précédents n'ont pas terminé leur action.",
"no": "Non",
"auto-reminder": "Rappel automatique",
"remind-once": "Rappeler une fois tous les (jours)",
"next": "Suivant",
"select-folder": "Sélectionner le dossier",
"OpenSign-drive": "Lecteur {{appName}}",
"drive-document-status": {
"Completed": "Complété",
@@ -255,6 +262,7 @@
"signers": "Signataires",
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
"bcc-help": "Commencez à taper le nom d'un contact pour voir les suggestions parmi vos contacts enregistrés ou en ajouter de nouveaux. Utilisez le bouton '+' pour ajouter un utilisateur et le bouton 'x' pour le supprimer. L'adresse e-mail de l'utilisateur sélectionné sera ajoutée en Bcc (copie carbone invisible). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
"cc-help": "Commencez à saisir le nom d'un contact pour voir des suggestions à partir de vos contacts enregistrés ou en ajouter de nouveaux. Utilisez le bouton « + » pour ajouter un utilisateur et le « x » pour le supprimer. L'e-mail de l'utilisateur sélectionné sera ajouté en CC (copie carbone). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
"add-signer": "Ajouter un signataire",
"contact-not-found": "Contact introuvable",
"add-yourself": "Ajoutez-vous",
@@ -264,7 +272,7 @@
"signature": "Signature",
"upload-image": "Télécharger une image",
"clear": "Vider",
"upload-signature/Image": "télécharger-signature/Image",
"upload-signature/Image": "télécharger-signature/Image/timbre",
"initials": "Initiales",
"API": "API",
"api-token": "Jeton API",
@@ -290,7 +298,7 @@
"edit-team": "Modifier l'équipe",
"name-of-team": "Nom de l'équipe",
"prev": "précédent",
"no-data-avaliable": "pas de données disponibles",
"no-data-available": "pas de données disponibles",
"add-team": "Ajouter une équipe",
"page-not-found": "Page non trouvée",
"users-from-teams": "utilisateurs de votre Équipe",
@@ -301,12 +309,37 @@
"delete-user": "Supprimer l'utilisateur",
"delete": "Supprimer",
"add-user": "Ajouter un utilisateur",
"password-generateed": "Le mot de passe ne sera généré qu'une seule fois ; assurez-vous de le copier.",
"password-generated": "Le mot de passe ne sera généré qu'une seule fois ; assurez-vous de le copier.",
"Team status": "Statut de l'équipe",
"user-deactivated": "Utilisateur désactivé.",
"user-activated": "Activé par l'utilisateur.",
"public": "Public",
"download": "Télécharger",
"sign-offline": "Signer hors ligne",
"sign-offline-instructions": "Téléchargez le document, signez-le avec un outil externe, puis téléversez la copie signée ici. Le propriétaire du document examinera votre soumission et l'approuvera ou la rejettera.",
"sign-offline-step-1": "Télécharger le PDF non signé.",
"sign-offline-step-2": "Signer le PDF avec votre outil préféré.",
"sign-offline-step-3": "Téléverser le PDF signé pour approbation du propriétaire.",
"sign-offline-upload-hint": "Sélectionner le PDF signé pour le soumettre à l'approbation.",
"sign-offline-submitted-title": "Soumis pour approbation",
"sign-offline-submitted-body": "Le propriétaire du document a été notifié par e-mail pour examiner votre copie signée hors ligne.",
"offline-already-submitted-title": "Document soumis hors ligne",
"offline-already-submitted-body": "Vous avez déjà soumis ce document pour la vérification de la signature hors ligne. Vous serez notifié une fois qu'il sera approuvé ou rejeté.",
"offline-action-approve-title": "Approuver la signature hors ligne",
"offline-action-approve-confirm": "Approuver",
"offline-action-reject-title": "Rejeter la signature hors ligne",
"offline-action-reject-hint": "Fournissez éventuellement un motif. Le signataire sera notifié par e-mail.",
"offline-action-reject-reason-placeholder": "Motif (facultatif)",
"offline-action-reject-confirm": "Confirmer le rejet",
"offline-action-review-title": "Examiner la signature hors ligne",
"offline-action-review-hint": "Prévisualisez le PDF signé téléversé par le signataire, puis approuvez-le ou rejetez-le. Aucune action n'est prise avant confirmation.",
"offline-action-submitted-at": "Soumis",
"offline-action-no-preview": "Aucun aperçu disponible pour cette soumission.",
"offline-action-open-in-new-tab": "Ouvrir le PDF dans un nouvel onglet",
"offline-action-invalid-link": "Ce lien de révision est invalide ou a expiré.",
"offline-action-success": "Action effectuée.",
"reject": "Rejeter",
"document": "Document",
"view": "voir",
"make-template-public": "Rendre le modèle public",
"make-template-private": "Rendre le modèle privé",
@@ -331,7 +364,7 @@
"quick-send-alert-1": "Tous les rôles dans ce document sont actuellement liés à des contacts. Pour envoyer rapidement des copies de ce modèle à plusieurs signataires, veuillez vous assurer qu'au moins un rôle n'est lié à aucun contact.",
"quick-send-alert-2": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
"quick-send-alert-4": "L'envoi rapide a atteint la limite.",
"quick-send-alert-4": "{{flow}} prend en charge jusqu'à {{count}} signataires par lot. Divisez votre liste et réessayez.",
"copy-link": "Copier le lien",
"copy": "Copier",
"revoke-document": "Révoquer le document",
@@ -387,7 +420,11 @@
"radio button": "bouton radio",
"image": "image",
"email": "e-mail",
"number": "nombre"
"number": "nombre",
"draw": "Dessiner",
"attachments": "pièces jointes",
"attachments-add": "Cliquer pour ajouter des pièces jointes",
"attachments-count": "{{count}} pièce(s) jointe(s)"
},
"fields": "Des champs",
"recipients": "Destinataires",
@@ -399,6 +436,14 @@
"certificate": "Certificat",
"decline": "refusé",
"finish": "terminé",
"approve": "Approuver",
"approve-mssg": " Êtes-vous sûr de vouloir approuver le document ?",
"signer-role-label": "Rôle du destinataire",
"signer-role-signer": "Signataire",
"signer-role-viewer": "Lecteur",
"signer-role-approver": "Approbateur",
"signer-role-hint": "Le signataire doit signer. L'approbateur approuve sans signer. Le lecteur peut uniquement consulter.",
"signer-role-upgrade-msg": "Les rôles Lecteur et Approbateur sont uniquement disponibles dans les forfaits payants. Mettez à niveau votre forfait pour attribuer ces rôles aux destinataires.",
"done": "Terminé",
"mail": "Mail",
"sign-now": "Signez maintenant",
@@ -431,16 +476,18 @@
"reset-password-alert-1": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie",
"reset-password-alert-2": "Veuillez configurer l'adaptateur de messagerie",
"reset-password-alert-3": "Réinitialisez votre mot de passe",
"faild-animation": "Échec du chargement de l'animation",
"failed-animation": "Échec du chargement de l'animation",
"apply": "Appliquer",
"select-columns": "Sélectionner les colonnes",
"copy-type": {
"All pages": "Toutes les pages",
"All pages but last": "Toutes les pages sauf la dernière",
"All pages but first": "Toutes les pages sauf la première",
"Next to current widget": "À côté du widget actuel"
"Next to current widget": "À côté du widget actuel",
"Page range": "Plage de pages"
},
"options": "Possibilités",
"minimun-check": "Vérification minimale",
"minimum-check": "Vérification minimale",
"maximum-check": "Contrôle maximum",
"cell-count": "numération cellulaire",
"default-value": "Valeur par défaut",
@@ -454,6 +501,19 @@
"formula-error-braces": "Chaque {{ ouvrante doit avoir une }} correspondante.",
"formula-error-operators": "Lorsque vous utilisez des opérateurs ou des parenthèses, ajoutez au moins deux valeurs.",
"formula-error-leading-operator": "Les formules ne peuvent pas commencer par des opérateurs.",
"formula-error-trailing-operator": "Les formules ne peuvent pas se terminer par un opérateur.",
"formula-error-adjacent-placeholders": "Deux espaces réservés ne peuvent pas être écrits lun à côté de lautre sans opérateur entre eux.",
"formula-error-continuous-operators": "Deux opérateurs ne peuvent pas être utilisés à la suite.",
"formula-error-empty-parentheses": "Les parenthèses ne peuvent pas être vides.",
"formula-error-invalid-parentheses-content": "Les parenthèses ne peuvent pas commencer ou se terminer par un opérateur.",
"formula-error-before-open-parenthesis": "Opérateur manquant avant '('. Ajoutez un opérateur avant la parenthèse ouvrante.",
"formula-error-after-close-parenthesis": "Opérateur manquant après ')'. Ajoutez un opérateur comme '+' ou '*' après la parenthèse fermante.",
"formula-error-between-parentheses": "Opérateur manquant entre ')' et '('. Ajoutez un opérateur comme '*' entre les deux groupes.",
"formula-error-division-by-zero": "La division par zéro n'est pas autorisée. Remplacez '0' par un nombre ou une variable valide non nul.",
"formula-error-invalid-number-format": "Format de nombre invalide détecté. Les nombres doivent être dans un format valide comme '1' ou '3.14', et non '.5', '1.' ou '1.2.3'.",
"formula-error-unclosed-parenthesis": "Parenthèse '(' non fermée détectée. Chaque parenthèse ouvrante '(' doit avoir une parenthèse fermante ')' correspondante.",
"formula-error-unexpected-closing-parenthesis": "Parenthèse fermante ')' inattendue détectée sans parenthèse ouvrante '(' correspondante.",
"formula-error-empty-placeholder": "Un espace réservé vide '{{}}' n'est pas autorisé. Indiquez un nom de variable à l'intérieur des accolades.",
"decimal-places": "Décimales",
"number-of-decimal-places": "Nombre de décimales",
"decimal-places-helper": "Définir sur 0 pour les nombres entiers",
@@ -510,18 +570,19 @@
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
"placeholder-mail-alert": "Vous avez envoyé un e-mail avec succès à {{name}}. Les signataires suivants recevront un e-mail une fois que {{name}} aura signé le document.",
"placeholder-mail-alert-you": "Les signataires suivants recevront un e-mail une fois que vous aurez signé le document.",
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
"placeholder-alert-5": "Vous pouvez signer maintenant ou lenvoyer à votre e-mail pour le terminer plus tard.",
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
"email-subject": "Sujet du courriel",
"email-body": "corps de l'e-mail",
"email-placeholder": "Ajouter le corps de l'e-mail",
"reset-to-default": "Réinitialiser aux valeurs par défaut",
"cutomize-email": "Personnaliser l'e-mail",
"customize-email": "Personnaliser l'e-mail",
"upgrade-to-customize-email": "Mise à niveau pour personnaliser l'e-mail",
"sign-url": "Signer l'URL",
"mails-sent": "Mails envoyés avec succès !",
"mail-not-delivered": "Courrier non distribué",
"email-not-verified-head": "E-mail non vérifié",
"document-alert": "Alerte document",
"owner-subscription-expired": "L'abonnement du propriétaire a expiré.",
"owner-doesnt-have-paid-plan": "Le propriétaire n'a pas de plan payant.",
@@ -582,7 +643,7 @@
"provide-your-details": "Fournissez vos coordonnées",
"provide-email": "Veuillez fournir un e-mail.",
"additional-info": "Information additionnelle",
"signature-saved-alert": "Signature enregistrée avec succès.",
"signature-saved-alert": "enregistrée avec succès.",
"saved-successfully": "Enregistré avec succès.",
"fill-field": "Veuillez remplir ce champ",
"error-template": "Erreur : modèle introuvable ! ",
@@ -597,6 +658,9 @@
"document-signed-alert-7": "Ce document n'est plus disponible.",
"document-signed-alert-8": "Le document a déjà été envoyé pour signature.",
"yet-to-sign": "Reste à signer",
"viewers": "Lecteurs",
"viewer-no-widgets": "Les lecteurs reçoivent une copie en lecture seule du document et ne peuvent pas avoir de widgets assignés.",
"viewer-confirm-widget-removal": "Le passage de ce destinataire en Lecteur supprimera tous les widgets déjà assignés. Continuer ?",
"validation-alert-1": "L'entrée ne répond pas aux critères définis par l'expression régulière.",
"account-already-exists": "Le compte existe déjà!",
"choose-password": "Choisissez un mot de passe",
@@ -619,7 +683,7 @@
"date": "Date",
"report-not-found": "Rapport introuvable",
"please-wait": "S'il vous plaît, attendez",
"select-foler": "Sélectionner le dossier",
"select-folder": "Sélectionner le dossier",
"generated-on": "Généré le",
"text-field": "Champ de texte",
"font-size": "Taille de police",
@@ -678,14 +742,14 @@
"public-template": "Cette vidéo montre comment configurer votre profil public personnalisé, tel que 'https://opensign.me/your-username'. Vous apprendrez également comment personnaliser votre slogan et rendre vos modèles disponibles pour la signature publique.",
"allowModify-widgets": "Vous pouvez faire glisser et déposer n'importe lequel de ces champs sur le document, en plus des champs déjà désignés pour vous par le créateur du document."
},
"enter-email-plaholder": "Ajoutez une adresse email et appuyez sur Entrée",
"enter-email-placeholder": "Ajoutez une adresse email et appuyez sur Entrée",
"success-email-alert": "E-mail envoyé avec succès!",
"expired-doc-title": "Document expiré",
"expired-on-mssg": "Ce document a expiré le {{expiredDate}} et n'est plus disponible pour signature.",
"signature-validate-alert": "Veuillez confirmer que vous avez coché au moins {{minRequiredCount}} cases.",
"signature-validate-alert-2": "Assurez-vous que ce champ est rempli avec précision et répond à toutes les exigences.",
"user-name-exist": "le nom d'utilisateur existe déjà",
"remaing-users": "Utilisateurs restants",
"remaining-users": "Utilisateurs restants",
"Role": "Rôle",
"additional-users": "Veuillez acheter des utilisateurs supplémentaires.",
"Quantity-of-users": "Quantité d'utilisateurs",
@@ -699,14 +763,14 @@
"quantity-of-credits": "Quantité de crédits de prime",
"remaining-credits": "Crédits premium disponibles :",
"additional-credits": "Veuillez acheter des crédits premium",
"remaining-credits-help": "Utilisez des crédits premium pour la signature de documents API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits achetés supplémentaires.",
"remaining-credits-help": "Utilisez des crédits premium pour la signature de documents via l'API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits supplémentaires achetés. Si vous êtes sur un plan Teams ou Enterprise, ces crédits peuvent être partagés entre tous les utilisateurs de votre entreprise, sauf si l'administrateur du compte a défini une limite pour votre compte.",
"quota-err-quicksend": "Quota atteint, vous n'avez pas assez de crédits.",
"buy-credits": "Acheter des crédits premium",
"rotate-right": "Faire pivoter à droite",
"rotate-left": "Faire pivoter à gauche",
"rotate-alert-mssg": "Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?",
"templateid": "ID de modèle",
"bulk-send-subcription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
"bulk-send-subscription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
"generate-test-token": "Générer jeton de test",
"regenerate-test-token": "Régénérer le jeton de test",
"help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à l'un de nos forfaits payants pour générer un jeton de production.",
@@ -726,8 +790,9 @@
"copied-code": "COPIÉ",
"Installation": "Installation",
"Usage": "Usage",
"insufficient-credits": "Crédits de signature insuffisants",
"insufficient-credits-mssg": "Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
"insufficient-credits": "Crédits de signature insuffisants. Veuillez contacter {{ownerEmail}}",
"insufficient-credits-title": "Crédits de signature insuffisants",
"insufficient-credits-mssg": "Le propriétaire de ce document ne dispose actuellement pas des crédits {{appName}} nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
"angular-npm-mssg-1": "Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
"quota-mail-info-head": "Limite mensuelle d'e-mails",
"quota-mail-info": "Pour maintenir la qualité du service et prévenir le spam, OpenSign permet jusqu'à 15 e-mails par mois avec le plan gratuit. Passez à l'offre supérieure pour un envoi illimité d'e-mails.",
@@ -746,6 +811,13 @@
"p3": "La sélection de cette option désactivera la vérification OTP, permettant aux utilisateurs de signer le document directement sans étapes supplémentaires.",
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
},
"allow-offline-sign": "Autoriser la signature hors ligne",
"allow-offline-sign-help": {
"p1": "Choisissez si les signataires peuvent soumettre une copie de ce document signée avec un outil externe pour examen par le propriétaire.",
"p2": "Les signataires verront une option Signer hors ligne et pourront téléverser un PDF signé en externe pour validation.",
"p3": "Les signataires ne peuvent signer que dans l'application. L'option Signer hors ligne sera masquée.",
"p4": "Ce paramètre prend par défaut votre préférence de compte et est disponible sur les forfaits payants."
},
"advanced-options": "Options avancées",
"hide-advanced-options": "Masquer les options avancées",
"download-files": "Télécharger des fichiers",
@@ -755,7 +827,7 @@
"server-down": "Impossible de se connecter au serveur {{appName}}. Si vous hébergez vous-même {{appName}}, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez {{appName}} localement, vous y accédez peut-être via un numéro de port incorrect.",
"admin-exists": "L'administrateur existe déjà. Veuillez vous connecter à l'application en utilisant les informations d'identification de l'administrateur afin de gérer les utilisateurs.",
"enable-tour": "Activer la visite",
"istourenabled-otp-help": {
"istourenabled-help": {
"p1": "Cela permettra une visite guidée pour les signataires, fournissant des instructions pendant le processus de signature.",
"p2": "Cela désactivera la visite guidée, garantissant ainsi une expérience de signature plus rapide et ininterrompue.",
"p3": "Conseil: si vos signataires connaissent {{appName}} et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
@@ -808,6 +880,7 @@
"delete-folder-alert-1": "Impossible de supprimer : le dossier doit être vide.",
"defaultSign-alert": "Les widgets {{widgetsType}} ne sont pas disponibles pour la signature automatique.",
"embed-heading": "Intégrer des modèles {{appName}}",
"other-apps-by-opensign": "Autres applications par l'équipe OpenSign",
"public-template-edit-title": "Modifier le modèle public",
"public-template-edit-mssg": "Vous ne pouvez pas modifier directement un modèle public. Pour apporter des modifications, le modèle sera défini comme privé et vous devrez ensuite le rendre à nouveau public. Voulez-vous continuer ?",
"agree-p1": "Je confirme avoir lu et compris les ",
@@ -859,7 +932,6 @@
"term-cond-p30": " ou par email",
"js-snippet-msg": "Pour intégrer des modèles {{appName}} dans vos sites Web ou pages de destination HTML, vous pouvez utiliser le code ci-dessous:",
"js-snippet-msg-1": "Vous pouvez obtenir le TemplateId à partir de la page Gérer les modèles.",
"agrrement-alert": "Pour procéder, vous devez consentir à la divulgation des enregistrements et signatures électroniques.",
"webhook-already-exists": "L'URL existe déjà ! Veuillez en essayer un autre.",
"webhook-must-be-secure": "L'URL du webhook doit être sécurisée et utiliser https://",
"provide-test-token": "Veuillez générer un jeton de test API",
@@ -873,6 +945,7 @@
"p3": "Sélectionner cette option empêche les signataires d'apporter des ajouts au document pendant le processus de signature."
},
"signature-setting": "Paramètres de signature",
"rotation": "Rotation",
"change-signer": "Changer de signataire",
"signyour-self-button": "Utilisez cette option pour signer le document vous-même sans en ajouter d'autres.",
"requestsign-button": "Utilisez cette option pour demander les signatures des autres et de vous-même.",
@@ -907,6 +980,8 @@
"visit-below-link": "Visitez le lien ci-dessous pour en savoir plus -",
"storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité.",
"daily-quota-reached": "Vous avez atteint votre quota quotidien. Pour obtenir de l'aide, veuillez contacter quotas@opensignlabs.com.",
"email-not-verified-send": "Veuillez vérifier votre adresse e-mail avant d'envoyer des e-mails. Accédez à vos <1>paramètres de profil</1> pour terminer la vérification de l'e-mail.",
"email-not-verified-send-plain": "Veuillez vérifier votre adresse e-mail avant d'envoyer des e-mails. Accédez à vos paramètres de profil pour terminer la vérification de l'e-mail.",
"enabled-signature-type": "Types de signature activés",
"enabled-signature-type-help": "Le paramètre 'Types de signature activés' détermine quelles options de signature sont disponibles dans votre organisation. Par exemple, si vous désactivez l'option 'Dessiner', les membres de votre organisation ne la verront pas dans le widget de signature, tandis que les trois autres options resteront accessibles.",
"indexing-public-profile": "Autoriser l'indexation du profil public par les moteurs de recherche",
@@ -934,6 +1009,7 @@
"please-provide-templateid": "Veuillez fournir templateid",
"this-template-is-not-public": "Ce template n'est pas public",
"invalid-templateid": "templateid invalide",
"kiosk-no-active-role": "Ce modèle n'a aucun rôle de signataire ou d'approbateur disponible pour la signature en mode kiosque.",
"contact-billing-at-opensign": "Pour ajouter plus de places, veuillez contacter OpenSign™ à l'adresse <1>billing@opensignlabs.com</1> pour obtenir de l'aide.",
"title-length-alert": "Le titre doit comporter au maximum 250 caractères.",
"note-length-alert": "La note doit comporter au maximum 200 caractères.",
@@ -1023,7 +1099,7 @@
},
"signer-already-present": "Signataire déjà présent",
"kiosk-sign": "Signature sur kiosque",
"dont-have-access-to-template": "Le template a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
"do-not-have-access-to-template": "Le template a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
"kiosk-info": "Le Mode Kiosque vous permet de recueillir des signatures en personne rapidement et efficacement. Idéal pour les salons, événements ou situations où tous les signataires sont physiquement présents. ",
"learn-more": "En savoir plus",
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
@@ -1079,8 +1155,6 @@
"search-contacts": "Rechercher des contacts…",
"add-role-alert": "Veuillez ajouter au moins un rôle",
"edit-draft": "Modifier le brouillon",
"invalid-email-found": "Adresse e-mail invalide trouvée : {{email}}",
"duplicate-email-found": "Adresse e-mail en double trouvée : {{email}}",
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Facturation",
@@ -1094,7 +1168,7 @@
"access-denied": "Accès refusé",
"upgrade": "Mettre à jour",
"do-not-access-app": "Vous n'avez pas accès à cette application.",
"dont-have-access": "Vous n'avez pas accès.",
"do-not-have-access": "Vous n'avez pas accès.",
"valid-email-alert": "Veuillez saisir une adresse e-mail valide.",
"otp-not-validate": "OTP non valide.",
"domain-not-allowed": "Ce domaine n'est pas autorisé",
@@ -1111,10 +1185,12 @@
"branding-help": "Le branding permet le white labelling de votre application",
"custom-sub-domain": "Sous-domaine personnalisé",
"app-name": "Nom de l'application",
"provide-domain-name": "Fournissez votre nom de domaine",
"provide-domain-name": "Seuls les sous-domaines sont autorisés (p. ex., app.votredomaine.com). Les domaines principaux/racine comme votredomaine.com ne sont pas pris en charge.",
"provide-app-name-help": "Indiquez un nom d'application unique. Il sera affiché comme le nom de votre marque dans l'application et dans tous les e-mails partout où l'image de marque est appliquée.",
"upload-app-logo": "Téléversez un logo au format .png, .jpg ou .svg. Taille recommandée : {{size}} pixels (ratio 5:2) pour une meilleure qualité d'affichage.",
"upload-app-favicon": "Téléversez une image de favicon carrée (.ico, .png ou .svg). La taille recommandée est de {{size}} pixels pour une meilleure compatibilité avec les navigateurs.",
"provide-app-name": "Fournissez le nom de votre application",
"logo": "Logo",
"upload-app-logo": "Téléchargez le logo de votre application",
"prefill-unfilled-widget": "Les champs obligatoires suivants ne peuvent pas être vides : {{emptyWidget}}. Veuillez les remplir pour continuer.",
"Dashboard": "Tableau de bord",
"Analytics": "Analytique",
@@ -1237,6 +1313,7 @@
},
"read-only-date-error": "Le widget de date en lecture seule doit avoir une date par défaut.",
"set-date": "Définir la date",
"default-date": "Date par défaut",
"set-today": "Date de signature",
"enter-name": "Saisir le nom",
"enter-email": "Saisir l'e-mail",
@@ -1256,5 +1333,244 @@
"Please provide required parameters.": "Veuillez fournir les paramètres requis.",
"reset-password": "Réinitialiser le mot de passe utilisateur",
"enter-strong-password": "Entrez un mot de passe sécurisé ou générez-en un automatiquement.",
"autogenerate": "Générer automatiquement"
}
"autogenerate": "Générer automatiquement",
"extend": "Prolonger",
"console-general-help": "Pour permettre aux utilisateurs individuels de personnaliser leurs propres modèles d'e-mails, accédez à",
"console-mail-help": "Pour mettre à jour les modèles d'e-mails à l'échelle de l'entreprise, allez dans",
"admin-setting-disabled": "Ce paramètre est désactivé. Veuillez contacter l'administrateur pour obtenir de l'aide.",
"company-email-settings": "Paramètres de messagerie de l'entreprise",
"allow-email-template-individually": "Autoriser la personnalisation individuelle des modèles d'e-mails pour chaque utilisateur ",
"user-email-template-general-help": "Si cette option est désactivée, les utilisateurs ne pourront plus personnaliser leurs modèles d'e-mails.",
"disable-individual-user-smtp": "désactiver les paramètres smtp individuels de l'utilisateur",
"enable-individual-user-smtp": "activer les paramètres smtp individuels de l'utilisateur",
"user-smtp-general-help": "Lorsqu'elle est désactivée, les utilisateurs ne pourront pas configurer leur propre SMTP. Tous les e-mails utiliseront la configuration SMTP globale.",
"global-smtp-settings": "Paramètres SMTP globaux",
"global-smtp-settings-help-p1": "Ces paramètres s'appliquent globalement à l'ensemble de votre compte {{appName}}. Si un utilisateur n'a pas configuré ses propres paramètres de messagerie, le système utilisera automatiquement les paramètres SMTP globaux définis ici.",
"global-smtp-settings-help-p2": "Pour assurer une meilleure délivrabilité de toutes les demandes de signature, vous pouvez connecter votre fournisseur de messagerie préféré. Choisissez l'une des options suivantes :",
"global-connect-to-gmail-help": "Utilisez un compte Gmail pour envoyer des demandes de signature directement depuis la boîte de réception de votre organisation. Cela améliore la délivrabilité, la réputation et la fiabilité.",
"global-connect-to-smtp-help": "Utilisez votre propre serveur SMTP pour envoyer des e-mails via votre domaine. Cela offre un contrôle total sur votre infrastructure de messagerie, améliore la délivrabilité et garantit une cohérence de marque.",
"global-smtp-settings-help-p3": "Si aucun paramètre utilisateur individuel n'est configuré, ces paramètres globaux seront appliqués automatiquement.",
"hide-text-with-asterisks": "Masquer le texte avec des astérisques",
"hide-text-with-asterisks-help": "Le texte sur le document signé sera masqué, mais restera accessible à l'expéditeur du document (propriétaire) depuis son compte {{appName}}.",
"document-id": "ID du document",
"sent-date": "Date d'envoi",
"status": "Statut",
"signer-form-data-for": "Données du formulaire pour le signataire : {{signerName}} <{{signerEmail}}>",
"widget-name": "Nom du widget",
"widget-responses": "Réponses du widget",
"formdata-download-note": "Remarque : Les signatures, tampons, initiales et images ne seront pas exportés. Les données des signataires hors ligne ne seront pas disponibles.",
"export-all-formdata-error": "Vous ne pouvez pas exporter les données du formulaire car le modèle contient des noms de widgets en double. Veuillez créer un nouveau modèle pour pouvoir exporter les données.",
"export-formdata-error": "Vous ne pouvez pas exporter les données du formulaire car le document contient des noms de widgets en double. Veuillez créer un nouveau document sans doublons pour pouvoir exporter les données.",
"duplicate-widget-name-error": "Ce nom de widget est déjà attribué à un autre signataire. Veuillez choisir un autre nom.",
"duplicate-template-widget-error": "Impossible de dupliquer le modèle, car certains noms de widgets sont dupliqués dans différents rôles. Renommez les widgets en double, puis réessayez.",
"credit-alert": "Alerte de crédit",
"document-form-data": "Données du formulaire du document",
"view-formdata-paid-title-error": "La fonction 'Afficher les données du formulaire' vous permet d'imprimer et de télécharger les données des widgets remplies par les signataires lors de la signature.",
"view-formdata-paid-error": "Remarque : La fonction 'Afficher les données du formulaire' est uniquement disponible pour les documents créés à partir de la version v2.32.0 et ne contenant pas de noms de widgets en double.",
"download-all-formdata-paid-title-error": "La fonction 'Télécharger toutes les données du formulaire' vous permet de télécharger les données des widgets remplies par les signataires pour tous les documents créés à partir du modèle.",
"download-all-formdata-paid-error": "Remarque : La fonction 'Télécharger toutes les données du formulaire' est uniquement disponible pour les modèles créés à partir de la version v2.32.0 et ne contenant pas de noms de widgets en double.",
"api-token-not-found": "Jeton API introuvable.",
"sub-head-api-credits": "Définissez le nombre maximal de crédits API que cet utilisateur est autorisé à consommer.",
"consumed-credits": "Crédits consommés",
"allowed-api-credits": "Crédits API autorisés",
"allowed-api-credits-help": "Les crédits autorisés doivent être supérieurs ou égaux aux crédits consommés.",
"Enter-allowed-api-credits": "Saisissez les crédits API autorisés",
"login-to-sandbox": "Se connecter au sandbox",
"your-credits-usage": "Votre utilisation des crédits",
"remaining": "restants",
"used": "utilisés",
"your-credits-usage-help": "Ceci est le nombre de crédits consommés avec votre clé API.",
"api-credits-updated": "Les crédits API ont été attribués avec succès.",
"min-date": "Date minimale",
"max-date": "Date maximale",
"invalid-min-date": "Date minimale invalide.",
"min-less-max": "La date minimale doit être inférieure ou égale à la date maximale.",
"invalid-max-date": "Date maximale invalide.",
"max-less-min": "La date maximale doit être supérieure ou égale à la date minimale.",
"date-range": "Plage de dates",
"date-range-help": "Définissez une date minimale et/ou maximale pour restreindre la sélection. La date choisie doit se situer dans cette plage.",
"option-disabled-by-owner": "Le créateur du document a désactivé cette option",
"pen-colors": "Couleurs du stylo",
"pen-colors-help": "Choisissez quelles couleurs de stylo sont disponibles pour les widgets Signature et Initiales. Cochez pour afficher une couleur, décochez pour la masquer.",
"security-key": "Clé de sécurité",
"remove-limit": "Supprimer la limite",
"removed-api-credits": "La limite de crédits API a été supprimée.",
"enable-authentication": "Activer l'authentification",
"working": "En cours",
"test-security-key": "Tester la clé de sécurité",
"generate": "Générer",
"regenerate": "Régénérer",
"signing-date": "Date de signature",
"date-widget": "Widget de date",
"mail-failed": "Une erreur s'est produite lors de l'envoi de l'e-mail. Veuillez réessayer.",
"mail-status-head": "Étape suivante : signer votre document ?",
"send-to-email": "Envoyer par e-mail",
"date-pref-help-sub-title": "Les préférences du widget de date vous permettent de définir les paramètres par défaut du widget de date. Ces paramètres sont automatiquement appliqués lorsque vous ajoutez un nouveau widget de date à un document ou à un modèle. Vous pouvez toujours personnaliser chaque widget de date individuellement en cliquant sur l'icône d'engrenage.",
"date-pref-help-format": "Choisissez le format de date par défaut utilisé pour les widgets de date.",
"date-pref-help-default-date": "Définissez une valeur de date par défaut pour les widgets de date.",
"date-pref-help-signing-date": "Lorsqu'elle est activée, le widget de date utilise automatiquement la date à laquelle le signataire signe le document.",
"date-pref-help-read-only": "Lorsqu'elle est activée, le widget de date est en lecture seule pour le signataire.",
"upload-csv": "Téléverser un CSV",
"file-selected": "fichier sélectionné",
"bulk-send-csv-helper": "Utilisez des en-têtes pour les rôles des signataires (E-mail/Nom/Téléphone du rôle) et les valeurs de préremplissage (Préremplissage : Nom du champ). Les champs de préremplissage requis doivent être complétés.",
"bulk-send-csv-helper-note": "Remarque : Pour les champs de préremplissage de type date dans le CSV, la valeur doit être fournie dans le format suivant : {{value}} (exemple : {{example}}).",
"bulk-send-csv-summary": "{{total}} lignes importées depuis le CSV.",
"bulk-send-csv-missing-headers": "En-têtes CSV requis manquants. Rôles : {{roles}}.",
"bulk-send-no-records": "Veuillez ajouter au moins un destinataire avant l'envoi.",
"csv-only-support": "Seuls les fichiers CSV sont pris en charge.",
"csv-no-records": "Aucun enregistrement trouvé dans le CSV.",
"select-at-least-option": "Veuillez sélectionner au moins une option.",
"draw-required": "Veuillez dessiner dans le champ.",
"enter-smtp-username": "Saisissez votre nom d'utilisateur SMTP",
"enter-webhook-url": "Saisissez l'URL du webhook",
"enter-user-name": "Saisissez le nom d'utilisateur",
"enter-tagline": "Saisissez le slogan",
"enter-password-or-click-autogenerate": "Saisissez le mot de passe ou cliquez sur Génération automatique",
"enter-daily-email-quota": "Saisissez le quota quotidien d'e-mails",
"enter-custom-regular-expression": "Saisissez une expression régulière personnalisée",
"enter-value": "Saisissez {{value}}",
"are-you-sure-want-to-send-document": "Êtes-vous sûr de vouloir envoyer le document ?",
"favicon": "Favicon",
"delete-two-factor-authentication": "Êtes-vous sûr de vouloir supprimer l'authentification à deux facteurs ?",
"paste-html-here": "Collez le HTML ici",
"open-email-builder": "Vous pouvez créer un modèle d'e-mail à l'aide de la plateforme <1>email builder</1> et copier le code HTML.",
"contact-saved": "Contact enregistré avec succès.",
"remove-limit-of-user": "Êtes-vous sûr de vouloir supprimer la limite de cet utilisateur ? Il pourra alors utiliser tous les crédits premium disponibles de votre organisation, partagés entre tous les utilisateurs.",
"current-expiry-date": "Date d'expiration actuelle",
"reminder-error": "« Rappeler une fois tous les (jours) » ne doit pas dépasser le « Temps pour terminer (jours) ».",
"showing-to-of-records": "Affichage de {{record}} à {{recordPerPage}} sur {{totalRows}} enregistrements",
"records-per-page": "Enregistrements par page",
"email-not-found-in-row": "Aucun e-mail trouvé à la ligne {{row}}.",
"invalid-email-found-in-row": "E-mail invalide trouvé à la ligne {{row}} : {{email}}",
"duplicate-email-found-in-row": "E-mail en double trouvé à la ligne {{row}} : {{email}}",
"prefill": "Préremplissage",
"recipients-&-data": "Destinataires et données",
"response": "Réponse",
"prefill-tab-help": "Ce sont les champs communs du modèle qui s'appliqueront à tous les documents de cet envoi groupé.",
"recipient-tab-help-p1": "Dans cette étape suivante, vous allez :",
"recipient-tab-help-p2": "Téléverser un CSV avec les détails des destinataires ou",
"recipient-tab-help-p3": "Saisir manuellement les informations des destinataires",
"recipient-tab-help-p4": "Chaque destinataire recevra son propre document généré à partir de ce modèle.",
"response-tab-help": "Vérifiez vos réponses.",
"summary": "Résumé",
"prefill-fields": "Champs de préremplissage",
"documents": "Documents",
"message": "Message",
"formula-help-p1": "Collectez des entrées numériques et effectuez des calculs dynamiques à l'aide de formules.",
"example": "Exemple",
"formula-help-p2": "Pour afficher un total dans number-3, utilisez {{number-1}} + {{number-2}}.",
"formula-help-p3": "Pour calculer une valeur dans number-4, utilisez ({{number-1}} * {{number-2}}) / {{number-3}}.",
"formula-help-p4": "Chaque widget numérique doit avoir un nom unique (par ex. number-1), car les formules dépendent de ces identifiants.",
"note-consume-premium-credits": "Remarque : Cette fonctionnalité consomme des crédits premium.",
"note-consume-premium-credits-on-document-send": "Remarque : Cette fonctionnalité consomme des crédits premium. Les crédits sont consommés chaque fois qu'un document est envoyé, qu'il soit signé ou non.",
"webhook-authentication-help": "{{appName}} sécurise chaque webhook en générant une signature HMAC-SHA256 à l'aide de votre clé de sécurité webhook et du payload brut de la requête. Cette signature est envoyée dans l'en-tête x-webhook-signature. Votre serveur doit générer la même signature en utilisant le payload reçu et votre clé secrète. Si les deux signatures correspondent, la requête webhook est authentique et n'a pas été modifiée.",
"invalid-widget-found-in-row": "Valeur invalide détectée à la ligne {{row}} pour le widget {{widget}}.",
"all-signer-status": "Statut de tous les signataires",
"viewed": "Consulté",
"signed": "Signé",
"switch-to-advanced": "Passer en mode avancé",
"switch-to-basic": "Passer en mode basique",
"use-name-as-sender": "Utiliser le nom du profil comme expéditeur",
"use-name-as-sender-help": "Lorsque cette option est activée, {{appName}} utilise le nom de votre profil comme expéditeur pour les demandes de signature et l'e-mail de finalisation.",
"acro-fields-detected": "Champs de formulaire détectés",
"acro-fields-detected-msg": "Nous avons détecté {{count}} champ(s) de formulaire dans le PDF téléchargé. Souhaitez-vous insérer automatiquement les widgets correspondants ?",
"documents-count": "Nombre de documents",
"templates-count": "Nombre de modèles",
"emails-sent": "E-mails envoyés",
"storage-used": "Stockage utilisé",
"docs-count-team-help": "Nombre total de documents créés par tous les utilisateurs de vos organisations.",
"docs-count-help": "Nombre total de documents que vous avez créés.",
"templates-count-team-help": "Nombre total de modèles créés par tous les utilisateurs de vos organisations.",
"templates-count-help": "Nombre total de modèles que vous avez créés.",
"emails-sent-team-help": "Nombre total de notifications e-mail envoyées dans vos organisations.",
"emails-sent-help": "Nombre total de notifications e-mail envoyées par vous.",
"storage-used-team-help": "Espace de stockage total consommé par tous les utilisateurs de vos organisations.",
"storage-used-help": "Espace de stockage total consommé par vous.",
"cloud": "Cloud",
"on-premises": "Sur site",
"plan-name-free": "OPENSIGN™ FREE",
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
"plan-name-teams": "OPENSIGN™ TEAMS",
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
"plan-subtitle-free": "Signatures électroniques illimitées gratuites, pour toujours.",
"plan-subtitle-professional": "Accès exclusif aux fonctionnalités avancées.",
"plan-subtitle-scalable": "Fonctionnalités évolutives avec support prioritaire.",
"subscribe": "S'abonner",
"contact-us": "Nous contacter",
"benefit-unlimited-digital-signatures": "Signatures numériques illimitées",
"benefit-sign-docs-yourself": "Signer des documents vous-même",
"benefit-request-signatures": "Demander des signatures à d'autres",
"benefit-unlimited-templates": "Modèles illimités",
"benefit-14-field-types": "14 types de champ",
"benefit-automatic-e-signatures": "Signatures électroniques automatiques",
"benefit-completion-certificates": "Certificats d'achèvement",
"benefit-send-in-order": "Envoyer dans l'ordre",
"benefit-organize-docs": "Organiser les documents dans OpenSign™ Drive",
"benefit-document-templates": "Modèles de documents",
"benefit-import-from-dropbox": "Importer depuis Dropbox",
"benefit-contact-book": "Carnet de contacts",
"benefit-document-expiry": "Prise en charge de l'expiration des documents",
"benefit-decline-document": "Prise en charge du refus de documents",
"benefit-email-notifications": "Notifications par e-mail",
"benefit-public-profiles": "Profils publics",
"benefit-and-much-more": "Et bien plus encore",
"benefit-everything-free": "Tout dans OpenSign™ Free",
"benefit-field-validations": "Validations de champ",
"benefit-regex-validations": "Validations par expression régulière",
"benefit-webhooks": "Webhooks",
"benefit-zapier": "Intégration Zapier",
"benefit-api-access": "Accès API",
"benefit-api-signatures-100": "jusqu'à 100 signatures API",
"benefit-api-signatures-240": "jusqu'à 240 signatures API",
"benefit-custom-email-templates": "Modèles d'e-mail personnalisés",
"benefit-custom-smtp": "Connectez votre propre compte Gmail ou SMTP pour envoyer des e-mails",
"benefit-auto-reminders": "Rappels automatiques",
"benefit-bulk-send-100": "Envoi en masse (jusqu'à 100 documents)",
"benefit-bulk-send-240": "Envoi en masse (jusqu'à 240 documents)",
"benefit-premium-profiles": "Noms d'utilisateur de profil public premium",
"benefit-email-verification": "Appliquer la vérification par e-mail pour confirmer l'identité du signataire",
"benefit-embedded-signing": "Signature intégrée",
"benefit-everything-professional": "Tout dans OpenSign™ Professional",
"benefit-teams-organizations": "Équipes et Organisations",
"benefit-share-templates-teams": "Partager des modèles avec des équipes",
"benefit-share-templates-individuals": "Partager des modèles avec des individus",
"benefit-byoc": "BYOC - Stockez vos documents dans votre propre stockage cloud",
"benefit-doc-id-removal": "Suppression de l'ID de document des documents signés",
"benefit-request-payments": "Demander des paiements (bientôt disponible)",
"benefit-mobile-app": "Application mobile (bientôt disponible)",
"benefit-api-signatures-500": "jusqu'à 500 signatures API",
"benefit-bulk-send-500": "Envoi en masse (jusqu'à 500 documents)",
"benefit-all-features": "Toutes les fonctionnalités",
"benefit-custom-domain": "Domaine personnalisé",
"benefit-custom-branding": "Identité visuelle personnalisée",
"benefit-uptime-sla": "SLA de disponibilité",
"benefit-sso-support": "Support SSO",
"benefit-priority-support": "Support prioritaire",
"benefit-3-users-1500-credits": "Comprend 3 utilisateurs et 1500 crédits premium",
"benefit-public-links": "Liens publics",
"benefit-kiosk-mode": "Mode kiosque",
"benefit-bulk-send-1500": "Envoi en masse (jusqu'à 1500 documents)",
"benefit-connect-gmail-smtp": "Connectez votre propre Gmail ou SMTP",
"benefit-analytics": "Analyses",
"something-went-wrong-mssg": "Une erreur s'est produite, actualiser cette page peut résoudre le problème.",
"benefit-bulksend-api-pro-month-help": "Ce forfait inclut 100 crédits premium par mois qui vous permettent d'envoyer des documents via l'API, de partager des liens publics de signature, de lancer des demandes de signature en masse, d'utiliser le mode Kiosque pour les signatures en personne ou de collecter des signatures à partir de documents intégrés directement à votre site web. À chaque fois que vous initiez ou envoyez un document, un crédit est utilisé — et ce document peut inclure jusqu'à 10 signataires.",
"benefit-bulksend-api-pro-year-help": "Ce forfait inclut 240 crédits premium par an qui vous permettent d'envoyer des documents via l'API, de partager des liens publics de signature, de lancer des demandes de signature en masse, d'utiliser le mode Kiosque pour les signatures en personne ou de collecter des signatures à partir de documents intégrés directement à votre site web. À chaque fois que vous initiez ou envoyez un document, un crédit est utilisé — et ce document peut inclure jusqu'à 10 signataires.",
"benefit-bulksend-api-team-month-help": "Ce forfait inclut 100 crédits premium par mois qui vous permettent d'envoyer des documents via l'API, de partager des liens publics de signature, de lancer des demandes de signature en masse, d'utiliser le mode Kiosque pour les signatures en personne ou de collecter des signatures à partir de documents intégrés directement à votre site web. À chaque fois que vous initiez ou envoyez un document, un crédit est utilisé — et ce document peut inclure jusqu'à 10 signataires.",
"benefit-bulksend-api-team-year-help": "Ce forfait inclut 500 crédits premium par an qui vous permettent d'envoyer des documents via l'API, de partager des liens publics de signature, de lancer des demandes de signature en masse, d'utiliser le mode Kiosque pour les signatures en personne ou de collecter des signatures à partir de documents intégrés directement à votre site web. À chaque fois que vous initiez ou envoyez un document, un crédit est utilisé — et ce document peut inclure jusqu'à 10 signataires.",
"failed-pdf-for-prefill-processing": "Impossible de charger le PDF pour le traitement du préremplissage.",
"attachments-docx-upload-failed": "Nous rencontrons actuellement un problème. Veuillez réessayer.",
"attachments-docx-upload-limit": "La limite de téléchargement pour les fichiers DOCX volumineux a été atteinte. Veuillez attendre 4 minutes avant de réessayer.",
"attach-all-role-to-signer": "Veuillez attribuer tous les rôles au signataire.",
"upgrade-to-edit": "Mettre à niveau pour modifier",
"hide-live-chat-for-signers": "Masquer le chat en direct pour les signataires",
"hide-live-chat-for-signers-help": "Lorsqu'activé, les widgets de chat en direct seront masqués pour les signataires sur les pages de signature.",
"attachments-creator-hint": "Les signataires peuvent téléverser des fichiers qui seront ajoutés à la fin du document.",
"attachments-drop-hint": "Déposez les fichiers ici ou cliquez pour choisir",
"attachments-limits": "PDF, DOCX, PNG, JPG. Jusqu'à {{maxFiles}} fichiers, {{maxSizeMB}} Mo chacun.",
"attachments-invalid-type": "Type de fichier non pris en charge : {{name}}",
"attachments-max-files": "Maximum {{maxFiles}} pièces jointes autorisées.",
"attachments-remove-confirm": "La suppression efface uniquement les métadonnées. Les pages déjà ajoutées resteront dans le document. Continuer ?",
"attachement-note": "Remarque : Ce widget permet aux signataires de télécharger un fichier pendant le processus de signature. Le fichier téléchargé sera ajouté en tant que pages supplémentaires à la fin du PDF existant.",
"attachments-docx-not-supported": "Nous rencontrons actuellement un problème avec le téléchargement des fichiers DOCX. Veuillez essayer de télécharger le document dans un autre format pris en charge ou réessayer plus tard."
}
+349 -34
View File
@@ -71,10 +71,11 @@
"login-page": "लॉगिन पृष्ठ",
"or": "या",
"signup-page": "साइनअप पृष्ठ",
"password-match-length": "नया पासवर्ड और पुष्टि पासवर्ड मेल खाना चाहिए",
"password-length": "पासवर्ड 8 वर्ण लंबा होना चाहिए",
"password-case": "पासवर्ड में अपरकेस अक्षर, लोअरकेस अक्षर, अंक होना चाहिए",
"password-special-char": " पासवर्ड में विशेष वर्ण होना चाहिए",
"agreee": "मैं सहमत हूँ",
"agree": "मैं सहमत हूँ",
"term": "सेवा की शर्तें",
"subscribe-to-opensign": "OpenSign न्यूज़लेटर की सदस्यता लें",
"register": "पंजीकृत करें",
@@ -120,7 +121,8 @@
"Teams": "टीमें",
"Team-update-successfully": "टीम सफलतापूर्वक अपडेट की गई।",
"Users": "उपयोगकर्ता"
}
},
"Global SMTP": "ग्लोबल SMTP"
},
"dashboard-card": {
"Need your Signature": "आपके हस्ताक्षर की आवश्यकता है",
@@ -166,7 +168,10 @@
"Save as template": "टेम्पलेट के रूप में सहेजें",
"Fix & resend": "ठीक करें और पुनः भेजें",
"Kiosk Mode": "कियोस्क मोड",
"Reset password": "पासवर्ड रीसेट करें"
"Reset password": "पासवर्ड रीसेट करें",
"View formdata": "फॉर्म डेटा देखें",
"Download all formdata": "सभी फॉर्म डेटा डाउनलोड करें",
"API credits": "API क्रेडिट्स"
},
"report-heading": {
"Sr.No": "क्रमांक",
@@ -201,7 +206,8 @@
"Redirect url": "रीडायरेक्ट URL",
"Created Date": "निर्माण तिथि",
"Updated Date": "अद्यतन तिथि",
"Expiry Date": "समाप्ति तिथि"
"Expiry Date": "समाप्ति तिथि",
"Sent Date": "भेजे जाने की तिथि"
},
"report-help": {
"Draft Documents": "ये वे दस्तावेज़ हैं जिन्हें आपने शुरू तो किया है लेकिन भेजने के लिए अंतिम रूप नहीं दिया है।",
@@ -218,7 +224,7 @@
"New Template": "नया टेम्पलेट",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "फ़ाइल चयनित",
"files-selected": "फ़ाइलें चयनित",
"template-title": "टेम्पलेट शीर्षक",
"document-title": "दस्तावेज़ शीर्षक",
"title": "शीर्षक",
@@ -231,11 +237,12 @@
"p3": "इस विकल्प का चयन करने पर हस्ताक्षर लिंक सभी हस्ताक्षरकर्ताओं को एक साथ भेजे जाएंगे। प्रत्येक हस्ताक्षरकर्ता अपनी सुविधानुसार दस्तावेज़ पर हस्ताक्षर कर सकता है, भले ही अन्य हस्ताक्षरकर्ताओं ने अपने हस्ताक्षर पूरे कर लिए हों या नहीं। यह विधि तेज़ है लेकिन प्रतिभागियों के बीच किसी भी हस्ताक्षर क्रम को लागू नहीं करती है।",
"p4": "वह विकल्प चुनें जो आपके दस्तावेज़ प्रसंस्करण की आवश्यकताओं के लिए सबसे उपयुक्त हो।"
},
"strict-order": "सख्त क्रम लागू करें",
"strict-order-help": "जब सक्षम होता है, तो कोई भी हस्ताक्षरकर्ता तब तक हस्ताक्षर या अनुमोदन नहीं कर सकता जब तक कि पिछले सभी हस्ताक्षरकर्ता/अनुमोदक अपनी कार्रवाई पूरी न कर लें।",
"no": "नहीं",
"auto-reminder": "स्वतः अनुस्मारक",
"remind-once": " प्रत्येक (दिन) में एक बार याद दिलाएं",
"next": "अगला",
"select-folder": "फ़ोल्डर चुनें",
"OpenSign-drive": "{{appName}} ड्राइव",
"drive-document-status": {
"Completed": "पूरा हुआ",
@@ -255,6 +262,7 @@
"signers": "हस्ताक्षरकर्ता",
"signers-help": "अपने सहेजे गए संपर्कों से सुझाए गए हस्ताक्षरकर्ताओं को देखने के लिए किसी संपर्क का नाम टाइप करना शुरू करें या नए जोड़ें। वांछित क्रम में हस्ताक्षरकर्ताओं को जोड़कर हस्ताक्षर क्रम की व्यवस्था करें। हस्ताक्षरकर्ताओं को शामिल करने के लिए '+' बटन और उन्हें हटाने के लिए 'x' का उपयोग करें। प्रत्येक हस्ताक्षरकर्ता को सूचीबद्ध क्रम में दस्तावेज़ पर हस्ताक्षर करने के लिए एक ईमेल संकेत प्राप्त होगा।",
"bcc-help": "अपने सहेजे गए संपर्कों से सुझाव देखने के लिए किसी संपर्क का नाम टाइप करना शुरू करें या नए जोड़ें। किसी उपयोगकर्ता को जोड़ने के लिए '+' बटन और उसे हटाने के लिए 'x' का उपयोग करें। चयनित उपयोगकर्ता का ईमेल Bcc (ब्लाइंड कार्बन कॉपी) के रूप में जोड़ा जाएगा। दस्तावेज़ पूरा होने के बाद प्रत्येक उपयोगकर्ता को एक ईमेल सूचना प्राप्त होगी।",
"cc-help": "सुझाव देखने के लिए अपने सेव किए गए कॉन्टैक्ट्स में से किसी कॉन्टैक्ट का नाम टाइप करना शुरू करें या नए जोड़ें। यूज़र जोड़ने के लिए '+' बटन और हटाने के लिए 'x' का उपयोग करें। चुने गए यूज़र का ईमेल CC (कार्बन कॉपी) के रूप में जोड़ा जाएगा। दस्तावेज़ पूर्ण होने पर प्रत्येक यूज़र को एक ईमेल सूचना मिलेगी।",
"add-signer": "हस्ताक्षरकर्ता जोड़ें",
"contact-not-found": "संपर्क नहीं मिला",
"add-yourself": "स्वयं को जोड़ें",
@@ -264,7 +272,7 @@
"signature": "हस्ताक्षर",
"upload-image": " छवि अपलोड करें",
"clear": "स्पष्ट",
"upload-signature/Image": "कृपया हस्ताक्षर/छवि अपलोड करें",
"upload-signature/Image": "कृपया हस्ताक्षर/छवि अपलोड करें/मोहर",
"initials": "हस्ताक्षर",
"API": "एपीआई",
"api-token": "एपीआई टोकन",
@@ -276,13 +284,15 @@
"yes": "हाँ",
"copied": "कॉपी किया गया",
"something-went-wrong-mssg": "कुछ गलत हो गया, इस पृष्ठ को रीफ्रेश करने से यह समस्या हल हो सकती है।",
"attachments-docx-upload-failed": "फिलहाल एक समस्या आ रही है। कृपया पुनः प्रयास करें।",
"attachments-docx-upload-limit": "बड़ी DOCX फ़ाइलों के लिए अपलोड सीमा को पार कर लिया गया है। कृपया 4 मिनट तक प्रतीक्षा करें और फिर प्रयास करें।",
"token-generated": "टोकन सफलतापूर्वक उत्पन्न हुआ।",
"webhook": "वेबहूक",
"update-webhook": "वेबहूक अपडेट करें",
"add-webhook": "वेबहूक जोड़ें",
"webhook-added": "वेबहूक सफलतापूर्वक जोड़ा गया।",
"team-disabled": "टीम अक्षम",
"Team enabled": "टीम सक्षम",
"team-enabled": "टीम सक्षम",
"are-you-sure": "क्या आप वाकई चाहते हैं",
"disable": "अक्षम करें",
"enable": "सक्षम करें",
@@ -290,7 +300,7 @@
"edit-team": "टीम संपादित करें",
"name-of-team": "टीम का नाम",
"prev": "पिछला",
"no-data-avaliable": "कोई डेटा उपलब्ध नहीं है",
"no-data-available": "कोई डेटा उपलब्ध नहीं है",
"add-team": "टीम जोड़ें",
"page-not-found": "पृष्ठ नहीं मिला",
"users-from-teams": "टीमों से उपयोगकर्ता",
@@ -301,12 +311,37 @@
"delete-user": "उपयोगकर्ता हटाएं",
"delete": "हटाएं",
"add-user": "उपयोगकर्ता जोड़ें",
"password-generateed": "पासवर्ड केवल एक बार उत्पन्न होगा; इसे कॉपी करना सुनिश्चित करें।",
"password-generated": "पासवर्ड केवल एक बार उत्पन्न होगा; इसे कॉपी करना सुनिश्चित करें।",
"Team status": "टीम स्थिति",
"user-deactivated": "उपयोगकर्ता निष्क्रिय।",
"user-activated": "उपयोगकर्ता सक्रिय।",
"public": "सार्वजनिक",
"download": "डाउनलोड करें",
"sign-offline": "ऑफ़लाइन हस्ताक्षर करें",
"sign-offline-instructions": "दस्तावेज़ डाउनलोड करें, इसे किसी बाहरी टूल से हस्ताक्षरित करें, फिर यहाँ हस्ताक्षरित प्रति अपलोड करें। दस्तावेज़ का मालिक आपकी सबमिशन की समीक्षा करके उसे स्वीकृत या अस्वीकार करेगा।",
"sign-offline-step-1": "बिना हस्ताक्षर का PDF डाउनलोड करें।",
"sign-offline-step-2": "अपने पसंदीदा टूल से PDF हस्ताक्षरित करें।",
"sign-offline-step-3": "मालिक की स्वीकृति के लिए हस्ताक्षरित PDF अपलोड करें।",
"sign-offline-upload-hint": "स्वीकृति के लिए सबमिट करने हेतु हस्ताक्षरित PDF चुनें।",
"sign-offline-submitted-title": "स्वीकृति के लिए सबमिट किया गया",
"sign-offline-submitted-body": "दस्तावेज़ के मालिक को आपकी ऑफ़लाइन हस्ताक्षरित प्रति की समीक्षा करने के लिए ईमेल द्वारा सूचित किया गया है।",
"offline-already-submitted-title": "दस्तावेज़ ऑफ़लाइन सबमिट किया गया",
"offline-already-submitted-body": "आपने यह दस्तावेज़ पहले ही ऑफ़लाइन हस्ताक्षर समीक्षा के लिए सबमिट कर दिया है। स्वीकृति या अस्वीकृति होने पर आपको सूचित किया जाएगा।",
"offline-action-approve-title": "ऑफ़लाइन हस्ताक्षर स्वीकृत करें",
"offline-action-approve-confirm": "स्वीकृत करें",
"offline-action-reject-title": "ऑफ़लाइन हस्ताक्षर अस्वीकार करें",
"offline-action-reject-hint": "वैकल्पिक रूप से कारण बताएं। हस्ताक्षरकर्ता को ईमेल द्वारा सूचित किया जाएगा।",
"offline-action-reject-reason-placeholder": "कारण (वैकल्पिक)",
"offline-action-reject-confirm": "अस्वीकृति की पुष्टि करें",
"offline-action-review-title": "ऑफ़लाइन हस्ताक्षर की समीक्षा करें",
"offline-action-review-hint": "हस्ताक्षरकर्ता द्वारा अपलोड किए गए हस्ताक्षरित PDF का पूर्वावलोकन करें, फिर उसे स्वीकृत या अस्वीकार करें। पुष्टि करने तक कोई कार्रवाई नहीं की जाएगी।",
"offline-action-submitted-at": "सबमिट किया गया",
"offline-action-no-preview": "इस सबमिशन के लिए कोई पूर्वावलोकन उपलब्ध नहीं है।",
"offline-action-open-in-new-tab": "PDF नए टैब में खोलें",
"offline-action-invalid-link": "यह समीक्षा लिंक अमान्य है या समाप्त हो गई है।",
"offline-action-success": "कार्रवाई पूर्ण हुई।",
"reject": "अस्वीकार करें",
"document": "दस्तावेज़",
"view": "देखें",
"make-template-public": "टेम्पलेट को सार्वजनिक करें",
"make-template-private": "टेम्पलेट को निजी बनाएं",
@@ -331,7 +366,7 @@
"quick-send-alert-1": "इस दस्तावेज़ में सभी भूमिकाएँ वर्तमान में संपर्कों से जुड़ी हुई हैं। इस टेम्पलेट की प्रतियां कई हस्ताक्षरकर्ताओं को त्वरित रूप से भेजने के लिए, कृपया सुनिश्चित करें कि कम से कम एक भूमिका किसी भी संपर्क से जुड़ी नहीं है।",
"quick-send-alert-2": "कृपया सुनिश्चित करें कि सभी प्राप्तकर्ताओं के लिए कम से कम एक हस्ताक्षर विजेट जोड़ा गया है।",
"quick-send-alert-3": "कृपया इस टेम्पलेट में कम से कम एक भूमिका जोड़ें।",
"quick-send-alert-4": "त्वरित भेजें सीमा तक पहुँच गया।",
"quick-send-alert-4": "{{flow}} प्रति बैच अधिकतम {{count}} साइनरों का समर्थन करता है। अपनी सूची विभाजित करें और पुनः प्रयास करें।",
"copy-link": "लिंक कॉपी करें",
"copy": "कॉपी करें",
"revoke-document": "दस्तावेज़ रद्द करें",
@@ -387,7 +422,11 @@
"radio button": "रेडियो बटन",
"image": "छवि",
"email": "ईमेल",
"number": "संख्या"
"number": "संख्या",
"draw": "आरेखित करें",
"attachments": "संलग्नक",
"attachments-add": "संलग्नक अपलोड करने के लिए क्लिक करें",
"attachments-count": "{{count}} संलग्नक"
},
"fields": "क्षेत्र",
"recipients": "प्राप्तकर्ता",
@@ -399,6 +438,14 @@
"certificate": "प्रमाण पत्र",
"decline": "अस्वीकार करें",
"finish": "समाप्त करें",
"approve": "स्वीकृत करें",
"approve-mssg": " क्या आप वाकई दस्तावेज़ को स्वीकृत करना चाहते हैं?",
"signer-role-label": "प्राप्तकर्ता की भूमिका",
"signer-role-signer": "हस्ताक्षरकर्ता",
"signer-role-viewer": "दर्शक",
"signer-role-approver": "अनुमोदक",
"signer-role-hint": "हस्ताक्षरकर्ता को हस्ताक्षर करना होगा। अनुमोदक बिना हस्ताक्षर किए अनुमोदन करता है। दर्शक केवल देखता है।",
"signer-role-upgrade-msg": "दर्शक और अनुमोदक भूमिकाएँ केवल सशुल्क योजनाओं में उपलब्ध हैं। प्राप्तकर्ताओं को ये भूमिकाएँ सौंपने के लिए अपनी योजना अपग्रेड करें।",
"done": "हो गया",
"mail": "मेल",
"sign-now": "अभी हस्ताक्षर करें",
@@ -431,16 +478,18 @@
"reset-password-alert-1": "पासवर्ड रीसेट लिंक आपके ईमेल आईडी पर भेज दिया गया है",
"reset-password-alert-2": "कृपया ईमेल एडॉप्टर सेटअप करें ",
"reset-password-alert-3": "अपना पासवर्ड रीसेट करें",
"faild-animation": "एनिमेशन लोड करने में विफल",
"failed-animation": "एनिमेशन लोड करने में विफल",
"apply": "लागू करें",
"select-columns": "स्तंभ चुनें",
"copy-type": {
"All pages": "सभी पृष्ठ",
"All pages but last": "अंतिम को छोड़कर सभी पृष्ठ",
"All pages but first": "पहले को छोड़कर सभी पृष्ठ",
"Next to current widget": "वर्तमान विजेट के आगे"
"Next to current widget": "वर्तमान विजेट के आगे",
"Page range": "पृष्ठ सीमा"
},
"options": "विकल्प",
"minimun-check": "न्यूनतम जाँच",
"minimum-check": "न्यूनतम जाँच",
"maximum-check": "अधिकतम जाँच",
"cell-count": "सेल्स संख्या",
"default-value": "डिफ़ॉल्ट मान",
@@ -454,6 +503,19 @@
"formula-error-braces": "हर खुलने वाली {{ के लिए एक मिलान करने वाली }} होना चाहिए।",
"formula-error-operators": "ऑपरेटर या कोष्ठक का उपयोग करते समय कम से कम दो मान जोड़ें।",
"formula-error-leading-operator": "सूत्र ऑपरेटर से शुरू नहीं हो सकते।",
"formula-error-trailing-operator": "सूत्र किसी ऑपरेटर पर समाप्त नहीं हो सकते।",
"formula-error-adjacent-placeholders": "दो प्लेसहोल्डर बिना बीच में ऑपरेटर के साथ-साथ नहीं लिखे जा सकते।",
"formula-error-continuous-operators": "दो ऑपरेटर लगातार साथ में उपयोग नहीं किए जा सकते।",
"formula-error-empty-parentheses": "कोष्ठक (parentheses) खाली नहीं हो सकते।",
"formula-error-invalid-parentheses-content": "कोष्ठक किसी ऑपरेटर से शुरू या समाप्त नहीं हो सकते।",
"formula-error-before-open-parenthesis": "'(' से पहले ऑपरेटर गायब है। ओपनिंग कोष्ठक से पहले एक ऑपरेटर जोड़ें।",
"formula-error-after-close-parenthesis": "')' के बाद ऑपरेटर गायब है। क्लोज़िंग कोष्ठक के बाद '+' या '*' जैसा ऑपरेटर जोड़ें।",
"formula-error-between-parentheses": "')' और '(' के बीच ऑपरेटर गायब है। दोनों समूहों के बीच '*' जैसा ऑपरेटर जोड़ें।",
"formula-error-division-by-zero": "शून्य से भाग देना अनुमति नहीं है। '0' को किसी मान्य गैर-शून्य संख्या या वेरिएबल से बदलें।",
"formula-error-invalid-number-format": "अमान्य संख्या फ़ॉर्मेट मिला। संख्याएँ '1', '3.14' जैसे मान्य फ़ॉर्मेट में होनी चाहिए, '.5', '1.' या '1.2.3' नहीं।",
"formula-error-unclosed-parenthesis": "बिना बंद किया हुआ '(' मिला। हर ओपनिंग '(' के लिए एक मैचिंग क्लोज़िंग ')' होना चाहिए।",
"formula-error-unexpected-closing-parenthesis": "बिना मैचिंग ओपनिंग '(' के अप्रत्याशित क्लोज़िंग ')' मिला।",
"formula-error-empty-placeholder": "खाली प्लेसहोल्डर '{{}}' अनुमति नहीं है। ब्रेसेज़ के अंदर वेरिएबल का नाम दें।",
"decimal-places": "दशमलव स्थान",
"number-of-decimal-places": "दशमलव स्थानों की संख्या",
"decimal-places-helper": "पूर्णांक के लिए 0 पर सेट करें",
@@ -510,18 +572,19 @@
"placeholder-alert-4": "आपने सभी प्राप्तकर्ताओं को सफलतापूर्वक मेल भेज दिए हैं!",
"placeholder-mail-alert": "आपने {{name}} को सफलतापूर्वक ईमेल भेज दिया है। {{name}} द्वारा दस्तावेज़ पर हस्ताक्षर करने के बाद बाद के हस्ताक्षरकर्ताओं को ईमेल प्राप्त होंगे",
"placeholder-mail-alert-you": "आपके दस्तावेज़ पर हस्ताक्षर करने के बाद अगले हस्ताक्षरकर्ता को ईमेल प्राप्त होगा।",
"placeholder-alert-5": "क्या आप अभी स्तावेज़ पर हस्ताक्षर करना चाहते हैं?",
"placeholder-alert-5": "आप अभी स्ताक्षर कर सकते हैं या इसे बाद में पूरा करने के लिए अपने ईमेल पर भेज सकते हैं",
"placeholder-alert-6": "मेल भेजने के लिए कृपया मेल एडॉप्टर सेटअप करें!",
"placeholder-alert-7": "प्लेसहोल्डर जोड़ने के लिए कृपया हस्ताक्षरकर्ता चुनें!",
"email-subject": "ईमेल विषय",
"email-body": "ईमेल मुख्य भाग",
"email-placeholder": "ईमेल का मुख्य भाग जोड़ें",
"reset-to-default": "डिफ़ॉल्ट पर रीसेट करें",
"cutomize-email": "ईमेल अनुकूलित करें",
"customize-email": "ईमेल अनुकूलित करें",
"upgrade-to-customize-email": "ईमेल अनुकूलित करने के लिए अपग्रेड करें",
"sign-url": "यूआरएल पर हस्ताक्षर करें",
"mails-sent": "मेल भेजे गए",
"mail-not-delivered": "मेल वितरित नहीं किया गया",
"email-not-verified-head": "ईमेल सत्यापित नहीं है",
"document-alert": "दस्तावेज़ चेतावनी",
"owner-subscription-expired": "मालिक की सदस्यता समाप्त हो गई है।",
"subscription-expired": "सदस्यता समाप्त",
@@ -582,7 +645,7 @@
"provide-your-details": "अपना विवरण प्रदान करें",
"provide-email": "कृपया ईमेल प्रदान करें।",
"additional-info": "अतिरिक्त जानकारी",
"signature-saved-alert": "हस्ताक्षर सफलतापूर्वक सहेजा गया।",
"signature-saved-alert": "सफलतापूर्वक सहेजा गया।",
"saved-successfully": "सफलतापूर्वक सहेजा गया।",
"fill-field": "कृपया इस फ़ील्ड को भरें",
"error-template": "त्रुटि: टेम्पलेट नहीं मिला!",
@@ -597,6 +660,9 @@
"document-signed-alert-7": "यह दस्तावेज़ अब उपलब्ध नहीं है।",
"document-signed-alert-8": "दस्तावेज़ पर हस्ताक्षर के लिए पहले ही भेज दिया गया है।",
"yet-to-sign": "अभी हस्ताक्षर करना बाकी है",
"viewers": "दर्शक",
"viewer-no-widgets": "दर्शकों को दस्तावेज़ की केवल-पढ़ने योग्य प्रति मिलती है और उन्हें विजेट असाइन नहीं किए जा सकते।",
"viewer-confirm-widget-removal": "इस प्राप्तकर्ता को दर्शक में बदलने पर उन्हें पहले से असाइन किए गए सभी विजेट हटा दिए जाएंगे। जारी रखें?",
"validation-alert-1": "इनपुट नियमित अभिव्यक्ति द्वारा निर्धारित मानदंडों को पूरा नहीं करता है।",
"account-already-exists": "खाता पहले से मौजूद है!",
"choose-password": "पासवर्ड चुनें",
@@ -619,7 +685,7 @@
"date": "दिनांक",
"report-not-found": "रिपोर्ट नहीं मिली",
"please-wait": "कृपया प्रतीक्षा करें",
"select-foler": "फ़ोल्डर चुनें",
"select-folder": "फ़ोल्डर चुनें",
"generated-on": "पर उत्पन्न",
"text-field": "पाठ क्षेत्र",
"font-size": "फ़ॉन्ट आकार",
@@ -678,13 +744,13 @@
"public-template": "यह वीडियो दर्शाता है कि आप अपना व्यक्तिगत सार्वजनिक प्रोफ़ाइल कैसे सेट अप कर सकते हैं, जैसे 'https://opensign.me/your-username'। आप यह भी सीखेंगे कि अपनी टैगलाइन को कैसे अनुकूलित करें और अपने टेम्पलेट्स को सार्वजनिक हस्ताक्षर के लिए उपलब्ध कराएं।",
"allowModify-widgets": "आप इन फ़ील्ड में से किसी को भी दस्तावेज़ पर खींच और छोड़ सकते हैं, इसके अतिरिक्त दस्तावेज़ निर्माता द्वारा आपके लिए पहले से निर्दिष्ट फ़ील्ड के। "
},
"enter-email-plaholder": "एक ईमेल पता जोड़ें और एंटर दबाएं",
"enter-email-placeholder": "एक ईमेल पता जोड़ें और एंटर दबाएं",
"success-email-alert": "ईमेल सफलतापूर्वक भेजा गया!",
"expired-doc-title": "समाप्त दस्तावेज़",
"expired-on-mssg": "यह दस्तावेज़ {{expiredDate}} को समाप्त हो गया है और अब हस्ताक्षर के लिए उपलब्ध नहीं है।",
"signature-validate-alert": "कृपया पुष्टि करें कि आपने कम से कम {{minRequiredCount}} चेकबॉक्स चुने हैं।",
"signature-validate-alert-2": "सुनिश्चित करें कि यह फ़ील्ड सटीक रूप से भरा गया है और सभी आवश्यकताओं को पूरा करता है।",
"remaing-users": "शेष उपयोगकर्ता",
"remaining-users": "शेष उपयोगकर्ता",
"Role": "भूमिका",
"additional-users": "कृपया ऐड-ऑन उपयोगकर्ता खरीदें।",
"Quantity-of-users": "उपयोगकर्ताओं की मात्रा",
@@ -698,7 +764,7 @@
"select-date-format": "एक दिनांक प्रारूप चुनें",
"quantity-of-credits": "प्रीमियम क्रेडिट की मात्रा",
"remaining-credits": "प्रीमियम क्रेडिट उपलब्ध:",
"remaining-credits-help": "अपनी वेबसाइट पर एपीआई दस्तावेज़ हस्ताक्षर, थोक भेजने या OpenSign एकीकरण को एम्बेड करने के लिए प्रीमियम क्रेडिट का उपयोग करें। आपके पास {{allowedcredits}} शामिल क्रेडिट और {{addoncredits}} अतिरिक्त खरीदे ए क्रेडिट शेष हैं।",
"remaining-credits-help": "API दस्तावेज़ साइनिंग, बल्क सेंडिंग या अपनी वेबसाइट पर OpenSign इंटीग्रेशन एम्बेड करने के लिए प्रीमियम क्रेडिट का उपयोग करें। आपके पास {{allowedcredits}} शामिल क्रेडिट और {{addoncredits}} अतिरिक्त खरीदे हुए क्रेडिट शेष हैं। यदि आप Teams या Enterprise प्लान पर हैं, तो ये क्रेडिट आपकी कंपनी के सभी उपयोगकर्ताओं के बीच साझा किए जा सकते हैं, जब तक कि आपके खाते के लिए अकाउंट एडमिन ने कोई सीमा निर्धारित न की हो।",
"additional-credits": "कृपया प्रीमियम क्रेडिट खरीदें",
"quota-err-quicksend": "कोटा पहुँच गया, आपके पास पर्याप्त क्रेडिट नहीं हैं।",
"buy-credits": "प्रीमियम क्रेडिट खरीदें",
@@ -706,7 +772,7 @@
"rotate-left": "बाईं ओर घुमाएँ",
"rotate-alert-mssg": "इस पृष्ठ पर सभी विजेट खो जाएंगे। क्या आप वाकई आगे बढ़ना चाहते हैं?",
"templateid": "टेम्पलेट-आईडी",
"bulk-send-subcription-alert": "थोक भेजने का उपयोग करने के लिए कृपया व्यावसायिक या टीम योजना में अपग्रेड करें।",
"bulk-send-subscription-alert": "थोक भेजने का उपयोग करने के लिए कृपया व्यावसायिक या टीम योजना में अपग्रेड करें।",
"generate-test-token": "परीक्षण टोकन उत्पन्न करें",
"regenerate-test-token": "परीक्षण टोकन पुनः उत्पन्न करें",
"help-test-token": "इस टोकन का उपयोग https://sandbox.opensignlabs.com/api/v1 समापन बिंदु पर एपीआई का परीक्षण करने के लिए किया जा सकता है, जिससे आप असीमित दस्तावेज़ हस्ताक्षर कर सकते हैं। कृपया ध्यान दें कि सैंडबॉक्स एपीआई आपके दस्तावेज़ों पर स्व-हस्ताक्षरित प्रमाणपत्रों के साथ हस्ताक्षर करेगा, जिन्हें एडोब द्वारा मान्य के रूप में मान्यता नहीं दी जा सकती है। एक बार जब आप अपना परीक्षण पूरा कर लेते हैं, तो आप उत्पादन टोकन उत्पन्न करने के लिए हमारी किसी भुगतान योजना में अपग्रेड कर सकते हैं।",
@@ -726,8 +792,9 @@
"copied-code": "कॉपी किया गया",
"Installation": "इंस्टॉलेशन",
"Usage": "उपयोग",
"insufficient-credits": "अपर्याप्त हस्ताक्षर करेडिट",
"insufficient-credits-mssg": "इस दस्तावेज़ के स्वामी के पास वर्तमान में आपके हस्ताक्षर करने के लिए आवश्यक OpenSign क्रेडिट की कमी है। यदि आपको अधिक विवरण चाहिए तो कृपया स्वामी से संपर्क करें।",
"insufficient-credits": "हस्ताक्षर क्रेडिट अपर्याप्त हैं। कृपया {{ownerEmail}} से संपर्क करे",
"insufficient-credits-title": "अपर्याप्त हस्ताक्षर क्रेडिट",
"insufficient-credits-mssg": "इस दस्तावेज़ के स्वामी के पास वर्तमान में आपके हस्ताक्षर करने के लिए आवश्यक {{appName}} क्रेडिट की कमी है। यदि आपको अधिक विवरण चाहिए तो कृपया स्वामी से संपर्क करें।",
"angular-npm-mssg-1": "OpenSign को अपने Angular प्रोजेक्ट में एकीकृत करने के लिए, बस निम्नलिखित कमांड चलाएँ:",
"quota-mail-info-head": "मासिक ईमेल सीमा",
"quota-mail-info": "सेवा की गुणवत्ता बनाए रखने और स्पैम को रोकने के लिए, OpenSign निःशुल्क योजना पर प्रति माह 15 ईमेल तक की अनुमति देता है। असीमित ईमेल भेजने के लिए अभी अपग्रेड करें।",
@@ -746,6 +813,13 @@
"p3": "इस विकल्प का चयन करने पर ओटीपी सत्यापन अक्षम हो जाएगा, जिससे उपयोगकर्ता अतिरिक्त चरणों के बिना सीधे दस्तावेज़ पर हस्ताक्षर कर सकेंगे।",
"p4": "कृपया वह विकल्प चुनें जो आपके दस्तावेज़ हस्ताक्षर आवश्यकताओं के लिए सबसे उपयुक्त हो।"
},
"allow-offline-sign": "ऑफ़लाइन हस्ताक्षर की अनुमति दें",
"allow-offline-sign-help": {
"p1": "चुनें कि क्या हस्ताक्षरकर्ता बाहरी टूल से हस्ताक्षरित इस दस्तावेज़ की एक प्रति स्वामी की समीक्षा के लिए जमा कर सकते हैं।",
"p2": "हस्ताक्षरकर्ताओं को ऑफ़लाइन हस्ताक्षर विकल्प दिखाई देगा और वे बाहरी रूप से हस्ताक्षरित PDF स्वीकृति के लिए अपलोड कर सकते हैं।",
"p3": "हस्ताक्षरकर्ता केवल एप के भीतर ही हस्ताक्षर कर सकते हैं। ऑफ़लाइन हस्ताक्षर विकल्प छिपा दिया जाएगा।",
"p4": "यह सेटिंग डिफ़ॉल्ट रूप से आपकी खाता प्राथमिकता का उपयोग करती है और भुगतान योजनाओं पर उपलब्ध है।"
},
"advanced-options": "उन्नत विकल्प",
"hide-advanced-options": "उन्नत विकल्प छिपाएँ",
"download-files": "फ़ाइलें डाउनलोड करें",
@@ -808,6 +882,7 @@
"delete-folder-alert-1": "हटाने में असमर्थ: फ़ोल्डर खाली होना चाहिए।",
"defaultSign-alert": "{{widgetsType}} विजेट स्वतः हस्ताक्षर के लिए उपलब्ध नहीं हैं।",
"embed-heading": "{{appName}} टेम्पलेट एम्बेड करें",
"other-apps-by-opensign": "OpenSign टीम के अन्य ऐप्स",
"public-template-edit-title": "सार्वजनिक टेम्पलेट संपादित करें",
"public-template-edit-mssg": "आप सीधे सार्वजनिक टेम्पलेट संपादित नहीं कर सकते। परिवर्तन करने के लिए, टेम्पलेट को निजी पर सेट किया जाएगा, और आपको बाद में इसे फिर से सार्वजनिक करना होगा। क्या आप जारी रखना चाहते हैं?",
"agree-p1": "मैं पुष्टि करता हूं कि मैंने पढ़ लिया है और समझ लिया है ",
@@ -859,7 +934,6 @@
"js-snippet-msg-1": "आप टेम्पलेट आईडी इससे प्राप्त कर सकते हैं",
"js-snippet-msg-2": "टेम्पलेट प्रबंधित करें",
"js-snippet-msg-3": "पृष्ठ।",
"agrrement-alert": "आगे बढ़ने के लिए, आपको इलेक्ट्रॉनिक रिकॉर्ड और हस्ताक्षर प्रकटीकरण के लिए सहमति देनी होगी।",
"webhook-already-exists": "URL पहले से मौजूद है! कृपया कोई दूसरा प्रयास करें।",
"webhook-must-be-secure": "वेबहूक URL सुरक्षित होना चाहिए और https:// का उपयोग करना चाहिए",
"provide-test-token": "कृपया एपीआई परीक्षण टोकन उत्पन्न करें",
@@ -879,6 +953,7 @@
"p3": "इस विकल्प का चयन करने से हस्ताक्षरकर्ताओं को हस्ताक्षर प्रक्रिया के दौरान दस्तावेज़ में कोई भी সংযোজন करने से प्रतिबंधित किया जाता है।"
},
"signature-setting": "हस्ताक्षर सेटिंग्स",
"rotation": "घुमाव",
"activation-key": "सक्रियण कुंजी",
"paid-selfhost-help": "अधिक जानकारी के लिए कृपया {{appName}} से संपर्क करें ",
"remaining-credits-selfhost-help": "अपनी वेबसाइट पर एपीआई दस्तावेज़ हस्ताक्षर, थोक भेजने {{appName}} एकीकरण के लिए प्रीमियम क्रेडिट का उपयोग करें। आपके पास {{allowedcredits}} शामिल क्रेडिट और {{addoncredits}} अतिरिक्त खरीदे गए क्रेडिट शेष हैं।",
@@ -907,6 +982,8 @@
"visit-below-link": "अधिक जानने के लिए नीचे दिए गए लिंक पर जाएं -",
"storage-help": "BYOC सक्षम करने से आप अपना स्वयं का S3 संग्रहण कनेक्ट कर सकते हैं ताकि आपकी फ़ाइलें पूरी तरह से आपके नियंत्रण में रहें - कोई बाहरी प्रतियां बरकरार न रहें। यदि डेटा स्वायत्तता आपके लिए मायने रखती है, तो इस सुविधा को अनलॉक करने के लिए टीमों में अपग्रेड करने पर विचार करें।",
"daily-quota-reached": "आपने अपना दैनिक कोटा पूरा कर लिया है। सहायता के लिए, कृपया quotas@opensignlabs.com पर संपर्क करें।",
"email-not-verified-send": "ईमेल भेजने से पहले कृपया अपना ईमेल पता सत्यापित करें। ईमेल सत्यापन पूरा करने के लिए अपनी <1>प्रोफ़ाइल सेटिंग्स</1> में जाएँ।",
"email-not-verified-send-plain": "ईमेल भेजने से पहले कृपया अपना ईमेल पता सत्यापित करें। ईमेल सत्यापन पूरा करने के लिए अपनी प्रोफ़ाइल सेटिंग्स में जाएँ।",
"enabled-signature-type": "सक्षम हस्ताक्षर प्रकार",
"enabled-signature-type-help": "'सक्षम हस्ताक्षर प्रकार' सेटिंग यह निर्धारित करती है कि आपके संगठन में कौन से हस्ताक्षर विकल्प उपलब्ध हैं। उदाहरण के लिए, यदि आप 'ड्रा' विकल्प को अक्षम करते हैं, तो आपके संगठन के सदस्य इसे हस्ताक्षर विजेट में नहीं देख पाएंगे, जबकि अन्य तीन विकल्प पहुंच योग्य रहेंगे।",
"indexing-public-profile": "खोज इंजन द्वारा सार्वजनिक प्रोफ़ाइल को अनुक्रमित करने की अनुमति दें",
@@ -934,6 +1011,7 @@
"please-provide-templateid": "कृपया टेम्पलेट आईडी प्रदान करें",
"this-template-is-not-public": "यह टेम्पलेट सार्वजनिक नहीं है",
"invalid-templateid": "अमान्य टेम्पलेट आईडी",
"kiosk-no-active-role": "इस टेम्पलेट में कियोस्क हस्ताक्षर के लिए कोई हस्ताक्षरकर्ता या अनुमोदक भूमिका उपलब्ध नहीं है।",
"contact-billing-at-opensign": "अधिक सीटें जोड़ने के लिए, कृपया सहायता के लिए <1>billing@opensignlabs.com</1> पर OpenSign™ से संपर्क करें",
"title-length-alert": "शीर्षक अधिकतम 250 वर्ण लंबा होना चाहिए।",
"note-length-alert": "टिप्पणी अधिकतम 200 वर्ण लंबी होनी चाहिए।",
@@ -1023,7 +1101,7 @@
},
"signer-already-present": "हस्ताक्षरकर्ता पहले से मौजूद है",
"kiosk-sign": "कियोस्क साइन",
"dont-have-access-to-template": "टेम्पलेट हटा दिया गया है या आपके पास पहुंच नहीं है। कृपया प्रेषक से संपर्क करें।",
"do-not-have-access-to-template": "टेम्पलेट हटा दिया गया है या आपके पास पहुंच नहीं है। कृपया प्रेषक से संपर्क करें।",
"kiosk-info": "कियोस्क मोड आपको व्यक्तिगत रूप से हस्ताक्षर शीघ्रता और कुशलता से एकत्र करने देता है। ट्रेड शो, कार्यक्रमों या वॉक-इन परिदृश्यों के लिए आदर्श जहां सभी हस्ताक्षरकर्ता शारीरिक रूप से मौजूद हों। ",
"learn-more": "और जानें",
"finish-mssg": " क्या आप वाकई दस्तावेज़ समाप्त करना चाहते हैं?",
@@ -1079,8 +1157,6 @@
"search-contacts": "संपर्क खोजें…",
"add-role-alert": "कृपया कम से कम एक भूमिका जोड़ें",
"edit-draft": "ड्राफ्ट संपादित करें",
"invalid-email-found": "अमान्य ईमेल पाया गया: {{email}}",
"duplicate-email-found": "डुप्लिकेट ईमेल पाया गया: {{email}}",
"vertical": "वर्टिकल",
"horizontal": "हॉरिज़ॉन्टल",
"billing": "बिलिंग",
@@ -1094,7 +1170,7 @@
"access-denied": "पहुंच अस्वीकृत",
"upgrade": "अपग्रेड",
"do-not-access-app": "आपको इस एप्लिकेशन तक पहुंच नहीं है।",
"dont-have-access": "आपको पहुंच नहीं है।",
"do-not-have-access": "आपको पहुंच नहीं है।",
"valid-email-alert": "कृपया एक मान्य ईमेल पता दर्ज करें।",
"otp-not-validate": "ओटीपी मान्य नहीं है।",
"domain-not-allowed": "यह डोमेन अनुमति नहीं है",
@@ -1111,10 +1187,12 @@
"branding-help": "ब्रांडिंग आपकी ऐप में वाइट लेबलिंग की सुविधा देती है",
"custom-sub-domain": "कस्टम सब-डोमेन",
"app-name": "ऐप का नाम",
"provide-domain-name": "अपना डोमेन नाम दर्ज करें",
"provide-domain-name": "केवल सबडोमेन की अनुमति है (उदा., app.yourdomain.com)। yourdomain.com जैसे प्राथमिक/रूट डोमेन समर्थित नहीं हैं।",
"provide-app-name-help": "एक अनोखा ऐप नाम प्रदान करें। यह एप्लिकेशन के भीतर आपके ब्रांड नाम के रूप में और सभी ईमेल में, जहाँ भी ब्रांडिंग लागू हो, प्रदर्शित होगा।",
"upload-app-logo": ".png, .jpg या .svg फ़ॉर्मेट में लोगो अपलोड करें। सर्वोत्तम डिस्प्ले गुणवत्ता के लिए अनुशंसित आकार: {{size}} पिक्सेल (5:2 आस्पेक्ट रेशियो)।",
"upload-app-favicon": "एक चौकोर फ़ेविकॉन इमेज (.ico, .png या .svg) अपलोड करें। सर्वोत्तम ब्राउज़र अनुकूलता के लिए अनुशंसित आकार: {{size}} पिक्सेल।",
"provide-app-name": "अपना ऐप नाम दर्ज करें",
"logo": "लोगो",
"upload-app-logo": "अपने ऐप का लोगो अपलोड करें",
"prefill-unfilled-widget": "निम्न अनिवार्य फ़ील्ड खाली नहीं छोड़े जा सकते: {{emptyWidget}}। कृपया आगे बढ़ने के लिए इन्हें भरें।",
"Dashboard": "डैशबोर्ड",
"Analytics": "विश्लेषिकी",
@@ -1237,6 +1315,7 @@
},
"read-only-date-error": "रीड-ओनली दिनांक विजेट में एक डिफ़ॉल्ट दिनांक होना आवश्यक है।",
"set-date": "दिनांक सेट करें",
"default-date": "डिफ़ॉल्ट तिथि",
"set-today": "हस्ताक्षर की तिथि",
"enter-name": "नाम दर्ज करें",
"enter-email": "ईमेल दर्ज करें",
@@ -1256,5 +1335,241 @@
"Please provide required parameters.": "कृपया आवश्यक पैरामीटर प्रदान करें।",
"reset-password": "उपयोगकर्ता पासवर्ड रीसेट करें",
"enter-strong-password": "एक मज़बूत पासवर्ड दर्ज करें या स्वचालित रूप से उत्पन्न करें।",
"autogenerate": "स्वतः उत्पन्न करें"
}
"autogenerate": "स्वतः उत्पन्न करें",
"extend": "बढ़ाएँ",
"console-general-help": "व्यक्तिगत उपयोगकर्ताओं को अपनी स्वयं की ईमेल टेम्पलेट्स अनुकूलित करने की अनुमति देने के लिए, जाएँ",
"console-mail-help": "कंपनी-व्यापी ईमेल टेम्पलेट्स अपडेट करने के लिए, जाएँ",
"admin-setting-disabled": "यह सेटिंग अक्षम है। सहायता के लिए कृपया व्यवस्थापक से संपर्क करें।",
"company-email-settings": "कंपनी ईमेल सेटिंग्स",
"allow-email-template-individually": "उपयोगकर्ताओं को व्यक्तिगत रूप से ईमेल टेम्पलेट अनुकूलित करने की अनुमति दें ",
"user-email-template-general-help": "इसे निष्क्रिय करने पर उपयोगकर्ता अपनी ईमेल टेम्पलेट्स को अनुकूलित नहीं कर पाएंगे।",
"disable-individual-user-smtp": "व्यक्तिगत उपयोगकर्ता SMTP सेटिंग्स अक्षम करें",
"enable-individual-user-smtp": "व्यक्तिगत उपयोगकर्ता SMTP सेटिंग्स सक्षम करें",
"user-smtp-general-help": "जब यह निष्क्रिय हो, तो उपयोगकर्ता अपनी स्वयं की SMTP सेटिंग्स कॉन्फ़िगर नहीं कर पाएंगे। सभी ईमेल ग्लोबल SMTP कॉन्फ़िगरेशन का उपयोग करेंगे।",
"global-smtp-settings": "ग्लोबल SMTP सेटिंग्स",
"global-smtp-settings-help-p1": "ये सेटिंग्स आपके पूरे {{appName}} खाते में वैश्विक रूप से लागू होती हैं। यदि किसी उपयोगकर्ता ने अपनी स्वयं की ईमेल डिलीवरी सेटिंग्स कॉन्फ़िगर नहीं की हैं, तो सिस्टम स्वचालित रूप से यहाँ परिभाषित ग्लोबल SMTP सेटिंग्स का उपयोग करेगा।",
"global-smtp-settings-help-p2": "सभी हस्ताक्षर अनुरोध ईमेल की बेहतर इनबॉक्स डिलीवरी सुनिश्चित करने के लिए, आप अपना पसंदीदा ईमेल प्रदाता जोड़ सकते हैं। निम्न विकल्पों में से एक चुनें:",
"global-connect-to-gmail-help": "अपनी संस्था की इनबॉक्स से सीधे हस्ताक्षर अनुरोध ईमेल भेजने के लिए Gmail खाते का उपयोग करें। यह डिलीवरी दर, प्रतिष्ठा और विश्वसनीयता में सुधार करता है।",
"global-connect-to-smtp-help": "अपने डोमेन के माध्यम से ईमेल भेजने के लिए अपना स्वयं का SMTP सर्वर उपयोग करें। यह आपकी ईमेल संरचना पर पूर्ण नियंत्रण प्रदान करता है, डिलीवरी को बेहतर बनाता है और ब्रांड की निरंतरता बनाए रखता है।",
"global-smtp-settings-help-p3": "यदि कोई व्यक्तिगत उपयोगकर्ता-स्तरीय सेटिंग्स कॉन्फ़िगर नहीं की गई हैं, तो ये वैश्विक सेटिंग्स स्वचालित रूप से लागू होंगी।",
"hide-text-with-asterisks": "टेक्स्ट को तारांकन (*) के साथ छुपाएँ",
"hide-text-with-asterisks-help": "हस्ताक्षरित दस्तावेज़ पर टेक्स्ट दृश्य से छिपा रहेगा, लेकिन दस्तावेज़ भेजने वाले (मालिक) के लिए उनके {{appName}} खाते से सुलभ रहेगा।",
"document-id": "दस्तावेज़ ID",
"sent-date": "भेजे जाने की तिथि",
"status": "स्थिति",
"signer-form-data-for": "हस्ताक्षरकर्ता के लिए फ़ॉर्म डेटा: {{signerName}} <{{signerEmail}}>",
"widget-name": "विजेट नाम",
"widget-responses": "विजेट प्रतिक्रियाएँ",
"formdata-download-note": "नोट: हस्ताक्षर, स्टैम्प, प्रारंभिक अक्षर और छवियाँ निर्यात नहीं की जाएँगी। ऑफ़लाइन हस्ताक्षरकर्ताओं का डेटा उपलब्ध नहीं होगा।",
"export-all-formdata-error": "आप फ़ॉर्म डेटा निर्यात नहीं कर सकते क्योंकि टेम्पलेट में डुप्लिकेट विजेट नाम हैं। कृपया नया टेम्पलेट बनाएं ताकि आप फ़ॉर्म डेटा निर्यात कर सकें।",
"export-formdata-error": "आप फ़ॉर्म डेटा निर्यात नहीं कर सकते क्योंकि दस्तावेज़ में डुप्लिकेट विजेट नाम हैं। कृपया नया दस्तावेज़ बनाएं जिसमें डुप्लिकेट विजेट नाम न हों।",
"duplicate-widget-name-error": "यह विजेट नाम पहले से ही किसी अन्य साइनर को सौंपा गया है। कृपया कोई दूसरा नाम चुनें।",
"duplicate-template-widget-error": "टेम्पलेट को डुप्लिकेट नहीं किया जा सकता क्योंकि कुछ विजेट नाम अलग-अलग भूमिकाओं में डुप्लिकेट हैं। डुप्लिकेट विजेट्स का नाम बदलें और फिर से प्रयास करें।",
"credit-alert": "क्रेडिट अलर्ट",
"document-form-data": "दस्तावेज़ फ़ॉर्म डेटा",
"view-formdata-paid-title-error": "'फ़ॉर्म डेटा देखें' सुविधा आपको हस्ताक्षर करते समय साइनरों द्वारा भरे गए विजेट डेटा को प्रिंट और डाउनलोड करने की सुविधा देती है.",
"view-formdata-paid-error": "नोट: 'फ़ॉर्म डेटा देखें' सुविधा केवल उन दस्तावेज़ों के लिए उपलब्ध है जो v2.32.0 या उसके बाद बनाए गए हों और जिनमें डुप्लिकेट विजेट नाम न हों.",
"download-all-formdata-paid-title-error": "'सभी फ़ॉर्म डेटा डाउनलोड करें' सुविधा आपको टेम्पलेट से बनाए गए सभी दस्तावेज़ों के लिए साइनरों द्वारा भरे गए विजेट डेटा को डाउनलोड करने देती है.",
"download-all-formdata-paid-error": "नोट: 'सभी फ़ॉर्म डेटा डाउनलोड करें' सुविधा केवल उन टेम्पलेट्स के लिए उपलब्ध है जो v2.32.0 या उसके बाद बनाए गए हों और जिनमें डुप्लिकेट विजेट नाम न हों.",
"api-token-not-found": "API टोकन नहीं मिला।",
"sub-head-api-credits": "इस उपयोगकर्ता द्वारा उपभोग किए जाने वाले API क्रेडिट की अधिकतम संख्या निर्धारित करें।",
"consumed-credits": "उपयोग किए गए क्रेडिट",
"allowed-api-credits": "अनुमत API क्रेडिट",
"allowed-api-credits-help": "अनुमत क्रेडिट उपयोग किए गए क्रेडिट के बराबर या उससे अधिक होने चाहिए।",
"Enter-allowed-api-credits": "अनुमत API क्रेडिट दर्ज करें",
"login-to-sandbox": "सैंडबॉक्स में लॉगिन करें",
"your-credits-usage": "आपके क्रेडिट उपयोग",
"remaining": "शेष",
"used": "उपयोग किए गए",
"your-credits-usage-help": "यह आपके API कुंजी द्वारा उपभोग किए गए क्रेडिट की संख्या है।",
"api-credits-updated": "API क्रेडिट सफलतापूर्वक आवंटित कर दिए गए हैं।",
"min-date": "न्यूनतम तिथि",
"max-date": "अधिकतम तिथि",
"invalid-min-date": "अमान्य न्यूनतम तिथि।",
"min-less-max": "न्यूनतम तिथि अधिकतम तिथि से कम या उसके बराबर होनी चाहिए।",
"invalid-max-date": "अमान्य अधिकतम तिथि।",
"max-less-min": "अधिकतम तिथि न्यूनतम तिथि से अधिक या उसके बराबर होनी चाहिए।",
"date-range": "तिथि सीमा",
"date-range-help": "चयन को सीमित करने के लिए न्यूनतम और/या अधिकतम तिथि सेट करें। चयनित तिथि इस सीमा के भीतर होनी चाहिए।",
"option-disabled-by-owner": "दस्तावेज़ निर्माता ने इस विकल्प को अक्षम कर दिया है",
"pen-colors": "पेन के रंग",
"pen-colors-help": "हस्ताक्षर और प्रारंभिक विजेट्स के लिए उपलब्ध पेन रंग चुनें। किसी रंग को दिखाने के लिए चेक करें और छिपाने के लिए अनचेक करें।",
"security-key": "सुरक्षा कुंजी",
"remove-limit": "सीमा हटाएँ",
"removed-api-credits": "API क्रेडिट सीमा हटा दी गई है।",
"enable-authentication": "प्रमाणीकरण सक्षम करें",
"working": "कार्यरत",
"test-security-key": "सुरक्षा कुंजी का परीक्षण करें",
"generate": "जनरेट करें",
"regenerate": "पुनः जनरेट करें",
"signing-date": "हस्ताक्षर तिथि",
"date-widget": "तिथि विजेट",
"mail-failed": "ईमेल भेजते समय कुछ गलत हो गया। कृपया पुनः प्रयास करें।",
"mail-status-head": "अगला कदम: अपने दस्तावेज़ पर हस्ताक्षर करें?",
"send-to-email": "ईमेल पर भेजें",
"date-pref-help-sub-title": "डेट विजेट प्राथमिकताएँ आपको डेट विजेट के लिए डिफ़ॉल्ट सेटिंग्स निर्धारित करने की अनुमति देती हैं। जब भी आप किसी दस्तावेज़ या टेम्पलेट में नया डेट विजेट जोड़ते हैं, ये डिफ़ॉल्ट सेटिंग्स स्वतः लागू हो जाती हैं। आप गियर आइकन पर क्लिक करके किसी भी डेट विजेट को कभी भी अनुकूलित कर सकते हैं।",
"date-pref-help-format": "डेट विजेट के लिए उपयोग किए जाने वाले डिफ़ॉल्ट तिथि प्रारूप का चयन करें।",
"date-pref-help-default-date": "डेट विजेट के लिए एक डिफ़ॉल्ट तिथि मान सेट करें।",
"date-pref-help-signing-date": "सक्षम होने पर, डेट विजेट स्वतः उस तिथि का उपयोग करता है जब साइनर दस्तावेज़ पर हस्ताक्षर करता है।",
"date-pref-help-read-only": "सक्षम होने पर, डेट विजेट साइनर के लिए केवल-पढ़ने योग्य होता है।",
"upload-csv": "CSV अपलोड करें",
"file-selected": "फ़ाइल चयनित",
"bulk-send-csv-helper": "साइनर भूमिकाओं (भूमिका ईमेल/नाम/फ़ोन) और प्रीफ़िल मानों (प्रीफ़िल: फ़ील्ड नाम) के लिए हेडर का उपयोग करें। आवश्यक प्रीफ़िल फ़ील्ड भरना अनिवार्य है।",
"bulk-send-csv-helper-note": "नोट: CSV में तिथि-प्रकार के प्रीफ़िल फ़ील्ड्स के लिए मान इस प्रारूप में प्रदान किया जाना चाहिए: {{value}} (उदाहरण: {{example}})।",
"bulk-send-csv-summary": "CSV से {{total}} पंक्तियाँ आयात की गईं।",
"bulk-send-csv-missing-headers": "आवश्यक CSV हेडर अनुपलब्ध हैं। भूमिकाएँ: {{roles}}।",
"bulk-send-no-records": "भेजने से पहले कृपया कम से कम एक प्राप्तकर्ता जोड़ें।",
"csv-only-support": "केवल CSV फ़ाइलें समर्थित हैं।",
"csv-no-records": "CSV में कोई रिकॉर्ड नहीं मिला।",
"select-at-least-option": "कृपया कम से कम एक विकल्प चुनें।",
"draw-required": "कृपया बॉक्स में ड्रॉ करें।",
"enter-smtp-username": "अपना SMTP उपयोगकर्ता नाम दर्ज करें",
"enter-webhook-url": "Webhook URL दर्ज करें",
"enter-user-name": "उपयोगकर्ता नाम दर्ज करें",
"enter-tagline": "टैगलाइन दर्ज करें",
"enter-password-or-click-autogenerate": "पासवर्ड दर्ज करें या ऑटो-जनरेट पर क्लिक करें",
"enter-daily-email-quota": "दैनिक ईमेल कोटा दर्ज करें",
"enter-custom-regular-expression": "कस्टम रेगुलर एक्सप्रेशन दर्ज करें",
"enter-value": "{{value}} दर्ज करें",
"are-you-sure-want-to-send-document": "क्या आप वाकई दस्तावेज़ भेजना चाहते हैं?",
"favicon": "फ़ेविकॉन",
"delete-two-factor-authentication": "क्या आप वाकई दो-कारक प्रमाणीकरण हटाना चाहते हैं?",
"paste-html-here": "यहाँ HTML पेस्ट करें",
"open-email-builder": "आप <1>email builder</1> प्लेटफ़ॉर्म का उपयोग करके ईमेल टेम्पलेट बना सकते हैं और HTML कोड कॉपी कर सकते हैं।",
"contact-saved": "संपर्क सफलतापूर्वक सहेजा गया।",
"remove-limit-of-user": "क्या आप वाकई इस उपयोगकर्ता की सीमा हटाना चाहते हैं? इसके बाद यह उपयोगकर्ता आपकी संस्था के सभी उपलब्ध प्रीमियम क्रेडिट का उपयोग कर सकेगा, जो सभी उपयोगकर्ताओं के बीच साझा किए जाते हैं।",
"current-expiry-date": "वर्तमान समाप्ति तिथि",
"reminder-error": "“हर (दिन) में एक बार याद दिलाएं” का मान “पूरा करने का समय (दिन)” से अधिक नहीं होना चाहिए।",
"showing-to-of-records": "{{totalRows}} रिकॉर्ड में से {{record}} से {{recordPerPage}} तक दिखाया जा रहा है",
"records-per-page": "प्रति पृष्ठ रिकॉर्ड",
"email-not-found-in-row": "पंक्ति {{row}} में ईमेल नहीं मिला।",
"invalid-email-found-in-row": "पंक्ति {{row}} में अमान्य ईमेल मिला: {{email}}",
"duplicate-email-found-in-row": "पंक्ति {{row}} में डुप्लिकेट ईमेल मिला: {{email}}",
"prefill": "प्रीफ़िल",
"recipients-&-data": "प्राप्तकर्ता और डेटा",
"response": "प्रतिक्रिया",
"prefill-tab-help": "ये सामान्य टेम्पलेट फ़ील्ड हैं जो इस बल्क सेंड के सभी दस्तावेज़ों पर लागू होंगे।",
"recipient-tab-help-p1": "इस अगले चरण में, आप:",
"recipient-tab-help-p2": "प्राप्तकर्ता विवरण के साथ CSV अपलोड करेंगे या",
"recipient-tab-help-p3": "प्राप्तकर्ता जानकारी मैन्युअल रूप से दर्ज करेंगे",
"recipient-tab-help-p4": "प्रत्येक प्राप्तकर्ता को इस टेम्पलेट से जनरेट किया गया अपना दस्तावेज़ प्राप्त होगा।",
"response-tab-help": "अपनी प्रतिक्रियाओं की समीक्षा करें।",
"summary": "सारांश",
"prefill-fields": "प्रीफ़िल फ़ील्ड",
"documents": "दस्तावेज़",
"message": "संदेश",
"formula-help-p1": "संख्यात्मक इनपुट एकत्र करें और सूत्रों का उपयोग करके डायनामिक गणनाएँ करें।",
"example": "उदाहरण",
"formula-help-p2": "number-3 में कुल दिखाने के लिए {{number-1}} + {{number-2}} का उपयोग करें।",
"formula-help-p3": "number-4 में मान निकालने के लिए ({{number-1}} * {{number-2}}) / {{number-3}} का उपयोग करें।",
"formula-help-p4": "प्रत्येक नंबर विजेट का एक अद्वितीय नाम होना चाहिए (जैसे number-1), क्योंकि सूत्र इन पहचानकर्ताओं पर निर्भर करते हैं।",
"note-consume-premium-credits": "नोट: यह सुविधा प्रीमियम क्रेडिट का उपयोग करती है।",
"note-consume-premium-credits-on-document-send": "नोट: यह सुविधा प्रीमियम क्रेडिट का उपयोग करती है। दस्तावेज़ भेजे जाने पर हर बार क्रेडिट उपयोग किए जाते हैं, चाहे दस्तावेज़ पर हस्ताक्षर किए जाएँ या नहीं।",
"webhook-authentication-help": "{{appName}} प्रत्येक वेबहुक को सुरक्षित करता है, आपके वेबहुक सुरक्षा कुंजी और मूल अनुरोध पेलोड का उपयोग करके HMAC-SHA256 हस्ताक्षर उत्पन्न करके। यह हस्ताक्षर x-webhook-signature हेडर में भेजा जाता है। आपके सर्वर को प्राप्त पेलोड और आपकी सीक्रेट कुंजी का उपयोग करके वही हस्ताक्षर उत्पन्न करना होगा। यदि दोनों हस्ताक्षर मेल खाते हैं, तो वेबहुक अनुरोध प्रमाणिक है और उसमें कोई परिवर्तन नहीं किया गया है।",
"invalid-widget-found-in-row": "पंक्ति {{row}} में विजेट {{widget}} के लिए अमान्य मान पाया गया।",
"all-signer-status": "सभी हस्ताक्षरकर्ताओं की स्थिति",
"viewed": "देखा गया",
"signed": "हस्ताक्षरित",
"switch-to-advanced": "एडवांस्ड पर स्विच करें",
"switch-to-basic": "बेसिक पर स्विच करें",
"use-name-as-sender": "प्रेषक के रूप में प्रोफ़ाइल नाम का उपयोग करें",
"use-name-as-sender-help": "सक्षम होने पर, {{appName}} हस्ताक्षर अनुरोध और पूर्णता ईमेल के लिए आपके प्रोफ़ाइल नाम को प्रेषक के रूप में उपयोग करता है।",
"acro-fields-detected": "फॉर्म फ़ील्ड मिले",
"acro-fields-detected-msg": "अपलोड किए गए PDF में {{count}} फॉर्म फ़ील्ड पाए गए। क्या आप स्वचालित रूप से संबंधित विजेट सम्मिलित करना चाहेंगे?",
"documents-count": "दस्तावेज़ों की संख्या",
"templates-count": "टेम्पलेट्स की संख्या",
"emails-sent": "भेजे गए ईमेल",
"storage-used": "उपयोग किया गया स्टोरेज",
"docs-count-team-help": "आपकी संगठनों में सभी उपयोगकर्ताओं द्वारा बनाए गए दस्तावेज़ों की कुल संख्या।",
"docs-count-help": "आपके द्वारा बनाए गए दस्तावेज़ों की कुल संख्या।",
"templates-count-team-help": "आपकी संगठनों में सभी उपयोगकर्ताओं द्वारा बनाए गए टेम्पलेट्स की कुल संख्या।",
"templates-count-help": "आपके द्वारा बनाए गए टेम्पलेट्स की कुल संख्या।",
"emails-sent-team-help": "आपकी संगठनों में भेजी गई ईमेल सूचनाओं की कुल संख्या।",
"emails-sent-help": "आपके द्वारा भेजी गई ईमेल सूचनाओं की कुल संख्या।",
"storage-used-team-help": "आपकी संगठनों में सभी उपयोगकर्ताओं द्वारा उपयोग किया गया कुल स्टोरेज स्पेस।",
"storage-used-help": "आपके द्वारा उपयोग किया गया कुल स्टोरेज स्पेस।",
"cloud": "क्लाउड",
"on-premises": "ऑन-प्रिमाइसेस",
"plan-name-free": "OPENSIGN™ FREE",
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
"plan-name-teams": "OPENSIGN™ TEAMS",
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
"plan-subtitle-free": "निःशुल्क असीमित ई-हस्ताक्षर, हमेशा के लिए।",
"plan-subtitle-professional": "उन्नत सुविधाओं तक विशेष पहुँच।",
"plan-subtitle-scalable": "प्राथमिकता समर्थन के साथ स्केलेबल सुविधाएँ।",
"subscribe": "सदस्यता लें",
"contact-us": "हमसे संपर्क करें",
"benefit-unlimited-digital-signatures": "असीमित डिजिटल हस्ताक्षर",
"benefit-sign-docs-yourself": "स्वयं दस्तावेज़ पर हस्ताक्षर करें",
"benefit-request-signatures": "दूसरों से हस्ताक्षर का अनुरोध करें",
"benefit-unlimited-templates": "असीमित टेम्पलेट",
"benefit-14-field-types": "14 फ़ील्ड प्रकार",
"benefit-automatic-e-signatures": "स्वचालित ई-हस्ताक्षर",
"benefit-completion-certificates": "समापन प्रमाण पत्र",
"benefit-send-in-order": "क्रम में भेजें",
"benefit-organize-docs": "OpenSign™ Drive में दस्तावेज़ व्यवस्थित करें",
"benefit-document-templates": "दस्तावेज़ टेम्पलेट",
"benefit-import-from-dropbox": "Dropbox से आयात करें",
"benefit-contact-book": "संपर्क पुस्तक",
"benefit-document-expiry": "दस्तावेज़ समाप्ति समर्थन",
"benefit-decline-document": "दस्तावेज़ अस्वीकृति समर्थन",
"benefit-email-notifications": "ईमेल सूचनाएं",
"benefit-public-profiles": "सार्वजनिक प्रोफ़ाइल",
"benefit-and-much-more": "और बहुत कुछ",
"benefit-everything-free": "OpenSign™ Free में सब कुछ",
"benefit-field-validations": "फ़ील्ड सत्यापन",
"benefit-regex-validations": "नियमित अभिव्यक्ति सत्यापन",
"benefit-webhooks": "वेबहुक",
"benefit-zapier": "Zapier एकीकरण",
"benefit-api-access": "API एक्सेस",
"benefit-api-signatures-100": "100 तक API हस्ताक्षर",
"benefit-api-signatures-240": "240 तक API हस्ताक्षर",
"benefit-custom-email-templates": "कस्टम ईमेल टेम्पलेट",
"benefit-custom-smtp": "ईमेल भेजने के लिए अपना Gmail या SMTP खाता कनेक्ट करें",
"benefit-auto-reminders": "स्वचालित अनुस्मारक",
"benefit-bulk-send-100": "बल्क भेजें (100 दस्तावेज़ तक)",
"benefit-bulk-send-240": "बल्क भेजें (240 दस्तावेज़ तक)",
"benefit-premium-profiles": "प्रीमियम सार्वजनिक प्रोफ़ाइल उपयोगकर्ता नाम",
"benefit-email-verification": "हस्ताक्षरकर्ता की पहचान की पुष्टि के लिए ईमेल-आधारित सत्यापन लागू करें",
"benefit-embedded-signing": "एम्बेडेड हस्ताक्षर",
"benefit-everything-professional": "OpenSign™ Professional में सब कुछ",
"benefit-teams-organizations": "टीम और संगठन",
"benefit-share-templates-teams": "टीमों के साथ टेम्पलेट साझा करें",
"benefit-share-templates-individuals": "व्यक्तियों के साथ टेम्पलेट साझा करें",
"benefit-byoc": "BYOC - अपने दस्तावेज़ अपने क्लाउड स्टोरेज में संग्रहीत करें",
"benefit-doc-id-removal": "हस्ताक्षरित दस्तावेज़ों से DocumentId हटाना",
"benefit-request-payments": "भुगतान का अनुरोध (जल्द आ रहा है)",
"benefit-mobile-app": "मोबाइल ऐप (जल्द आ रहा है)",
"benefit-api-signatures-500": "500 तक API हस्ताक्षर",
"benefit-bulk-send-500": "बल्क भेजें (500 दस्तावेज़ तक)",
"benefit-all-features": "सभी सुविधाएँ",
"benefit-custom-domain": "कस्टम डोमेन",
"benefit-custom-branding": "कस्टम ब्रांडिंग",
"benefit-uptime-sla": "अपटाइम SLA",
"benefit-sso-support": "SSO समर्थन",
"benefit-priority-support": "प्राथमिकता समर्थन",
"benefit-3-users-1500-credits": "3 उपयोगकर्ता और 1500 प्रीमियम क्रेडिट शामिल हैं",
"benefit-public-links": "सार्वजनिक लिंक",
"benefit-kiosk-mode": "कियोस्क मोड",
"benefit-bulk-send-1500": "बल्क भेजें (1500 दस्तावेज़ तक)",
"benefit-connect-gmail-smtp": "अपना Gmail या SMTP कनेक्ट करें",
"benefit-analytics": "विश्लेषण",
"benefit-bulksend-api-pro-month-help": "इस प्लान में प्रति माह 100 प्रीमियम क्रेडिट मिलते हैं, जिनसे आप API के जरिए दस्तावेज़ भेज सकते हैं, पब्लिक साइनिंग लिंक शेयर कर सकते हैं, बल्क सिग्नेचर रिक्वेस्ट चला सकते हैं, इन-पर्सन साइनिंग के लिए Kiosk मोड का उपयोग कर सकते हैं, या अपनी वेबसाइट पर सीधे एम्बेड किए गए दस्तावेज़ों से सिग्नेचर एकत्र कर सकते हैं। हर बार जब आप किसी दस्तावेज़ को शुरू (initiate) करते हैं या भेजते हैं, 1 क्रेडिट उपयोग होता है — और उस एक दस्तावेज़ में अधिकतम 10 साइनर हो सकते हैं।",
"benefit-bulksend-api-pro-year-help": "इस प्लान में प्रति वर्ष 240 प्रीमियम क्रेडिट मिलते हैं, जिनसे आप API के जरिए दस्तावेज़ भेज सकते हैं, पब्लिक साइनिंग लिंक शेयर कर सकते हैं, बल्क सिग्नेचर रिक्वेस्ट चला सकते हैं, इन-पर्सन साइनिंग के लिए Kiosk मोड का उपयोग कर सकते हैं, या अपनी वेबसाइट पर सीधे एम्बेड किए गए दस्तावेज़ों से सिग्नेचर एकत्र कर सकते हैं। हर बार जब आप किसी दस्तावेज़ को शुरू (initiate) करते हैं या भेजते हैं, 1 क्रेडिट उपयोग होता है — और उस एक दस्तावेज़ में अधिकतम 10 साइनर हो सकते हैं।",
"benefit-bulksend-api-team-month-help": "इस प्लान में प्रति माह 100 प्रीमियम क्रेडिट मिलते हैं, जिनसे आप API के जरिए दस्तावेज़ भेज सकते हैं, पब्लिक साइनिंग लिंक शेयर कर सकते हैं, बल्क सिग्नेचर रिक्वेस्ट चला सकते हैं, इन-पर्सन साइनिंग के लिए Kiosk मोड का उपयोग कर सकते हैं, या अपनी वेबसाइट पर सीधे एम्बेड किए गए दस्तावेज़ों से सिग्नेचर एकत्र कर सकते हैं। हर बार जब आप किसी दस्तावेज़ को शुरू (initiate) करते हैं या भेजते हैं, 1 क्रेडिट उपयोग होता है — और उस एक दस्तावेज़ में अधिकतम 10 साइनर हो सकते हैं।",
"benefit-bulksend-api-team-year-help": "इस प्लान में प्रति वर्ष 500 प्रीमियम क्रेडिट मिलते हैं, जिनसे आप API के जरिए दस्तावेज़ भेज सकते हैं, पब्लिक साइनिंग लिंक शेयर कर सकते हैं, बल्क सिग्नेचर रिक्वेस्ट चला सकते हैं, इन-पर्सन साइनिंग के लिए Kiosk मोड का उपयोग कर सकते हैं, या अपनी वेबसाइट पर सीधे एम्बेड किए गए दस्तावेज़ों से सिग्नेचर एकत्र कर सकते हैं। हर बार जब आप किसी दस्तावेज़ को शुरू (initiate) करते हैं या भेजते हैं, 1 क्रेडिट उपयोग होता है — और उस एक दस्तावेज़ में अधिकतम 10 साइनर हो सकते हैं।",
"failed-pdf-for-prefill-processing": "प्रीफिल प्रोसेसिंग के लिए PDF लोड करने में विफल।",
"attach-all-role-to-signer": "कृपया सभी भूमिकाएँ साइनर से जोड़ें।",
"upgrade-to-edit": "एडिट करने के लिए अपग्रेड करें",
"hide-live-chat-for-signers": "साइनर के लिए लाइव चैट छुपाएं",
"hide-live-chat-for-signers-help": "सक्षम होने पर, साइनिंग पेजों पर साइनर के लिए लाइव चैट विजेट छुपाए जाएंगे।",
"attachments-creator-hint": "हस्ताक्षरकर्ता ऐसी फ़ाइलें अपलोड कर सकते हैं जो दस्तावेज़ के अंत में जोड़ी जाएँगी।",
"attachments-drop-hint": "फ़ाइलें यहाँ छोड़ें या चुनने के लिए क्लिक करें",
"attachments-limits": "PDF, DOCX, PNG, JPG. अधिकतम {{maxFiles}} फ़ाइलें, प्रत्येक {{maxSizeMB}} MB।",
"attachments-invalid-type": "असमर्थित फ़ाइल प्रकार: {{name}}",
"attachments-max-files": "अधिकतम {{maxFiles}} संलग्नक अनुमत हैं।",
"attachments-remove-confirm": "हटाने से केवल मेटाडेटा हटेगा। पहले से जोड़े गए पृष्ठ दस्तावेज़ में बने रहेंगे। जारी रखें?",
"attachement-note": "नोट: यह विजेट हस्ताक्षरकर्ताओं को साइनिंग प्रक्रिया के दौरान फ़ाइल अपलोड करने की अनुमति देता है। अपलोड की गई फ़ाइल मौजूदा PDF के अंत में अतिरिक्त पेज के रूप में जोड़ दी जाएगी।",
"attachments-docx-not-supported": "हम वर्तमान में DOCX फ़ाइल अपलोड में एक समस्या का सामना कर रहे हैं। कृपया दस्तावेज़ को किसी अन्य समर्थित प्रारूप में अपलोड करने का प्रयास करें, या बाद में पुनः प्रयास करें।"
}
+350 -35
View File
@@ -71,10 +71,11 @@
"login-page": "Pagina di accesso",
"or": "O",
"signup-page": "Pagina di registrazione",
"password-match-length": "La nuova password e la conferma devono corrispondere",
"password-length": "La password deve contenere almeno 8 caratteri",
"password-case": "La password deve contenere una lettera maiuscola, una minuscola e un numero",
"password-special-char": "La password deve contenere un carattere speciale",
"agreee": "Accetto i",
"agree": "Accetto i",
"term": "Termini di servizio",
"subscribe-to-opensign": "Iscriviti alla newsletter di OpenSign",
"register": "Registrati",
@@ -120,7 +121,8 @@
"Teams": "Team",
"Team-update-successfully": "Team aggiornato con successo.",
"Users": "Utenti"
}
},
"Global SMTP": "SMTP Globale"
},
"dashboard-card": {
"Need your Signature": "Necessita della tua firma",
@@ -166,7 +168,10 @@
"Save as template": "Salva come modello",
"Fix & resend": "Correggi e reinvia",
"Kiosk Mode": "Modalità Kiosk",
"Reset password": "Reimposta password"
"Reset password": "Reimposta password",
"View formdata": "Visualizza i dati del modulo",
"Download all formdata": "Scarica tutti i dati del modulo",
"API credits": "Crediti API"
},
"report-heading": {
"Sr.No": "Nr.",
@@ -201,7 +206,8 @@
"Redirect url": "URL di reindirizzamento",
"Created Date": "Data di creazione",
"Updated Date": "Data di aggiornamento",
"Expiry Date": "Data di scadenza"
"Expiry Date": "Data di scadenza",
"Sent Date": "Data di invio"
},
"report-help": {
"Draft Documents": "Questi sono documenti che hai iniziato ma non hai ancora finalizzato per l'invio.",
@@ -218,7 +224,7 @@
"New Template": "Nuovo modello",
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "file selezionato",
"files-selected": "file selezionati",
"template-title": "Titolo del modello",
"document-title": "Titolo del documento",
"title": "Titolo",
@@ -231,11 +237,12 @@
"p3": "Selezionando questa opzione, i link di firma verranno inviati a tutti i firmatari contemporaneamente. Ogni firmatario può firmare il documento a sua discrezione, indipendentemente dal fatto che altri firmatari abbiano completato le firme. Questo metodo è più veloce ma non impone un ordine di firma tra i partecipanti.",
"p4": "Seleziona l'opzione che meglio si adatta alle esigenze del tuo processo documentale."
},
"strict-order": "Imponi ordine rigoroso",
"strict-order-help": "Se abilitato, nessun firmatario può firmare o approvare finché tutti i firmatari/approvatori precedenti non hanno completato la loro azione.",
"no": "No",
"auto-reminder": "Promemoria automatico",
"remind-once": "Ricorda una volta ogni (giorni)",
"next": "Avanti",
"select-folder": "Seleziona cartella",
"OpenSign-drive": "{{appName}} Drive",
"drive-document-status": {
"Completed": "Completato",
@@ -255,6 +262,7 @@
"signers": "Firmatari",
"signers-help": "Inizia a digitare il nome di un contatto per vedere i firmatari suggeriti dai tuoi contatti salvati o aggiungerne di nuovi. Organizza l'ordine di firma aggiungendo firmatari nella sequenza desiderata. Usa il pulsante '+' per includere firmatari e 'x' per rimuoverli. Ogni firmatario riceverà una notifica via email per firmare il documento nell'ordine elencato.",
"bcc-help": "it-Start it-typing it-a it-contact's it-name it-to it-see it-suggestions it-from it-your it-saved it-contacts it-or it-add it-new it-ones. it-Use it-the '+' it-button it-to it-add it-a it-user it-and it-the 'x' it-to it-remove it-them. it-The it-selected it-user's it-email it-will it-be it-added it-as it-a it-Bcc (it-blind it-carbon it-copy). it-Each it-user it-will it-receive it-an it-email it-notification it-once it-the it-document it-is it-completed.",
"cc-help": "Inizia a digitare il nome di un contatto per vedere i suggerimenti dai tuoi contatti salvati oppure aggiungerne di nuovi. Usa il pulsante '+' per aggiungere un utente e la 'x' per rimuoverlo. L'email dell'utente selezionato verrà aggiunta come CC (copia conoscenza). Ogni utente riceverà una notifica via email una volta completato il documento.",
"add-signer": "Aggiungi firmatario",
"contact-not-found": "Contatto non trovato",
"add-yourself": "Aggiungi te stesso",
@@ -263,8 +271,8 @@
"my-signature": "La mia firma",
"signature": "Firma",
"upload-image": "Carica immagine",
"clear": "Chiara",
"upload-signature/Image": "Carica firma/immagine",
"clear": "Cancella",
"upload-signature/Image": "Carica firma/immagine/timbro",
"initials": "Iniziali",
"API": "API",
"api-token": "Token API",
@@ -276,13 +284,15 @@
"yes": "Sì",
"copied": "Copiato",
"something-went-wrong-mssg": "Si è verificato un errore, L'aggiornamento della pagina potrebbe risolvere il problema.",
"attachments-docx-upload-failed": "Al momento si è verificato un problema. Riprova.",
"attachments-docx-upload-limit": "È stato raggiunto il limite di caricamento per file DOCX grandi. Attendi 4 minuti prima di riprovare.",
"token-generated": "Token generato con successo.",
"webhook": "Webhook",
"update-webhook": "Aggiorna Webhook",
"add-webhook": "Aggiungi Webhook",
"webhook-added": "Webhook aggiunto con successo.",
"team-disabled": "Team disabilitato",
"Team enabled": "Team abilitato",
"team-enabled": "Team abilitato",
"are-you-sure": "Sei sicuro di voler",
"disable": "disabilitare",
"enable": "abilitare",
@@ -290,7 +300,7 @@
"edit-team": "Modifica Team",
"name-of-team": "Nome del Team",
"prev": "Precedente",
"no-data-avaliable": "Nessun dato disponibile",
"no-data-available": "Nessun dato disponibile",
"add-team": "Aggiungi Team",
"page-not-found": "Pagina non trovata",
"users-from-teams": "utenti dai Team",
@@ -301,12 +311,37 @@
"delete-user": "Elimina utente",
"delete": "Elimina",
"add-user": "Aggiungi utente",
"password-generateed": "La password verrà generata solo una volta; assicurati di copiarla.",
"password-generated": "La password verrà generata solo una volta; assicurati di copiarla.",
"Team status": "Stato del Team",
"user-deactivated": "Utente disattivato.",
"user-activated": "Utente attivato.",
"public": "Pubblico",
"download": "Scarica",
"sign-offline": "Firma offline",
"sign-offline-instructions": "Scarica il documento, firmalo con uno strumento esterno, quindi carica qui la copia firmata. Il proprietario del documento esaminerà l'invio e lo approverà o rifiuterà.",
"sign-offline-step-1": "Scarica il PDF non firmato.",
"sign-offline-step-2": "Firma il PDF con il tuo strumento preferito.",
"sign-offline-step-3": "Carica il PDF firmato per l'approvazione del proprietario.",
"sign-offline-upload-hint": "Seleziona il PDF firmato da inviare per l'approvazione.",
"sign-offline-submitted-title": "Inviato per approvazione",
"sign-offline-submitted-body": "Il proprietario del documento è stato notificato via e-mail per esaminare la copia firmata offline.",
"offline-already-submitted-title": "Documento inviato offline",
"offline-already-submitted-body": "Hai già inviato questo documento per la revisione della firma offline. Riceverai una notifica una volta approvato o rifiutato.",
"offline-action-approve-title": "Approva firma offline",
"offline-action-approve-confirm": "Approva",
"offline-action-reject-title": "Rifiuta firma offline",
"offline-action-reject-hint": "Facoltativamente, fornisci un motivo. Il firmatario verrà notificato via e-mail.",
"offline-action-reject-reason-placeholder": "Motivo (facoltativo)",
"offline-action-reject-confirm": "Conferma rifiuto",
"offline-action-review-title": "Esamina firma offline",
"offline-action-review-hint": "Visualizza in anteprima il PDF firmato caricato dal firmatario, quindi approvalo o rifiutalo. Nessuna azione viene intrapresa fino alla conferma.",
"offline-action-submitted-at": "Inviato",
"offline-action-no-preview": "Nessuna anteprima disponibile per questo invio.",
"offline-action-open-in-new-tab": "Apri PDF in una nuova scheda",
"offline-action-invalid-link": "Questo link di revisione non è valido o è scaduto.",
"offline-action-success": "Azione completata.",
"reject": "Rifiuta",
"document": "Documento",
"view": "Visualizza",
"make-template-public": "Rendi il modello pubblico",
"make-template-private": "Rendi il modello privato",
@@ -331,7 +366,7 @@
"quick-send-alert-1": "Tutti i ruoli in questo documento sono attualmente collegati a contatti. Per inviare rapidamente copie di questo modello a più firmatari, assicurati che almeno un ruolo non sia collegato a nessun contatto.",
"quick-send-alert-2": "Assicurati che ci sia almeno un widget firma aggiunto per tutti i destinatari.",
"quick-send-alert-3": "Si prega di aggiungere almeno un ruolo a questo template.",
"quick-send-alert-4": "Limite di invio rapido raggiunto.",
"quick-send-alert-4": "{{flow}} supporta fino a {{count}} firmatari per batch. Dividi il tuo elenco e riprova.",
"copy-link": "Copia link",
"copy": "Copia",
"revoke-document": "Revoca documento",
@@ -387,7 +422,11 @@
"radio button": "pulsante di opzione",
"image": "immagine",
"email": "email",
"number": "numero"
"number": "numero",
"draw": "Disegno",
"attachments": "allegati",
"attachments-add": "Clicca per caricare allegati",
"attachments-count": "{{count}} allegato/i"
},
"fields": "Campi",
"recipients": "Destinatari",
@@ -399,6 +438,14 @@
"certificate": "Certificato",
"decline": "Rifiuta",
"finish": "Completa",
"approve": "Approva",
"approve-mssg": " Sei sicuro di voler approvare il documento?",
"signer-role-label": "Ruolo del destinatario",
"signer-role-signer": "Firmatario",
"signer-role-viewer": "Visualizzatore",
"signer-role-approver": "Approvatore",
"signer-role-hint": "Il firmatario deve firmare. L'approvatore approva senza firmare. Il visualizzatore può solo visualizzare.",
"signer-role-upgrade-msg": "I ruoli Visualizzatore e Approvatore sono disponibili solo nei piani a pagamento. Esegui l'upgrade del tuo piano per assegnare questi ruoli ai destinatari.",
"done": "Fatto",
"mail": "Email",
"sign-now": "Firma ora",
@@ -431,16 +478,18 @@
"reset-password-alert-1": "Il link per reimpostare la password è stato inviato al tuo indirizzo email",
"reset-password-alert-2": "Si prega di configurare l'adattatore email",
"reset-password-alert-3": "Reimposta la tua password",
"faild-animation": "Caricamento animazione non riuscito",
"failed-animation": "Caricamento animazione non riuscito",
"apply": "Applica",
"select-columns": "Seleziona colonne",
"copy-type": {
"All pages": "Tutte le pagine",
"All pages but last": "Tutte le pagine tranne l'ultima",
"All pages but first": "Tutte le pagine tranne la prima",
"Next to current widget": "Accanto al widget corrente"
"Next to current widget": "Accanto al widget corrente",
"Page range": "Intervallo di pagine"
},
"options": "Opzioni",
"minimun-check": "Controllo minimo",
"minimum-check": "Controllo minimo",
"maximum-check": "Controllo massimo",
"cell-count": "conteggio delle cellule",
"default-value": "Valore predefinita",
@@ -454,6 +503,19 @@
"formula-error-braces": "Ogni {{ di apertura deve avere una }} corrispondente.",
"formula-error-operators": "Quando usi operatori o parentesi, aggiungi almeno due valori.",
"formula-error-leading-operator": "Le formule non possono iniziare con operatori.",
"formula-error-trailing-operator": "Le formule non possono terminare con un operatore.",
"formula-error-adjacent-placeholders": "Due segnaposto non possono essere scritti insieme senza un operatore tra di loro.",
"formula-error-continuous-operators": "Non è possibile usare due operatori consecutivi.",
"formula-error-empty-parentheses": "Le parentesi non possono essere vuote.",
"formula-error-invalid-parentheses-content": "Le parentesi non possono iniziare o terminare con un operatore.",
"formula-error-before-open-parenthesis": "Operatore mancante prima di '('. Aggiungi un operatore prima della parentesi di apertura.",
"formula-error-after-close-parenthesis": "Operatore mancante dopo ')'. Aggiungi un operatore come '+' o '*' dopo la parentesi di chiusura.",
"formula-error-between-parentheses": "Operatore mancante tra ')' e '('. Aggiungi un operatore come '*' tra i due gruppi.",
"formula-error-division-by-zero": "La divisione per zero non è consentita. Sostituisci '0' con un numero o una variabile valida diversa da zero.",
"formula-error-invalid-number-format": "Trovato un formato numerico non valido. I numeri devono essere in un formato valido come '1' o '3.14', non '.5', '1.' o '1.2.3'.",
"formula-error-unclosed-parenthesis": "Trovata una '(' non chiusa. Ogni parentesi di apertura '(' deve avere una parentesi di chiusura ')' corrispondente.",
"formula-error-unexpected-closing-parenthesis": "Trovata una parentesi di chiusura ')' inattesa senza una parentesi di apertura '(' corrispondente.",
"formula-error-empty-placeholder": "Il segnaposto vuoto '{{}}' non è consentito. Fornisci un nome variabile all'interno delle parentesi graffe.",
"decimal-places": "Cifre decimali",
"number-of-decimal-places": "Numero di cifre decimali",
"decimal-places-helper": "Imposta su 0 per numeri interi",
@@ -510,18 +572,19 @@
"placeholder-alert-4": "Hai inviato con successo le mail a tutti i destinatari!",
"placeholder-mail-alert": "Hai inviato con successo un'e-mail a {{name}}. I firmatari successivi riceveranno un'e-mail una volta che {{name}} avrà firmato il documento.",
"placeholder-mail-alert-you": "I firmatari successivi riceveranno un'email dopo che avrai firmato il documento.",
"placeholder-alert-5": "Vuoi firmare i documenti ora?",
"placeholder-alert-5": "Puoi firmare ora oppure inviarlo alla tua email per completarlo in seguito.",
"placeholder-alert-6": "Configura l'adattatore email per inviare mail!",
"placeholder-alert-7": "Seleziona il firmatario per aggiungere un segnaposto!",
"email-subject": "Oggetto email",
"email-body": "Corpo email",
"email-placeholder": "aggiungi corpo dell'email",
"reset-to-default": "Reimposta predefinita",
"cutomize-email": "Personalizza Email",
"customize-email": "Personalizza Email",
"upgrade-to-customize-email": "Aggiorna per personalizzare l'Email",
"sign-url": "URL Firma",
"mails-sent": "Mail inviate",
"mail-not-delivered": "Mail non consegnata",
"email-not-verified-head": "Email non verificata",
"document-alert": "Avviso Documento",
"owner-subscription-expired": "L'abbonamento del proprietario è scaduto.",
"owner-doesnt-have-paid-plan": "Il proprietario non ha un piano a pagamento.",
@@ -582,7 +645,7 @@
"provide-your-details": "Fornisci i tuoi dettagli",
"provide-email": "Fornisci l'email.",
"additional-info": "Informazioni aggiuntive",
"signature-saved-alert": "Firma salvata con successo.",
"signature-saved-alert": "salvata con successo.",
"saved-successfully": "Salvato con successo.",
"fill-field": "Compila questo campo",
"error-template": "Errore: Modello non trovato!",
@@ -597,6 +660,9 @@
"document-signed-alert-7": "Questo documento non è più disponibile.",
"document-signed-alert-8": "Il documento è già stato inviato per la firma.",
"yet-to-sign": "Ancora da firmare",
"viewers": "Visualizzatori",
"viewer-no-widgets": "I visualizzatori ricevono una copia in sola lettura del documento e non possono avere widget assegnati.",
"viewer-confirm-widget-removal": "Cambiando questo destinatario in Visualizzatore verranno rimossi tutti i widget già assegnati. Continuare?",
"validation-alert-1": "L'input non soddisfa i criteri impostati dall'espressione regolare.",
"account-already-exists": "L'account esiste già!",
"choose-password": "Scegli Password",
@@ -619,7 +685,7 @@
"date": "Data",
"report-not-found": "Report non trovato",
"please-wait": "attendere prego",
"select-foler": "Seleziona cartella",
"select-folder": "Seleziona cartella",
"generated-on": "Generato il",
"text-field": "Campo di testo",
"font-size": "Dimensione carattere",
@@ -678,13 +744,13 @@
"public-template": "Questo video dimostra come configurare il tuo profilo pubblico personalizzato, come 'https://opensign.me/tuo-username'. Imparerai anche come personalizzare il tuo slogan e rendere i tuoi modelli disponibili per la firma pubblica.",
"allowModify-widgets": "È possibile trascinare e rilasciare uno qualsiasi di questi campi nel documento, oltre ai campi già designati dal creatore del documento."
},
"enter-email-plaholder": "Aggiungi un indirizzo email e premi invio",
"enter-email-placeholder": "Aggiungi un indirizzo email e premi invio",
"success-email-alert": "Email inviata con successo!",
"expired-doc-title": "Documento Scaduto",
"expired-on-mssg": "Questo documento è scaduto il {{expiredDate}} e non è più disponibile per la firma.",
"signature-validate-alert": "Conferma di aver selezionato almeno {{minRequiredCount}} caselle di controllo.",
"signature-validate-alert-2": "Assicurati che questo campo sia compilato correttamente e soddisfi tutti i requisiti.",
"remaing-users": "Utenti rimanenti",
"remaining-users": "Utenti rimanenti",
"Role": "Ruolo",
"additional-users": "Acquista utenti aggiuntivi.",
"Quantity-of-users": "Quantità di utenti",
@@ -698,7 +764,7 @@
"select-date-format": "Seleziona un formato data",
"quantity-of-credits": "Quantità di crediti premium",
"remaining-credits": "Crediti premium disponibili:",
"remaining-credits-help": "Usa i crediti premium per firmare documenti API, invii in massa o per integrare OpenSign nel tuo sito web. Hai a disposizione {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti acquistati aggiuntivi rimanenti.",
"remaining-credits-help": "Usa i crediti premium per la firma di documenti tramite API, l'invio massivo o per integrare OpenSign nel tuo sito web. Hai ancora {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti aggiuntivi acquistati. Se sei in un piano Teams o Enterprise, questi crediti possono essere condivisi tra tutti gli utenti della tua azienda, a meno che l'amministratore dell'account non abbia impostato un limite specifico per il tuo account.",
"additional-credits": "Acquista crediti premium",
"quota-err-quicksend": "Quota raggiunta, non hai abbastanza crediti.",
"buy-credits": "Acquista Crediti Premium",
@@ -706,7 +772,7 @@
"rotate-left": "Ruota a sinistra",
"rotate-alert-mssg": "Tutti i widget su questa pagina andranno persi. Sei sicuro di voler procedere?",
"templateid": "ID Modello",
"bulk-send-subcription-alert": "Esegui l'upgrade al piano Professionale o Team per utilizzare l'invio in massa.",
"bulk-send-subscription-alert": "Esegui l'upgrade al piano Professionale o Team per utilizzare l'invio in massa.",
"generate-test-token": "Genera Token di Test",
"regenerate-test-token": "Rigenera Token di Test",
"help-test-token": "Questo token può essere utilizzato per testare le API all'endpoint https://sandbox.opensignlabs.com/api/v1, consentendoti di firmare documenti illimitati. Nota che le API sandbox firmeranno i tuoi documenti con certificati autofirmati, che potrebbero non essere riconosciuti come validi da Adobe. Una volta completati i test, puoi eseguire l'upgrade a uno dei nostri piani a pagamento per generare un token di produzione.",
@@ -726,8 +792,9 @@
"copied-code": "COPIATO",
"Installation": "Installazione",
"Usage": "Utilizzo",
"insufficient-credits": "Crediti di firma insufficienti",
"insufficient-credits-mssg": "Il proprietario di questo documento non dispone attualmente dei crediti OpenSign necessari affinché tu possa firmare. Contatta il proprietario per ulteriori dettagli.",
"insufficient-credits": "Crediti di firma insufficienti. Si prega di contattare {{ownerEmail}}",
"insufficient-credits-title": "Crediti di firma insufficienti",
"insufficient-credits-mssg": "Il proprietario di questo documento non dispone attualmente dei crediti {{appName}} necessari affinché tu possa firmare. Contatta il proprietario per ulteriori dettagli.",
"angular-npm-mssg-1": "Per integrare OpenSign nel tuo progetto Angular, esegui semplicemente il seguente comando:",
"quota-mail-info-head": "Limite mensile di email",
"quota-mail-info": "Per mantenere la qualità del servizio e prevenire lo spam, OpenSign consente fino a 15 email al mese con il piano gratuito. Esegui l'upgrade ora per l'invio illimitato di email.",
@@ -746,6 +813,13 @@
"p3": "Selezionando questa opzione, disabiliterai la verifica OTP, consentendo agli utenti di firmare il documento direttamente senza passaggi aggiuntivi.",
"p4": "Scegli l'opzione che meglio si adatta alle tue esigenze di firma dei documenti."
},
"allow-offline-sign": "Consenti firma offline",
"allow-offline-sign-help": {
"p1": "Scegli se i firmatari possono inviare una copia di questo documento firmata tramite uno strumento esterno per la revisione del proprietario.",
"p2": "I firmatari vedranno l'opzione Firma offline e potranno caricare un PDF firmato esternamente per l'approvazione.",
"p3": "I firmatari possono firmare solo all'interno dell'app. L'opzione Firma offline sarà nascosta.",
"p4": "Questa impostazione utilizza come predefinita la tua preferenza account ed è disponibile sui piani a pagamento."
},
"advanced-options": "Opzioni avanzate",
"hide-advanced-options": "Nascondi opzioni avanzate",
"download-files": "Scarica file",
@@ -808,6 +882,7 @@
"delete-folder-alert-1": "Impossibile eliminare: la cartella deve essere vuota.",
"defaultSign-alert": "I widget {{widgetsType}} non sono disponibili per la firma automatica.",
"embed-heading": "Incorpora modelli {{appName}}",
"other-apps-by-opensign": "Altre app del team OpenSign",
"public-template-edit-title": "Modifica Modello Pubblico",
"public-template-edit-mssg": "Non puoi modificare direttamente un modello pubblico. Per apportare modifiche, il modello sarà impostato come privato e dovrai renderlo pubblico di nuovo successivamente. Vuoi continuare?",
"agree-p1": "Confermo di aver letto e compreso il",
@@ -859,7 +934,6 @@
"js-snippet-msg-1": "Puoi ottenere il TemplateId dalla",
"js-snippet-msg-2": "pagina di gestione modelli",
"js-snippet-msg-3": "pagina.",
"agrrement-alert": "Per procedere, devi acconsentire alla divulgazione delle registrazioni elettroniche e delle firme.",
"webhook-already-exists": "L'URL esiste già! Prova con uno diverso.",
"webhook-must-be-secure": "L'URL del webhook deve essere sicuro e utilizzare https://",
"provide-test-token": "Si prega di generare il token di test API",
@@ -879,6 +953,7 @@
"p3": "Selezionando questa opzione, i firmatari non potranno apportare alcuna aggiunta al documento durante il processo di firma."
},
"signature-setting": "Impostazioni Firma",
"rotation": "Rotazione",
"activation-key": "Chiave di attivazione",
"paid-selfhost-help": "Si prega di contattare {{appName}} per ulteriori informazioni all'indirizzo ",
"remaining-credits-selfhost-help": "Utilizza i crediti premium per la firma di documenti tramite API, l'invio in massa e l'integrazione di {{appName}} sul tuo sito web. Hai {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti aggiuntivi acquistati rimanenti.",
@@ -907,6 +982,8 @@
"visit-below-link": "Visita il link qui sotto per saperne di più -",
"storage-help": "Abilitare BYOC ti consente di collegare il tuo archivio S3 in modo che i tuoi file rimangano completamente sotto il tuo controllo, senza copie esterne conservate. Se l'autonomia dei dati è importante per te, considera l'upgrade a Teams per sbloccare questa funzionalità.",
"daily-quota-reached": "Hai raggiunto la tua quota giornaliera. Per assistenza, contatta quotas@opensignlabs.com.",
"email-not-verified-send": "Verifica il tuo indirizzo email prima di inviare email. Vai alle <1>impostazioni del profilo</1> per completare la verifica dellemail.",
"email-not-verified-send-plain": "Verifica il tuo indirizzo email prima di inviare email. Vai alle impostazioni del profilo per completare la verifica dellemail.",
"enabled-signature-type": "Tipi di firma abilitati",
"enabled-signature-type-help": "L'impostazione 'Tipi di firma abilitati' determina quali opzioni di firma sono disponibili nella tua organizzazione. Ad esempio, se disabiliti l'opzione 'Disegna', i membri della tua organizzazione non la vedranno nel widget della firma, mentre le altre tre opzioni resteranno accessibili.",
"indexing-public-profile": "Consenti l'indicizzazione del profilo pubblico dai motori di ricerca",
@@ -934,6 +1011,7 @@
"please-provide-templateid": "Si prega di fornire templateid",
"this-template-is-not-public": "Questo template non è pubblico",
"invalid-templateid": "templateid non valido",
"kiosk-no-active-role": "Questo modello non ha ruoli di firmatario o approvatore disponibili per la firma in modalità chiosco.",
"contact-billing-at-opensign": " Per aggiungere altri posti, contattare OpenSign™ all'indirizzo <1>billing@opensignlabs.com</1> per assistenza.",
"title-length-alert": "Il titolo può contenere al massimo 250 caratteri.",
"note-length-alert": "La nota può contenere al massimo 200 caratteri.",
@@ -1023,7 +1101,7 @@
},
"signer-already-present": "Firmatario già presente",
"kiosk-sign": "Firma su chiosco",
"dont-have-access-to-template": "Il template è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
"do-not-have-access-to-template": "Il template è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
"kiosk-info": "La Modalità Kiosk consente di raccogliere firme in presenza in modo rapido ed efficiente. Ideale per fiere, eventi o situazioni con firmatari fisicamente presenti. ",
"learn-more": "Scopri di più",
"finish-mssg": "Sei sicuro di voler completare il documento?",
@@ -1079,8 +1157,6 @@
"search-contacts": "Cerca contatti…",
"add-role-alert": "Si prega di aggiungere almeno un ruolo",
"edit-draft": "Modifica bozza",
"invalid-email-found": "Email non valida trovata: {{email}}",
"duplicate-email-found": "Email duplicata trovata: {{email}}",
"vertical": "Verticale",
"horizontal": "Orizzontale",
"billing": "Fatturazione",
@@ -1094,7 +1170,7 @@
"access-denied": "Accesso negato",
"upgrade": "Aggiorna",
"do-not-access-app": "Non hai accesso a questa applicazione.",
"dont-have-access": "Non hai accesso.",
"do-not-have-access": "Non hai accesso.",
"valid-email-alert": "Inserisci un indirizzo email valido.",
"otp-not-validate": "OTP non valido.",
"domain-not-allowed": "Questo dominio non è consentito",
@@ -1111,10 +1187,12 @@
"branding-help": "Il branding consente il white labelling della tua app",
"custom-sub-domain": "Sottodominio personalizzato",
"app-name": "Nome dell'app",
"provide-domain-name": "Fornisci il tuo nome di dominio",
"provide-domain-name": "Sono consentiti solo i sottodomini (ad es., app.tuodominio.com). I domini principali/root come tuodominio.com non sono supportati.",
"provide-app-name-help": "Fornisci un nome univoco per l'app. Verrà mostrato come nome del tuo brand nell'applicazione e in tutte le e-mail ovunque venga applicato il branding.",
"upload-app-logo": "Carica un logo in formato .png, .jpg o .svg. Dimensione consigliata: {{size}} pixel (rapporto 5:2) per la migliore qualità di visualizzazione.",
"upload-app-favicon": "Carica un'immagine favicon quadrata (.ico, .png o .svg). La dimensione consigliata è {{size}} pixel per la migliore compatibilità del browser.",
"provide-app-name": "Fornisci il nome della tua app",
"logo": "Logo",
"upload-app-logo": "Carica il logo della tua app",
"prefill-unfilled-widget": "I seguenti campi obbligatori non possono essere lasciati vuoti: {{emptyWidget}}. Per favore compilali per procedere.",
"Dashboard": "Dashboard",
"Analytics": "Analitica",
@@ -1237,6 +1315,7 @@
},
"read-only-date-error": "Il widget data di sola lettura deve avere una data predefinita.",
"set-date": "Imposta data",
"default-date": "Data predefinita",
"set-today": "Data di firma",
"enter-name": "Inserisci nome",
"enter-email": "Inserisci e-mail",
@@ -1256,5 +1335,241 @@
"Please provide required parameters.": "Fornisci i parametri richiesti.",
"reset-password": "Reimposta password utente",
"enter-strong-password": "Inserisci una password sicura o generane una automaticamente.",
"autogenerate": "Genera automaticamente"
}
"autogenerate": "Genera automaticamente",
"extend": "Estendi",
"console-general-help": "Per consentire ai singoli utenti di personalizzare i propri modelli di e-mail, vai a",
"console-mail-help": "Per aggiornare i modelli di e-mail aziendali, vai a",
"admin-setting-disabled": "Questa impostazione è disabilitata. Contatta l'amministratore per assistenza.",
"company-email-settings": "Impostazioni e-mail aziendali",
"allow-email-template-individually": "Consenti la personalizzazione individuale dei modelli di e-mail per gli utenti ",
"user-email-template-general-help": "Disabilitando questa opzione, gli utenti non potranno più personalizzare i loro modelli di e-mail.",
"disable-individual-user-smtp": "disabilita le impostazioni smtp individuali dell'utente",
"enable-individual-user-smtp": "abilita le impostazioni smtp individuali dell'utente",
"user-smtp-general-help": "Quando disattivato, gli utenti non potranno configurare il proprio SMTP. Tutte le e-mail utilizzeranno la configurazione SMTP globale.",
"global-smtp-settings": "Impostazioni SMTP Globali",
"global-smtp-settings-help-p1": "Queste impostazioni si applicano globalmente all'intero account {{appName}}. Se un utente non ha configurato le proprie impostazioni di consegna e-mail, il sistema utilizzerà automaticamente le impostazioni SMTP globali definite qui.",
"global-smtp-settings-help-p2": "Per garantire una migliore consegna delle e-mail di richiesta firma, puoi collegare il tuo provider di posta preferito. Scegli una delle seguenti opzioni:",
"global-connect-to-gmail-help": "Utilizza un account Gmail per inviare e-mail di richiesta firma direttamente dalla casella di posta della tua organizzazione. Migliora la consegna, la reputazione e l'affidabilità.",
"global-connect-to-smtp-help": "Utilizza il tuo server SMTP per inviare e-mail tramite il tuo dominio. Questo offre pieno controllo sulla tua infrastruttura e-mail, migliora la consegna e mantiene la coerenza del marchio.",
"global-smtp-settings-help-p3": "Se non sono configurate impostazioni a livello utente, verranno applicate automaticamente queste impostazioni globali.",
"hide-text-with-asterisks": "Nascondi il testo con asterischi",
"hide-text-with-asterisks-help": "Il testo nel documento firmato sarà nascosto dalla visualizzazione ma rimarrà accessibile al mittente (proprietario) tramite il suo account {{appName}}.",
"document-id": "ID documento",
"sent-date": "Data di invio",
"status": "Stato",
"signer-form-data-for": "Dati del modulo per il firmatario: {{signerName}} <{{signerEmail}}>",
"widget-name": "Nome del widget",
"widget-responses": "Risposte del widget",
"formdata-download-note": "Nota: Firme, timbri, iniziali e immagini non verranno esportati. I dati dei firmatari offline non saranno disponibili.",
"export-all-formdata-error": "Non è possibile esportare i dati del modulo perché il modello contiene nomi di widget duplicati. Creare un nuovo modello per poter esportare i dati.",
"export-formdata-error": "Non è possibile esportare i dati del modulo perché il documento contiene nomi di widget duplicati. Creare un nuovo documento senza nomi duplicati per poter esportare i dati.",
"duplicate-widget-name-error": "Questo nome di widget è già assegnato a un altro firmatario. Scegli un nome diverso.",
"duplicate-template-widget-error": "Impossibile duplicare il modello perché alcuni nomi di widget sono duplicati in ruoli diversi. Rinomina i widget duplicati e riprova.",
"credit-alert": "Avviso di credito",
"document-form-data": "Dati del modulo del documento",
"view-formdata-paid-title-error": "La funzione 'Visualizza dati del modulo' consente di stampare e scaricare i dati dei widget compilati dai firmatari durante la firma.",
"view-formdata-paid-error": "Nota: La funzione 'Visualizza dati del modulo' è disponibile solo per documenti creati dalla versione v2.32.0 in poi che non contengono nomi di widget duplicati.",
"download-all-formdata-paid-title-error": "La funzione 'Scarica tutti i dati del modulo' consente di scaricare i dati dei widget compilati dai firmatari per tutti i documenti creati dal modello.",
"download-all-formdata-paid-error": "Nota: La funzione 'Scarica tutti i dati del modulo' è disponibile solo per modelli creati dalla versione v2.32.0 in poi che non contengano nomi di widget duplicati.",
"api-token-not-found": "Token API non trovato.",
"sub-head-api-credits": "Definisci il numero massimo di crediti API che questo utente può consumare.",
"consumed-credits": "Crediti consumati",
"allowed-api-credits": "Crediti API consentiti",
"allowed-api-credits-help": "I crediti consentiti devono essere maggiori o uguali ai crediti consumati.",
"Enter-allowed-api-credits": "Inserisci i crediti API consentiti",
"login-to-sandbox": "Accedi alla sandbox",
"your-credits-usage": "Utilizzo dei tuoi crediti",
"remaining": "rimanenti",
"used": "usati",
"your-credits-usage-help": "Questo è il numero di crediti consumati con la tua chiave API.",
"api-credits-updated": "I crediti API sono stati assegnati con successo.",
"min-date": "Data minima",
"max-date": "Data massima",
"invalid-min-date": "Data minima non valida.",
"min-less-max": "La data minima deve essere minore o uguale alla data massima.",
"invalid-max-date": "Data massima non valida.",
"max-less-min": "La data massima deve essere maggiore o uguale alla data minima.",
"date-range": "Intervallo di date",
"date-range-help": "Imposta una data minima e/o massima per limitare la selezione. La data scelta deve rientrare nell'intervallo.",
"option-disabled-by-owner": "Il creatore del documento ha disabilitato questa opzione.",
"pen-colors": "Colori della penna",
"pen-colors-help": "Scegli quali colori della penna sono disponibili per i widget Firma e Iniziali. Seleziona una casella per mostrare un colore e deselezionala per nasconderlo.",
"security-key": "Chiave di sicurezza",
"remove-limit": "Rimuovi limite",
"removed-api-credits": "Il limite dei crediti API è stato rimosso.",
"enable-authentication": "Abilita autenticazione",
"working": "In funzione",
"test-security-key": "Testa la chiave di sicurezza",
"generate": "Genera",
"regenerate": "Rigenera",
"signing-date": "Data di firma",
"date-widget": "Widget data",
"mail-failed": "Si è verificato un errore durante l'invio dell'e-mail. Per favore riprova.",
"mail-status-head": "Passaggio successivo: firmare il tuo documento?",
"send-to-email": "Invia tramite email",
"date-pref-help-sub-title": "Le preferenze del widget Data consentono di definire le impostazioni predefinite per il widget Data. Queste impostazioni vengono applicate automaticamente ogni volta che si aggiunge un nuovo widget Data a un documento o a un modello. È comunque possibile personalizzare qualsiasi widget Data in qualsiasi momento facendo clic sull'icona a forma di ingranaggio.",
"date-pref-help-format": "Scegli il formato di data predefinito utilizzato per i widget Data.",
"date-pref-help-default-date": "Imposta un valore di data predefinito per i widget Data.",
"date-pref-help-signing-date": "Quando abilitato, il widget Data utilizza automaticamente la data in cui il firmatario firma il documento.",
"date-pref-help-read-only": "Quando abilitato, il widget Data è in sola lettura per il firmatario.",
"upload-csv": "Carica CSV",
"file-selected": "file selezionato",
"bulk-send-csv-helper": "Utilizza le intestazioni per i ruoli dei firmatari (Email/Nome/Telefono del ruolo) e i valori di precompilazione (Precompilazione: Nome campo). I campi di precompilazione obbligatori devono essere compilati.",
"bulk-send-csv-helper-note": "Nota: Per i campi di precompilazione di tipo data nel CSV, il valore deve essere fornito in questo formato: {{value}} (esempio: {{example}}).",
"bulk-send-csv-summary": "{{total}} righe importate dal CSV.",
"bulk-send-csv-missing-headers": "Intestazioni CSV obbligatorie mancanti. Ruoli: {{roles}}.",
"bulk-send-no-records": "Aggiungi almeno un destinatario prima dell'invio.",
"csv-only-support": "Sono supportati solo file CSV.",
"csv-no-records": "Nessun record trovato nel CSV.",
"select-at-least-option": "Seleziona almeno un'opzione.",
"draw-required": "Per favore, disegna nel riquadro.",
"enter-smtp-username": "Inserisci il nome utente SMTP",
"enter-webhook-url": "Inserisci l'URL del webhook",
"enter-user-name": "Inserisci il nome utente",
"enter-tagline": "Inserisci lo slogan",
"enter-password-or-click-autogenerate": "Inserisci la password o fai clic su Genera automaticamente",
"enter-daily-email-quota": "Inserisci la quota giornaliera di e-mail",
"enter-custom-regular-expression": "Inserisci un'espressione regolare personalizzata",
"enter-value": "Inserisci {{value}}",
"are-you-sure-want-to-send-document": "Sei sicuro di voler inviare il documento?",
"favicon": "Favicon",
"delete-two-factor-authentication": "Sei sicuro di voler eliminare l'autenticazione a due fattori?",
"paste-html-here": "Incolla HTML qui",
"open-email-builder": "Puoi creare un modello di e-mail utilizzando la piattaforma <1>email builder</1> e copiare il codice HTML.",
"contact-saved": "Contatto salvato con successo.",
"remove-limit-of-user": "Sei sicuro di voler rimuovere il limite di questo utente? Potrà consumare tutti i crediti premium disponibili della tua organizzazione, condivisi tra tutti gli utenti.",
"current-expiry-date": "Data di scadenza attuale",
"reminder-error": "“Ricorda una volta ogni (giorni)” non deve superare il “Tempo per completare (giorni)”.",
"showing-to-of-records": "Visualizzazione da {{record}} a {{recordPerPage}} di {{totalRows}} record",
"records-per-page": "Record per pagina",
"email-not-found-in-row": "Email non trovata nella riga {{row}}.",
"invalid-email-found-in-row": "Email non valida trovata nella riga {{row}}: {{email}}",
"duplicate-email-found-in-row": "Email duplicata trovata nella riga {{row}}: {{email}}",
"prefill": "Precompilazione",
"recipients-&-data": "Destinatari e dati",
"response": "Risposta",
"prefill-tab-help": "Questi sono i campi comuni del modello che verranno applicati a tutti i documenti in questo invio massivo.",
"recipient-tab-help-p1": "Nel passaggio successivo, dovrai:",
"recipient-tab-help-p2": "Caricare un CSV con i dettagli dei destinatari oppure",
"recipient-tab-help-p3": "Inserire manualmente le informazioni dei destinatari",
"recipient-tab-help-p4": "Ogni destinatario riceverà il proprio documento generato da questo modello.",
"response-tab-help": "Rivedi le tue risposte.",
"summary": "Riepilogo",
"prefill-fields": "Campi di precompilazione",
"documents": "Documenti",
"message": "Messaggio",
"formula-help-p1": "Raccogli input numerici ed esegui calcoli dinamici utilizzando formule.",
"example": "Esempio",
"formula-help-p2": "Per visualizzare un totale in number-3, usa {{number-1}} + {{number-2}}.",
"formula-help-p3": "Per calcolare un valore in number-4, usa ({{number-1}} * {{number-2}}) / {{number-3}}.",
"formula-help-p4": "Ogni widget numerico deve avere un nome univoco (es. number-1), poiché le formule si basano su questi identificatori.",
"note-consume-premium-credits": "Nota: Questa funzione consuma crediti premium.",
"note-consume-premium-credits-on-document-send": "Nota: Questa funzione consuma crediti premium. I crediti vengono consumati ogni volta che un documento viene inviato, indipendentemente dal fatto che venga firmato o meno.",
"webhook-authentication-help": "{{appName}} protegge ogni webhook generando una firma HMAC-SHA256 utilizzando la tua chiave di sicurezza webhook e il payload originale della richiesta. Questa firma viene inviata nell'intestazione x-webhook-signature. Il tuo server deve generare la stessa firma utilizzando il payload ricevuto e la tua chiave segreta. Se entrambe le firme coincidono, la richiesta webhook è autentica e non è stata modificata.",
"invalid-widget-found-in-row": "Valore non valido rilevato nella riga {{row}} per il widget {{widget}}.",
"all-signer-status": "Stato di tutti i firmatari",
"viewed": "Visualizzato",
"signed": "Firmato",
"switch-to-advanced": "Passa alla modalità avanzata",
"switch-to-basic": "Passa alla modalità base",
"use-name-as-sender": "Usa il nome del profilo come mittente",
"use-name-as-sender-help": "Quando abilitato, {{appName}} usa il nome del tuo profilo come mittente per le richieste di firma e l'email di completamento.",
"acro-fields-detected": "Campi modulo rilevati",
"acro-fields-detected-msg": "Sono stati rilevati {{count}} campo/i modulo nel PDF caricato. Vuoi inserire automaticamente i widget corrispondenti?",
"documents-count": "Numero di documenti",
"templates-count": "Numero di modelli",
"emails-sent": "Email inviate",
"storage-used": "Spazio utilizzato",
"docs-count-team-help": "Numero totale di documenti creati da tutti gli utenti nelle tue organizzazioni.",
"docs-count-help": "Numero totale di documenti creati da te.",
"templates-count-team-help": "Numero totale di modelli creati da tutti gli utenti nelle tue organizzazioni.",
"templates-count-help": "Numero totale di modelli creati da te.",
"emails-sent-team-help": "Numero totale di notifiche email inviate nelle tue organizzazioni.",
"emails-sent-help": "Numero totale di notifiche email inviate da te.",
"storage-used-team-help": "Spazio di archiviazione totale consumato da tutti gli utenti nelle tue organizzazioni.",
"storage-used-help": "Spazio di archiviazione totale consumato da te.",
"cloud": "Cloud",
"on-premises": "On-premise",
"plan-name-free": "OPENSIGN™ FREE",
"plan-name-professional": "OPENSIGN™ PROFESSIONAL",
"plan-name-teams": "OPENSIGN™ TEAMS",
"plan-name-enterprise": "OPENSIGN™ ENTERPRISE",
"plan-name-self-hosted": "OPENSIGN™ SELF-HOSTED",
"plan-subtitle-free": "Firme elettroniche illimitate gratuite, per sempre.",
"plan-subtitle-professional": "Accesso esclusivo a funzionalità avanzate.",
"plan-subtitle-scalable": "Funzionalità scalabili con supporto prioritario.",
"subscribe": "Abbonati",
"contact-us": "Contattaci",
"benefit-unlimited-digital-signatures": "Firme digitali illimitate",
"benefit-sign-docs-yourself": "Firma i documenti tu stesso",
"benefit-request-signatures": "Richiedi firme ad altri",
"benefit-unlimited-templates": "Modelli illimitati",
"benefit-14-field-types": "14 tipi di campo",
"benefit-automatic-e-signatures": "Firme elettroniche automatiche",
"benefit-completion-certificates": "Certificati di completamento",
"benefit-send-in-order": "Invia in ordine",
"benefit-organize-docs": "Organizza i documenti in OpenSign™ Drive",
"benefit-document-templates": "Modelli di documento",
"benefit-import-from-dropbox": "Importa da Dropbox",
"benefit-contact-book": "Rubrica contatti",
"benefit-document-expiry": "Supporto per la scadenza dei documenti",
"benefit-decline-document": "Supporto per il rifiuto dei documenti",
"benefit-email-notifications": "Notifiche via e-mail",
"benefit-public-profiles": "Profili pubblici",
"benefit-and-much-more": "E molto altro",
"benefit-everything-free": "Tutto in OpenSign™ Free",
"benefit-field-validations": "Validazioni dei campi",
"benefit-regex-validations": "Validazioni con espressioni regolari",
"benefit-webhooks": "Webhook",
"benefit-zapier": "Integrazione Zapier",
"benefit-api-access": "Accesso API",
"benefit-api-signatures-100": "fino a 100 firme API",
"benefit-api-signatures-240": "fino a 240 firme API",
"benefit-custom-email-templates": "Modelli e-mail personalizzati",
"benefit-custom-smtp": "Collega il tuo account Gmail o SMTP per l'invio di e-mail",
"benefit-auto-reminders": "Promemoria automatici",
"benefit-bulk-send-100": "Invio in massa (fino a 100 documenti)",
"benefit-bulk-send-240": "Invio in massa (fino a 240 documenti)",
"benefit-premium-profiles": "Nomi utente del profilo pubblico premium",
"benefit-email-verification": "Imponi la verifica via e-mail per confermare l'identità del firmatario",
"benefit-embedded-signing": "Firma incorporata",
"benefit-everything-professional": "Tutto in OpenSign™ Professional",
"benefit-teams-organizations": "Team e Organizzazioni",
"benefit-share-templates-teams": "Condividi modelli con i team",
"benefit-share-templates-individuals": "Condividi modelli con individui",
"benefit-byoc": "BYOC - Archivia i tuoi documenti nel tuo cloud storage",
"benefit-doc-id-removal": "Rimozione del DocumentId dai documenti firmati",
"benefit-request-payments": "Richiedi pagamenti (prossimamente)",
"benefit-mobile-app": "App mobile (prossimamente)",
"benefit-api-signatures-500": "fino a 500 firme API",
"benefit-bulk-send-500": "Invio in massa (fino a 500 documenti)",
"benefit-all-features": "Tutte le funzionalità",
"benefit-custom-domain": "Dominio personalizzato",
"benefit-custom-branding": "Branding personalizzato",
"benefit-uptime-sla": "SLA di disponibilità",
"benefit-sso-support": "Supporto SSO",
"benefit-priority-support": "Supporto prioritario",
"benefit-3-users-1500-credits": "Include 3 utenti e 1500 crediti premium",
"benefit-public-links": "Link pubblici",
"benefit-kiosk-mode": "Modalità chiosco",
"benefit-bulk-send-1500": "Invio in massa (fino a 1500 documenti)",
"benefit-connect-gmail-smtp": "Collega il tuo Gmail o SMTP",
"benefit-analytics": "Analisi",
"benefit-bulksend-api-pro-month-help": "Questo piano include 100 crediti premium al mese che ti permettono di inviare documenti tramite API, condividere link pubblici per la firma, eseguire richieste di firma in blocco, usare la modalità Kiosk per la firma di persona o raccogliere firme da documenti incorporati direttamente nel tuo sito web. Ogni volta che avvii o invii un documento, viene utilizzato 1 credito — e quel singolo documento può includere fino a 10 firmatari.",
"benefit-bulksend-api-pro-year-help": "Questo piano include 240 crediti premium all'anno che ti permettono di inviare documenti tramite API, condividere link pubblici per la firma, eseguire richieste di firma in blocco, usare la modalità Kiosk per la firma di persona o raccogliere firme da documenti incorporati direttamente nel tuo sito web. Ogni volta che avvii o invii un documento, viene utilizzato 1 credito — e quel singolo documento può includere fino a 10 firmatari.",
"benefit-bulksend-api-team-month-help": "Questo piano include 100 crediti premium al mese che ti permettono di inviare documenti tramite API, condividere link pubblici per la firma, eseguire richieste di firma in blocco, usare la modalità Kiosk per la firma di persona o raccogliere firme da documenti incorporati direttamente nel tuo sito web. Ogni volta che avvii o invii un documento, viene utilizzato 1 credito — e quel singolo documento può includere fino a 10 firmatari.",
"benefit-bulksend-api-team-year-help": "Questo piano include 500 crediti premium all'anno che ti permettono di inviare documenti tramite API, condividere link pubblici per la firma, eseguire richieste di firma in blocco, usare la modalità Kiosk per la firma di persona o raccogliere firme da documenti incorporati direttamente nel tuo sito web. Ogni volta che avvii o invii un documento, viene utilizzato 1 credito — e quel singolo documento può includere fino a 10 firmatari.",
"failed-pdf-for-prefill-processing": "Impossibile caricare il PDF per l'elaborazione del precompilamento.",
"attach-all-role-to-signer": "Per favore, assegna tutti i ruoli al firmatario.",
"upgrade-to-edit": "Esegui l'upgrade per modificare",
"hide-live-chat-for-signers": "Nascondi chat dal vivo per i firmatari",
"hide-live-chat-for-signers-help": "Se attivato, i widget di chat dal vivo saranno nascosti per i firmatari nelle pagine di firma.",
"attachments-creator-hint": "I firmatari possono caricare file che verranno aggiunti alla fine del documento.",
"attachments-drop-hint": "Trascina i file qui o clicca per selezionare",
"attachments-limits": "PDF, DOCX, PNG, JPG. Fino a {{maxFiles}} file, {{maxSizeMB}} MB ciascuno.",
"attachments-invalid-type": "Tipo di file non supportato: {{name}}",
"attachments-max-files": "Massimo {{maxFiles}} allegati consentiti.",
"attachments-remove-confirm": "La rimozione cancella solo i metadati. Le pagine già aggiunte resteranno nel documento. Continuare?",
"attachement-note": "Nota: Questo widget consente ai firmatari di caricare un file durante il processo di firma. Il file caricato verrà aggiunto come pagine aggiuntive alla fine del PDF esistente.",
"attachments-docx-not-supported": "Al momento stiamo riscontrando un problema con il caricamento dei file DOCX. Prova a caricare il documento in un altro formato supportato oppure riprova più tardi."
}
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
{
"rewrites": [
{ "source": "/:path((?!\\.well-known/).*)", "destination": "/index.html" }
]
}
Binary file not shown.
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env node
/* eslint-env node */
/* eslint-disable no-console */
// Tiny static file server for the production build.
// - Serves real files from ./build (including dotfile dirs like .well-known)
// - Falls back to /index.html only when the requested path does not exist
// (SPA client-side routing).
const http = require("node:http");
const fs = require("node:fs");
const path = require("node:path");
const root = path.join(__dirname, "build");
const port = Number(process.env.PORT) || 3000;
const host = process.env.HOST || "0.0.0.0";
const mime = {
".html": "text/html; charset=utf-8",
".js": "application/javascript; charset=utf-8",
".mjs": "application/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
".json": "application/json; charset=utf-8",
".map": "application/json; charset=utf-8",
".svg": "image/svg+xml",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".ico": "image/x-icon",
".webp": "image/webp",
".woff": "font/woff",
".woff2": "font/woff2",
".ttf": "font/ttf",
".otf": "font/otf",
".txt": "text/plain; charset=utf-8",
".pdf": "application/pdf",
".csv": "text/csv; charset=utf-8",
".wasm": "application/wasm",
".xml": "application/xml; charset=utf-8"
};
function contentType(filePath) {
return mime[path.extname(filePath).toLowerCase()] || "application/octet-stream";
}
function safeJoin(reqPath) {
let decoded;
try {
decoded = decodeURIComponent(reqPath.split("?")[0].split("#")[0]);
} catch {
return null;
}
const resolved = path.normalize(path.join(root, decoded));
if (resolved !== root && !resolved.startsWith(root + path.sep)) return null;
return resolved;
}
function cacheControl(filePath) {
const ext = path.extname(filePath).toLowerCase();
const relativePath = path.relative(root, filePath);
const isBuildAsset =
relativePath === "assets" ||
relativePath.startsWith(`assets${path.sep}`);
if (ext === ".html" || ext === "") {
return "no-cache";
}
return isBuildAsset
? "public, max-age=31536000, immutable"
: "no-cache";
}
function streamFile(req, res, filePath, stats) {
const headers = {
"Content-Type": contentType(filePath),
"Content-Length": stats.size,
"Last-Modified": stats.mtime.toUTCString(),
"Cache-Control": cacheControl(filePath)
};
if (req.method === "HEAD") {
res.writeHead(200, headers);
return res.end();
}
const stream = fs.createReadStream(filePath);
stream.on("error", (err) => {
console.error("Stream error:", err);
if (!res.headersSent) {
res.writeHead(500, { "Content-Type": "text/plain" });
res.end("Internal Server Error");
} else {
res.destroy();
}
});
res.on("close", () => {
if (!stream.destroyed) stream.destroy();
});
res.on("error", (err) => {
console.error("Response error:", err);
if (!stream.destroyed) stream.destroy();
});
res.writeHead(200, headers);
stream.pipe(res);
}
function sendIndex(req, res) {
const indexPath = path.join(root, "index.html");
fs.stat(indexPath, (err, stats) => {
if (err || !stats || !stats.isFile()) {
res.writeHead(500, { "Content-Type": "text/plain" });
return res.end("index.html not found");
}
streamFile(req, res, indexPath, stats);
});
}
const server = http.createServer((req, res) => {
if (req.method === "OPTIONS") {
const filePath = safeJoin(req.url || "/");
res.writeHead(204);
return res.end();
}
if (req.method !== "GET" && req.method !== "HEAD") {
res.writeHead(405, { Allow: "GET, HEAD, OPTIONS" });
return res.end();
}
const reqUrl = req.url || "/";
const filePath = safeJoin(reqUrl);
if (!filePath) {
res.writeHead(400);
return res.end("Bad Request");
}
fs.stat(filePath, (err, stats) => {
if (err) {
// No file at this path → SPA fallback to index.html
return sendIndex(req, res);
}
if (stats.isFile()) {
return streamFile(req, res, filePath, stats);
}
if (stats.isDirectory()) {
const indexInDir = path.join(filePath, "index.html");
return fs.stat(indexInDir, (dirErr, dirStats) => {
if (!dirErr && dirStats && dirStats.isFile()) {
return streamFile(req, res, indexInDir, dirStats);
}
return sendIndex(req, res);
});
}
// Path exists but is neither file nor directory → SPA fallback
return sendIndex(req, res);
});
});
server.listen(port, host, () => {
console.log(`Serving ${root} on http://${host}:${port}`);
});
+3
View File
@@ -35,6 +35,8 @@ const UpdateExistUserAdmin = lazyWithRetry(
const Preferences = lazyWithRetry(() => import("./pages/Preferences"));
const Login = lazyWithRetry(() => import("./pages/Login"));
const VerifyDocument = lazyWithRetry(() => import("./pages/VerifyDocument"));
const EmailBuilder = lazyWithRetry(() => import("./pages/EmailBuilder"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
@@ -137,6 +139,7 @@ function App() {
/>
</Route>
<Route path="/success" element={<DocSuccessPage />} />
<Route path="/emailbuilder" element={<EmailBuilder />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
-302
View File
@@ -1,302 +0,0 @@
import React, { useState, useEffect } from "react";
import Parse from "parse";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import { emailRegex } from "../constant/const";
const AddSigner = (props) => {
const { t } = useTranslation();
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
const [addYourself, setAddYourself] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [isUserExist, setIsUserExist] = useState(false);
useEffect(() => {
checkUserExist();
}, []);
// Load user details from localStorage when the component mounts
useEffect(() => {
const savedUserDetails = JSON.parse(
localStorage.getItem("UserInformation")
);
if (savedUserDetails && addYourself) {
setName(savedUserDetails.name);
setPhone(savedUserDetails?.phone || "");
setEmail(savedUserDetails.email?.toLowerCase()?.replace(/\s/g, ""));
}
}, [addYourself]);
const checkUserExist = async () => {
const user = Parse.User.current();
try {
const query = new Parse.Query("contracts_Contactbook");
query.equalTo("CreatedBy", user);
query.notEqualTo("IsDeleted", true);
query.equalTo("Email", user.getEmail());
const res = await query.first();
// console.log(res);
if (!res) {
setIsUserExist(true);
}
} catch (err) {
console.log("err", err);
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(email)) {
alert(t("valid-email-alert"));
} else {
setIsLoader(true);
if (localStorage.getItem("TenantId")) {
try {
const user = Parse.User.current();
const query = new Parse.Query("contracts_Contactbook");
query.equalTo("CreatedBy", user);
query.notEqualTo("IsDeleted", true);
query.equalTo("Email", email);
const res = await query.first();
// console.log(res);
if (!res) {
const contactQuery = new Parse.Object("contracts_Contactbook");
contactQuery.set("Name", name);
if (phone) {
contactQuery.set("Phone", phone);
}
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest");
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", name);
_user.set("username", email);
_user.set("email", email);
_user.set("password", email);
if (phone) {
_user.set("phone", phone);
}
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey],
email: parseData?.Email
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: email };
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey],
email: parseData?.Email
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
}
} else {
alert(t("add-signer-alert"));
setIsLoader(false);
}
} catch (err) {
console.log("err in fetch contact", err);
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
}
} else {
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
}
}
};
// Define a function to handle the "add yourself" checkbox
const handleAddYourselfChange = () => {
if (addYourself) {
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
} else {
setAddYourself(true);
}
};
const handleReset = () => {
setAddYourself(false);
};
return (
<div className="h-full p-[20px]">
{isLoader && (
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<div className="w-full mx-auto p-2 text-base-content">
<p className="mb-3 text-xs text-gray-500">{t("add-signer-note")}</p>
{isUserExist && (
<div className="mb-3 flex items-center">
<input
type="checkbox"
id="addYourself"
checked={addYourself}
onChange={handleAddYourselfChange}
className="op-checkbox op-checkbox-sm"
/>
<label htmlFor="addYourself" className="ml-2 mb-0">
{t("add-yourself")}
</label>
</div>
)}
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="name" className="block text-xs font-semibold">
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
required
disabled={addYourself}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("enter-name")}
/>
</div>
<div className="mb-3">
<label htmlFor="email" className="block text-xs font-semibold">
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) =>
setEmail(e.target.value?.toLowerCase()?.replace(/\s/g, ""))
}
required
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
disabled={addYourself}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("enter-email")}
/>
</div>
<div className="mb-3">
<label htmlFor="phone" className="block text-xs font-semibold">
{t("phone")}
</label>
<input
type="text"
id="phone"
value={phone}
onChange={(e) => setPhone(e.target.value)}
disabled={addYourself}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("phone-optional")}
/>
</div>
<div className="mt-4 flex gap-x-2 justify-start">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<button
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("reset")}
</button>
</div>
</form>
</div>
</div>
);
};
export default AddSigner;
+5 -4
View File
@@ -11,6 +11,8 @@ import {
import {
useTranslation
} from "react-i18next";
import { withSessionValidation } from "../utils";
function generatePassword(length) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -67,7 +69,7 @@ const AddUser = (props) => {
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
const handleSubmit = withSessionValidation(async (e) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(formdata.email)) {
@@ -130,7 +132,7 @@ const AddUser = (props) => {
}
}
}
};
});
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
@@ -216,7 +218,7 @@ const AddUser = (props) => {
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
{t("password-generated")}
</div>
</div>
<div className="mb-3">
@@ -225,7 +227,6 @@ const AddUser = (props) => {
className="block text-xs font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
<input
type="text"
-316
View File
@@ -1,316 +0,0 @@
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import SuggestionInput from "./shared/fields/SuggestionInput";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import {
emailRegex,
} from "../constant/const";
const BulkSendUi = (props) => {
const { t } = useTranslation();
const [forms, setForms] = useState([]);
const formRef = useRef(null);
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [isSignatureExist, setIsSignatureExist] = useState();
const [isDisableBulkSend, setIsDisableBulkSend] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [signers, setSigners] = useState([]);
const [emails, setEmails] = useState([]);
const [isPrefillExist, setIsPrefillExist] = useState(false);
useEffect(() => {
signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function to check at least one signature field exist
const signatureExist = async () => {
const isPrefill = props?.Placeholders.some((x) => x?.Role === "prefill");
if (isPrefill) {
setIsPrefillExist(isPrefill);
}
setIsDisableBulkSend(false);
const getPlaceholder = props?.Placeholders;
const removePrefill = getPlaceholder.filter((x) => x?.Role !== "prefill");
const checkIsSignatureExistt = removePrefill?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
};
useEffect(() => {
if (scrollOnNextUpdate && formRef.current) {
formRef.current.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest"
});
setScrollOnNextUpdate(false);
}
}, [forms, scrollOnNextUpdate]);
useEffect(() => {
(() => {
if (props?.Placeholders?.length > 0) {
let users = [];
let emails = [];
props?.Placeholders?.forEach((element) => {
const signerEmail = element?.email || element?.signerPtr?.Email;
// only add when there's a non-empty signerEmail
if (signerEmail) {
emails = [...emails, signerEmail];
}
if (!element.signerObjId) {
users = [
...users,
{
fieldId: element.Id,
email: "",
label: element.Role,
signer: {}
}
];
}
});
setEmails(emails);
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
const signer = props.item?.Signers?.filter((x) => x?.objectId);
setSigners(signer);
}
})();
// eslint-disable-next-line
}, []);
const handleInputChange = (index, signer, fieldIndex) => {
const newForms = [...forms];
newForms[index].fields[fieldIndex].email = signer?.Email
? signer?.Email
: signer || "";
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
setForms(newForms);
};
const handleRemoveForm = (index) => {
const updatedForms = forms.filter((_, i) => i !== index);
setForms(updatedForms);
};
function validateEmails(data) {
for (const item of data) {
let email = "";
for (const field of item.fields) {
if (!emailRegex.test(field.email)) {
alert(t("invalid-email-found", { email: field.email }));
return false;
} else if (email === field.email || emails?.includes(field.email)) {
alert(t("duplicate-email-found", { email: field.email }));
return false;
} else {
email = field.email;
}
}
}
return true;
}
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmit(true);
if (validateEmails(forms)) {
// Create a copy of Placeholders array from props.item
let Placeholders = [...props.Placeholders];
// Initialize an empty array to store updated documents
let Documents = [];
// Loop through each form
forms.forEach((form) => {
//checking if user enter email which already exist as a signer then add user in a signers array
let existSigner = [];
form.fields.map((data) => {
if (data.signer) {
existSigner.push(data.signer);
}
});
// Map through the copied Placeholders array to update email values
const updatedPlaceholders = Placeholders.map((placeholder) => {
// Find the field in the current form that matches the placeholder Id
const field = form.fields.find(
(element) => parseInt(element.fieldId) === placeholder.Id
);
// If a matching field is found, update the email value in the placeholder
const signer = field?.signer?.objectId ? field.signer : "";
if (field) {
if (signer) {
return {
...placeholder,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
} else {
return {
...placeholder,
email: field.email,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
}
}
// If no matching field is found, keep the placeholder as is
return placeholder;
});
// Push a new document object with updated Placeholders into the Documents array
if (existSigner?.length > 0) {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
Signers: signers ? [...signers, ...existSigner] : [...existSigner]
});
} else {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
SignatureType: props.signatureType,
Signers: signers
});
}
});
await batchQuery(Documents);
} else {
setIsSubmit(false);
}
};
const batchQuery = async (Documents) => {
const token =
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const functionsUrl = `${localStorage.getItem(
"baseUrl"
)}functions/batchdocuments`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
...token,
};
const params = { Documents: JSON.stringify(Documents) };
try {
const res = await axios.post(functionsUrl, params, { headers: headers });
// console.log("res ", res);
if (res.data && res.data.result) {
props.handleClose("success", Documents?.length);
}
} catch (err) {
console.log("Err ", err);
props.handleClose("error", 0);
} finally {
setIsSubmit(false);
}
};
return (
<>
{isLoader ? (
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
<Loader />
</div>
) : (
<>
{isSubmit && (
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
{isPrefillExist ? (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("prefill-bulk-error")}
</div>
) : !isDisableBulkSend ? (
<>
{props.Placeholders?.length > 0 ? (
isSignatureExist ? (
<>
{props.Placeholders?.some((x) => !x.signerObjId) ? (
<>
<form onSubmit={handleSubmit}>
<div className="min-h-max max-h-[250px] overflow-y-auto">
{forms?.map((form, index) => (
<div
key={form.Id}
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
>
{form?.fields?.map((field, fieldIndex) => (
<div
className="flex flex-col"
key={field.fieldId}
>
<label className="block text-xs font-semibold">
{field.label}
</label>
<SuggestionInput
required
type="email"
value={field.value}
index={fieldIndex}
onChange={(signer) =>
handleInputChange(
index,
signer,
fieldIndex
)
}
/>
</div>
))}
{forms?.length > 1 && (
<button
onClick={() => handleRemoveForm(index)}
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
>
<i className="fa-light fa-trash"></i>
</button>
)}
<div ref={formRef}></div>
</div>
))}
</div>
<div className="flex flex-col mx-4 mb-4 gap-3">
<button
type="submit"
className="op-btn op-btn-accent focus:outline-none"
>
<i className="fa-light fa-paper-plane"></i>{" "}
<span>{t("send")}</span>
</button>
</div>
</form>
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-1")}
</div>
)}
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-2")}
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-3")}
</div>
)}
</>
) : (
<>
</>
)}
</>
)}
</>
);
};
export default BulkSendUi;
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import ModalUi from "../primitives/ModalUi";
import { useTranslation } from "react-i18next";
@@ -7,7 +7,6 @@ const ColumnSelector = ({
allColumns = [],
visibleColumns = [],
columnLabels = {},
defaultColumns = [],
onApply,
onClose
}) => {
@@ -42,7 +41,7 @@ const ColumnSelector = ({
type="checkbox"
checked={selected.includes(col)}
onChange={() => handleChange(col)}
className="mb-0"
className="mb-0 cursor-pointer"
/>
<span className="whitespace-nowrap ml-1">
{t(`report-heading.${col}`, { defaultValue: col })}
@@ -0,0 +1,35 @@
import { useTranslation } from "react-i18next";
const DateFormat = ({ selectDate, dateFormatList, handleChangeFormat }) => {
const selectedFormatIndex = dateFormatList?.findIndex(
(item) => item.format === selectDate?.format
);
const { t } = useTranslation();
return (
<div className="flex flex-col md:items-center md:flex-row gap-y-3 my-2">
<span className="capitalize">{t("format")}: </span>
<select
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs md:ml-2"
value={selectedFormatIndex >= 0 ? selectedFormatIndex : ""}
onChange={(e) => handleChangeFormat(e)}
>
<option value="" disabled>
{t("select-date-format")}
</option>
{dateFormatList.map((data, ind) => {
return (
<option className="text-[13px]" value={ind} key={ind}>
{data?.date ? data?.date : "nodata"}
</option>
);
})}
</select>
<span className="text-xs text-gray-400 ml-1 uppercase">
{selectDate.format}
</span>
</div>
);
};
export default DateFormat;
@@ -0,0 +1,95 @@
import { forwardRef } from "react";
import ReactDatePicker from "react-datepicker";
import {
getDefaultDate,
getMonth,
getYear,
months,
years
} from "../constant/Utils";
import { useTranslation } from "react-i18next";
const DatePicker = ({
selectDate,
format,
minDate,
maxDate,
onChange,
handleClear,
label,
showLabel = true,
showClear = true,
dateClassName = ""
}) => {
const { t } = useTranslation();
const CustomInput = forwardRef(({ value, onClick }, ref) => (
<div
className="w-full border-gray-400 rounded-[50px] border-[1px] px-3 text-xs py-2 focus:outline-none hover:border-base-content flex items-center justify-between"
onClick={onClick}
ref={ref}
>
<span className={`${dateClassName} truncate`}>{value}</span>
<i className={`fa-light fa-calendar`}></i>
</div>
));
CustomInput.displayName = "CustomInput";
return (
<>
{showLabel && (
<span className="flex-shrink-0">{label || t("default-date")}: </span>
)}
<ReactDatePicker
renderCustomHeader={({ date, changeYear, changeMonth }) => (
<div className="flex justify-start md:ml-2">
<select
className="bg-transparent outline-none"
value={months[getMonth(date)]}
onChange={({ target: { value } }) =>
changeMonth(months.indexOf(value))
}
>
{months.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<select
className="bg-transparent outline-none"
value={getYear(date)}
onChange={({ target: { value } }) => changeYear(value)}
>
{years.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
)}
wrapperClassName="w-full"
closeOnScroll={true}
selected={getDefaultDate(selectDate?.date, selectDate?.format)}
popperPlacement="top-end"
customInput={<CustomInput />}
onChange={(date) => onChange(date)}
dateFormat={
selectDate ? selectDate?.format : format ? format : "MM/dd/yyyy"
}
portalId="root-portal"
/>
{showClear && handleClear && (
<span
onClick={() => handleClear()}
className="underline text-blue-500 cursor-pointer ml-2"
>
{t("clear")}
</span>
)}
</>
);
};
export default DatePicker;
+1 -25
View File
@@ -1,12 +1,8 @@
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
import {
DndProvider,
TouchTransition,
MouseTransition,
Preview
MouseTransition
} from "react-dnd-multi-backend";
import DragElement from "./pdf/DragElement";
import LazyPage from "../primitives/LazyPage";
import { GuidelinesProvider } from "../context/GuidelinesContext";
@@ -16,33 +12,13 @@ const HTML5toTouch = {
id: "html5",
backend: HTML5Backend,
transition: MouseTransition
},
{
id: "touch",
backend: TouchBackend,
options: { enableMouseEvents: true },
preview: true,
transition: TouchTransition
}
]
};
/* This handling in only for devices which support touch */
const generatePreview = (props) => {
const { item, style } = props;
const newStyle = { ...style };
return (
<div style={newStyle}>
<DragElement {...item} />
</div>
);
};
export default function DragProvider({ Page, lazy = false }) {
return (
<DndProvider options={HTML5toTouch}>
<Preview>{generatePreview}</Preview>
<GuidelinesProvider>
{lazy ? <LazyPage Page={Page} /> : <Page />}
</GuidelinesProvider>
@@ -0,0 +1,130 @@
import { useState, useEffect } from "react";
import DOMPurify from "dompurify";
import juice from "juice";
import { Trans, useTranslation } from "react-i18next";
const EmailBodyEditor = ({
value,
onChange,
smallscreen = false,
bodyName,
isReset = false,
isTemplateLoaded
}) => {
const { t } = useTranslation();
const [inputHtml, setInputHtml] = useState("");
const [cleanPreview, setCleanPreview] = useState("");
useEffect(() => {
initProcessContent();
}, [isTemplateLoaded]);
useEffect(() => {
if (isReset) {
initProcessContent(value);
}
}, [isReset]);
const initProcessContent = () => {
// 1. Sanitize immediately to strip scripts/malicious tags
const sanitized = DOMPurify.sanitize(value, {
USE_PROFILES: { html: true }, // Ensures basic HTML structure
ADD_ATTR: ["target"] // Allow links to open in new tabs
});
// 2. Inline CSS for email compatibility
// Note: Juice is fast, but for huge files, you might debounce this
try {
const inlined = juice(sanitized);
setInputHtml(inlined);
onChange?.(inlined);
setCleanPreview(inlined);
} catch (err) {
onChange?.(sanitized);
setInputHtml(sanitized); // Fallback if juice fails
setCleanPreview(sanitized);
}
};
const processContent = (value) => {
// 1. Sanitize immediately to strip scripts/malicious tags
const sanitized = DOMPurify.sanitize(value, {
USE_PROFILES: { html: true }, // Ensures basic HTML structure
ADD_ATTR: ["target"] // Allow links to open in new tabs
});
// 2. Inline CSS for email compatibility
// Note: Juice is fast, but for huge files, you might debounce this
try {
const inlined = juice(sanitized);
setCleanPreview(inlined);
onChange?.(inlined);
} catch (err) {
setCleanPreview(sanitized); // Fallback if juice fails
onChange?.(sanitized);
}
};
const handleChange = (e) => {
const value = e.target.value;
setInputHtml(value);
processContent(value);
};
const screen = smallscreen ? "flex-col" : "flex-col md:flex-row ";
const template =
bodyName === "request"
? "#sample/requestemail"
: bodyName === "completion"
? "#sample/completionemail"
: "#";
return (
<>
<p className="text-sm ">
<Trans i18nKey={"open-email-builder"}>
{"You can create email template using "}
<a
href={`/emailbuilder${template}`}
target="_blank"
referrerPolicy="no-referrer"
className="op-link op-link-primary font-medium"
>
email builder
</a>
{" platform and copy html code."}
</Trans>
</p>
<div className={`flex ${screen} gap-5`}>
{/* Editor Pane */}
<div className="flex flex-1 flex-col mt-2">
<label>{t("paste-html-here")}:</label>
<div className="flex-1 mt-1 p-3 text-xs op-textarea op-textarea-bordered min-h-[70vh]">
<textarea
className="w-full min-h-[70vh] focus:outline-none"
value={inputHtml}
onChange={(e) => handleChange(e)}
placeholder="<html><body><h1>Hello!</h1></body></html>"
/>
</div>
</div>
{/* Live Preview Pane */}
<div className="flex flex-1 flex-col mt-2">
<label>{t("preview")}</label>
<div className="flex-1 mt-1 bg-white border-[1px] op-textarea border-[#ccc] min-h-[70vh]">
{cleanPreview && (
<iframe
title="Safe Preview"
srcDoc={cleanPreview}
// SECURITY: sandbox prevents scripts from running even if they slip through
sandbox="allow-popups allow-popups-to-escape-sandbox"
className="w-full min-h-[70vh]"
/>
)}
</div>
</div>
</div>
</>
);
};
export default EmailBodyEditor;
+8 -6
View File
@@ -13,8 +13,8 @@ import {
import { useTranslation } from "react-i18next";
import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { toggleSidebar } from "../redux/reducers/sidebarReducer.js";
import { sessionStatus } from "../redux/reducers/userReducer.js";
import { toggleSidebar } from "../redux/reducers/sidebarReducer";
import { sessionStatus } from "../redux/reducers/userReducer";
const Header = ({ isConsole, setIsLoggingOut }) => {
const { t, i18n } = useTranslation();
@@ -60,7 +60,6 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
setAppLogo(logo);
}
}
const handleLogout = async () => {
setIsOpen(false);
setIsLoggingOut(true);
@@ -141,7 +140,10 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
</button>
</div>
<div className="flex-1 ml-2">
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
<div
onClick={() => navigate("/dashboard/35KBoSgoAK")}
className="h-[25px] md:h-[40px] w-auto overflow-hidden cursor-pointer"
>
{applogo && (
<img
className="object-contain h-full w-auto"
@@ -157,7 +159,7 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
</div>
<div id="profile-menu" className="flex-none gap-2">
<div>
<FullScreenButton />
<FullScreenButton />
</div>
{width >= 768 && (
<div
@@ -195,7 +197,7 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
</div>
<ul
tabIndex={0}
className={`mt-3 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-56 ${
className={`mt-4 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-56 ${
isOpen ? "" : "hidden"
}`}
>
+3 -4
View File
@@ -1,4 +1,3 @@
import { Helmet } from "react-helmet";
import { useLocation, matchPath } from "react-router";
import { useTranslation } from "react-i18next";
import { useMemo } from "react";
@@ -70,13 +69,13 @@ export default function Title() {
[t, prefix, appName]
);
const manifestUrl = useManifestUrl(appName, logo);
return (
<Helmet>
<>
<title>{title}</title>
<meta name="description" content={title} />
{logo && <link rel="icon" type="image/png" href={logo} />}
<link rel="manifest" href={manifestUrl} />
</Helmet>
</>
);
}
@@ -0,0 +1,650 @@
import {
useState,
useEffect,
useMemo
} from "react";
import axios from "axios";
import Loader from "../../primitives/Loader";
import { useTranslation } from "react-i18next";
import {
emailRegex,
} from "../../constant/const";
import {
getSignedUrl,
isBase64,
getBase64FromUrl,
generateId,
} from "../../constant/Utils";
import {
handleEmbedPrefillToDoc,
isWidgetResponseCompatible,
formatCSVDate,
normalizeKey,
loadPdfOnce,
hasSignatureWidget,
} from "../../utils";
import { useDispatch, useSelector } from "react-redux";
import {
setBulkLoader,
} from "../../redux/reducers/widgetSlice";
import Table from "./components/Table";
import PrefillWidgets from "./components/PrefillWidgets";
import WizardHeader from "./components/WizardHeader";
import ResponseTab from "./components/ResponseTab";
import { steps } from "../../json/BulkSendSteps";
const EXCLUDED_PREFILL_TYPES = new Set(["image", "draw"]);
const EXCLUDED_WIDGET_TYPES = new Set([
"image",
"signature",
"stamp",
"initials",
]);
const ALL_EXCLUDED_TYPES = new Set([
...EXCLUDED_PREFILL_TYPES,
...EXCLUDED_WIDGET_TYPES
]);
const BulkSendUi = (props) => {
const { t } = useTranslation();
const appName =
"OpenSign™";
const dispatch = useDispatch();
const { isBulkLoader } = useSelector((state) => state.widget);
const [forms, setForms] = useState([]);
const [isSignatureExist, setIsSignatureExist] = useState();
const [isLoader, setIsLoader] = useState(false);
const [signers, setSigners] = useState([]);
const [emails, setEmails] = useState([]);
const [isVacantRoles, setIsVacantRoles] = useState(false);
const [fields, setFields] = useState([]);
const [headers, setHeaders] = useState([]);
const [prefillWidgets, setPrefillWidgets] = useState([]);
const [step, setStep] = useState(0);
const [stepsList, setStepsList] = useState(steps(t));
const [stepKey, setStepKey] = useState(steps(t)?.[0]?.key || "");
const [message, setMessage] = useState({ status: "", message: "" });
const tokenHeader = useMemo(() => {
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
};
return {
...headers,
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
};
}, [props?.jwttoken]);
// Initialize form state (fields, widgets/responses, emails, headers, forms, signers, allowedForm) from template placeholders with existing values.
const initializeWidgetsWithValues = async () => {
if (props?.Placeholders?.length > 0) {
const roles = props?.Placeholders?.filter((p) => !p.signerObjId) || [];
const reservedEmails = (
props?.Placeholders?.filter((p) => !!p?.signerObjId) || []
)
.map(
(role) =>
normalizeKey(role?.email) || normalizeKey(role?.signerPtr?.Email)
)
.filter(Boolean);
const users = await Promise.all(
roles?.map(async (role) => {
const isPrefill = role.Role?.toLowerCase() === "prefill";
const uniqueNames = new Set();
const pages = role?.placeHolder ?? [];
const widgets = await Promise.all(
pages.flatMap((page) =>
page.pos
.filter((widget) => {
const widgetName = widget?.options?.name;
if (!widgetName) return false; //|| widget?.options?.isReadOnly
// exclude certain widget types for non-prefill
if (EXCLUDED_WIDGET_TYPES.has(widget.type) && !isPrefill)
return false;
if (widget?.options?.formula) return false;
// unique by widget name
if (uniqueNames.has(widgetName)) return false;
uniqueNames.add(widgetName);
return true;
})
.map(async (widget) => {
let response = "";
if (ALL_EXCLUDED_TYPES.has(widget.type)) {
const url =
widget.options?.response ||
widget.options?.defaultValue ||
"";
if (url && !isBase64(url)) {
const signedUrl = await getSignedUrl(
url,
"", // docId
props.item.objectId, // templateId
);
response = await getBase64FromUrl(signedUrl, true);
} else {
response = "";
}
} else {
if (widget.type === "date") {
const value =
widget.options?.response ||
widget.options?.defaultValue ||
"";
response = value ? formatCSVDate(widget, value) : "";
} else {
response =
widget.options?.response ||
widget.options?.defaultValue ||
"";
}
}
const label = `${role.Role}::${widget.options?.name}`;
return {
...widget,
options: { ...widget.options, response: response },
label: label,
response,
pageNumber: page.pageNumber
};
})
)
);
return {
fieldId: role.Id,
email: "",
label: role.Role,
signer: {},
widgets: widgets
};
})
);
const prefills = users.find((r) => r?.label?.toLowerCase() === "prefill");
const signers = users.filter(
(r) => r?.label?.toLowerCase() !== "prefill"
);
setFields(signers);
if (prefills && prefills?.widgets?.length) {
setPrefillWidgets(prefills?.widgets);
} else {
setStepsList((s) => s.filter((step) => step.key !== "prefill"));
setStepKey("recipients");
setStep(0);
}
setEmails(reservedEmails);
// Build headers with required ordering:
// 1) all role emails first
// 2) then all widgets
const emailHeaders = [];
const widgetHeaders = [];
for (const role of signers) {
const isPrefill = role?.label?.toLowerCase() === "prefill";
if (!isPrefill) {
emailHeaders.push({
label: `${role.label}::Email`,
role: role.label,
type: "role",
isRequired: true
});
}
for (const widget of role?.widgets ?? []) {
const isRequired =
widget?.options?.status === "required" && isPrefill;
widgetHeaders.push({
label: widget?.label,
role: role?.label,
type: widget?.type,
isRequired
});
}
}
setHeaders([...emailHeaders, ...widgetHeaders]);
setForms((prev) => [...prev, { Id: generateId(8), fields: signers }]);
const signer = props.item?.Signers?.filter((x) => x?.objectId);
setSigners(signer);
}
};
useEffect(() => {
signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const checkSignatureAndRoles = (placeholders = []) => {
const filtered = placeholders.filter((x) => x?.Role !== "prefill");
// Viewer/approver roles do not require a signature widget; only
// signer-role placeholders must have one.
const isSignExist =
filtered?.length > 0 && filtered.every((p) => hasSignatureWidget(p));
setIsSignatureExist(isSignExist);
setIsVacantRoles(filtered?.some((x) => !x.signerObjId));
};
//function to check at least one signature field exist
const signatureExist = async () => {
setIsLoader(true);
await initializeWidgetsWithValues();
checkSignatureAndRoles(props?.Placeholders);
setIsLoader(false);
};
const handleInputChange = (formIndex, signer, role) => {
setForms((prev) => {
const next = [...prev];
const form = next[formIndex];
if (!form) return prev;
const fields = [...(form.fields ?? [])];
const fieldId = fields.findIndex((f) => f.label === role);
if (fieldId < 0) return prev;
const email = signer?.Email ? signer.Email : signer || "";
fields[fieldId] = {
...fields[fieldId],
email: normalizeKey(email),
signer: signer?.objectId ? signer : ""
};
next[formIndex] = { ...form, fields };
return next;
});
};
const handleWidgetDetails = (
value = "",
formIndex,
roleIndex = 0,
widgetIndex
) => {
setForms((prev) => {
const next = [...prev];
const form = next[formIndex];
if (!form) return prev;
const fields = [...(form.fields ?? [])];
const field = fields[roleIndex];
if (!field) return prev;
const widgets = [...(field.widgets ?? [])];
const w = widgets[widgetIndex];
if (!w) return prev;
widgets[widgetIndex] = {
...w,
options: { ...w?.options, response: value },
response: value
};
fields[roleIndex] = { ...field, widgets };
next[formIndex] = { ...form, fields };
return next;
});
};
function validateEmails(data) {
for (const [rowIndex, item] of data.entries()) {
const rowEmails = new Set();
const rowNumber = rowIndex + 1;
const filterFields = item?.fields || [];
for (const field of filterFields) {
// skip prefill
if (field?.label === "prefill") continue;
const normalizedEmail = normalizeKey(field?.email);
const params = { email: field.email, row: rowNumber };
if (!normalizedEmail) {
alert(t("email-not-found-in-row", { ...params }));
return false;
} else if (!emailRegex.test(normalizedEmail)) {
alert(t("invalid-email-found-in-row", { ...params }));
return false;
} else if (
rowEmails.has(normalizedEmail) ||
emails?.includes(normalizedEmail)
) {
alert(t("duplicate-email-found-in-row", { ...params }));
return false;
} else {
rowEmails.add(normalizedEmail);
}
}
}
return true;
}
// `updateWidgetValues` will update responses for all widget types
const updateWidgetValues = (widgetValues = [], placeholders = []) => {
if (!widgetValues?.length) return placeholders;
const responseByName = new Map(
widgetValues
.map((w) => [w?.options?.name, w?.response])
.filter(([name]) => !!name)
);
return placeholders.map((ph) => {
const updatedPages = (ph?.placeHolder ?? []).map((page) => {
const updatedPos = (page?.pos ?? []).map((widgetDetails) => {
const name = widgetDetails?.options?.name;
if (!name || !responseByName.has(name)) return widgetDetails;
const newResponse = responseByName?.get(name) || "";
const isValidResponse = isWidgetResponseCompatible(
widgetDetails,
newResponse
);
if (!isValidResponse) return widgetDetails;
return {
...widgetDetails,
options: {
...widgetDetails.options,
// update response; if you DON'T want undefined to overwrite, keep the ?? fallback
response:
newResponse ??
widgetDetails.options?.response ??
widgetDetails.options?.defaultValue
}
};
});
return { ...page, pos: updatedPos };
});
return { ...ph, placeHolder: updatedPages };
});
};
// `removeWidgetValues` will clear out responses for draw/image/signature/stamp/initials widgets
const removeWidgetValues = (widgetValues = [], placeholders = []) => {
if (!widgetValues?.length) return placeholders;
return placeholders.map((ph) => {
const updatedPages = (ph?.placeHolder ?? []).map((page) => {
const updatedPos = (page?.pos ?? []).map((widgetDetails) => {
const isImageType = ALL_EXCLUDED_TYPES.has(widgetDetails?.type);
return {
...widgetDetails,
options: {
...widgetDetails.options,
// update response; if you DON'T want undefined to overwrite, keep the ?? fallback
response: isImageType ? "" : widgetDetails.options?.response,
defaultValue: isImageType
? ""
: widgetDetails.options?.defaultValue
}
};
});
return { ...page, pos: updatedPos };
});
return { ...ph, placeHolder: updatedPages };
});
};
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
dispatch(setBulkLoader(true));
if (!forms.length) {
alert(t("bulk-send-no-records"));
dispatch(setBulkLoader(false));
return;
}
if (validateEmails(forms)) {
// Create a copy of Placeholders array from props.item
let Placeholders = [...props.Placeholders];
let pdfUrl = props.item?.URL || props.item?.SignedUrl;
const prefillExist = Placeholders?.some(
(data) => data.Role === "prefill"
);
if (prefillExist) {
const updatedPrefills = updateWidgetValues(
prefillWidgets,
Placeholders
);
const prefillDetails = updatedPrefills?.find(
(data) => data.Role === "prefill"
);
const pdfSignedUrl = await getSignedUrl(
pdfUrl,
"", //docId
props.item.objectId, // templateId
);
const pdfArrayBuffer = await loadPdfOnce(pdfSignedUrl);
if (pdfArrayBuffer === "Error") {
const error = t("something-went-wrong-mssg");
alert(error);
dispatch(setBulkLoader(false));
return;
}
pdfUrl = await handleEmbedPrefillToDoc(
prefillDetails,
1, // scale
pdfArrayBuffer,
[], // prefillImg,
props.item?.ExtUserPtr?.UserId?.objectId // userId
);
if (pdfUrl?.error) {
const error = pdfUrl?.error?.includes("not compatible")
? t("pdf-uncompatible", { appName: appName })
: pdfUrl?.error;
alert(error);
dispatch(setBulkLoader(false));
return;
}
}
// Initialize an empty array to store updated documents
let Documents = [];
let error = "";
// Loop through each form
for (const form of forms) {
//checking if user enter email which already exist as a signer then add user in a signers array
let existSigner = [];
form.fields.map((data) => {
if (data.signer) {
existSigner.push(data.signer);
}
});
// Map through the copied Placeholders array to update email values
const updatedPlaceholders = Placeholders.map((placeholder) => {
// Find the field in the current form that matches the placeholder Id
const field = form.fields.find(
(element) => parseInt(element.fieldId) === placeholder.Id
);
// If a matching field is found, update the email value in the placeholder
const signer = field?.signer?.objectId ? field.signer : "";
if (field) {
if (signer) {
return {
...placeholder,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
} else {
return {
...placeholder,
email: normalizeKey(field.email),
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
}
}
// If no matching field is found, keep the placeholder as is
return placeholder;
});
const widgetValues = form?.fields?.flatMap((f) => f.widgets);
const updateWidgetPlaceholder = updateWidgetValues(
widgetValues,
updatedPlaceholders
);
let signedUrl = pdfUrl;
const placeholders = removeWidgetValues(
widgetValues,
updateWidgetPlaceholder
);
if (!signedUrl) {
error = t("something-went-wrong-mssg");
dispatch(setBulkLoader(false));
break;
}
// Push a new document object with updated Placeholders into the Documents array
if (existSigner?.length > 0) {
Documents.push({
...props.item,
URL: signedUrl,
Placeholders: placeholders,
Signers: signers ? [...signers, ...existSigner] : [...existSigner]
});
} else {
Documents.push({
...props.item,
URL: signedUrl,
Placeholders: placeholders,
SignatureType: props.signatureType,
Signers: signers
});
}
}
if (error) {
alert(error);
} else {
await batchQuery(Documents);
}
} else {
dispatch(setBulkLoader(false));
}
};
const batchQuery = async (Documents) => {
const functionsUrl = `${localStorage.getItem("baseUrl")}functions/batchdocuments`;
const headers = {
...tokenHeader,
};
const params = { Documents: JSON.stringify(Documents) };
try {
const res = await axios.post(functionsUrl, params, { headers: headers });
if (res.data && res.data.result) {
setStep((s) => Math.min(2, s + 1));
setStepKey("response");
setMessage({
status: "success",
message: "Documents sent successfully."
});
// props.handleClose("success", Documents?.length);
}
} catch (err) {
const message =
err?.response?.data?.error || err?.message || "something went wrong.";
console.error("Error sending documents:", message);
setMessage({ status: "failed", message });
// props.handleClose("error", 0, message);
} finally {
dispatch(setBulkLoader(false));
}
};
if (isLoader) {
return (
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
<Loader />
</div>
);
}
return (
<div className="relative">
{props.Placeholders?.length > 0 ? (
isSignatureExist ? (
isVacantRoles ? (
<>
<div className="border-t mt-3" />
{/* Wizard header */}
<div className="px-6 py-3">
<WizardHeader
steps={stepsList}
step={step}
// onStepClick={(i) => i <= step && setStep(i)}
/>
</div>
{stepKey === "prefill" && (
<PrefillWidgets
prefills={prefillWidgets}
setPrefills={setPrefillWidgets}
onNext={() => {
setStep((s) => Math.min(2, s + 1));
setStepKey("recipients");
}}
/>
)}
{stepKey === "recipients" && (
<>
<form onSubmit={handleSubmit}>
<div className="min-h-max max-h-[250px] overflow-auto">
{
forms?.length > 0 && (
<div className="mx-4">
<Table
headers={headers}
rowData={forms}
handleInputChange={handleInputChange}
handleWidgetDetails={handleWidgetDetails}
/>
</div>
)
}
</div>
<div className="flex flex-row flex-wrap pb-3 pt-2 px-3 gap-3 justify-center">
<button
type="submit"
className="op-btn op-btn-accent w-[150px] focus:outline-none"
>
<i className="fa-light fa-paper-plane"></i>
<span>{t("send")}</span>
</button>
</div>
</form>
</>
)}
{stepKey === "response" && (
<ResponseTab
prefillCount={prefillWidgets?.length}
documentCount={forms?.length}
message={message}
/>
)}
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-1")}
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-2")}
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-3")}
</div>
)}
</div>
);
};
export default BulkSendUi;
@@ -0,0 +1,68 @@
import RenderWidgets from "./RenderWidgets";
import { useTranslation } from "react-i18next";
const PrefillWidgets = ({ prefills = [], setPrefills, onNext }) => {
const { t } = useTranslation();
const handleWidgetDetails = (value, widgetIndex) => {
setPrefills((prev) => {
const widgets = [...(prev ?? [])];
const w = widgets[widgetIndex];
if (!w) return prev;
widgets[widgetIndex] = {
...w,
options: { ...w.options, response: value },
response: value
};
return widgets;
});
};
const handleNext = (e) => {
e?.preventDefault();
e?.stopPropagation();
onNext?.();
};
return (
<>
{prefills?.length > 0 && (
<form
onSubmit={handleNext}
className="m-3 md:m-6 text-base-content flex flex-col relative"
>
<div className="py-3 px-[10px] op-card border-[1px] border-gray-400">
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 gap-y-4 w-full">
{[...prefills]
.sort((a, b) =>
a.pageNumber !== b.pageNumber
? a.pageNumber - b.pageNumber
: (a.yPosition ?? 0) - (b.yPosition ?? 0)
)
.map((widget, index) => (
<RenderWidgets
key={widget.key}
showLabel
widget={widget}
handleWidgetDetails={(value) =>
handleWidgetDetails(value, index)
}
/>
))}
</div>
</div>
<div className="flex flex-row flex-wrap mt-3 gap-3 justify-center">
<button
type="submit"
className="op-btn op-btn-primary w-[150px] focus:outline-none"
>
{t("next")}
</button>
</div>
</form>
)}
</>
);
};
export default PrefillWidgets;
@@ -0,0 +1,630 @@
import { forwardRef, memo, useEffect, useMemo, useRef, useState } from "react";
import DatePicker from "react-datepicker";
import SignatureCanvas from "react-signature-canvas";
import { useTranslation } from "react-i18next";
import moment from "moment";
import {
changeDateToMomentFormat,
compressedFileSize,
generateId,
getMonth,
getYear,
months,
radioButtonWidget,
textInputWidget,
textWidget,
years
} from "../../../constant/Utils";
import PenColorComponent from "../../pdf/tab/PenColorComponent";
import { getDatePickerDate, toHtmlPattern } from "../../../utils";
import { emailRegex } from "../../../constant/const";
const inputOpt = new Set(["text", "email", "number"]);
const PREFILL_PREFIX = "prefill::";
const inputTypes = {
email: emailRegex,
number: /^[0-9\s]*$/,
ssn: /^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}$/
};
const inputValidation = (pattern, type) => {
if (pattern) return pattern;
return inputTypes[type] || "";
};
const canavasTheme = `opensigncss:bg-white opensigndark:bg-[#121212] opensigndark:border-[#f6f3f4]/20 opensigncss:border-gray-300 opensigndark:hover:border-white opensigncss:hover:border-black border-[1px]`;
const widgetLabelCss = (isRequired = false) => {
return `${isRequired ? "after:content-['_*'] after:text-red-500" : ""} block text-xs font-semibold`;
};
const DateWidget = ({ widget, isRequired, onChange, showLabel }) => {
const format = widget?.options?.validation?.format || "MM/dd/yyyy";
const PrefillDateInput = forwardRef(({ value, onClick }, ref) => (
<div
style={{ fontFamily: "Arial, sans-serif" }}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full"
onClick={onClick}
ref={ref}
>
{value ? value : format}
<i className="fa-light fa-calendar ml-[5px]"></i>
</div>
));
PrefillDateInput.displayName = "PrefillDateInput";
const handleDate = (widget) => {
// The getDatePickerDate function retrieves the date in the correct format supported by the DatePicker.
try {
return getDatePickerDate(widget?.response, format);
} catch (err) {
console.error("handleDate error ", err);
return;
}
};
//function to set date with required date format onchange date
const handleOnDateChange = (date) => {
let updateDate = date;
let newDate;
const isSpecialDateFormat =
format && ["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(format);
if (isSpecialDateFormat) {
newDate = moment(updateDate).format(changeDateToMomentFormat(format));
} else {
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
newDate = new Date(updateDate);
newDate = moment(newDate.getTime()).format(
changeDateToMomentFormat(format)
);
}
onChange(newDate);
};
return (
<div className="flex flex-col">
{showLabel && (
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
{widget?.options?.name}
</div>
)}
<div className="min-w-max">
<DatePicker
portalId="datepicker-portal-root"
id={`widget-${widget?.options?.name}-${widget.key}`}
renderCustomHeader={({ date, changeYear, changeMonth }) => (
<div className="flex justify-start ml-2">
<select
className="bg-transparent outline-none"
value={months[getMonth(date)]}
onChange={({ target: { value } }) =>
changeMonth(months.indexOf(value))
}
>
{months.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<select
className="bg-transparent outline-none"
value={getYear(date)}
onChange={({ target: { value } }) => changeYear(value)}
>
{years.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
)}
wrapperClassName="w-full"
closeOnScroll={true}
selected={handleDate(widget)}
onChange={(date) => handleOnDateChange(date, widget)}
customInput={<PrefillDateInput />}
dateFormat={widget?.options?.validation?.format || "MM/dd/yyyy"}
required={isRequired}
/>
</div>
</div>
);
};
const TextWidget = ({
widget,
isRequired,
onChange,
showLabel,
}) => {
const { t } = useTranslation();
const inputType = widget?.options?.validation?.type || "";
const serverRegex = widget?.options?.validation?.pattern;
const isPredfineType = inputType ? inputOpt?.has(inputType) : "";
const regExpression = inputValidation(serverRegex, inputType);
const pattern = useMemo(() => toHtmlPattern(regExpression), [regExpression]);
const [value, setValue] = useState(() => {
const response =
widget?.options?.response ?? widget?.options?.defaultValue ?? "";
return inputType === "number" && response ? Number(response) : response;
});
const handleInputChange = (e) => {
const text = e.target.value || "";
setValue(text);
onChange(text);
};
return (
<div className="flex flex-col">
{showLabel && (
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
{widget?.options?.name}
</div>
)}
<div className="min-w-max">
<input
type={isPredfineType ? inputType : "text"}
id={`widget-${widget?.options?.name}-${widget.key}`}
value={value}
placeholder={
widget?.options?.hint ||
t("enter-value", { value: widget?.options?.name })
}
className={[
"op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs",
].join(" ")}
onChange={(e) => handleInputChange(e)}
pattern={pattern || undefined} // if no pattern, browser won't do pattern validation
onInvalid={(e) => {
const el = e.currentTarget;
// ✅ Only override message if pattern exists AND the error is patternMismatch
if (pattern && el.validity.patternMismatch) {
el.setCustomValidity(t("validation-alert-1"));
} else {
el.setCustomValidity("");
}
}}
onInput={(e) => {
e.currentTarget.setCustomValidity("");
}}
required={isRequired}
/>
</div>
</div>
);
};
const CheckboxWidget = ({ widget, isRequired, onChange, showLabel }) => {
const { t } = useTranslation();
const [selectedCheckbox, setSelectedCheckbox] = useState(
() => widget?.options?.response || widget?.options?.defaultValue || []
);
const groupName = `checkbox-group-${widget.key}`;
const noneSelected = (selectedCheckbox?.length ?? 0) === 0;
const handleCheckboxValue = (isChecked, ind) => {
// ✅ clear hidden input error immediately
const hidden = document.querySelector(`input[name="${groupName}"]`);
hidden?.setCustomValidity("");
const prevArr = Array.isArray(selectedCheckbox) ? selectedCheckbox : [];
const updateResponse = isChecked
? prevArr.includes(ind)
? prevArr
: [...prevArr, ind] // add once
: prevArr.filter((v) => v !== ind); // remove
setSelectedCheckbox(updateResponse);
onChange(updateResponse);
};
return (
<div className="flex flex-col">
{showLabel && (
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
{widget?.options?.name}
</div>
)}
<div className="flex flex-col gap-y-1 min-w-max relative">
{widget.options?.values?.map((value, ind) => (
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5"
>
<input
id={`checkbox-${widget.key + ind}`}
className="mt-[2px] op-checkbox op-checkbox-xs"
type="checkbox"
checked={selectedCheckbox.includes(ind)}
onChange={(e) => handleCheckboxValue(e.target.checked, ind)}
/>
<label
htmlFor={`checkbox-${widget.key + ind}`}
className="text-xs mb-0 text-center ml-[3px] cursor-pointer"
>
{value}
</label>
</div>
))}
{/* ✅ the validator */}
{isRequired && (
<input
tabIndex={-1}
aria-hidden="true"
className="absolute opacity-0 pointer-events-none w-0.5 h-0.5 left-[0.46rem] top-4"
name={groupName}
value={noneSelected ? "" : "selected"} // empty => invalid, non-empty => valid
required
onInvalid={(e) =>
e.target.setCustomValidity(t("select-at-least-option"))
}
onChange={(e) => e.target.setCustomValidity("")}
/>
)}
</div>
</div>
);
};
const DropdownWidget = ({ widget, isRequired, onChange, showLabel }) => {
const { t } = useTranslation();
const [selected, setSelected] = useState(
() =>
widget?.options?.response?.trim() ||
widget?.options?.defaultValue?.trim() ||
""
);
const handleDropdownChange = (value) => {
const select = value?.trim();
setSelected(select);
onChange(select);
};
return (
<div className="flex flex-col">
{showLabel && (
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
{widget?.options?.name}
</div>
)}
<div className="min-w-max">
<select
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-base-content w-full"
id={`widget-${widget?.options?.name}-${widget?.key}`}
value={selected}
onChange={(e) => handleDropdownChange(e.target.value)}
required={isRequired}
>
{/* Default/Title option */}
<option value="" disabled hidden>
{t("choose-one")}
</option>
{widget?.options?.values?.map((option, ind) => (
<option key={ind} value={option?.trim()}>
{option}
</option>
))}
</select>
</div>
</div>
);
};
const RadioButtonWidget = ({ widget, isRequired, onChange, showLabel }) => {
const [selected, setSelected] = useState(
() =>
widget?.options?.response?.trim() ||
widget?.options?.defaultValue?.trim() ||
""
);
const id = generateId(4);
const handleRadioChange = (value) => {
const select = value?.trim();
setSelected(select);
onChange(select);
};
return (
<div className="flex flex-col">
{showLabel && (
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
{widget?.options?.name}
</div>
)}
<div className="flex flex-col gap-y-1 min-w-max">
{widget.options?.values?.map((data, ind) => (
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5"
>
<input
id={`radio-${widget.key + ind}`}
className="mt-[2px] op-radio op-radio-xs"
name={`radio-group-${widget.key}-${id}`} // same name => browser treats all radios as ONE group
type="radio"
required={isRequired && ind === 0} // ✅ set required ONCE (on first radio) to make the whole group mandatory
checked={selected === data?.trim()}
onChange={() => handleRadioChange(data)}
/>
<label
htmlFor={`radio-${widget.key + ind}`}
className="text-xs mb-0 ml-[2px] cursor-pointer"
>
{data}
</label>
</div>
))}
</div>
</div>
);
};
const ImageWidget = ({ widget, isRequired, onChange, showLabel }) => {
const { t } = useTranslation();
const imageRef = useRef(null);
const [img, setImg] = useState({ src: "", imgType: "" });
const [defaultImg, setDefaultImg] = useState({ src: "", imgType: "" });
useEffect(() => {
if (widget?.response) {
setDefaultImg({ src: widget?.response, imgType: "" });
}
}, []);
const onImageChange = (e) => {
if (e.target.files && e.target.files?.[0]) {
const file = e.target.files?.[0];
compressedFileSize(file, ({ src, imgType }) => {
onChange(src);
setImg({ src, imgType });
});
}
};
const handleClearImage = () => {
setDefaultImg({ src: "", imgType: "" });
setImg({ src: "", imgType: "" });
onChange("");
};
return (
<div className="flex flex-col">
{showLabel && (
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
{widget?.options?.name}
</div>
)}
<div className="flex flex-col prefillCanvas">
{defaultImg?.src || img?.src ? (
<div
className={`cursor-pointer rounded-box ${canavasTheme} flex flex-col w-full h-full justify-center items-center`}
>
<img
alt={`image_${widget?.options?.name}`}
src={img.src || defaultImg?.src}
draggable="false"
className="object-contain h-full w-full aspect-[5/2]"
/>
</div>
) : (
<div
className={`cursor-pointer rounded-box ${canavasTheme} overflow-hidden w-full h-full aspect-[5/2] flex flex-col justify-center items-center`}
onClick={() => imageRef.current?.click()}
>
<i className="fa-light text-base-content fa-cloud-upload-alt text-[25px]"></i>
<div className="text-[10px] text-base-content">{t("upload")}</div>
<input
type="file"
onChange={(e) => onImageChange?.(e, widget)}
className="filetype w-[1px] h-[1px] opacity-0"
accept="image/png,image/jpeg"
ref={imageRef} // Assign ref dynamically
required={isRequired}
/>
</div>
)}
</div>
{(defaultImg?.src || img?.src) && (
<span
onClick={handleClearImage}
className="flex justify-start text-blue-500 underline cursor-pointer ml-1"
>
{t("clear")}
</span>
)}
</div>
);
};
const DrawWidget = ({ widget, isRequired, onChange, showLabel }) => {
const { t } = useTranslation();
const canvasRef = useRef(null);
const sigRequiredRef = useRef(null);
const [hasDraw, setHasDraw] = useState("");
const [penColor, setPenColor] = useState("blue");
const [image, setImage] = useState("");
useEffect(() => {
if (widget?.response) {
setImage(widget?.response);
setHasDraw("signed");
}
}, []);
const handleSignatureChange = () => {
const draw = canvasRef.current?.isEmpty?.() ? "" : "signed";
setHasDraw(draw);
onChange(canvasRef.current.toDataURL());
// clear custom error once user signs
sigRequiredRef.current?.setCustomValidity("");
};
const handleClear = () => {
setImage("");
if (canvasRef?.current) {
canvasRef.current.clear();
onChange("");
setHasDraw("");
// clear custom error once user signs
sigRequiredRef.current?.setCustomValidity("");
}
};
return (
<div className="flex flex-col">
{showLabel && (
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
{widget?.options?.name}
</div>
)}
<div className="relative w-fit">
{image ? (
<div className={`prefillCanvas ${canavasTheme} rounded-[10px]`}>
<img
alt={`draw_${widget?.options?.name}`}
src={image}
className="prefillCanvas object-contain"
/>
</div>
) : (
<SignatureCanvas
ref={canvasRef}
penColor={penColor}
canvasProps={{
className: `prefillCanvas ${canavasTheme} rounded-[10px]`
}}
onEnd={() => handleSignatureChange()}
dotSize={1}
/>
)}
{/* ✅ Native form validation hook */}
<input
ref={sigRequiredRef}
type="text"
value={hasDraw}
required={isRequired}
tabIndex={-1}
onInvalid={(e) => e.target.setCustomValidity(t("draw-required"))}
onChange={() => {}}
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 w-[1px] h-[1px] pointer-events-none"
/>
</div>
<div className="flex flex-row justify-between mt-[8px] w-[200px]">
<PenColorComponent
penColor={penColor}
setPenColor={setPenColor}
hideLabel
penSize="sm"
/>
<span
onClick={handleClear}
className="flex justify-start text-blue-500 underline cursor-pointer"
>
{t("clear")}
</span>
</div>
</div>
);
};
const CellsWidget = ({ widget, isRequired, onChange, showLabel }) => {
const { t } = useTranslation();
const count = widget?.options?.cellCount || 1;
const [word, setWord] = useState("");
const inputType = widget?.options?.validation?.type || "";
const serverRegex = widget?.options?.validation?.pattern;
const hint = widget?.options?.hint;
const regExpression = inputValidation(serverRegex, inputType);
const pattern = useMemo(() => toHtmlPattern(regExpression), [regExpression]);
useEffect(() => {
const response =
widget?.options?.response ?? widget?.options?.defaultValue ?? "";
setWord(String(response || ""));
}, []);
const handleChange = (e) => {
const value = e.target.value;
setWord(value);
onChange(value);
};
return (
<div className="flex flex-col">
{showLabel && (
<div className={`${widgetLabelCss(isRequired)} mb-[0.5rem]`}>
{widget?.options?.name}
</div>
)}
<div className="relative">
<input
type="text"
placeholder={
hint || t("enter-value", { value: widget?.options?.name })
}
value={word ?? ""}
onChange={(e) => handleChange(e)}
pattern={pattern || undefined} // if no pattern, browser won't do pattern validation
onInvalid={(e) => {
const el = e.currentTarget;
// ✅ Only override message if pattern exists AND the error is patternMismatch
if (pattern && el.validity.patternMismatch) {
el.setCustomValidity(t("validation-alert-1"));
} else {
el.setCustomValidity("");
}
}}
onInput={(e) => {
e.currentTarget.setCustomValidity("");
}}
className="op-input op-input-bordered op-input-sm focus:outline-none text-base-content hover:border-base-content w-full text-xs"
maxLength={count}
/>
</div>
</div>
);
};
const RenderWidgets = ({
widget,
handleWidgetDetails,
showLabel = false,
}) => {
const label = widget?.label?.toLowerCase() ?? "";
const isPrefill = label.includes(PREFILL_PREFIX);
const commonProps = {
widget,
isRequired: widget.options?.status === "required" && isPrefill,
showLabel: showLabel,
onChange: handleWidgetDetails,
};
switch (widget.type) {
case "date":
return <DateWidget {...commonProps} />;
case textWidget:
case textInputWidget:
case "name":
case "email":
case "company":
case "job title":
case "number":
return <TextWidget {...commonProps} />;
case "checkbox":
return <CheckboxWidget {...commonProps} />;
case radioButtonWidget:
return <RadioButtonWidget {...commonProps} />;
case "dropdown":
return <DropdownWidget {...commonProps} />;
case "image":
case "stamp":
return <ImageWidget {...commonProps} />;
case "initials":
case "draw":
return <DrawWidget {...commonProps} />;
case "cells":
return <CellsWidget {...commonProps} />;
default:
return null;
}
};
export default memo(RenderWidgets);
@@ -0,0 +1,33 @@
import { useTranslation } from "react-i18next";
function ResponseTab({
prefillCount = 0,
documentCount = 0,
message = { status: "", message: "" }
}) {
const { t } = useTranslation();
const { status, message: msg } = message;
return (
<div className="rounded-box border p-4 m-3 md:m-6 ">
<div className="font-medium">{t("summary")}</div>
<ul className="mt-2 list-disc pl-5 text-base-content/80">
<li>
{t("prefill-fields")}: {prefillCount}
</li>
<li>
{t("documents")}: {documentCount}
</li>
<li>
{t("status")}: {status}
</li>
{msg && (
<li>
{t("message")}: {msg}
</li>
)}
</ul>
</div>
);
}
export default ResponseTab;
@@ -0,0 +1,104 @@
import {
useState
} from "react";
import { useTranslation } from "react-i18next";
import SuggestionInput from "../../shared/fields/SuggestionInput";
import RenderWidgets from "./RenderWidgets";
const requiredAsteriskCls = (isRequired = false) => {
return isRequired ? "after:content-['_*'] after:text-red-500" : "";
};
const Table = ({
headers = [],
rowData = [],
handleInputChange,
handleWidgetDetails,
initialPerPage = 25,
}) => {
const { t } = useTranslation();
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(initialPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentData = rowData.slice(startIndex, endIndex);
return (
<div className="w-full">
{/* Table */}
<div
className="overflow-x-auto border border-gray-300 rounded-lg"
>
<table className="op-table border-collapse w-full">
<thead className="text-[13px] text-center">
<tr className="border-y-[1px]">
{headers?.map((header) => (
<th
key={header.label}
className={`${requiredAsteriskCls(header?.isRequired)} p-2`}
>
{header.label}
</th>
))}
{rowData.length > 1 && <th className="p-2">{t("action")}</th>}
</tr>
</thead>
<tbody>
{currentData.length > 0 &&
currentData.map((form, formIndex) => {
const globalIndex = startIndex + formIndex;
const fields = form?.fields ?? [];
const emailFields = fields.filter((f) => f.label !== "prefill");
const widgets = fields.flatMap((f, fieldIndex) =>
(f.widgets ?? []).map((widget, widgetIndex) => ({
widget,
fieldIndex, // keep the fieldIndex that produced this widget
widgetIndex
}))
);
return (
<tr key={form.Id} id={`table-row-${formIndex}`}>
{emailFields.map((field, fieldIndex) => (
<td key={field.fieldId} className="p-2">
<SuggestionInput
required
type="email"
value={field.email ?? ""}
index={fieldIndex}
onChange={(signer) =>
handleInputChange(globalIndex, signer, field.label)
}
/>
</td>
))}
{widgets.map(({ widget, fieldIndex, widgetIndex }) => (
<td key={`${form.Id}-${widget.key}`} className="p-2">
<RenderWidgets
widget={widget}
handleWidgetDetails={(value) =>
handleWidgetDetails(
value,
globalIndex, // formIndex,
fieldIndex,
widgetIndex
)
}
/>
</td>
))}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
};
export default Table;
@@ -0,0 +1,74 @@
/* ---------------- Wizard Header ---------------- */
import React from "react";
function WizardHeader({ steps, step, onStepClick }) {
return (
<div className="flex flex-col md:flex-row flex-wrap items-center justify-center gap-0 md:gap-2">
{steps.map((s, i) => {
const isActive = i === step;
const isDone = i < step;
return (
<React.Fragment key={s.key}>
<button
type="button"
onClick={() => onStepClick?.(i)}
className={[
"inline-flex items-center gap-2 rounded-md px-3 py-2 text-xs lg:text-sm font-medium border focus:outline-none",
isActive
? "op-bg-primary text-primary-content border-primary"
: isDone
? "bg-base-100 text-base-content border-base-300 hover:bg-base-200"
: "bg-base-100 text-base-content/70 border-base-300"
].join(" ")}
title={s.label}
>
<span
className={[
"inline-flex h-5 w-5 items-center justify-center rounded-full text-xs",
isActive
? "op-bg-primary-content/15 text-primary-content"
: isDone
? "bg-base-200 text-base-content"
: "bg-base-200 text-base-content/70"
].join(" ")}
>
{i + 1}
</span>
<span className="whitespace-nowrap">{s.label}</span>
{/* info icon like screenshot */}
<a data-tooltip-id={`${s.key}-tooltip`} className="ml-1">
<span
className={[
"inline-flex h-[1.10rem] w-[1.10rem] items-center justify-center border-[1px] rounded-full",
!isActive
? "border-[#33bbff] text-[#33bbff]"
: "border border-base-300 text-base-300"
].join(" ")}
aria-hidden="true"
>
<i className="fa-light fa-question rounded-full text-[13px]"></i>
</span>
</a>
<span className="text-left">{s.help}</span>
</button>
{i < steps.length - 1 && (
<span
className="mx-1 select-none text-base-content/40 text-center text-2xl md:mb-2 rotate-90 md:rotate-0"
aria-hidden="true"
>
&#8250;
</span>
)}
</React.Fragment>
);
})}
</div>
);
}
export default WizardHeader;
@@ -5,6 +5,7 @@ import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
import { useNavigate } from "react-router";
import Tooltip from "../../primitives/Tooltip";
import { useTranslation } from "react-i18next";
import { withSessionValidation } from "../../utils";
const DashboardCard = (props) => {
const navigate = useNavigate();
@@ -14,7 +15,7 @@ const DashboardCard = (props) => {
const [response, setresponse] = useState("");
const [loading, setLoading] = useState(false);
const renderData = async () => {
const renderData = withSessionValidation(async () => {
if (props.Data.queryType === "function") {
setLoading(true);
try {
@@ -186,9 +187,9 @@ const DashboardCard = (props) => {
setLoading(false);
}
}
};
});
const filterRender = async () => {
const filterRender = withSessionValidation(async () => {
if (props.FilterData && props.FilterData.queryType === "function") {
setLoading(true);
try {
@@ -266,7 +267,7 @@ const DashboardCard = (props) => {
setLoading(false);
}
}
};
});
const setFormat = (val) => {
switch (props.Format) {
@@ -5,9 +5,9 @@ import reportJson from "../../json/ReportJson";
import axios from "axios";
import Loader from "../../primitives/Loader";
import { useTranslation } from "react-i18next";
import { withSessionValidation } from "../../utils";
function DashboardReport(props) {
const { t } = useTranslation();
// console.log("props ", props)
const [List, setList] = useState([]);
const [isLoader, setIsLoader] = useState(true);
const [reportName, setReportName] = useState("");
@@ -47,7 +47,7 @@ function DashboardReport(props) {
// eslint-disable-next-line
}, [isNextRecord]);
const handleSearchChange = async (e) => {
const handleSearchChange = withSessionValidation(async (e) => {
const term = e.target.value.toLowerCase();
setSearchTerm(term);
if (debounceTimer.current) {
@@ -83,7 +83,7 @@ function DashboardReport(props) {
}
}, 300);
setIsSearchResult(false);
};
});
const handleSearchPaste = (e) => {
setTimeout(() => {
@@ -99,96 +99,95 @@ function DashboardReport(props) {
};
}, []);
const getReportData = async (
id,
skipUserRecord = 0,
limit = 20,
term = searchTerm
) => {
setIsLoader(true);
const json = reportJson(id);
if (json) {
setActions(json.actions);
setReportName(json.reportName);
setHeading(json.heading);
const currentUser = Parse.User.current().id;
const getReportData = withSessionValidation(
async (id, skipUserRecord = 0, limit = 20, term = searchTerm) => {
setIsLoader(true);
const json = reportJson(id);
if (json) {
setActions(json.actions);
setReportName(json.reportName);
setHeading(json.heading);
const currentUser = Parse.User.current().id;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
};
try {
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
if (term) {
params.searchTerm = term;
}
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const res = await axios.post(url, params, {
headers: headers,
signal: abortController.signal // is used to cancel fetch query
});
if (id === "5Go51Q7T8r") {
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
let arr = [];
for (const obj of listData) {
const isSigner = obj.Signers?.some(
(item) => item.UserId.objectId === currentUser
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
};
try {
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
if (term) {
params.searchTerm = term;
}
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const res = await axios.post(url, params, {
headers: headers,
signal: abortController.signal // is used to cancel fetch query
});
if (id === "5Go51Q7T8r") {
const listData = res.data?.result.filter(
(x) => x.Signers.length > 0
);
if (isSigner) {
let isRecord;
if (obj?.AuditTrail && obj?.AuditTrail.length > 0) {
isRecord = obj?.AuditTrail.some(
(item) =>
item?.UserPtr?.UserId?.objectId === currentUser &&
item.Activity === "Signed"
);
} else {
isRecord = false;
}
if (isRecord === false) {
arr.push(obj);
let arr = [];
for (const obj of listData) {
const isSigner = obj.Signers?.some(
(item) => item.UserId.objectId === currentUser
);
if (isSigner) {
let isRecord;
if (obj?.AuditTrail && obj?.AuditTrail.length > 0) {
isRecord = obj?.AuditTrail.some(
(item) =>
item?.UserPtr?.UserId?.objectId === currentUser &&
item.Activity === "Signed"
);
} else {
isRecord = false;
}
if (isRecord === false) {
arr.push(obj);
}
}
}
}
if (arr.length === docPerPage) {
setIsMoreDocs(true);
} else {
setIsMoreDocs(false);
}
setList((prevRecord) =>
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
);
} else {
if (res.data.result.length >= docPerPage) {
setIsMoreDocs(true);
} else {
setIsMoreDocs(false);
}
setIsNextRecord(false);
if (!res.data.result.error) {
if (arr.length === docPerPage) {
setIsMoreDocs(true);
} else {
setIsMoreDocs(false);
}
setList((prevRecord) =>
prevRecord.length > 0
? [...prevRecord, ...res.data.result]
: res.data.result
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
);
} else {
if (res.data.result.length >= docPerPage) {
setIsMoreDocs(true);
} else {
setIsMoreDocs(false);
}
setIsNextRecord(false);
if (!res.data.result.error) {
setList((prevRecord) =>
prevRecord.length > 0
? [...prevRecord, ...res.data.result]
: res.data.result
);
}
}
setIsLoader(false);
} catch (err) {
const isCancel = axios.isCancel(err);
if (!isCancel) {
console.log("err ", err);
setIsLoader(false);
}
}
setIsLoader(false);
} catch (err) {
const isCancel = axios.isCancel(err);
if (!isCancel) {
console.log("err ", err);
setIsLoader(false);
}
} else {
setIsLoader(false);
}
} else {
setIsLoader(false);
}
};
);
return (
<>
{isLoader ? (
@@ -1,4 +1,4 @@
import React, { Suspense } from "react";
import { useState, useEffect, Suspense } from "react";
import { lazyWithRetry } from "../../utils";
import { useTranslation } from "react-i18next";
const DashboardButton = lazyWithRetry(() => import("./DashboardButton"));
@@ -20,6 +20,7 @@ const buttonList = [
];
const GetDashboard = (props) => {
const { t } = useTranslation();
const Button = ({ label, redirectId, redirectType, icon }) => (
<DashboardButton
Icon={icon}
@@ -59,7 +60,9 @@ const GetDashboard = (props) => {
<div data-tut={col.widget.data.tourSection}>
<Suspense fallback={<div>please wait</div>}>
<div className="mb-3 md:mb-0">
<DashboardReport Record={col.widget} />
<DashboardReport
Record={col.widget}
/>
</div>
</Suspense>
</div>
@@ -93,7 +96,9 @@ const GetDashboard = (props) => {
return (
<Suspense fallback={<div>please wait</div>}>
<div className="mb-3 md:mb-0">
<DashboardReport Record={col.widget} />
<DashboardReport
Record={col.widget}
/>
</div>
</Suspense>
);
@@ -0,0 +1,70 @@
import React from 'react';
import { Box, Typography } from '@mui/material';
import { TEditorBlock } from '../../../documents/editor/core';
import { setDocument, useDocument, useSelectedBlockId } from '../../../documents/editor/EditorContext';
import AvatarSidebarPanel from './input-panels/AvatarSidebarPanel';
import ButtonSidebarPanel from './input-panels/ButtonSidebarPanel';
import ColumnsContainerSidebarPanel from './input-panels/ColumnsContainerSidebarPanel';
import ContainerSidebarPanel from './input-panels/ContainerSidebarPanel';
import DividerSidebarPanel from './input-panels/DividerSidebarPanel';
import EmailLayoutSidebarPanel from './input-panels/EmailLayoutSidebarPanel';
import HeadingSidebarPanel from './input-panels/HeadingSidebarPanel';
import HtmlSidebarPanel from './input-panels/HtmlSidebarPanel';
import ImageSidebarPanel from './input-panels/ImageSidebarPanel';
import SpacerSidebarPanel from './input-panels/SpacerSidebarPanel';
import TextSidebarPanel from './input-panels/TextSidebarPanel';
function renderMessage(val: string) {
return (
<Box sx={{ m: 3, p: 1, border: '1px dashed', borderColor: 'divider' }}>
<Typography color="text.secondary">{val}</Typography>
</Box>
);
}
export default function ConfigurationPanel() {
const document = useDocument();
const selectedBlockId = useSelectedBlockId();
if (!selectedBlockId) {
return renderMessage('Click on a block to inspect.');
}
const block = document[selectedBlockId];
if (!block) {
return renderMessage(`Block with id ${selectedBlockId} was not found. Click on a block to reset.`);
}
const setBlock = (conf: TEditorBlock) => setDocument({ [selectedBlockId]: conf });
const { data, type } = block;
switch (type) {
case 'Avatar':
return <AvatarSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Button':
return <ButtonSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'ColumnsContainer':
return (
<ColumnsContainerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />
);
case 'Container':
return <ContainerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Divider':
return <DividerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Heading':
return <HeadingSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Html':
return <HtmlSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Image':
return <ImageSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'EmailLayout':
return <EmailLayoutSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Spacer':
return <SpacerSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
case 'Text':
return <TextSidebarPanel key={selectedBlockId} data={data} setData={(data) => setBlock({ type, data })} />;
default:
return <pre>{JSON.stringify(block, null, ' ')}</pre>;
}
}
@@ -0,0 +1,89 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import { AspectRatioOutlined } from "@mui/icons-material";
import { ToggleButton } from "@mui/material";
import {
AvatarProps,
AvatarPropsDefaults,
AvatarPropsSchema
} from "@usewaypoint/block-avatar";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
import SliderInput from "./helpers/inputs/SliderInput";
import TextInput from "./helpers/inputs/TextInput";
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
type AvatarSidebarPanelProps = {
data: AvatarProps;
setData: (v: AvatarProps) => void;
};
export default function AvatarSidebarPanel({
data,
setData
}: AvatarSidebarPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = AvatarPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
const size = data.props?.size ?? AvatarPropsDefaults.size;
const imageUrl = data.props?.imageUrl ?? AvatarPropsDefaults.imageUrl;
const alt = data.props?.alt ?? AvatarPropsDefaults.alt;
const shape = data.props?.shape ?? AvatarPropsDefaults.shape;
return (
<BaseSidebarPanel title="Avatar block">
<SliderInput
label="Size"
iconLabel={<AspectRatioOutlined sx={{ color: "text.secondary" }} />}
units="px"
step={3}
min={32}
max={256}
defaultValue={size}
onChange={(size) => {
updateData({ ...data, props: { ...data.props, size } });
}}
/>
<RadioGroupInput
label="Shape"
defaultValue={shape}
onChange={(shape) => {
updateData({ ...data, props: { ...data.props, shape } });
}}
>
<ToggleButton value="circle">Circle</ToggleButton>
<ToggleButton value="square">Square</ToggleButton>
<ToggleButton value="rounded">Rounded</ToggleButton>
</RadioGroupInput>
<TextInput
label="Image URL"
defaultValue={imageUrl}
onChange={(imageUrl) => {
updateData({ ...data, props: { ...data.props, imageUrl } });
}}
/>
<TextInput
label="Alt text"
defaultValue={alt}
onChange={(alt) => {
updateData({ ...data, props: { ...data.props, alt } });
}}
/>
<MultiStylePropertyPanel
names={["textAlign", "padding"]}
value={data.style}
onChange={(style) => updateData({ ...data, style })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,132 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import { ToggleButton } from "@mui/material";
import {
ButtonProps,
ButtonPropsDefaults,
ButtonPropsSchema
} from "@usewaypoint/block-button";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import ColorInput from "./helpers/inputs/ColorInput";
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
import TextInput from "./helpers/inputs/TextInput";
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
type ButtonSidebarPanelProps = {
data: ButtonProps;
setData: (v: ButtonProps) => void;
};
export default function ButtonSidebarPanel({
data,
setData
}: ButtonSidebarPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = ButtonPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
const text = data.props?.text ?? ButtonPropsDefaults.text;
const url = data.props?.url ?? ButtonPropsDefaults.url;
const fullWidth = data.props?.fullWidth ?? ButtonPropsDefaults.fullWidth;
const size = data.props?.size ?? ButtonPropsDefaults.size;
const buttonStyle =
data.props?.buttonStyle ?? ButtonPropsDefaults.buttonStyle;
const buttonTextColor =
data.props?.buttonTextColor ?? ButtonPropsDefaults.buttonTextColor;
const buttonBackgroundColor =
data.props?.buttonBackgroundColor ??
ButtonPropsDefaults.buttonBackgroundColor;
return (
<BaseSidebarPanel title="Button block">
<TextInput
label="Text"
defaultValue={text}
onChange={(text) =>
updateData({ ...data, props: { ...data.props, text } })
}
/>
<TextInput
label="Url"
defaultValue={url}
onChange={(url) =>
updateData({ ...data, props: { ...data.props, url } })
}
/>
<RadioGroupInput
label="Width"
defaultValue={fullWidth ? "FULL_WIDTH" : "AUTO"}
onChange={(v) =>
updateData({
...data,
props: { ...data.props, fullWidth: v === "FULL_WIDTH" }
})
}
>
<ToggleButton value="FULL_WIDTH">Full</ToggleButton>
<ToggleButton value="AUTO">Auto</ToggleButton>
</RadioGroupInput>
<RadioGroupInput
label="Size"
defaultValue={size}
onChange={(size) =>
updateData({ ...data, props: { ...data.props, size } })
}
>
<ToggleButton value="x-small">Xs</ToggleButton>
<ToggleButton value="small">Sm</ToggleButton>
<ToggleButton value="medium">Md</ToggleButton>
<ToggleButton value="large">Lg</ToggleButton>
</RadioGroupInput>
<RadioGroupInput
label="Style"
defaultValue={buttonStyle}
onChange={(buttonStyle) =>
updateData({ ...data, props: { ...data.props, buttonStyle } })
}
>
<ToggleButton value="rectangle">Rectangle</ToggleButton>
<ToggleButton value="rounded">Rounded</ToggleButton>
<ToggleButton value="pill">Pill</ToggleButton>
</RadioGroupInput>
<ColorInput
label="Text color"
defaultValue={buttonTextColor}
onChange={(buttonTextColor) =>
updateData({ ...data, props: { ...data.props, buttonTextColor } })
}
/>
<ColorInput
label="Button color"
defaultValue={buttonBackgroundColor}
onChange={(buttonBackgroundColor) =>
updateData({
...data,
props: { ...data.props, buttonBackgroundColor }
})
}
/>
<MultiStylePropertyPanel
names={[
"backgroundColor",
"fontFamily",
"fontSize",
"fontWeight",
"textAlign",
"padding"
]}
value={data.style}
onChange={(style) => updateData({ ...data, style })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,100 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import {
SpaceBarOutlined,
VerticalAlignBottomOutlined,
VerticalAlignCenterOutlined,
VerticalAlignTopOutlined
} from "@mui/icons-material";
import { ToggleButton } from "@mui/material";
import ColumnsContainerPropsSchema, {
ColumnsContainerProps
} from "../../../../documents/blocks/ColumnsContainer/ColumnsContainerPropsSchema";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import ColumnWidthsInput from "./helpers/inputs/ColumnWidthsInput";
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
import SliderInput from "./helpers/inputs/SliderInput";
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
type ColumnsContainerPanelProps = {
data: ColumnsContainerProps;
setData: (v: ColumnsContainerProps) => void;
};
export default function ColumnsContainerPanel({
data,
setData
}: ColumnsContainerPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = ColumnsContainerPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
return (
<BaseSidebarPanel title="Columns block">
<RadioGroupInput
label="Number of columns"
defaultValue={data.props?.columnsCount === 2 ? "2" : "3"}
onChange={(v) => {
updateData({
...data,
props: { ...data.props, columnsCount: v === "2" ? 2 : 3 }
});
}}
>
<ToggleButton value="2">2</ToggleButton>
<ToggleButton value="3">3</ToggleButton>
</RadioGroupInput>
<ColumnWidthsInput
defaultValue={data.props?.fixedWidths}
onChange={(fixedWidths) => {
updateData({ ...data, props: { ...data.props, fixedWidths } });
}}
/>
<SliderInput
label="Columns gap"
iconLabel={<SpaceBarOutlined sx={{ color: "text.secondary" }} />}
units="px"
step={4}
marks
min={0}
max={80}
defaultValue={data.props?.columnsGap ?? 0}
onChange={(columnsGap) =>
updateData({ ...data, props: { ...data.props, columnsGap } })
}
/>
<RadioGroupInput
label="Alignment"
defaultValue={data.props?.contentAlignment ?? "middle"}
onChange={(contentAlignment) => {
updateData({ ...data, props: { ...data.props, contentAlignment } });
}}
>
<ToggleButton value="top">
<VerticalAlignTopOutlined fontSize="small" />
</ToggleButton>
<ToggleButton value="middle">
<VerticalAlignCenterOutlined fontSize="small" />
</ToggleButton>
<ToggleButton value="bottom">
<VerticalAlignBottomOutlined fontSize="small" />
</ToggleButton>
</RadioGroupInput>
<MultiStylePropertyPanel
names={["backgroundColor", "padding"]}
value={data.style}
onChange={(style) => updateData({ ...data, style })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,39 @@
import React, { useState } from "react";
import type { ZodError } from "zod";
import ContainerPropsSchema, {
ContainerProps
} from "../../../../documents/blocks/Container/ContainerPropsSchema";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
type ContainerSidebarPanelProps = {
data: ContainerProps;
setData: (v: ContainerProps) => void;
};
export default function ContainerSidebarPanel({
data,
setData
}: ContainerSidebarPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = ContainerPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
return (
<BaseSidebarPanel title="Container block">
<MultiStylePropertyPanel
names={["backgroundColor", "borderColor", "borderRadius", "padding"]}
value={data.style}
onChange={(style) => updateData({ ...data, style })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,66 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import { HeightOutlined } from "@mui/icons-material";
import {
DividerProps,
DividerPropsDefaults,
DividerPropsSchema
} from "@usewaypoint/block-divider";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import ColorInput from "./helpers/inputs/ColorInput";
import SliderInput from "./helpers/inputs/SliderInput";
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
type DividerSidebarPanelProps = {
data: DividerProps;
setData: (v: DividerProps) => void;
};
export default function DividerSidebarPanel({
data,
setData
}: DividerSidebarPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = DividerPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
const lineColor = data.props?.lineColor ?? DividerPropsDefaults.lineColor;
const lineHeight = data.props?.lineHeight ?? DividerPropsDefaults.lineHeight;
return (
<BaseSidebarPanel title="Divider block">
<ColorInput
label="Color"
defaultValue={lineColor}
onChange={(lineColor) =>
updateData({ ...data, props: { ...data.props, lineColor } })
}
/>
<SliderInput
label="Height"
iconLabel={<HeightOutlined sx={{ color: "text.secondary" }} />}
units="px"
step={1}
min={1}
max={24}
defaultValue={lineHeight}
onChange={(lineHeight) =>
updateData({ ...data, props: { ...data.props, lineHeight } })
}
/>
<MultiStylePropertyPanel
names={["backgroundColor", "padding"]}
value={data.style}
onChange={(style) => updateData({ ...data, style })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,84 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import { WidthWide, RoundedCornerOutlined } from "@mui/icons-material";
import EmailLayoutPropsSchema, {
EmailLayoutProps
} from "../../../../documents/blocks/EmailLayout/EmailLayoutPropsSchema";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import ColorInput, { NullableColorInput } from "./helpers/inputs/ColorInput";
import { NullableFontFamily } from "./helpers/inputs/FontFamily";
import SliderInput from "./helpers/inputs/SliderInput";
type EmailLayoutSidebarFieldsProps = {
data: EmailLayoutProps;
setData: (v: EmailLayoutProps) => void;
};
export default function EmailLayoutSidebarFields({
data,
setData
}: EmailLayoutSidebarFieldsProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = EmailLayoutPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
return (
<BaseSidebarPanel title="Global">
<ColorInput
label="Backdrop color"
defaultValue={data.backdropColor ?? "#F5F5F5"}
onChange={(backdropColor) => updateData({ ...data, backdropColor })}
/>
<ColorInput
label="Canvas color"
defaultValue={data.canvasColor ?? "#FFFFFF"}
onChange={(canvasColor) => updateData({ ...data, canvasColor })}
/>
<NullableColorInput
label="Canvas border color"
defaultValue={data.borderColor ?? null}
onChange={(borderColor) => updateData({ ...data, borderColor })}
/>
<SliderInput
iconLabel={<WidthWide />}
units="px"
step={10}
min={320}
max={1200}
label="Canvas width"
defaultValue={data.canvasWidth ?? 600}
onChange={(canvasWidth) => updateData({ ...data, canvasWidth })}
/>
<SliderInput
iconLabel={<RoundedCornerOutlined />}
units="px"
step={4}
marks
min={0}
max={48}
label="Canvas border radius"
defaultValue={data.borderRadius ?? 0}
onChange={(borderRadius) => updateData({ ...data, borderRadius })}
/>
<NullableFontFamily
label="Font family"
defaultValue="MODERN_SANS"
onChange={(fontFamily) => updateData({ ...data, fontFamily })}
/>
<ColorInput
label="Text color"
defaultValue={data.textColor ?? "#262626"}
onChange={(textColor) => updateData({ ...data, textColor })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,71 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import { ToggleButton } from "@mui/material";
import {
HeadingProps,
HeadingPropsDefaults,
HeadingPropsSchema
} from "@usewaypoint/block-heading";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
import TextInput from "./helpers/inputs/TextInput";
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
type HeadingSidebarPanelProps = {
data: HeadingProps;
setData: (v: HeadingProps) => void;
};
export default function HeadingSidebarPanel({
data,
setData
}: HeadingSidebarPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = HeadingPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
return (
<BaseSidebarPanel title="Heading block">
<TextInput
label="Content"
rows={3}
defaultValue={data.props?.text ?? HeadingPropsDefaults.text}
onChange={(text) => {
updateData({ ...data, props: { ...data.props, text } });
}}
/>
<RadioGroupInput
label="Level"
defaultValue={data.props?.level ?? HeadingPropsDefaults.level}
onChange={(level) => {
updateData({ ...data, props: { ...data.props, level } });
}}
>
<ToggleButton value="h1">H1</ToggleButton>
<ToggleButton value="h2">H2</ToggleButton>
<ToggleButton value="h3">H3</ToggleButton>
</RadioGroupInput>
<MultiStylePropertyPanel
names={[
"color",
"backgroundColor",
"fontFamily",
"fontWeight",
"textAlign",
"padding"
]}
value={data.style}
onChange={(style) => updateData({ ...data, style })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,54 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import { HtmlProps, HtmlPropsSchema } from "@usewaypoint/block-html";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import TextInput from "./helpers/inputs/TextInput";
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
type HtmlSidebarPanelProps = {
data: HtmlProps;
setData: (v: HtmlProps) => void;
};
export default function HtmlSidebarPanel({
data,
setData
}: HtmlSidebarPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = HtmlPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
return (
<BaseSidebarPanel title="Html block">
<TextInput
label="Content"
rows={5}
defaultValue={data.props?.contents ?? ""}
onChange={(contents) =>
updateData({ ...data, props: { ...data.props, contents } })
}
/>
<MultiStylePropertyPanel
names={[
"color",
"backgroundColor",
"fontFamily",
"fontSize",
"textAlign",
"padding"
]}
value={data.style}
onChange={(style) => updateData({ ...data, style })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,106 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import {
VerticalAlignBottomOutlined,
VerticalAlignCenterOutlined,
VerticalAlignTopOutlined
} from "@mui/icons-material";
import { Stack, ToggleButton } from "@mui/material";
import { ImageProps, ImagePropsSchema } from "@usewaypoint/block-image";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import RadioGroupInput from "./helpers/inputs/RadioGroupInput";
import TextDimensionInput from "./helpers/inputs/TextDimensionInput";
import TextInput from "./helpers/inputs/TextInput";
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
type ImageSidebarPanelProps = {
data: ImageProps;
setData: (v: ImageProps) => void;
};
export default function ImageSidebarPanel({
data,
setData
}: ImageSidebarPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = ImagePropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
return (
<BaseSidebarPanel title="Image block">
<TextInput
label="Source URL"
defaultValue={data.props?.url ?? ""}
onChange={(v) => {
const url = v.trim().length === 0 ? null : v.trim();
updateData({ ...data, props: { ...data.props, url } });
}}
/>
<TextInput
label="Alt text"
defaultValue={data.props?.alt ?? ""}
onChange={(alt) =>
updateData({ ...data, props: { ...data.props, alt } })
}
/>
<TextInput
label="Click through URL"
defaultValue={data.props?.linkHref ?? ""}
onChange={(v) => {
const linkHref = v.trim().length === 0 ? null : v.trim();
updateData({ ...data, props: { ...data.props, linkHref } });
}}
/>
<Stack direction="row" spacing={2}>
<TextDimensionInput
label="Width"
defaultValue={data.props?.width}
onChange={(width) =>
updateData({ ...data, props: { ...data.props, width } })
}
/>
<TextDimensionInput
label="Height"
defaultValue={data.props?.height}
onChange={(height) =>
updateData({ ...data, props: { ...data.props, height } })
}
/>
</Stack>
<RadioGroupInput
label="Alignment"
defaultValue={data.props?.contentAlignment ?? "middle"}
onChange={(contentAlignment) =>
updateData({ ...data, props: { ...data.props, contentAlignment } })
}
>
<ToggleButton value="top">
<VerticalAlignTopOutlined fontSize="small" />
</ToggleButton>
<ToggleButton value="middle">
<VerticalAlignCenterOutlined fontSize="small" />
</ToggleButton>
<ToggleButton value="bottom">
<VerticalAlignBottomOutlined fontSize="small" />
</ToggleButton>
</RadioGroupInput>
<MultiStylePropertyPanel
names={["backgroundColor", "textAlign", "padding"]}
value={data.style}
onChange={(style) => updateData({ ...data, style })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,50 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import { HeightOutlined } from "@mui/icons-material";
import {
SpacerProps,
SpacerPropsDefaults,
SpacerPropsSchema
} from "@usewaypoint/block-spacer";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import SliderInput from "./helpers/inputs/SliderInput";
type SpacerSidebarPanelProps = {
data: SpacerProps;
setData: (v: SpacerProps) => void;
};
export default function SpacerSidebarPanel({
data,
setData
}: SpacerSidebarPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = SpacerPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
return (
<BaseSidebarPanel title="Spacer block">
<SliderInput
label="Height"
iconLabel={<HeightOutlined sx={{ color: "text.secondary" }} />}
units="px"
step={4}
min={4}
max={128}
defaultValue={data.props?.height ?? SpacerPropsDefaults.height}
onChange={(height) =>
updateData({ ...data, props: { ...data.props, height } })
}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,64 @@
import React, { useState } from "react";
import { ZodError } from "zod";
import { TextProps, TextPropsSchema } from "@usewaypoint/block-text";
import BaseSidebarPanel from "./helpers/BaseSidebarPanel";
import BooleanInput from "./helpers/inputs/BooleanInput";
import TextInput from "./helpers/inputs/TextInput";
import MultiStylePropertyPanel from "./helpers/style-inputs/MultiStylePropertyPanel";
type TextSidebarPanelProps = {
data: TextProps;
setData: (v: TextProps) => void;
};
export default function TextSidebarPanel({
data,
setData
}: TextSidebarPanelProps) {
const [, setErrors] = useState<ZodError | null>(null);
const updateData = (d: unknown) => {
const res = TextPropsSchema.safeParse(d);
if (res.success) {
setData(res.data);
setErrors(null);
} else {
setErrors(res.error);
}
};
return (
<BaseSidebarPanel title="Text block">
<TextInput
label="Content"
rows={5}
defaultValue={data.props?.text ?? ""}
onChange={(text) =>
updateData({ ...data, props: { ...data.props, text } })
}
/>
<BooleanInput
label="Markdown (GitHub flavored)"
defaultValue={data.props?.markdown ?? false}
onChange={(markdown) =>
updateData({ ...data, props: { ...data.props, markdown } })
}
/>
<MultiStylePropertyPanel
names={[
"color",
"backgroundColor",
"fontFamily",
"fontSize",
"fontWeight",
"textAlign",
"padding"
]}
value={data.style}
onChange={(style) => updateData({ ...data, style })}
/>
</BaseSidebarPanel>
);
}
@@ -0,0 +1,27 @@
import React from "react";
import { Box, Stack, Typography } from "@mui/material";
type SidebarPanelProps = {
title: string;
children: React.ReactNode;
};
export default function BaseSidebarPanel({
title,
children
}: SidebarPanelProps) {
return (
<Box p={2}>
<Typography
variant="overline"
color="text.secondary"
sx={{ display: "block", mb: 2 }}
>
{title}
</Typography>
<Stack spacing={5} mb={3}>
{children}
</Stack>
</Box>
);
}
@@ -0,0 +1,27 @@
import React, { useState } from 'react';
import { FormControlLabel, Switch } from '@mui/material';
type Props = {
label: string;
defaultValue: boolean;
onChange: (value: boolean) => void;
};
export default function BooleanInput({ label, defaultValue, onChange }: Props) {
const [value, setValue] = useState(defaultValue);
return (
<FormControlLabel
label={label}
control={
<Switch
checked={value}
onChange={(_, checked: boolean) => {
setValue(checked);
onChange(checked);
}}
/>
}
/>
);
}
@@ -0,0 +1,102 @@
import React, { useState } from "react";
import { AddOutlined, CloseOutlined } from "@mui/icons-material";
import { ButtonBase, InputLabel, Menu, Stack } from "@mui/material";
import Picker from "./Picker";
const BUTTON_SX = {
border: "1px solid",
borderColor: "cadet.400",
width: 32,
height: 32,
borderRadius: "4px",
bgcolor: "#FFFFFF"
};
type Props =
| {
nullable: true;
label: string;
onChange: (value: string | null) => void;
defaultValue: string | null;
}
| {
nullable: false;
label: string;
onChange: (value: string) => void;
defaultValue: string;
};
export default function ColorInput({
label,
defaultValue,
onChange,
nullable
}: Props) {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [value, setValue] = useState(defaultValue);
const handleClickOpen = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
};
const renderResetButton = () => {
if (!nullable) {
return null;
}
if (typeof value !== "string" || value.trim().length === 0) {
return null;
}
return (
<ButtonBase
onClick={() => {
setValue(null);
onChange(null);
}}
>
<CloseOutlined fontSize="small" sx={{ color: "grey.600" }} />
</ButtonBase>
);
};
const renderOpenButton = () => {
if (value) {
return (
<ButtonBase
onClick={handleClickOpen}
sx={{ ...BUTTON_SX, bgcolor: value }}
/>
);
}
return (
<ButtonBase onClick={handleClickOpen} sx={{ ...BUTTON_SX }}>
<AddOutlined fontSize="small" />
</ButtonBase>
);
};
return (
<Stack alignItems="flex-start">
<InputLabel sx={{ mb: 0.5 }}>{label}</InputLabel>
<Stack direction="row" spacing={1}>
{renderOpenButton()}
{renderResetButton()}
</Stack>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={() => setAnchorEl(null)}
MenuListProps={{
sx: { height: "auto", padding: 0 }
}}
>
<Picker
value={value || ""}
onChange={(v) => {
setValue(v);
onChange(v);
}}
/>
</Menu>
</Stack>
);
}
@@ -0,0 +1,98 @@
import React, { useState } from "react";
import { HexColorInput, HexColorPicker } from "react-colorful";
import { Box, Stack, SxProps } from "@mui/material";
import Swatch from "./Swatch";
const DEFAULT_PRESET_COLORS = [
"#E11D48",
"#DB2777",
"#C026D3",
"#9333EA",
"#7C3AED",
"#4F46E5",
"#2563EB",
"#0284C7",
"#0891B2",
"#0D9488",
"#059669",
"#16A34A",
"#65A30D",
"#CA8A04",
"#D97706",
"#EA580C",
"#DC2626",
"#FFFFFF",
"#FAFAFA",
"#F5F5F5",
"#E5E5E5",
"#D4D4D4",
"#A3A3A3",
"#737373",
"#525252",
"#404040",
"#262626",
"#171717",
"#0A0A0A",
"#000000"
];
const SX: SxProps = {
p: 1,
".react-colorful__pointer ": {
width: 16,
height: 16
},
".react-colorful__saturation": {
mb: 1,
borderRadius: "4px"
},
".react-colorful__last-control": {
borderRadius: "4px"
},
".react-colorful__hue-pointer": {
width: "4px",
borderRadius: "4px",
height: 24,
cursor: "col-resize"
},
".react-colorful__saturation-pointer": {
cursor: "all-scroll"
},
input: {
padding: 1,
border: "1px solid",
borderColor: "grey.300",
borderRadius: "4px",
width: "100%"
}
};
type Props = {
value: string;
onChange: (v: string) => void;
};
export default function Picker({ value, onChange }: Props) {
const [internalValue, setInternalValue] = useState(value);
const handleChange = (v: string) => {
setInternalValue(v);
if (/^#[0-9a-fA-F]{6}$/.test(v)) {
onChange(v);
}
};
return (
<Stack spacing={1} sx={SX}>
<HexColorPicker color={value} onChange={handleChange} />
<Swatch
paletteColors={DEFAULT_PRESET_COLORS}
value={value}
onChange={handleChange}
/>
<Box pt={1}>
<HexColorInput prefixed color={internalValue} onChange={handleChange} />
</Box>
</Stack>
);
}
@@ -0,0 +1,48 @@
import React from "react";
import { Box, Button, SxProps } from "@mui/material";
type Props = {
paletteColors: string[];
value: string;
onChange: (value: string) => void;
};
const TILE_BUTTON: SxProps = {
width: 24,
height: 24
};
export default function Swatch({ paletteColors, value, onChange }: Props) {
const renderButton = (colorValue: string) => {
return (
<Button
key={colorValue}
onClick={() => onChange(colorValue)}
sx={{
...TILE_BUTTON,
backgroundColor: colorValue,
border: "1px solid",
borderColor: value === colorValue ? "black" : "grey.200",
minWidth: 24,
display: "inline-flex",
"&:hover": {
backgroundColor: colorValue,
borderColor: "grey.500"
}
}}
/>
);
};
return (
<Box
width="100%"
sx={{
display: "grid",
gap: 1,
gridTemplateColumns: "1fr 1fr 1fr 1fr 1fr 1fr"
}}
>
{paletteColors.map((c) => renderButton(c))}
</Box>
);
}
@@ -0,0 +1,21 @@
import React from "react";
import BaseColorInput from "./BaseColorInput";
type Props = {
label: string;
onChange: (value: string) => void;
defaultValue: string;
};
export default function ColorInput(props: Props) {
return <BaseColorInput {...props} nullable={false} />;
}
type NullableProps = {
label: string;
onChange: (value: null | string) => void;
defaultValue: null | string;
};
export function NullableColorInput(props: NullableProps) {
return <BaseColorInput {...props} nullable />;
}
@@ -0,0 +1,68 @@
import React, { useState } from 'react';
import { Stack } from '@mui/material';
import TextDimensionInput from './TextDimensionInput';
export const DEFAULT_2_COLUMNS = [6] as [number];
export const DEFAULT_3_COLUMNS = [4, 8] as [number, number];
type TWidthValue = number | null | undefined;
type FixedWidths = [
//
number | null | undefined,
number | null | undefined,
number | null | undefined,
];
type ColumnsLayoutInputProps = {
defaultValue: FixedWidths | null | undefined;
onChange: (v: FixedWidths | null | undefined) => void;
};
export default function ColumnWidthsInput({ defaultValue, onChange }: ColumnsLayoutInputProps) {
const [currentValue, setCurrentValue] = useState<[TWidthValue, TWidthValue, TWidthValue]>(() => {
if (defaultValue) {
return defaultValue;
}
return [null, null, null];
});
const setIndexValue = (index: 0 | 1 | 2, value: number | null | undefined) => {
const nValue: FixedWidths = [...currentValue];
nValue[index] = value;
setCurrentValue(nValue);
onChange(nValue);
};
const columnsCountValue = 3;
let column3 = null;
if (columnsCountValue === 3) {
column3 = (
<TextDimensionInput
label="Column 3"
defaultValue={currentValue?.[2]}
onChange={(v) => {
setIndexValue(2, v);
}}
/>
);
}
return (
<Stack direction="row" spacing={1}>
<TextDimensionInput
label="Column 1"
defaultValue={currentValue?.[0]}
onChange={(v) => {
setIndexValue(0, v);
}}
/>
<TextDimensionInput
label="Column 2"
defaultValue={currentValue?.[1]}
onChange={(v) => {
setIndexValue(1, v);
}}
/>
{column3}
</Stack>
);
}
@@ -0,0 +1,36 @@
import React, { useState } from 'react';
import { MenuItem, TextField } from '@mui/material';
import { FONT_FAMILIES } from '../../../../../../documents/blocks/helpers/fontFamily';
const OPTIONS = FONT_FAMILIES.map((option) => (
<MenuItem key={option.key} value={option.key} sx={{ fontFamily: option.value }}>
{option.label}
</MenuItem>
));
type NullableProps = {
label: string;
onChange: (value: null | string) => void;
defaultValue: null | string;
};
export function NullableFontFamily({ label, onChange, defaultValue }: NullableProps) {
const [value, setValue] = useState(defaultValue ?? 'inherit');
return (
<TextField
select
variant="standard"
label={label}
value={value}
onChange={(ev) => {
const v = ev.target.value;
setValue(v);
onChange(v === null ? null : v);
}}
>
<MenuItem value="inherit">Match email settings</MenuItem>
{OPTIONS}
</TextField>
);
}
@@ -0,0 +1,33 @@
import React, { useState } from 'react';
import { TextFieldsOutlined } from '@mui/icons-material';
import { InputLabel, Stack } from '@mui/material';
import RawSliderInput from './raw/RawSliderInput';
type Props = {
label: string;
defaultValue: number;
onChange: (v: number) => void;
};
export default function FontSizeInput({ label, defaultValue, onChange }: Props) {
const [value, setValue] = useState(defaultValue);
const handleChange = (value: number) => {
setValue(value);
onChange(value);
};
return (
<Stack spacing={1} alignItems="flex-start">
<InputLabel shrink>{label}</InputLabel>
<RawSliderInput
iconLabel={<TextFieldsOutlined sx={{ fontSize: 16 }} />}
value={value}
setValue={handleChange}
units="px"
step={1}
min={10}
max={48}
/>
</Stack>
);
}
@@ -0,0 +1,27 @@
import React, { useState } from 'react';
import { ToggleButton } from '@mui/material';
import RadioGroupInput from './RadioGroupInput';
type Props = {
label: string;
defaultValue: string;
onChange: (value: string) => void;
};
export default function FontWeightInput({ label, defaultValue, onChange }: Props) {
const [value, setValue] = useState(defaultValue);
return (
<RadioGroupInput
label={label}
defaultValue={value}
onChange={(fontWeight) => {
setValue(fontWeight);
onChange(fontWeight);
}}
>
<ToggleButton value="normal">Regular</ToggleButton>
<ToggleButton value="bold">Bold</ToggleButton>
</RadioGroupInput>
);
}
@@ -0,0 +1,95 @@
import React, { useState } from 'react';
import {
AlignHorizontalLeftOutlined,
AlignHorizontalRightOutlined,
AlignVerticalBottomOutlined,
AlignVerticalTopOutlined,
} from '@mui/icons-material';
import { InputLabel, Stack } from '@mui/material';
import RawSliderInput from './raw/RawSliderInput';
type TPaddingValue = {
top: number;
bottom: number;
right: number;
left: number;
};
type Props = {
label: string;
defaultValue: TPaddingValue | null;
onChange: (value: TPaddingValue) => void;
};
export default function PaddingInput({ label, defaultValue, onChange }: Props) {
const [value, setValue] = useState(() => {
if (defaultValue) {
return defaultValue;
}
return {
top: 0,
left: 0,
bottom: 0,
right: 0,
};
});
function handleChange(internalName: keyof TPaddingValue, nValue: number) {
const v = {
...value,
[internalName]: nValue,
};
setValue(v);
onChange(v);
}
return (
<Stack spacing={2} alignItems="flex-start" pb={1}>
<InputLabel shrink>{label}</InputLabel>
<RawSliderInput
iconLabel={<AlignVerticalTopOutlined sx={{ fontSize: 16 }} />}
value={value.top}
setValue={(num) => handleChange('top', num)}
units="px"
step={4}
min={0}
max={80}
marks
/>
<RawSliderInput
iconLabel={<AlignVerticalBottomOutlined sx={{ fontSize: 16 }} />}
value={value.bottom}
setValue={(num) => handleChange('bottom', num)}
units="px"
step={4}
min={0}
max={80}
marks
/>
<RawSliderInput
iconLabel={<AlignHorizontalLeftOutlined sx={{ fontSize: 16 }} />}
value={value.left}
setValue={(num) => handleChange('left', num)}
units="px"
step={4}
min={0}
max={80}
marks
/>
<RawSliderInput
iconLabel={<AlignHorizontalRightOutlined sx={{ fontSize: 16 }} />}
value={value.right}
setValue={(num) => handleChange('right', num)}
units="px"
step={4}
min={0}
max={80}
marks
/>
</Stack>
);
}
@@ -0,0 +1,33 @@
import React, { useState } from 'react';
import { InputLabel, Stack, ToggleButtonGroup } from '@mui/material';
type Props = {
label: string | JSX.Element;
children: JSX.Element | JSX.Element[];
defaultValue: string;
onChange: (v: string) => void;
};
export default function RadioGroupInput({ label, children, defaultValue, onChange }: Props) {
const [value, setValue] = useState(defaultValue);
return (
<Stack alignItems="flex-start">
<InputLabel shrink>{label}</InputLabel>
<ToggleButtonGroup
exclusive
fullWidth
value={value}
size="small"
onChange={(_, v: unknown) => {
if (typeof v !== 'string') {
throw new Error('RadioGroupInput can only receive string values');
}
setValue(v);
onChange(v);
}}
>
{children}
</ToggleButtonGroup>
</Stack>
);
}
@@ -0,0 +1,36 @@
import React, { useState } from 'react';
import { InputLabel, Stack } from '@mui/material';
import RawSliderInput from './raw/RawSliderInput';
type SliderInputProps = {
label: string;
iconLabel: JSX.Element;
step?: number;
marks?: boolean;
units: string;
min?: number;
max?: number;
defaultValue: number;
onChange: (v: number) => void;
};
export default function SliderInput({ label, defaultValue, onChange, ...props }: SliderInputProps) {
const [value, setValue] = useState(defaultValue);
return (
<Stack spacing={1} alignItems="flex-start">
<InputLabel shrink>{label}</InputLabel>
<RawSliderInput
value={value}
setValue={(value: number) => {
setValue(value);
onChange(value);
}}
{...props}
/>
</Stack>
);
}
@@ -0,0 +1,36 @@
import React, { useState } from 'react';
import { FormatAlignCenterOutlined, FormatAlignLeftOutlined, FormatAlignRightOutlined } from '@mui/icons-material';
import { ToggleButton } from '@mui/material';
import RadioGroupInput from './RadioGroupInput';
type Props = {
label: string;
defaultValue: string | null;
onChange: (value: string | null) => void;
};
export default function TextAlignInput({ label, defaultValue, onChange }: Props) {
const [value, setValue] = useState(defaultValue ?? 'left');
return (
<RadioGroupInput
label={label}
defaultValue={value}
onChange={(value) => {
setValue(value);
onChange(value);
}}
>
<ToggleButton value="left">
<FormatAlignLeftOutlined fontSize="small" />
</ToggleButton>
<ToggleButton value="center">
<FormatAlignCenterOutlined fontSize="small" />
</ToggleButton>
<ToggleButton value="right">
<FormatAlignRightOutlined fontSize="small" />
</ToggleButton>
</RadioGroupInput>
);
}
@@ -0,0 +1,33 @@
import React from 'react';
import { TextField, Typography } from '@mui/material';
type TextDimensionInputProps = {
label: string;
defaultValue: number | null | undefined;
onChange: (v: number | null) => void;
};
export default function TextDimensionInput({ label, defaultValue, onChange }: TextDimensionInputProps) {
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (ev) => {
const value = parseInt(ev.target.value);
onChange(isNaN(value) ? null : value);
};
return (
<TextField
fullWidth
onChange={handleChange}
defaultValue={defaultValue}
label={label}
variant="standard"
placeholder="auto"
size="small"
InputProps={{
endAdornment: (
<Typography variant="body2" color="text.secondary">
px
</Typography>
),
}}
/>
);
}
@@ -0,0 +1,35 @@
import React, { useState } from 'react';
import { InputProps, TextField } from '@mui/material';
type Props = {
label: string;
rows?: number;
placeholder?: string;
helperText?: string | JSX.Element;
InputProps?: InputProps;
defaultValue: string;
onChange: (v: string) => void;
};
export default function TextInput({ helperText, label, placeholder, rows, InputProps, defaultValue, onChange }: Props) {
const [value, setValue] = useState(defaultValue);
const isMultiline = typeof rows === 'number' && rows > 1;
return (
<TextField
fullWidth
multiline={isMultiline}
minRows={rows}
variant={isMultiline ? 'outlined' : 'standard'}
label={label}
placeholder={placeholder}
helperText={helperText}
InputProps={InputProps}
value={value}
onChange={(ev) => {
const v = ev.target.value;
setValue(v);
onChange(v);
}}
/>
);
}
@@ -0,0 +1,58 @@
import React from "react";
import { Box, Slider, Stack, Typography } from "@mui/material";
type SliderInputProps = {
iconLabel: JSX.Element;
step?: number;
marks?: boolean;
units: string;
min?: number;
max?: number;
value: number;
setValue: (v: number) => void;
};
export default function RawSliderInput({
iconLabel,
value,
setValue,
units,
...props
}: SliderInputProps) {
return (
<Stack
direction="row"
alignItems="center"
spacing={2}
justifyContent="space-between"
width="100%"
>
<Box sx={{ minWidth: 24, lineHeight: 1, flexShrink: 0 }}>{iconLabel}</Box>
<Slider
{...props}
value={value}
onChange={(_, value: unknown) => {
if (typeof value !== "number") {
throw new Error(
"RawSliderInput values can only receive numeric values"
);
}
setValue(value);
}}
/>
<Box sx={{ minWidth: 32, textAlign: "right", flexShrink: 0 }}>
<Typography
variant="body2"
color="text.secondary"
sx={{ lineHeight: 1 }}
>
{value}
{units}
</Typography>
</Box>
</Stack>
);
}
@@ -0,0 +1,29 @@
import React from "react";
import { TStyle } from "../../../../../../documents/blocks/helpers/TStyle";
import SingleStylePropertyPanel from "./SingleStylePropertyPanel";
type MultiStylePropertyPanelProps = {
names: (keyof TStyle)[];
value: TStyle | undefined | null;
onChange: (style: TStyle) => void;
};
export default function MultiStylePropertyPanel({
names,
value,
onChange
}: MultiStylePropertyPanelProps) {
return (
<>
{names.map((name) => (
<SingleStylePropertyPanel
key={name}
name={name}
value={value || {}}
onChange={onChange}
/>
))}
</>
);
}
@@ -0,0 +1,110 @@
import React from "react";
import { RoundedCornerOutlined } from "@mui/icons-material";
import { TStyle } from "../../../../../../documents/blocks/helpers/TStyle";
import { NullableColorInput } from "../inputs/ColorInput";
import { NullableFontFamily } from "../inputs/FontFamily";
import FontSizeInput from "../inputs/FontSizeInput";
import FontWeightInput from "../inputs/FontWeightInput";
import PaddingInput from "../inputs/PaddingInput";
import SliderInput from "../inputs/SliderInput";
import TextAlignInput from "../inputs/TextAlignInput";
type StylePropertyPanelProps = {
name: keyof TStyle;
value: TStyle;
onChange: (style: TStyle) => void;
};
export default function SingleStylePropertyPanel({
name,
value,
onChange
}: StylePropertyPanelProps) {
const defaultValue = value[name] ?? null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handleChange = (v: any) => {
onChange({ ...value, [name]: v });
};
switch (name) {
case "backgroundColor":
return (
<NullableColorInput
label="Background color"
defaultValue={defaultValue}
onChange={handleChange}
/>
);
case "borderColor":
return (
<NullableColorInput
label="Border color"
defaultValue={defaultValue}
onChange={handleChange}
/>
);
case "borderRadius":
return (
<SliderInput
iconLabel={<RoundedCornerOutlined />}
units="px"
step={4}
marks
min={0}
max={48}
label="Border radius"
defaultValue={defaultValue}
onChange={handleChange}
/>
);
case "color":
return (
<NullableColorInput
label="Text color"
defaultValue={defaultValue}
onChange={handleChange}
/>
);
case "fontFamily":
return (
<NullableFontFamily
label="Font family"
defaultValue={defaultValue}
onChange={handleChange}
/>
);
case "fontSize":
return (
<FontSizeInput
label="Font size"
defaultValue={defaultValue}
onChange={handleChange}
/>
);
case "fontWeight":
return (
<FontWeightInput
label="Font weight"
defaultValue={defaultValue}
onChange={handleChange}
/>
);
case "textAlign":
return (
<TextAlignInput
label="Alignment"
defaultValue={defaultValue}
onChange={handleChange}
/>
);
case "padding":
return (
<PaddingInput
label="Padding"
defaultValue={defaultValue}
onChange={handleChange}
/>
);
}
}
@@ -0,0 +1,25 @@
import React from "react";
import { setDocument, useDocument } from "../../documents/editor/EditorContext";
import EmailLayoutSidebarPanel from "./ConfigurationPanel/input-panels/EmailLayoutSidebarPanel";
export default function StylesPanel() {
const block = useDocument().root;
if (!block) {
return <p>Block not found</p>;
}
const { data, type } = block;
if (type !== "EmailLayout") {
throw new Error('Expected "root" element to be of type EmailLayout');
}
return (
<EmailLayoutSidebarPanel
key="root"
data={data}
setData={(data) => setDocument({ root: { type, data } })}
/>
);
}
@@ -0,0 +1,29 @@
import React from "react";
import { AppRegistrationOutlined, LastPageOutlined } from "@mui/icons-material";
import { IconButton } from "@mui/material";
import {
toggleInspectorDrawerOpen,
useInspectorDrawerOpen
} from "../../documents/editor/EditorContext";
export default function ToggleInspectorPanelButton() {
const inspectorDrawerOpen = useInspectorDrawerOpen();
const handleClick = () => {
toggleInspectorDrawerOpen();
};
if (inspectorDrawerOpen) {
return (
<IconButton onClick={handleClick}>
<LastPageOutlined fontSize="small" />
</IconButton>
);
}
return (
<IconButton onClick={handleClick}>
<AppRegistrationOutlined fontSize="small" />
</IconButton>
);
}
@@ -0,0 +1,67 @@
import React from "react";
import { Box, Drawer, Tab, Tabs } from "@mui/material";
import {
setSidebarTab,
useInspectorDrawerOpen,
useSelectedSidebarTab
} from "../../documents/editor/EditorContext";
import ConfigurationPanel from "./ConfigurationPanel";
import StylesPanel from "./StylesPanel";
export const INSPECTOR_DRAWER_WIDTH = 320;
export default function InspectorDrawer() {
const selectedSidebarTab = useSelectedSidebarTab();
const inspectorDrawerOpen = useInspectorDrawerOpen();
const renderCurrentSidebarPanel = () => {
switch (selectedSidebarTab) {
case "block-configuration":
return <ConfigurationPanel />;
case "styles":
return <StylesPanel />;
}
};
return (
<Drawer
variant="persistent"
anchor="right"
open={inspectorDrawerOpen}
sx={{
width: inspectorDrawerOpen ? INSPECTOR_DRAWER_WIDTH : 0
}}
>
<Box
sx={{
width: INSPECTOR_DRAWER_WIDTH,
height: 49,
borderBottom: 1,
borderColor: "divider"
}}
>
<Box px={2}>
<Tabs
value={selectedSidebarTab}
onChange={(_, v) => setSidebarTab(v)}
>
<Tab value="styles" label="Styles" />
<Tab value="block-configuration" label="Inspect" />
</Tabs>
</Box>
</Box>
<Box
sx={{
width: INSPECTOR_DRAWER_WIDTH,
height: "calc(100% - 49px)",
overflow: "auto"
}}
>
{renderCurrentSidebarPanel()}
</Box>
</Drawer>
);
}
@@ -0,0 +1,23 @@
import React from "react";
import { Button } from "@mui/material";
import { resetDocument } from "../../documents/editor/EditorContext";
import getConfiguration from "../../getConfiguration";
export default function SidebarButton({
href,
children
}: {
href: string;
children: JSX.Element | string;
}) {
const handleClick = () => {
resetDocument(getConfiguration(href));
};
return (
<Button size="small" href={href} onClick={handleClick}>
{children}
</Button>
);
}
@@ -0,0 +1,22 @@
import React from "react";
import { FirstPageOutlined, MenuOutlined } from "@mui/icons-material";
import { IconButton } from "@mui/material";
import {
toggleSamplesDrawerOpen,
useSamplesDrawerOpen
} from "../../documents/editor/EditorContext";
function useIcon() {
const samplesDrawerOpen = useSamplesDrawerOpen();
if (samplesDrawerOpen) {
return <FirstPageOutlined fontSize="small" />;
}
return <MenuOutlined fontSize="small" />;
}
export default function ToggleSamplesPanelButton() {
const icon = useIcon();
return <IconButton onClick={toggleSamplesDrawerOpen}>{icon}</IconButton>;
}
@@ -0,0 +1,54 @@
import React from "react";
import { Divider, Drawer, Stack, Typography } from "@mui/material";
import { useSamplesDrawerOpen } from "../../documents/editor/EditorContext";
import SidebarButton from "./SidebarButton";
export const SAMPLES_DRAWER_WIDTH = 240;
export default function SamplesDrawer() {
const samplesDrawerOpen = useSamplesDrawerOpen();
return (
<Drawer
variant="persistent"
anchor="left"
open={samplesDrawerOpen}
sx={{ width: samplesDrawerOpen ? SAMPLES_DRAWER_WIDTH : 0 }}
>
<Stack
spacing={3}
py={1}
px={2}
width={SAMPLES_DRAWER_WIDTH}
justifyContent="space-between"
height="100%"
>
<Stack
spacing={2}
sx={{
"& .MuiButtonBase-root": {
width: "100%",
justifyContent: "flex-start"
}
}}
>
<Typography variant="h6" component="h1" sx={{ p: 0.75 }}>
EmailBuilder
</Typography>
<Stack alignItems="flex-start">
<SidebarButton href="#">Empty</SidebarButton>
<SidebarButton href="#sample/requestemail">
Request Email
</SidebarButton>
<SidebarButton href="#sample/completionemail">
Completion Email
</SidebarButton>
</Stack>
<Divider />
</Stack>
</Stack>
</Drawer>
);
}
@@ -0,0 +1,31 @@
import React, { useMemo } from "react";
import { FileDownloadOutlined } from "@mui/icons-material";
import { IconButton, Tooltip } from "@mui/material";
import { useDocument } from "../../../documents/editor/EditorContext";
// import { renderToStaticMarkup } from "@usewaypoint/email-builder";
import renderEmailHtml from "../helper/renderEmailHtml";
export default function DownloadJson() {
const doc = useDocument();
// const code = useMemo(
// () => renderToStaticMarkup(doc, { rootBlockId: "root" }),
// [doc]
// );
const code = useMemo(() => renderEmailHtml(doc, "root"), [doc]);
const href = useMemo(() => {
const blob = new Blob([code], { type: "text/html;charset=utf-8" });
const url = URL.createObjectURL(blob);
return url;
}, [doc]);
return (
<Tooltip title="Download html of template in txt file">
<IconButton href={href} download="emailTemplate.txt">
<FileDownloadOutlined fontSize="small" />
</IconButton>
</Tooltip>
);
}
@@ -0,0 +1,18 @@
import React, { useMemo } from "react";
// import { renderToStaticMarkup } from "@usewaypoint/email-builder";
import { useDocument } from "../../documents/editor/EditorContext";
import HighlightedCodePanel from "./helper/HighlightedCodePanel";
import renderEmailHtml from "./helper/renderEmailHtml";
export default function HtmlPanel() {
const document = useDocument();
// const code = useMemo(
// () => renderToStaticMarkup(document, { rootBlockId: "root" }),
// [document]
// );
const code = useMemo(() => renderEmailHtml(document, "root"), [document]);
return <HighlightedCodePanel type="html" value={code} />;
}
@@ -0,0 +1,11 @@
import React, { useMemo } from "react";
import { useDocument } from "../../documents/editor/EditorContext";
import HighlightedCodePanel from "./helper/HighlightedCodePanel";
export default function JsonPanel() {
const document = useDocument();
const code = useMemo(() => JSON.stringify(document, null, " "), [document]);
return <HighlightedCodePanel type="json" value={code} />;
}
@@ -0,0 +1,66 @@
import React from "react";
// DataObjectOutlined,
import {
CodeOutlined,
EditOutlined,
PreviewOutlined
} from "@mui/icons-material";
import { Tab, Tabs, Tooltip } from "@mui/material";
import {
setSelectedMainTab,
useSelectedMainTab
} from "../../documents/editor/EditorContext";
export default function MainTabsGroup() {
const selectedMainTab = useSelectedMainTab();
const handleChange = (_: unknown, v: unknown) => {
switch (v) {
case "json":
case "preview":
case "editor":
case "html":
setSelectedMainTab(v);
return;
default:
setSelectedMainTab("editor");
}
};
return (
<Tabs value={selectedMainTab} onChange={handleChange}>
<Tab
value="editor"
label={
<Tooltip title="Edit">
<EditOutlined fontSize="small" />
</Tooltip>
}
/>
<Tab
value="preview"
label={
<Tooltip title="Preview">
<PreviewOutlined fontSize="small" />
</Tooltip>
}
/>
<Tab
value="html"
label={
<Tooltip title="HTML output">
<CodeOutlined fontSize="small" />
</Tooltip>
}
/>
{/* <Tab
value="json"
label={
<Tooltip title="JSON output">
<DataObjectOutlined fontSize="small" />
</Tooltip>
}
/> */}
</Tabs>
);
}
@@ -0,0 +1,86 @@
import React, { useMemo, useState } from "react";
import { ContentCopyOutlined } from "@mui/icons-material";
import { IconButton, Snackbar, Tooltip } from "@mui/material";
import { useDocument } from "../../documents/editor/EditorContext";
// import { renderToStaticMarkup } from "@usewaypoint/email-builder";
import renderEmailHtml from "./helper/renderEmailHtml";
export default function ShareButton() {
const document = useDocument();
// const code = useMemo(
// () => renderToStaticMarkup(document, { rootBlockId: "root" }),
// [document]
// );
const code = useMemo(() => renderEmailHtml(document, "root"), [document]);
const [message, setMessage] = useState<string | null>(null);
const onClick = async () => {
await copyToClipboard(code);
setMessage("Your current template is copied");
setTimeout(() => {
setMessage(null);
}, 1000);
};
async function copyToClipboard(text: string): Promise<boolean> {
// 1) Try modern Clipboard API
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return true;
}
} catch {
// Fall through to legacy approach
}
// 2) Fallback: execCommand('copy')
try {
const textarea = document.createElement("textarea");
textarea.value = text;
// Prevent scrolling to bottom on iOS
textarea.style.position = "fixed";
textarea.style.top = "0";
textarea.style.left = "0";
textarea.style.opacity = "0";
textarea.style.pointerEvents = "none";
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
const ok = document.execCommand("copy");
document.body.removeChild(textarea);
return ok;
} catch {
return false;
}
}
const onClose = () => {
setMessage(null);
};
return (
<>
{/* <IconButton onClick={onClick}>
<Tooltip title="Share current template">
<IosShareOutlined fontSize="small" />
</Tooltip>
</IconButton> */}
<IconButton onClick={onClick} sx={{ color: "text.primary" }}>
<Tooltip title="copy" placement="left-start">
<ContentCopyOutlined fontSize="small" />
</Tooltip>
</IconButton>
<Snackbar
anchorOrigin={{ vertical: "top", horizontal: "center" }}
open={message !== null}
onClose={onClose}
message={message}
/>
</>
);
}
@@ -0,0 +1,43 @@
import React, { useEffect, useState } from "react";
import { html, json } from "./highlighters";
type TextEditorPanelProps = {
type: "json" | "html" | "javascript";
value: string;
};
export default function HighlightedCodePanel({
type,
value
}: TextEditorPanelProps) {
const [code, setCode] = useState<string | null>(null);
useEffect(() => {
switch (type) {
case "html":
html(value).then(setCode);
return;
case "json":
json(value).then(setCode);
return;
}
}, [setCode, value, type]);
if (code === null) {
return null;
}
return (
<pre
style={{ margin: 0, padding: 16 }}
dangerouslySetInnerHTML={{ __html: code }}
onClick={(ev) => {
const s = window.getSelection();
if (s === null) {
return;
}
s.selectAllChildren(ev.currentTarget);
}}
/>
);
}
@@ -0,0 +1,28 @@
import hljs from "highlight.js";
import jsonHighlighter from "highlight.js/lib/languages/json";
import xmlHighlighter from "highlight.js/lib/languages/xml";
import prettierPluginBabel from "prettier/plugins/babel";
import prettierPluginEstree from "prettier/plugins/estree";
import prettierPluginHtml from "prettier/plugins/html";
import { format } from "prettier/standalone";
hljs.registerLanguage("json", jsonHighlighter);
hljs.registerLanguage("html", xmlHighlighter);
export async function html(value: string): Promise<string> {
const prettyValue = await format(value, {
parser: "html",
plugins: [prettierPluginHtml]
});
return hljs.highlight(prettyValue, { language: "html" }).value;
}
export async function json(value: string): Promise<string> {
const prettyValue = await format(value, {
parser: "json",
printWidth: 0,
trailingComma: "all",
plugins: [prettierPluginBabel, prettierPluginEstree]
});
return hljs.highlight(prettyValue, { language: "javascript" }).value;
}
@@ -0,0 +1,37 @@
import {
renderToStaticMarkup
} from "@usewaypoint/email-builder";
import { TEditorConfiguration } from "../../../documents/editor/core";
function overrideCanvasSize(
html: string,
document: TEditorConfiguration,
rootBlockId: string
) {
const rootBlock = document[rootBlockId];
if (!rootBlock || rootBlock.type !== "EmailLayout") {
return html;
}
const data = rootBlock.data as { canvasWidth?: number | null };
const canvasWidth =
typeof data?.canvasWidth === "number" && data.canvasWidth > 0
? data.canvasWidth
: 600;
let updatedHtml = html.replace(
/max-width:\s*600px/gi,
`max-width:${canvasWidth}px`
);
return updatedHtml;
}
export default function renderEmailHtml(
document: TEditorConfiguration,
rootBlockId = "root"
) {
const html = renderToStaticMarkup(document, { rootBlockId });
return overrideCanvasSize(html, document, rootBlockId);
}
@@ -0,0 +1,150 @@
import React, { useMemo } from "react";
import { MonitorOutlined, PhoneIphoneOutlined } from "@mui/icons-material";
import {
Box,
Stack,
SxProps,
ToggleButton,
ToggleButtonGroup,
Tooltip
} from "@mui/material";
import { Reader } from "@usewaypoint/email-builder";
import EditorBlock from "../../documents/editor/EditorBlock";
import {
setSelectedScreenSize,
useDocument,
useSelectedMainTab,
useSelectedScreenSize
} from "../../documents/editor/EditorContext";
import ToggleInspectorPanelButton from "../InspectorDrawer/ToggleInspectorPanelButton";
import ToggleSamplesPanelButton from "../SamplesDrawer/ToggleSamplesPanelButton";
import DownloadJson from "./DownloadJson";
import HtmlPanel from "./HtmlPanel";
import JsonPanel from "./JsonPanel";
import MainTabsGroup from "./MainTabsGroup";
import ShareButton from "./ShareButton";
import renderEmailHtml from "./helper/renderEmailHtml";
export default function TemplatePanel() {
const document = useDocument();
const selectedMainTab = useSelectedMainTab();
const selectedScreenSize = useSelectedScreenSize();
const previewHtml = useMemo(
() => renderEmailHtml(document, "root"),
[document]
);
let mainBoxSx: SxProps = { height: "100%" };
if (selectedScreenSize === "mobile") {
mainBoxSx = {
...mainBoxSx,
margin: "32px auto",
width: 370,
height: 800,
boxShadow:
"rgba(33, 36, 67, 0.04) 0px 10px 20px, rgba(33, 36, 67, 0.04) 0px 2px 6px, rgba(33, 36, 67, 0.04) 0px 0px 1px"
};
}
const handleScreenSizeChange = (_: unknown, value: unknown) => {
switch (value) {
case "mobile":
case "desktop":
setSelectedScreenSize(value);
return;
default:
setSelectedScreenSize("desktop");
}
};
const renderMainPanel = () => {
switch (selectedMainTab) {
case "editor":
return (
<Box sx={mainBoxSx}>
<EditorBlock id="root" />
</Box>
);
case "preview":
return (
<Box sx={mainBoxSx}>
<iframe
title="Email preview"
srcDoc={previewHtml}
sandbox="allow-popups allow-popups-to-escape-sandbox"
style={{ width: "100%", height: "100%", border: "none" }}
/>
{/* <Reader document={document} rootBlockId="root" /> */}
</Box>
);
case "html":
return <HtmlPanel />;
case "json":
return <JsonPanel />;
}
};
return (
<>
<Stack
sx={{
height: 49,
borderBottom: 1,
borderColor: "divider",
backgroundColor: "white",
position: "sticky",
top: 0,
zIndex: "appBar",
px: 1
}}
direction="row"
justifyContent="space-between"
alignItems="center"
>
<ToggleSamplesPanelButton />
<Stack
px={2}
direction="row"
gap={2}
width="100%"
justifyContent="space-between"
alignItems="center"
>
<Stack direction="row" spacing={2}>
<MainTabsGroup />
</Stack>
<Stack direction="row" spacing={2}>
<DownloadJson />
<ToggleButtonGroup
value={selectedScreenSize}
exclusive
size="small"
onChange={handleScreenSizeChange}
>
<ToggleButton value="desktop">
<Tooltip title="Desktop view">
<MonitorOutlined fontSize="small" />
</Tooltip>
</ToggleButton>
<ToggleButton value="mobile">
<Tooltip title="Mobile view">
<PhoneIphoneOutlined fontSize="small" />
</Tooltip>
</ToggleButton>
</ToggleButtonGroup>
<ShareButton />
</Stack>
</Stack>
<ToggleInspectorPanelButton />
</Stack>
<Box
sx={{ height: "calc(100vh - 49px)", overflow: "auto", minWidth: 370 }}
>
{renderMainPanel()}
</Box>
</>
);
}
@@ -0,0 +1,55 @@
import {
useState
} from "react";
import { Stack, useTheme } from "@mui/material";
import {
useInspectorDrawerOpen,
useSamplesDrawerOpen
} from "../documents/editor/EditorContext";
import InspectorDrawer, { INSPECTOR_DRAWER_WIDTH } from "./InspectorDrawer";
import SamplesDrawer, { SAMPLES_DRAWER_WIDTH } from "./SamplesDrawer";
import TemplatePanel from "./TemplatePanel";
function useDrawerTransition(
cssProperty: "margin-left" | "margin-right",
open: boolean
) {
const { transitions } = useTheme();
return transitions.create(cssProperty, {
easing: !open ? transitions.easing.sharp : transitions.easing.easeOut,
duration: !open
? transitions.duration.leavingScreen
: transitions.duration.enteringScreen
});
}
export default function EmailBuilderApp() {
const inspectorDrawerOpen = useInspectorDrawerOpen();
const samplesDrawerOpen = useSamplesDrawerOpen();
const marginLeftTransition = useDrawerTransition(
"margin-left",
samplesDrawerOpen
);
const marginRightTransition = useDrawerTransition(
"margin-right",
inspectorDrawerOpen
);
return (
<>
<InspectorDrawer />
<SamplesDrawer />
<Stack
sx={{
marginRight: inspectorDrawerOpen ? `${INSPECTOR_DRAWER_WIDTH}px` : 0,
marginLeft: samplesDrawerOpen ? `${SAMPLES_DRAWER_WIDTH}px` : 0,
transition: [marginLeftTransition, marginRightTransition].join(", ")
}}
>
<TemplatePanel />
</Stack>
</>
);
}
@@ -0,0 +1,15 @@
import { CssBaseline, ThemeProvider } from "@mui/material";
import App from "./App";
import theme from "./EmailBuildertheme";
const EmailBuilder = () => {
return (
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
);
};
export default EmailBuilder;
@@ -0,0 +1,520 @@
import { alpha, createTheme, darken, lighten } from "@mui/material/styles";
const BRAND_NAVY = "#212443";
const BRAND_BLUE = "#0079CC";
const BRAND_GREEN = "#1F8466";
const BRAND_RED = "#E81212";
const BRAND_YELLOW = "#F6DC9F";
const BRAND_PURPLE = "#6C0E7C";
const BRAND_BROWN = "#CC996C";
const STANDARD_FONT_FAMILY =
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"';
const MONOSPACE_FONT_FAMILY =
'ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace';
const BASE_THEME = createTheme({
palette: {
background: {
default: "#f2f5f7"
},
text: {
primary: "#1F1F21",
secondary: "#4F4F4F"
}
},
typography: {
fontFamily: STANDARD_FONT_FAMILY
}
});
const THEME = createTheme(BASE_THEME, {
palette: {
brand: {
navy: BRAND_NAVY,
blue: BRAND_BLUE,
red: BRAND_RED,
green: BRAND_GREEN,
yellow: BRAND_YELLOW,
purple: BRAND_PURPLE,
brown: BRAND_BROWN
},
success: {
main: BRAND_GREEN,
light: lighten(BRAND_GREEN, 0.15),
dark: darken(BRAND_GREEN, 0.15)
},
error: {
main: BRAND_RED,
light: lighten(BRAND_RED, 0.15),
dark: darken(BRAND_RED, 0.15)
},
cadet: {
100: "#F9FAFB",
200: "#F2F5F7",
300: "#DCE4EA",
400: "#A8BBCA",
500: "#6A8BA4"
},
highlight: {
100: lighten(BRAND_YELLOW, 0.8),
200: lighten(BRAND_YELLOW, 0.6),
300: lighten(BRAND_YELLOW, 0.4),
400: lighten(BRAND_YELLOW, 0.2),
500: BRAND_YELLOW
},
info: {
main: BRAND_BLUE
},
primary: {
main: BRAND_BLUE
}
},
components: {
MuiCssBaseline: {
styleOverrides: `
address {
font-style: normal;
}
fieldset {
border: none;
padding: 0;
}
pre {
font-family: ${MONOSPACE_FONT_FAMILY}
white-space: pre-wrap;
font-size: 12px;
}
`
},
MuiAlert: {
styleOverrides: {
root: {
fontSize: BASE_THEME.typography.pxToRem(14)
},
action: {
paddingTop: 0,
marginRight: 0
},
filledSuccess: {
backgroundColor: BRAND_GREEN
}
}
},
MuiStepLabel: {
styleOverrides: {
label: {
fontWeight: BASE_THEME.typography.fontWeightMedium
}
}
},
MuiDialog: {
defaultProps: {
fullWidth: true
}
},
MuiDialogContent: {
styleOverrides: {
root: {
paddingTop: BASE_THEME.spacing(1),
paddingBottom: BASE_THEME.spacing(2)
}
}
},
MuiDialogTitle: {
defaultProps: {
variant: "h4"
},
styleOverrides: {
root: {
paddingTop: BASE_THEME.spacing(3),
paddingBottom: BASE_THEME.spacing(1)
}
}
},
MuiDialogActions: {
styleOverrides: {
root: {
borderTop: "1px solid",
borderTopColor: BASE_THEME.palette.divider,
marginTop: BASE_THEME.spacing(2.5),
padding: `${BASE_THEME.spacing(1.5)} ${BASE_THEME.spacing(3)}`
}
}
},
MuiTableCell: {
styleOverrides: {
root: {
...BASE_THEME.typography.body2,
borderColor: BASE_THEME.palette.grey[200]
},
head: {
...BASE_THEME.typography.overline,
fontWeight: BASE_THEME.typography.fontWeightMedium,
letterSpacing: "0.075em",
color: BASE_THEME.palette.text.secondary
}
}
},
MuiTableRow: {
styleOverrides: {
root: {
"&:last-child td": {
borderBottom: 0
}
}
}
},
MuiAvatar: {
styleOverrides: {
root: {
textTransform: "uppercase",
fontSize: BASE_THEME.typography.pxToRem(14)
}
}
},
MuiChip: {
styleOverrides: {
root: {
"&.MuiChip-filledError, &.MuiChip-filledSuccess": {
fill: BASE_THEME.palette.primary.contrastText
}
},
sizeSmall: {
borderRadius: BASE_THEME.spacing(0.5),
fontSize: 12
},
iconSmall: {
fontSize: 14,
marginLeft: BASE_THEME.spacing(1)
},
colorSecondary: {
borderColor: BASE_THEME.palette.grey[400],
color: BASE_THEME.palette.text.secondary
},
label: {
fontWeight: BASE_THEME.typography.fontWeightMedium
}
}
},
MuiDrawer: {
defaultProps: {
PaperProps: {
elevation: 2
}
}
},
MuiTooltip: {
styleOverrides: {
tooltip: {
fontSize: BASE_THEME.typography.pxToRem(12),
backgroundColor: alpha(BASE_THEME.palette.text.primary, 0.9)
}
}
},
MuiSlider: {
styleOverrides: {
root: {
height: 1
},
track: {
height: 1,
border: "none"
},
rail: {
height: 1,
backgroundColor: BASE_THEME.palette.grey[500]
},
mark: {
backgroundColor: BASE_THEME.palette.grey[500]
},
markActive: {
height: 0
},
thumb: {
height: 16,
width: 16,
cursor: "col-resize",
"&:hover, &.Mui-active, &.Mui-focusVisible": {
boxShadow: `0 0 0 4px ${alpha(BRAND_BLUE, 0.2)}`
},
"&:before": {
display: "none"
}
}
}
},
MuiPaper: {
defaultProps: {
elevation: 2,
square: true
}
},
MuiButtonBase: {
defaultProps: {
disableTouchRipple: true,
focusRipple: true
},
styleOverrides: {
root: {
"&.MuiButton-containedSecondary.Mui-disabled": {
backgroundColor: BASE_THEME.palette.grey[100]
}
}
}
},
MuiButtonGroup: {
defaultProps: {
disableElevation: true
}
},
MuiIconButton: {
styleOverrides: {
edgeStart: {
marginLeft: BASE_THEME.spacing(-1)
},
colorSecondary: {
color: BASE_THEME.palette.grey[500]
}
}
},
MuiButton: {
defaultProps: {
disableElevation: true
},
styleOverrides: {
textPrimary: {
color: BASE_THEME.palette.text.primary
},
textSecondary: {
color: BASE_THEME.palette.text.secondary
},
outlinedPrimary: {
borderColor: BASE_THEME.palette.grey[300],
color: BASE_THEME.palette.text.primary,
"&:hover, &:active, &:focus": {
borderColor: BASE_THEME.palette.grey[500],
color: BASE_THEME.palette.text.primary
}
},
containedSecondary: {
backgroundColor: BASE_THEME.palette.common.white,
border: `1px solid ${BASE_THEME.palette.grey[300]}`,
color: BASE_THEME.palette.text.primary,
"&:hover, &:active, &:focus": {
backgroundColor: BASE_THEME.palette.common.white,
borderColor: BASE_THEME.palette.grey[500],
color: BASE_THEME.palette.text.primary
}
}
}
},
MuiToggleButton: {
styleOverrides: {
root: {
paddingLeft: BASE_THEME.spacing(1.5),
paddingRight: BASE_THEME.spacing(1.5)
}
}
},
MuiInputBase: {
styleOverrides: {
root: {
"&:not(.Mui-disabled, .Mui-error):before": {
borderBottom: `1px solid ${BASE_THEME.palette.grey[400]}`
},
"&:hover:not(.Mui-disabled, .Mui-error):before": {
borderBottom: `1px solid ${BASE_THEME.palette.grey[500]} !important`
},
"&:after": {
borderBottom: `1px solid ${BASE_THEME.palette.text.primary} !important`
},
"&.MuiOutlinedInput-root:not(.Mui-error)": {
"& fieldset": {
borderColor: BASE_THEME.palette.grey[300],
transition: "border-color 0.2s"
}
},
"&.MuiOutlinedInput-root:not(.Mui-disabled, .Mui-error)": {
"&:hover fieldset": {
borderColor: BASE_THEME.palette.grey[400]
},
"&.Mui-focused fieldset": {
borderColor: BASE_THEME.palette.text.secondary,
borderWidth: 1
}
}
},
input: {
fontSize: BASE_THEME.typography.pxToRem(14),
"&.Mui-disabled": {
WebkitTextFillColor: "inherit",
color: BASE_THEME.palette.text.secondary
}
},
inputSizeSmall: {}
}
},
MuiOutlinedInput: {
styleOverrides: {
notchedOutline: {
"& legend": {
fontSize: "0.85em",
maxWidth: "100%"
}
}
}
},
MuiInputAdornment: {
styleOverrides: {
root: {
"& .MuiTypography-root": {
fontSize: BASE_THEME.typography.pxToRem(14),
color: BASE_THEME.palette.text.secondary
}
}
}
},
MuiInputLabel: {
defaultProps: {
shrink: true
},
styleOverrides: {
shrink: {
transform: "scale(0.85)",
fontWeight: BASE_THEME.typography.fontWeightMedium,
"&.Mui-focused": {
color: BASE_THEME.palette.text.primary
},
"&.MuiInputLabel-standard": {
transform: "translate(0, -4px) scale(0.85)",
color: "#4F4F4F"
},
"&.MuiInputLabel-outlined": {
transform: "translate(15px, -8px) scale(0.85)"
}
}
}
},
MuiTabs: {
defaultProps: {
variant: "scrollable"
},
styleOverrides: {
indicator: {
height: 1,
backgroundColor: BASE_THEME.palette.text.primary
}
}
},
MuiTab: {
styleOverrides: {
root: {
textTransform: "none",
minWidth: BASE_THEME.spacing(2),
paddingLeft: BASE_THEME.spacing(1.5),
paddingRight: BASE_THEME.spacing(1.5),
fontSize: BASE_THEME.typography.pxToRem(14),
fontFamily: BASE_THEME.typography.fontFamily,
lineHeight: 1.5,
fontWeight: BASE_THEME.typography.fontWeightMedium,
transition: "color 0.2s",
"&.Mui-selected": {
color: BASE_THEME.palette.text.primary
},
"&:hover": {
color: BASE_THEME.palette.text.primary
}
}
}
},
MuiCard: {
styleOverrides: {
root: {
borderRadius: 0
}
}
},
MuiCardHeader: {
styleOverrides: {
title: {
fontSize: BASE_THEME.typography.pxToRem(18),
fontWeight: BASE_THEME.typography.fontWeightMedium
}
}
}
},
typography: {
fontFamily: BASE_THEME.typography.fontFamily,
h1: {
fontFamily: BASE_THEME.typography.fontFamily,
fontSize: BASE_THEME.typography.pxToRem(40),
lineHeight: 1.2,
letterSpacing: "-0.02em",
fontWeight: BASE_THEME.typography.fontWeightMedium
},
h2: {
fontFamily: BASE_THEME.typography.fontFamily,
fontSize: BASE_THEME.typography.pxToRem(32),
lineHeight: 1.2,
letterSpacing: "-0.02em",
fontWeight: BASE_THEME.typography.fontWeightMedium
},
h3: {
fontFamily: BASE_THEME.typography.fontFamily,
fontSize: BASE_THEME.typography.pxToRem(24),
lineHeight: 1.5,
letterSpacing: "-0.01em",
fontWeight: BASE_THEME.typography.fontWeightMedium
},
h4: {
fontFamily: BASE_THEME.typography.fontFamily,
fontSize: BASE_THEME.typography.pxToRem(20),
lineHeight: 1.5,
letterSpacing: "-0.01em",
fontWeight: BASE_THEME.typography.fontWeightMedium
},
h5: {
fontFamily: BASE_THEME.typography.fontFamily,
fontSize: BASE_THEME.typography.pxToRem(18),
lineHeight: 1.5,
letterSpacing: "-0.01em",
fontWeight: BASE_THEME.typography.fontWeightMedium
},
h6: {
fontFamily: BASE_THEME.typography.fontFamily,
fontSize: BASE_THEME.typography.pxToRem(16),
lineHeight: 1.5,
letterSpacing: "-0.005em",
fontWeight: BASE_THEME.typography.fontWeightMedium
},
body1: {
fontSize: BASE_THEME.typography.pxToRem(14)
},
body2: {
fontSize: BASE_THEME.typography.pxToRem(12)
},
overline: {
fontWeight: BASE_THEME.typography.fontWeightMedium,
letterSpacing: "0.05em"
},
button: {
textTransform: "none",
fontWeight: BASE_THEME.typography.fontWeightMedium,
lineHeight: 1.5
},
caption: {
letterSpacing: 0,
lineHeight: 1.5
}
},
shadows: [
"none",
"0px 4px 15px rgba(33, 36, 67, 0.04), 0px 0px 2px rgba(33, 36, 67, 0.04), 0px 0px 1px rgba(33, 36, 67, 0.04)",
"0px 10px 20px rgba(33, 36, 67, 0.04), 0px 2px 6px rgba(33, 36, 67, 0.04), 0px 0px 1px rgba(33, 36, 67, 0.04)",
"0px 16px 24px rgba(33, 36, 67, 0.05), 0px 2px 6px rgba(33, 36, 67, 0.05), 0px 0px 1px rgba(33, 36, 67, 0.05)",
"0px 24px 32px rgba(33, 36, 67, 0.06), 0px 16px 24px rgba(33, 36, 67, 0.06), 0px 4px 8px rgba(33, 36, 67, 0.06)",
...Array(20).fill("none")
]
});
export default THEME;
@@ -0,0 +1,72 @@
import React from "react";
import { ColumnsContainer as BaseColumnsContainer } from "@usewaypoint/block-columns-container";
import { useCurrentBlockId } from "../../editor/EditorBlock";
import { setDocument, setSelectedBlockId } from "../../editor/EditorContext";
import EditorChildrenIds, {
EditorChildrenChange
} from "../helpers/EditorChildrenIds";
import ColumnsContainerPropsSchema, {
ColumnsContainerProps
} from "./ColumnsContainerPropsSchema";
const EMPTY_COLUMNS = [
{ childrenIds: [] },
{ childrenIds: [] },
{ childrenIds: [] }
];
export default function ColumnsContainerEditor({
style,
props
}: ColumnsContainerProps) {
const currentBlockId = useCurrentBlockId();
const { columns, ...restProps } = props ?? {};
const columnsValue = columns ?? EMPTY_COLUMNS;
const updateColumn = (
columnIndex: 0 | 1 | 2,
{ block, blockId, childrenIds }: EditorChildrenChange
) => {
const nColumns = [...columnsValue];
nColumns[columnIndex] = { childrenIds };
setDocument({
[blockId]: block,
[currentBlockId]: {
type: "ColumnsContainer",
data: ColumnsContainerPropsSchema.parse({
style,
props: {
...restProps,
columns: nColumns
}
})
}
});
setSelectedBlockId(blockId);
};
return (
<BaseColumnsContainer
props={restProps}
style={style}
columns={[
<EditorChildrenIds
childrenIds={columns?.[0]?.childrenIds}
onChange={(change) => updateColumn(0, change)}
/>,
<EditorChildrenIds
childrenIds={columns?.[1]?.childrenIds}
onChange={(change) => updateColumn(1, change)}
/>,
<EditorChildrenIds
childrenIds={columns?.[2]?.childrenIds}
onChange={(change) => updateColumn(2, change)}
/>
]}
/>
);
}
@@ -0,0 +1,25 @@
import { z } from "zod";
import { ColumnsContainerPropsSchema as BaseColumnsContainerPropsSchema } from "@usewaypoint/block-columns-container";
const BasePropsShape = BaseColumnsContainerPropsSchema.shape.props
.unwrap()
.unwrap().shape;
const ColumnsContainerPropsSchema = z.object({
style: BaseColumnsContainerPropsSchema.shape.style,
props: z
.object({
...BasePropsShape,
columns: z.tuple([
z.object({ childrenIds: z.array(z.string()) }),
z.object({ childrenIds: z.array(z.string()) }),
z.object({ childrenIds: z.array(z.string()) })
])
})
.optional()
.nullable()
});
export type ColumnsContainerProps = z.infer<typeof ColumnsContainerPropsSchema>;
export default ColumnsContainerPropsSchema;
@@ -0,0 +1,41 @@
import React from "react";
import { Container as BaseContainer } from "@usewaypoint/block-container";
import { useCurrentBlockId } from "../../editor/EditorBlock";
import {
setDocument,
setSelectedBlockId,
useDocument
} from "../../editor/EditorContext";
import EditorChildrenIds from "../helpers/EditorChildrenIds";
import { ContainerProps } from "./ContainerPropsSchema";
export default function ContainerEditor({ style, props }: ContainerProps) {
const childrenIds = props?.childrenIds ?? [];
const document = useDocument();
const currentBlockId = useCurrentBlockId();
return (
<BaseContainer style={style}>
<EditorChildrenIds
childrenIds={childrenIds}
onChange={({ block, blockId, childrenIds }) => {
setDocument({
[blockId]: block,
[currentBlockId]: {
type: "Container",
data: {
...document[currentBlockId].data,
props: { childrenIds: childrenIds }
}
}
});
setSelectedBlockId(blockId);
}}
/>
</BaseContainer>
);
}
@@ -0,0 +1,17 @@
import { z } from "zod";
import { ContainerPropsSchema as BaseContainerPropsSchema } from "@usewaypoint/block-container";
const ContainerPropsSchema = z.object({
style: BaseContainerPropsSchema.shape.style,
props: z
.object({
childrenIds: z.array(z.string()).optional().nullable()
})
.optional()
.nullable()
});
export default ContainerPropsSchema;
export type ContainerProps = z.infer<typeof ContainerPropsSchema>;
@@ -0,0 +1,109 @@
import React from "react";
import { useCurrentBlockId } from "../../editor/EditorBlock";
import {
setDocument,
setSelectedBlockId,
useDocument
} from "../../editor/EditorContext";
import EditorChildrenIds from "../helpers/EditorChildrenIds";
import { EmailLayoutProps } from "./EmailLayoutPropsSchema";
function getFontFamily(fontFamily: EmailLayoutProps["fontFamily"]) {
const f = fontFamily ?? "MODERN_SANS";
switch (f) {
case "MODERN_SANS":
return '"Helvetica Neue", "Arial Nova", "Nimbus Sans", Arial, sans-serif';
case "BOOK_SANS":
return 'Optima, Candara, "Noto Sans", source-sans-pro, sans-serif';
case "ORGANIC_SANS":
return 'Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif';
case "GEOMETRIC_SANS":
return 'Avenir, "Avenir Next LT Pro", Montserrat, Corbel, "URW Gothic", source-sans-pro, sans-serif';
case "HEAVY_SANS":
return 'Bahnschrift, "DIN Alternate", "Franklin Gothic Medium", "Nimbus Sans Narrow", sans-serif-condensed, sans-serif';
case "ROUNDED_SANS":
return 'ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa, Manjari, "Arial Rounded MT Bold", Calibri, source-sans-pro, sans-serif';
case "MODERN_SERIF":
return 'Charter, "Bitstream Charter", "Sitka Text", Cambria, serif';
case "BOOK_SERIF":
return '"Iowan Old Style", "Palatino Linotype", "URW Palladio L", P052, serif';
case "MONOSPACE":
return '"Nimbus Mono PS", "Courier New", "Cutive Mono", monospace';
}
}
export default function EmailLayoutEditor(props: EmailLayoutProps) {
const childrenIds = props.childrenIds ?? [];
const canvasWidth = props.canvasWidth ?? 600;
const document = useDocument();
const currentBlockId = useCurrentBlockId();
return (
<div
onClick={() => {
setSelectedBlockId(null);
}}
style={{
backgroundColor: props.backdropColor ?? "#F5F5F5",
color: props.textColor ?? "#262626",
fontFamily: getFontFamily(props.fontFamily),
fontSize: "16px",
fontWeight: "400",
letterSpacing: "0.15008px",
lineHeight: "1.5",
margin: "0",
padding: "32px 0",
width: "100%",
minHeight: "100%"
}}
>
<table
align="center"
width="100%"
style={{
margin: "0 auto",
// maxWidth: "600px",
maxWidth: `${canvasWidth}px`,
backgroundColor: props.canvasColor ?? "#FFFFFF",
borderRadius: props.borderRadius ?? undefined,
border: (() => {
const v = props.borderColor;
if (!v) {
return undefined;
}
return `1px solid ${v}`;
})()
}}
role="presentation"
cellSpacing="0"
cellPadding="0"
border={0}
>
<tbody>
<tr style={{ width: "100%" }}>
<td>
<EditorChildrenIds
childrenIds={childrenIds}
onChange={({ block, blockId, childrenIds }) => {
setDocument({
[blockId]: block,
[currentBlockId]: {
type: "EmailLayout",
data: {
...document[currentBlockId].data,
childrenIds: childrenIds
}
}
});
setSelectedBlockId(blockId);
}}
/>
</td>
</tr>
</tbody>
</table>
</div>
);
}
@@ -0,0 +1,37 @@
import { z } from "zod";
const COLOR_SCHEMA = z
.string()
.regex(/^#[0-9a-fA-F]{6}$/)
.nullable()
.optional();
const FONT_FAMILY_SCHEMA = z
.enum([
"MODERN_SANS",
"BOOK_SANS",
"ORGANIC_SANS",
"GEOMETRIC_SANS",
"HEAVY_SANS",
"ROUNDED_SANS",
"MODERN_SERIF",
"BOOK_SERIF",
"MONOSPACE"
])
.nullable()
.optional();
const EmailLayoutPropsSchema = z.object({
backdropColor: COLOR_SCHEMA,
borderColor: COLOR_SCHEMA,
borderRadius: z.number().optional().nullable(),
canvasColor: COLOR_SCHEMA,
canvasWidth: z.number().optional().nullable(),
textColor: COLOR_SCHEMA,
fontFamily: FONT_FAMILY_SCHEMA,
childrenIds: z.array(z.string()).optional().nullable()
});
export default EmailLayoutPropsSchema;
export type EmailLayoutProps = z.infer<typeof EmailLayoutPropsSchema>;
@@ -0,0 +1,40 @@
import React from "react";
import { Box, Button, SxProps, Typography } from "@mui/material";
type BlockMenuButtonProps = {
label: string;
icon: React.ReactNode;
onClick: () => void;
};
const BUTTON_SX: SxProps = { p: 1.5, display: "flex", flexDirection: "column" };
const ICON_SX: SxProps = {
mb: 0.75,
width: "100%",
bgcolor: "cadet.200",
display: "flex",
justifyContent: "center",
p: 1,
border: "1px solid",
borderColor: "cadet.300"
};
export default function BlockTypeButton({
label,
icon,
onClick
}: BlockMenuButtonProps) {
return (
<Button
sx={BUTTON_SX}
onClick={(ev) => {
ev.stopPropagation();
onClick();
}}
>
<Box sx={ICON_SX}>{icon}</Box>
<Typography variant="body2">{label}</Typography>
</Button>
);
}
@@ -0,0 +1,55 @@
import React from "react";
import { Box, Menu } from "@mui/material";
import { TEditorBlock } from "../../../../editor/core";
import BlockButton from "./BlockButton";
import { BUTTONS } from "./buttons";
type BlocksMenuProps = {
anchorEl: HTMLElement | null;
setAnchorEl: (v: HTMLElement | null) => void;
onSelect: (block: TEditorBlock) => void;
};
export default function BlocksMenu({
anchorEl,
setAnchorEl,
onSelect
}: BlocksMenuProps) {
const onClose = () => {
setAnchorEl(null);
};
const onClick = (block: TEditorBlock) => {
onSelect(block);
setAnchorEl(null);
};
if (anchorEl === null) {
return null;
}
return (
<Menu
open
anchorEl={anchorEl}
onClose={onClose}
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
transformOrigin={{ vertical: "top", horizontal: "center" }}
>
<Box
sx={{ p: 1, display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr" }}
>
{BUTTONS.map((k, i) => (
<BlockButton
key={i}
label={k.label}
icon={k.icon}
onClick={() => onClick(k.block())}
/>
))}
</Box>
</Menu>
);
}
@@ -0,0 +1,64 @@
import React, { useEffect, useState } from "react";
import { AddOutlined } from "@mui/icons-material";
import { Fade, IconButton } from "@mui/material";
type Props = {
buttonElement: HTMLElement | null;
onClick: () => void;
};
export default function DividerButton({ buttonElement, onClick }: Props) {
const [visible, setVisible] = useState(false);
useEffect(() => {
function listener({ clientX, clientY }: MouseEvent) {
if (!buttonElement) {
return;
}
const rect = buttonElement.getBoundingClientRect();
const rectY = rect.y;
const bottomX = rect.x;
const topX = bottomX + rect.width;
if (Math.abs(clientY - rectY) < 20) {
if (bottomX < clientX && clientX < topX) {
setVisible(true);
return;
}
}
setVisible(false);
}
window.addEventListener("mousemove", listener);
return () => {
window.removeEventListener("mousemove", listener);
};
}, [buttonElement, setVisible]);
return (
<Fade in={visible}>
<IconButton
size="small"
sx={{
p: 0.12,
position: "absolute",
top: "-12px",
left: "50%",
transform: "translateX(-10px)",
bgcolor: "brand.blue",
color: "primary.contrastText",
zIndex: "fab",
"&:hover, &:active, &:focus": {
bgcolor: "brand.blue",
color: "primary.contrastText"
}
}}
onClick={(ev) => {
ev.stopPropagation();
onClick();
}}
>
<AddOutlined fontSize="small" />
</IconButton>
</Fade>
);
}
@@ -0,0 +1,36 @@
import React from "react";
import { AddOutlined } from "@mui/icons-material";
import { ButtonBase } from "@mui/material";
type Props = {
onClick: () => void;
};
export default function PlaceholderButton({ onClick }: Props) {
return (
<ButtonBase
onClick={(ev) => {
ev.stopPropagation();
onClick();
}}
sx={{
display: "flex",
alignContent: "center",
justifyContent: "center",
height: 48,
width: "100%",
bgcolor: "rgba(0,0,0, 0.05)"
}}
>
<AddOutlined
sx={{
p: 0.12,
bgcolor: "brand.blue",
borderRadius: 24,
color: "primary.contrastText"
}}
fontSize="small"
/>
</ButtonBase>
);
}
@@ -0,0 +1,161 @@
import React from "react";
import {
AccountCircleOutlined,
Crop32Outlined,
HMobiledataOutlined,
HorizontalRuleOutlined,
HtmlOutlined,
ImageOutlined,
LibraryAddOutlined,
NotesOutlined,
SmartButtonOutlined,
ViewColumnOutlined
} from "@mui/icons-material";
import { TEditorBlock } from "../../../../editor/core";
type TButtonProps = {
label: string;
icon: JSX.Element;
block: () => TEditorBlock;
};
export const BUTTONS: TButtonProps[] = [
{
label: "Heading",
icon: <HMobiledataOutlined />,
block: () => ({
type: "Heading",
data: {
props: { text: "Hello friend" },
style: {
padding: { top: 16, bottom: 16, left: 24, right: 24 }
}
}
})
},
{
label: "Text",
icon: <NotesOutlined />,
block: () => ({
type: "Text",
data: {
props: { text: "My new text block" },
style: {
padding: { top: 16, bottom: 16, left: 24, right: 24 },
fontWeight: "normal"
}
}
})
},
{
label: "Button",
icon: <SmartButtonOutlined />,
block: () => ({
type: "Button",
data: {
props: { text: "Button", url: window.location.origin },
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
}
})
},
{
label: "Image",
icon: <ImageOutlined />,
block: () => ({
type: "Image",
data: {
props: {
url: "https://ui-avatars.com/api/?size=128",
alt: "Sample product",
contentAlignment: "middle",
linkHref: null
},
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
}
})
},
{
label: "Avatar",
icon: <AccountCircleOutlined />,
block: () => ({
type: "Avatar",
data: {
props: {
imageUrl: "https://ui-avatars.com/api/?size=128",
shape: "circle"
},
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
}
})
},
{
label: "Divider",
icon: <HorizontalRuleOutlined />,
block: () => ({
type: "Divider",
data: {
style: { padding: { top: 16, right: 0, bottom: 16, left: 0 } },
props: {
lineColor: "#CCCCCC"
}
}
})
},
{
label: "Spacer",
icon: <Crop32Outlined />,
block: () => ({
type: "Spacer",
data: {}
})
},
{
label: "Html",
icon: <HtmlOutlined />,
block: () => ({
type: "Html",
data: {
props: { contents: "<strong>Hello world</strong>" },
style: {
fontSize: 16,
textAlign: null,
padding: { top: 16, bottom: 16, left: 24, right: 24 }
}
}
})
},
{
label: "Columns",
icon: <ViewColumnOutlined />,
block: () => ({
type: "ColumnsContainer",
data: {
props: {
columnsGap: 16,
columnsCount: 3,
columns: [
{ childrenIds: [] },
{ childrenIds: [] },
{ childrenIds: [] }
]
},
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
}
})
},
{
label: "Container",
icon: <LibraryAddOutlined />,
block: () => ({
type: "Container",
data: {
style: { padding: { top: 16, bottom: 16, left: 24, right: 24 } }
}
})
}
// { label: 'ProgressBar', icon: <ProgressBarOutlined />, block: () => ({}) },
// { label: 'LoopContainer', icon: <ViewListOutlined />, block: () => ({}) },
];
@@ -0,0 +1,46 @@
import React, { useState } from "react";
import { TEditorBlock } from "../../../../editor/core";
import BlocksMenu from "./BlocksMenu";
import DividerButton from "./DividerButton";
import PlaceholderButton from "./PlaceholderButton";
type Props = {
placeholder?: boolean;
onSelect: (block: TEditorBlock) => void;
};
export default function AddBlockButton({ onSelect, placeholder }: Props) {
const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLElement | null>(null);
const [buttonElement, setButtonElement] = useState<HTMLElement | null>(null);
const handleButtonClick = () => {
setMenuAnchorEl(buttonElement);
};
const renderButton = () => {
if (placeholder) {
return <PlaceholderButton onClick={handleButtonClick} />;
} else {
return (
<DividerButton
buttonElement={buttonElement}
onClick={handleButtonClick}
/>
);
}
};
return (
<>
<div ref={setButtonElement} style={{ position: "relative" }}>
{renderButton()}
</div>
<BlocksMenu
anchorEl={menuAnchorEl}
setAnchorEl={setMenuAnchorEl}
onSelect={onSelect}
/>
</>
);
}
@@ -0,0 +1,61 @@
import React, { Fragment } from "react";
import { TEditorBlock } from "../../../editor/core";
import EditorBlock from "../../../editor/EditorBlock";
import AddBlockButton from "./AddBlockMenu";
export type EditorChildrenChange = {
blockId: string;
block: TEditorBlock;
childrenIds: string[];
};
function generateId() {
return `block-${Date.now()}`;
}
export type EditorChildrenIdsProps = {
childrenIds: string[] | null | undefined;
onChange: (val: EditorChildrenChange) => void;
};
export default function EditorChildrenIds({
childrenIds,
onChange
}: EditorChildrenIdsProps) {
const appendBlock = (block: TEditorBlock) => {
const blockId = generateId();
return onChange({
blockId,
block,
childrenIds: [...(childrenIds || []), blockId]
});
};
const insertBlock = (block: TEditorBlock, index: number) => {
const blockId = generateId();
const newChildrenIds = [...(childrenIds || [])];
newChildrenIds.splice(index, 0, blockId);
return onChange({
blockId,
block,
childrenIds: newChildrenIds
});
};
if (!childrenIds || childrenIds.length === 0) {
return <AddBlockButton placeholder onSelect={appendBlock} />;
}
return (
<>
{childrenIds.map((childId, i) => (
<Fragment key={childId}>
<AddBlockButton onSelect={(block) => insertBlock(block, i)} />
<EditorBlock id={childId} />
</Fragment>
))}
<AddBlockButton onSelect={appendBlock} />
</>
);
}
@@ -0,0 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export type TStyle = {
backgroundColor?: any;
borderColor?: any;
borderRadius?: any;
color?: any;
fontFamily?: any;
fontSize?: any;
fontWeight?: any;
padding?: any;
textAlign?: any;
};
@@ -0,0 +1,63 @@
import React, { CSSProperties, useState } from "react";
import { Box } from "@mui/material";
import { useCurrentBlockId } from "../../../editor/EditorBlock";
import {
setSelectedBlockId,
useSelectedBlockId
} from "../../../editor/EditorContext";
import TuneMenu from "./TuneMenu";
type TEditorBlockWrapperProps = {
children: JSX.Element;
};
export default function EditorBlockWrapper({
children
}: TEditorBlockWrapperProps) {
const selectedBlockId = useSelectedBlockId();
const [mouseInside, setMouseInside] = useState(false);
const blockId = useCurrentBlockId();
let outline: CSSProperties["outline"];
if (selectedBlockId === blockId) {
outline = "2px solid rgba(0,121,204, 1)";
} else if (mouseInside) {
outline = "2px solid rgba(0,121,204, 0.3)";
}
const renderMenu = () => {
if (selectedBlockId !== blockId) {
return null;
}
return <TuneMenu blockId={blockId} />;
};
return (
<Box
sx={{
position: "relative",
maxWidth: "100%",
outlineOffset: "-1px",
outline
}}
onMouseEnter={(ev) => {
setMouseInside(true);
ev.stopPropagation();
}}
onMouseLeave={() => {
setMouseInside(false);
}}
onClick={(ev) => {
setSelectedBlockId(blockId);
ev.stopPropagation();
ev.preventDefault();
}}
>
{renderMenu()}
{children}
</Box>
);
}

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