Compare commits

...
Author SHA1 Message Date
prafull-opensignlabs acb0f3083c Merge pull request 2026-03-04 07:31:19 +00:00
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 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 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
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
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 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
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 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
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
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 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 ca1b87309f Merge pull request #2011 from OpenSignLabs/updates-18996309218
feat: handle undefine username/password for SMTP
2025-11-01 17:24:58 +05:30
prafull-opensignlabs 0b4548130d feat: handle undefine username/password for SMTP 2025-11-01 11:53:04 +00:00
prafull-opensignlabsandGitHub e8b3c7fed8 Merge pull request #1994 from OpenSignLabs/updates-18555669223
v2.30.0
2025-10-16 14:19:28 +05:30
prafull-opensignlabs 2ce43ec2c8 v2.30.0 2025-10-16 08:48:28 +00:00
prafull-opensignlabsandGitHub d5af1e8a17 Merge pull request #1973 from OpenSignLabs/updates-18164868546
v2.29.2
2025-10-01 19:38:24 +05:30
prafull-opensignlabs fab7998e22 refactor: handle session expired modal 2025-10-01 14:07:45 +00:00
prafull-opensignlabsandGitHub 4a4db6851e Merge pull request #1969 from OpenSignLabs/updates-18129802734
v2.29.1
2025-09-30 17:57:55 +05:30
prafull-opensignlabs 870973e7f3 Merge pull request 2025-09-30 12:26:28 +00:00
prafull-opensignlabsandGitHub 4146680c1e Merge pull request #1966 from OpenSignLabs/updates-18086083096
v2.29.0
2025-09-29 10:24:16 +05:30
prafull-opensignlabs d9fcc69ca4 v2.29.0 2025-09-29 04:47:23 +00:00
RaistlinandGitHub a878ca1a96 Update README.md
updated link to api documentation v1.1
2025-09-23 11:33:29 +02:00
prafull-opensignlabsandGitHub d1c405912b Merge pull request #1945 from OpenSignLabs/updates-17798985943
refactor: remove unnecessary code
2025-09-17 18:51:19 +05:30
prafull-opensignlabs 745aca1754 Merge pull request #1298 from nxglabs/sync-to-public_repo-17798472199
Merge pull request #1297 from nxglabs/staging
2025-09-17 13:19:46 +00:00
prafull-opensignlabsandGitHub 672eeea851 Merge pull request #1943 from OpenSignLabs/updates-17788011031
build(deps): build dependencies and dark mode ui
2025-09-17 11:21:02 +05:30
prafull-opensignlabs 98efff65c9 Merge pull request #1295 from nxglabs/sync-to-public_repo-17787708673
fix: show signer name in typed signature instead in selfsign flow
2025-09-17 05:48:01 +00:00
prafull-opensignlabsandGitHub fe21978538 Merge pull request #1941 from OpenSignLabs/updates-17737216200
v2.28.0
2025-09-15 20:23:17 +05:30
prafull-opensignlabs ca4ccfd889 Merge pull request #1292 from nxglabs/sync-to-public_repo-17730712175
Merge pull request #1291 from nxglabs/staging
2025-09-15 14:51:16 +00:00
prafull-opensignlabsandGitHub 6cb2d69566 Merge pull request #1931 from OpenSignLabs/updates-17548337132
fix: getting error 'user not found' while deleting user
2025-09-08 16:22:46 +05:30
prafull-opensignlabs 98b9ebb228 fix: getting error 'user not found' while deleting user 2025-09-08 10:51:52 +00:00
prafull-opensignlabsandGitHub 1c2f453496 Merge pull request #1929 from OpenSignLabs/updates-17540166746
v2.27.2
2025-09-08 10:30:18 +05:30
prafull-opensignlabs b01d8125d9 Merge pull request #1250 from nxglabs/sync-to-public_repo-17512737859
Merge pull request
2025-09-08 04:57:25 +00:00
prafull-opensignlabsandGitHub 85bc2fbbb0 Merge pull request #1902 from OpenSignLabs/updates-16958097538
fix: save sign, initials not working
2025-08-14 12:27:31 +05:30
prafull-opensignlabs e857724502 Merge pull request #1137 from nxglabs/signers_completed_main
fix: not able update public username and save sign, initials not working
2025-08-14 06:53:43 +00:00
prafull-opensignlabsandGitHub 086d8ee5be Merge pull request #1889 from OpenSignLabs/updates-16770567266
fix: managesign report alignment and loader issue
2025-08-06 13:06:40 +05:30
prafull-opensignlabs 04543e826a Merge pull request #1105 from nxglabs/raktima-main-patch-2
fix: managesign report alignment and loader issue
2025-08-06 07:35:09 +00:00
prafull-opensignlabsandGitHub 04f6e38778 Merge pull request #1881 from OpenSignLabs/updates-16679049560
refactor: remove form-name from form title
2025-08-01 21:03:59 +05:30
prafull-opensignlabs 1b5c85fd5f Merge pull request #1085 from nxglabs/signers_completed_main
refactor: remove form-name from form title
2025-08-01 15:33:06 +00:00
prafull-opensignlabsandGitHub 123425d7d1 Merge pull request #1880 from OpenSignLabs/updates-16678047946
feat: allow preferences menu for all user
2025-08-01 20:18:07 +05:30
prafull-opensignlabs a5b9d8178c Merge pull request #1084 from nxglabs/signers_completed_main
feat: allow preferences menu for all user and add smtp setting in it
2025-08-01 14:46:44 +00:00
prafull-opensignlabsandGitHub aefd2ff933 Merge pull request #1868 from OpenSignLabs/updates-16625254839
refactor: correct grammar and normalise font size of signer email in report
2025-07-30 19:54:36 +05:30
nxglabs fe93be8be7 Merge pull request #1070 from nxglabs/signers_completed_main 2025-07-30 14:22:07 +00:00
prafull-opensignlabsandGitHub 369e3660fc Merge pull request #1865 from OpenSignLabs/updates-16598173522
fix: add dynamic truncate for email in list of signers
2025-07-29 19:29:57 +05:30
nxglabs 378c0f057b Merge pull request #1066 from nxglabs/signers_completed_main 2025-07-29 13:54:44 +00:00
prafull-opensignlabsandGitHub 4f3c034cec Merge pull request #1864 from OpenSignLabs/updates-16597572999
Fix: show signers list in completed documents
2025-07-29 19:04:08 +05:30
nxglabs e7d3453744 Merge pull request #1064 from nxglabs/signers_completed_main 2025-07-29 13:30:45 +00:00
prafull-opensignlabsandGitHub 47cc0ef580 Merge pull request #1858 from OpenSignLabs/updates-16525889391
remove checkbox from sign agreement screen
2025-07-25 21:18:33 +05:30
prafull-opensignlabs 04f71be6a2 Merge pull request #1044 from nxglabs/raktima-patch-main-2
fix: remove agreement checkbox and remove agreement checkbox tour
2025-07-25 15:38:38 +00:00
prafull-opensignlabsandGitHub 07603f4bc2 Merge pull request #1856 from OpenSignLabs/imgbot
[ImgBot] Optimize images
2025-07-24 14:40:19 +05:30
ImgBotApp ea16180551 [ImgBot] Optimize images
*Total -- 487.98kb -> 238.96kb (51.03%)

/apps/OpenSign/public/logo512.png -- 10.72kb -> 2.72kb (74.62%)
/apps/OpenSignServer/logo.png -- 86.48kb -> 24.83kb (71.28%)
/apps/OpenSign/src/assets/images/logo.png -- 86.48kb -> 24.83kb (71.28%)
/apps/OpenSign/public/static/js/assets/images/logo.2a7bff0c1189183fafe71d7d5b94e0cd.png -- 86.48kb -> 24.83kb (71.28%)
/apps/OpenSign/public/logo192.png -- 2.13kb -> 0.65kb (69.69%)
/apps/OpenSign/src/assets/images/dp.png -- 12.83kb -> 4.25kb (66.9%)
/apps/OpenSign/src/assets/images/folder.png -- 4.39kb -> 1.93kb (56.05%)
/apps/OpenSign/public/static/js/assets/images/logo-dark.png -- 95.21kb -> 57.21kb (39.91%)
/apps/OpenSignServer/public/assets/images/parse-logo.png -- 5.16kb -> 3.81kb (26.21%)
/apps/OpenSign/src/assets/images/pdf3.png -- 11.66kb -> 9.77kb (16.21%)
/apps/OpenSign/src/assets/images/login_img.svg -- 40.75kb -> 39.10kb (4.04%)
/apps/OpenSign/src/assets/images/recreatedoc.png -- 40.69kb -> 40.08kb (1.48%)
/apps/OpenSign/src/assets/images/pad.svg -- 5.00kb -> 4.94kb (1.07%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-07-24 08:28:02 +00:00
prafull-opensignlabsandGitHub 75ad566d54 Merge pull request #1841 from OpenSignLabs/updates-16144605887
v2.26.1
2025-07-08 19:01:19 +05:30
nxglabs fda9fb534b Merge pull request #963 from nxglabs/staging
v2.26.1
2025-07-08 13:27:37 +00:00
prafull-opensignlabsandGitHub cb3e31ebbf Merge pull request #1838 from OpenSignLabs/updates-16124490027
dark-theme logo
2025-07-07 23:43:31 +05:30
nxglabs 400525debb Merge pull request #960 from nxglabs/fix_customlogo
fix: custom applogo not visible in header
2025-07-07 18:07:24 +00:00
prafull-opensignlabsandGitHub 1cc017bb05 Merge pull request #1829 from OpenSignLabs/updates-16070658635
fix: quicksend screen get black out while sending document
2025-07-04 15:06:16 +05:30
prafull-opensignlabs 531dd11185 Merge pull request #942 from nxglabs/staging
fix: quicksend screen get black out while sending document
2025-07-04 09:32:50 +00:00
prafull-opensignlabsandGitHub 1e80754750 Merge pull request #1827 from OpenSignLabs/updates-16068582405
revert: css of drive
2025-07-04 13:14:27 +05:30
prafull-opensignlabs 0d286938c7 Merge pull request #940 from nxglabs/staging
revert: css of drive
2025-07-04 07:43:32 +00:00
prafull-opensignlabsandGitHub 360c13df25 Merge pull request #1825 from OpenSignLabs/updates-16055306436
v2.26.0
2025-07-04 10:35:33 +05:30
nxglabs 71b9bd6367 Merge pull request #937 from nxglabs/tenant_mailtemplate
fix: if user not provide default value use it from template in createdocument from template API
2025-07-03 16:04:35 +00:00
prafull-opensignlabsandGitHub 4a5b35e147 Merge pull request #1818 from OpenSignLabs/updates-15966812948
fix: handle undefine appId
2025-06-30 13:09:02 +05:30
prafull-opensignlabs 7930a8f439 Merge pull request #917 from nxglabs/handle_appid
fix: handle undefine appId
2025-06-30 07:37:32 +00:00
prafull-opensignlabsandGitHub 7204183542 Merge pull request #1804 from OpenSignLabs/updates-15754544632
fix: resolve issues
2025-06-19 15:03:08 +05:30
nxglabs 5561673fdc Merge pull request #866 from nxglabs/fix_main_issues 2025-06-19 09:31:17 +00:00
prafull-opensignlabsandGitHub 0df44062d6 Merge pull request #1803 from OpenSignLabs/updates-15736278136
fix: correct baseapi of docxtopdf and decryptpdf
2025-06-18 20:25:53 +05:30
nxglabs 8983440763 Merge pull request #864 from nxglabs/staging 2025-06-18 14:54:13 +00:00
prafull-opensignlabsandGitHub 5a28c0ed06 Merge pull request #1802 from OpenSignLabs/updates-15735030679
v2.25.0
2025-06-18 19:43:43 +05:30
nxglabs cbca22c308 Merge pull request #861 from nxglabs/staging
fix: handle password protected pdf
2025-06-18 14:03:05 +00:00
prafull-opensignlabsandGitHub b0e0b38e8f Merge pull request #1789 from OpenSignLabs/updates-15483575034
v2.24.1
2025-06-06 11:17:12 +05:30
prafull-opensignlabs 49c23fb52b Merge pull request #753 from OpenSignLabs/staging
v2.24.1
2025-06-06 05:33:37 +00:00
prafull-opensignlabsandGitHub da4b604710 Merge pull request #1785 from OpenSignLabs/updates-15425322075
handle crashing pattern for text input widgets
2025-06-04 00:17:30 +05:30
prafull-opensignlabs 82d518b3c9 Merge pull request #739 from OpenSignLabs/staging
disable signing request email when send_email flag is false
2025-06-03 18:45:49 +00:00
prafull-opensignlabsandGitHub dcbde2b661 Merge pull request #1784 from OpenSignLabs/updates-15419310132
v2.23.1
2025-06-03 19:28:35 +05:30
prafull-opensignlabs 27b0b426ad v2.23.1
Enable widget placement on click in desktop view
Disable signature types not working
Change icon of agreement sign checkbox
Update dependencies
2025-06-03 13:55:57 +00:00
prafull-opensignlabsandGitHub 8788afaa8e Merge pull request #1766 from OpenSignLabs/updates-15350472621
fix: add verify-document route
2025-05-30 21:19:35 +05:30
prafull-opensignlabs 428aa65b38 Merge pull request #710 from OpenSignLabs/staging
fix: add verify-document route
2025-05-30 15:45:27 +00:00
AmolandGitHub 295d942427 feat: add deploy to digitalocean button 2025-05-30 20:33:43 +05:30
prafull-opensignlabsandGitHub c4e28e6e6e Merge pull request #1764 from OpenSignLabs/updates-15349515719
v2.23.0
2025-05-30 20:26:41 +05:30
prafull-opensignlabs b19fb29b82 Merge pull request #708 from OpenSignLabs/staging
refactor: unnecessary code
2025-05-30 14:54:53 +00:00
prafull-opensignlabsandGitHub 3d223ba343 Merge pull request #1756 from OpenSignLabs/updates-15275255429
v2.22.0
2025-05-27 18:13:12 +05:30
prafull-opensignlabs 98754c776f Merge pull request #675 from OpenSignLabs/staging
v2.22.0
2025-05-27 12:28:40 +00:00
prafull-opensignlabsandGitHub 1550e3a3d0 Merge pull request #1748 from OpenSignLabs/updates-15156338467
v2.21.2
2025-05-21 13:09:14 +05:30
prafull-opensignlabs 3500883205 v2.21.2
fix: handle subscription error in publicsign
2025-05-21 07:37:15 +00:00
prafull-opensignlabsandGitHub d888897b62 Update README.md 2025-05-20 20:10:44 +05:30
prafull-opensignlabsandGitHub 8f2433fbea Merge pull request #1737 from OpenSignLabs/updates-15043583319
v2.21.1
2025-05-15 16:48:53 +05:30
prafull-opensignlabs b671dbac19 v2.21.1
fix: handle undefined placeholders condition
2025-05-15 11:17:30 +00:00
prafull-opensignlabsandGitHub 79cdbc9c8c Merge pull request #1735 from OpenSignLabs/updates-15024699698
fix: resolve error cannot read undefined props
2025-05-14 20:55:08 +05:30
prafull-opensignlabs e585b7c943 fix: resolve error cannot read undefined props 2025-05-14 15:24:27 +00:00
prafull-opensignlabsandGitHub 5336e2cb7c Merge pull request #1734 from OpenSignLabs/prafull-opensignlabs-patch-7
refactor: remove unnecessary file
2025-05-14 20:26:36 +05:30
prafull-opensignlabsandGitHub 72401291f8 Delete apps/OpenSign/vite.public-template.config.js 2025-05-14 20:24:54 +05:30
prafull-opensignlabsandGitHub 7b34314b9e Merge pull request #1733 from OpenSignLabs/updates-15023728155
fix: correct build commands for selfhosting
2025-05-14 20:13:14 +05:30
prafull-opensignlabs b6f1cceeca Merge pull request #612 from OpenSignLabs/staging
fix: correct build commands for selfhosting
2025-05-14 14:42:30 +00:00
prafull-opensignlabsandGitHub c422cdac06 Merge pull request #1732 from OpenSignLabs/updates-15023371589
fix: edit template option not visible in mobile view
2025-05-14 20:00:02 +05:30
prafull-opensignlabsandGitHub ab08d2615d Update package.json 2025-05-14 19:59:01 +05:30
prafull-opensignlabs 2b0c7488d9 Merge pull request #610 from OpenSignLabs/staging
fix: edit template option not visible in mobile view
2025-05-14 14:27:57 +00:00
prafull-opensignlabsandGitHub 6927540741 Merge pull request #1731 from OpenSignLabs/updates-15022725718
v2.21.0
2025-05-14 19:33:19 +05:30
prafull-opensignlabs 1ceeee36ac v2.21.0
Merge pull request #601 from OpenSignLabs/staging
2025-05-14 14:00:50 +00:00
prafull-opensignlabsandGitHub c4779c14c1 feat: add all three docker command 2025-05-14 19:11:18 +05:30
prafull-opensignlabsandGitHub e48869cb2f feat: add deploy to digitalocean button 2025-05-14 18:36:47 +05:30
prafull-opensignlabsandGitHub 81d3969d1f Merge pull request #1719 from OpenSignLabs/updates-14856876951
v2.20.1
2025-05-06 15:35:42 +05:30
prafull-opensignlabs 5819103f57 v2.20.1 2025-05-06 10:01:20 +00:00
prafull-opensignlabsandGitHub 6653b7ae5a Merge pull request #1717 from OpenSignLabs/updates-14829517615
v2.20.0
2025-05-05 10:46:07 +05:30
prafull-opensignlabs b50c1e70ee v2.20.0 2025-05-05 05:12:54 +00:00
prafull-opensignlabsandGitHub 365c2a217e Merge pull request #1709 from OpenSignLabs/prafull-opensignlabs-patch-5
fix: quick send not working
2025-04-29 12:11:39 +05:30
prafull-opensignlabsandGitHub b1b9b20e97 Update Validate.js 2025-04-29 12:10:14 +05:30
prafull-opensignlabsandGitHub 75c0cdb5f9 fix: quick send not working 2025-04-29 12:09:05 +05:30
prafull-opensignlabsandGitHub 740ccca1f6 Merge pull request #1704 from OpenSignLabs/updates-14689408163
Merge pull request #547 from OpenSignLabs/staging
2025-04-27 12:21:00 +05:30
prafull-opensignlabs f3b79cb71f Merge pull request #547 from OpenSignLabs/staging
refactor: remove extra comment
2025-04-27 06:50:12 +00:00
prafull-opensignlabsandGitHub f95ea0b9e9 Merge pull request #1702 from OpenSignLabs/updates-14689261849
fix: update migration script
2025-04-27 12:01:50 +05:30
prafull-opensignlabs 647a9ce5b4 Merge pull request #545 from OpenSignLabs/staging
fix: update migration script
2025-04-27 06:30:15 +00:00
prafull-opensignlabsandGitHub 3f3ca668f5 Merge pull request #1700 from OpenSignLabs/updates-14666117248
fix: return https protocol for signing urls
2025-04-25 19:17:21 +05:30
prafull-opensignlabs 2fd6d1a37a fix: return https protocol for signing urls 2025-04-25 13:46:14 +00:00
prafull-opensignlabsandGitHub 307137f926 Merge pull request #1699 from OpenSignLabs/updates-14660165068
remove non-exist class from migration script
2025-04-25 13:51:06 +05:30
prafull-opensignlabs 2394880dfc Merge pull request #541 from OpenSignLabs/staging
remove non-exist defaultOtp class from migration
2025-04-25 08:19:46 +00:00
AmolandGitHub 414cdcd4b5 Merge pull request #1698 from OpenSignLabs/prafull-opensignlabs-patch-4
fix: Workflow does not contain permissions
2025-04-25 13:29:43 +05:30
prafull-opensignlabsandGitHub 1fa6b25890 fix: Workflow does not contain permissions 2025-04-25 13:29:03 +05:30
prafull-opensignlabsandGitHub ff2fd44b19 Merge pull request #1697 from OpenSignLabs/updates-14659684433
build(deps): update react-router package to fix vulnerabilities
2025-04-25 13:22:28 +05:30
prafull-opensignlabs ad466fb066 build(deps): update react-router package to fix vulnerabilities 2025-04-25 07:51:45 +00:00
AmolandGitHub 0f179fed67 Merge pull request #1696 from OpenSignLabs/updates-14659482113
v2.19.0
2025-04-25 13:13:08 +05:30
nxglabs 3ded6c35a1 Merge pull request #536 from OpenSignLabs/doc_load_drive
refactor: remove protected fields migration
2025-04-25 07:39:21 +00:00
AmolandGitHub 64293db3a1 Merge pull request #1695 from OpenSignLabs/prafull-opensignlabs-patch-3
Update Docker.yml
2025-04-25 13:06:15 +05:30
prafull-opensignlabsandGitHub 18a4c8523a Update Docker.yml 2025-04-25 13:03:50 +05:30
prafull-opensignlabsandGitHub f7b39409a8 Merge pull request #1692 from OpenSignLabs/updates-14590419587
fix: normalize emails, migrate user logic to cloud function
2025-04-22 14:09:18 +05:30
prafull-opensignlabs 6283524d2f Merge pull request #524 from OpenSignLabs/publicurl_adduser
fix: correct public URL, normalize emails, migrate user/orgAdmin logic to cloud function
2025-04-22 08:36:56 +00:00
prafull-opensignlabsandGitHub 216288fd09 Merge pull request #1689 from OpenSignLabs/updates-14509601184
v2.18.0
2025-04-17 12:06:43 +05:30
prafull-opensignlabs 3b1d8a48b7 v2.18.0
New Features:
1. Added support to recreate a new document from declined document without having to redo everything from scratch.
2. Added replace pdf file feature for Templates. PDF underlying a Template can now be replaced while keeping all the fields the same.
3. Introduced the ability to replace pdf file for in UpdateTemplate and CreateDocumentfromtemplate API.
4. Revamped the preferences menu UI.

Bug Fixes:
1. Fixed issue with capital letters in contact emails via API.
2. Better validations & error handling in APIs
2025-04-17 06:34:06 +00:00
prafull-opensignlabsandGitHub ec68770ff0 Merge pull request #1681 from OpenSignLabs/updates-14398222591
v2.17.0
2025-04-11 13:20:43 +05:30
prafull-opensignlabs f5d2027e3a refactor: resolve warnings 2025-04-11 07:45:23 +00:00
prafull-opensignlabsandGitHub 8af0010f55 Merge pull request #1672 from OpenSignLabs/updates-14279056892
v2.16.0
2025-04-05 11:36:59 +05:30
prafull-opensignlabs d7b7ad6285 Merge pull request 2025-04-05 06:01:24 +00:00
prafull-opensignlabsandGitHub fca45ced38 Merge pull request #1663 from OpenSignLabs/updates-14122971328
v2.15.0
2025-03-28 11:37:55 +05:30
prafull-opensignlabs ac8ab70246 v2.15.0
v2.15.0
2025-03-28 06:02:14 +00:00
prafull-opensignlabsandGitHub 8373679f27 Merge pull request #1661 from OpenSignLabs/do_deploy
feat: update proxy file
2025-03-24 13:47:33 +05:30
prafull-opensignlabs 0a23aabbe4 feat: update proxy file 2025-03-24 13:45:45 +05:30
prafull-opensignlabsandGitHub 4a85599703 Merge pull request #1660 from OpenSignLabs/updates-14030275569
feat: changes to implement one click deployment
2025-03-24 13:42:41 +05:30
prafull-opensignlabs 3e9b84de72 feat: changes to implement one click deployment
feat: changes to implement one click deployment
2025-03-24 08:11:52 +00:00
prafull-opensignlabsandGitHub 4313d8d0e4 Merge pull request #1654 from OpenSignLabs/updates-14016837992
refactor: remove unnecessary code
2025-03-23 13:11:28 +05:30
prafull-opensignlabs 3088fdd163 Merge pull request #403 from OpenSignLabs/staging
refactor: remove unnecessary code
2025-03-23 07:40:01 +00:00
ZathielandGitHub 8c86fe27ec Merge pull request #1616 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/i18next-browser-languagedetector-8.0.4
build(deps): bump i18next-browser-languagedetector from 8.0.2 to 8.0.4 in /apps/OpenSign
2025-03-21 21:50:38 +01:00
ZathielandGitHub 01e09998e5 Merge pull request #1650 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/npm_and_yarn-1c007eb283
build(deps): bump axios from 1.8.3 to 1.8.4 in /apps/OpenSignServer in the npm_and_yarn group across 1 directory
2025-03-21 21:49:55 +01:00
ZathielandGitHub 55fdb52bf3 Merge pull request #1651 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/npm_and_yarn-1c007eb283
build(deps): bump axios from 1.8.3 to 1.8.4 in /apps/OpenSign in the npm_and_yarn group across 1 directory
2025-03-21 21:49:17 +01:00
dependabot[bot]andGitHub 08f46a393b build(deps): bump axios
Bumps the npm_and_yarn group with 1 update in the /apps/OpenSign directory: [axios](https://github.com/axios/axios).


Updates `axios` from 1.8.3 to 1.8.4
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.3...v1.8.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 14:41:30 +00:00
dependabot[bot]andGitHub 9345da543a build(deps): bump axios
Bumps the npm_and_yarn group with 1 update in the /apps/OpenSignServer directory: [axios](https://github.com/axios/axios).


Updates `axios` from 1.8.3 to 1.8.4
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.8.3...v1.8.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 14:41:20 +00:00
dependabot[bot]andGitHub 208891e10d build(deps): bump i18next-browser-languagedetector in /apps/OpenSign
Bumps [i18next-browser-languagedetector](https://github.com/i18next/i18next-browser-languageDetector) from 8.0.2 to 8.0.4.
- [Changelog](https://github.com/i18next/i18next-browser-languageDetector/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next-browser-languageDetector/compare/v8.0.2...v8.0.4)

---
updated-dependencies:
- dependency-name: i18next-browser-languagedetector
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-21 14:41:17 +00:00
prafull-opensignlabsandGitHub db0fa61b2b Merge pull request #1648 from OpenSignLabs/updates-13994222227
v2.14.0
2025-03-21 20:09:57 +05:30
prafull-opensignlabs 221f098de6 v2.14.0
v2.14.0
2025-03-21 14:38:32 +00:00
423 changed files with 64850 additions and 52397 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"
]
}
]
}
+31 -12
View File
@@ -1,8 +1,15 @@
name: ci
# 👇 add this block
permissions:
contents: read # allow checkout & metadata-action to read repo
id-token: write # needed by docker/metadata-action v4
on:
push:
branches:
- 'main'
- 'staging'
jobs:
docker:
@@ -14,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 -2
View File
@@ -1,7 +1,7 @@
{$HOST_URL} {
reverse_proxy client:3000
handle_path /app/* {
handle_path /api/* {
reverse_proxy server:8080
rewrite * /app{uri}
rewrite * {uri}
}
}
+19 -2
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'>
@@ -73,12 +73,28 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
---
### Installation
### Deploy
Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL.
#### DigitalOcean
[![Deploy on DigitalOcean](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
#### Docker
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
**Command for linux/MacOS**
```
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
```
**Command for Windows (Powershell)**
```
$env:HOST_URL="https://opensign.yourdomain.com"; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml -OutFile docker-compose.yml; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile -OutFile Caddyfile; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev -OutFile .env.local_dev; Rename-Item -Path .env.local_dev -NewName .env.prod; docker compose up --force-recreate
```
**Command for Windows (CMD/Terminal)**
```
set HOST_URL=https://opensign.yourdomain.com && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && rename .env.local_dev .env.prod && docker compose up --force-recreate
```
Make sure that you have `Docker` and `git` installed before you run this command -
Please refer to the [Installation Guide](https://docs.opensignlabs.com/docs/self-host/docker/run-locally/) for detailed instructions on how to install OpenSign on your system.
@@ -149,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>
-25
View File
@@ -1,25 +0,0 @@
# Use an official Node runtime as the base image
FROM node:18
# Set the working directory inside the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json first to leverage Docker cache
COPY ./package*.json ./
# Install application dependencies
RUN npm install
# Copy the current directory contents into the container
COPY ./ .
COPY ./.husky .
# Make port 3000 available to the world outside this container
EXPOSE 3000
# Define environment variables if needed
# ENV NODE_ENV production
# Run the application
ENTRYPOINT npm run start
+9
View File
@@ -13,6 +13,10 @@ RUN npm install
# Copy the current directory contents into the container
COPY apps/OpenSign/ .
COPY apps/OpenSign/.husky .
COPY apps/OpenSign/entrypoint.sh .
# make the entrypoint.sh file executable
RUN chmod +x entrypoint.sh
# Define environment variables if needed
ENV NODE_ENV=production
@@ -20,8 +24,13 @@ ENV GENERATE_SOURCEMAP=false
# build
RUN npm run build
# Inject env.js loader into index.html
RUN sed -i '/<head>/a\<script src="/env.js"></script>' build/index.html
# Make port 3000 available to the world outside this container
EXPOSE 3000
ENTRYPOINT ["./entrypoint.sh"]
# Run the application
CMD ["npm", "start"]
+213
View File
@@ -0,0 +1,213 @@
# Tailwind Dark Mode Usage Guide
## Overview
This guide shows how to use the new Tailwind utilities for better dark mode visibility in OpenSign.
## Button Styling
### VS Code-style Disabled Buttons
```jsx
// Option 1: Direct VS Code styling
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
Disabled Button
</button>
// Option 2: Themed disabled styling
<button className="op-btn btn-themed-disabled">
Themed Button
</button>
// Option 3: Conditional styling
<button
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
disabled={isDisabled}
>
Dynamic Button
</button>
```
## Icon Styling
### Theme-aware Icons
```jsx
// Better visibility in dark mode
<i className="fa-light fa-folder icon-improved"></i>
// Muted but still visible
<i className="fa-light fa-plus icon-muted"></i>
// Disabled state
<i className="fa-light fa-trash icon-disabled"></i>
```
### CSS Variable Approach
```jsx
// Using CSS variables
<i className="fa-light fa-search icon-themed"></i>
<i className="fa-light fa-settings icon-themed-muted"></i>
// Inline styles with CSS variables
<i
className="fa-light fa-plus"
style={{ color: 'var(--icon-color)' }}
/>
```
### Legacy JavaScript Function (Still Supported)
```jsx
// Existing approach - still works
<i
className="fa-light fa-plus"
style={{ color: getThemeIconColor() }}
/>
```
## Text Styling
### Improved Gray Text
```jsx
// These automatically improve in dark mode
<span className="text-gray-500">More visible in dark mode</span>
<span className="text-gray-400">Muted but readable</span>
<span className="text-gray-600">Clear text</span>
```
## Complete Examples
### Toolbar with Better Visibility
```jsx
const Toolbar = () => (
<div className="flex space-x-2 p-2">
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-plus icon-improved"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded" disabled>
<i className="fa-light fa-trash icon-disabled"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-edit icon-improved"></i>
</button>
</div>
);
```
### Form with Disabled States
```jsx
const Form = ({ isSubmitting }) => (
<form>
<input className="op-input" />
<button
className={`op-btn op-btn-primary ${isSubmitting ? 'op-btn-vscode-disabled' : ''}`}
disabled={isSubmitting}
>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
```
## React-Tour and Tooltip Dark Mode Support
### React-Tour Modals
The react-tour modals now automatically support dark mode with VS Code-inspired styling:
```jsx
// These components automatically get dark mode styling
<Tour
onRequestClose={closeTour}
steps={tourConfig}
isOpen={isOpen}
rounded={5}
/>
```
### ReactTooltip Components
All ReactTooltip instances now support dark mode:
```jsx
// Automatically styled for dark mode
<ReactTooltip id="my-tooltip" className="z-[999]">
<div className="max-w-[200px]">
<p>Tooltip content</p>
</div>
</ReactTooltip>
```
### HoverCard Balloon UI
The balloon tooltips in OpenSign Drive now properly support dark mode:
```jsx
// These automatically get dark styling in dark mode
<HoverCard>
<HoverCardContent>
Document information
</HoverCardContent>
</HoverCard>
```
## Dark Mode Features Added
### 1. **React-Tour Modal Styling**
- Background: `#1F2937` (VS Code modal background)
- Text: `#E5E7EB` (soft white for readability)
- Borders: `#374151` (subtle borders)
- Buttons: VS Code-style primary/secondary buttons
### 2. **ReactTooltip Styling**
- Background: `#1F2937` with proper contrast
- Border: `#374151` for definition
- Box shadow: Enhanced for dark backgrounds
- Text: `#E5E7EB` for optimal readability
### 3. **HoverCard Balloon UI**
- Background: `#1F2937` (matches VS Code)
- Text: `#E5E7EB` for readability
- Arrow: Automatically matches background color
- Enhanced shadows for dark backgrounds
### 4. **React-Datepicker Support**
- Calendar background: `#1F2937`
- Selected dates: VS Code blue (`#007ACC`)
- Hover states: Proper contrast ratios
- Navigation arrows: Themed appropriately
## CSS Classes Reference
| Class | Purpose | Dark Mode Color |
|-------|---------|----------------|
| `icon-improved` | Better icon visibility | `#CCCCCC` |
| `icon-muted` | Muted but visible icons | `#999999` |
| `icon-disabled` | Disabled icon state | `#858585` |
| `op-btn-vscode-disabled` | VS Code disabled button | Background: `#3C3C3C` |
| `btn-themed-disabled` | Themed disabled button | Uses CSS variables |
| `icon-themed` | Variable-based icon color | `var(--icon-color)` |
| `.reactour__helper` | `#1F2937` background |
| `.react-tooltip` | `#1F2937` background |
| `.HoverCardContent` | `#1F2937` background |
| `.react-datepicker` | `#1F2937` background |
## Migration Guide
### From JavaScript Function to Tailwind
```jsx
// Before
<i style={{ color: getThemeIconColor() }} className="fa-light fa-plus" />
// After
<i className="fa-light fa-plus icon-improved" />
```
### From Hardcoded Colors to Theme-aware
```jsx
// Before
<i className="fa-light fa-plus text-gray-500" />
// After (automatic improvement)
<i className="fa-light fa-plus text-gray-500" />
// OR explicitly
<i className="fa-light fa-plus icon-improved" />
```
## Migration Notes
All existing tooltip and tour components will automatically inherit the new dark mode styling when the theme is set to `opensigndark`. No code changes required for existing implementations.
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
ENV_FILE=./build/env.js
DOTENV_FILE=./.env.prod # ✅ use .env.prod
echo "Generating runtime env file at $ENV_FILE..."
echo "window.RUNTIME_ENV = {" > $ENV_FILE
# List of keys to include
RUNTIME_KEYS="REACT_APP_SERVERURL"
for key in $RUNTIME_KEYS; do
# First check docker env (-e), fallback to .env file
value=$(printenv "$key")
if [ -z "$value" ] && [ -f "$DOTENV_FILE" ]; then
# fallback: read from .env
value=$(grep "^$key=" "$DOTENV_FILE" | cut -d '=' -f2- | tr -d '\r\n' | sed 's/"/\\"/g')
else
value=$(echo "$value" | sed 's/"/\\"/g')
fi
echo " $key: \"$value\"," >> $ENV_FILE
done
echo "};" >> $ENV_FILE
exec "$@"
@@ -0,0 +1,90 @@
/**
* Tailwind Dark Mode Usage Examples for OpenSign
*
* This file demonstrates how to use the new Tailwind utilities
* for better dark mode visibility of buttons and icons.
*/
// Example 1: VS Code-style disabled buttons
const DisabledButtonExamples = () => {
return (
<div>
{/* Option A: Using the VS Code disabled style */}
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
VS Code Style Disabled Button
</button>
{/* Option B: Using themed disabled style */}
<button className="op-btn btn-themed-disabled">
Themed Disabled Button
</button>
{/* Option C: Conditional styling */}
<button
className={`op-btn op-btn-primary ${isDisabled ? "op-btn-vscode-disabled" : ""}`}
disabled={isDisabled}
>
Conditional Button
</button>
</div>
);
};
// Example 2: Icon visibility improvements
const IconExamples = () => {
return (
<div>
{/* Theme-aware icons with better visibility */}
<i className="fa-light fa-folder icon-improved"></i>
<i className="fa-light fa-plus icon-muted"></i>
<i className="fa-light fa-trash icon-disabled"></i>
{/* Using CSS variables */}
<i className="fa-light fa-search icon-themed"></i>
<i className="fa-light fa-settings icon-themed-muted"></i>
{/* Gray text that automatically improves in dark mode */}
<span className="text-gray-500">
This text is now more visible in dark mode
</span>
<span className="text-gray-400">Muted but still readable</span>
</div>
);
};
// Example 3: Using CSS variables in inline styles
const InlineStyleExamples = () => {
return (
<div>
{/* Using CSS variables directly */}
<i className="fa-light fa-plus" style={{ color: "var(--icon-color)" }} />
{/* Using the existing JavaScript function */}
<i className="fa-light fa-minus" style={{ color: getThemeIconColor() }} />
</div>
);
};
// Example 4: Toolbar with improved icons
const ToolbarExample = () => {
return (
<div className="flex space-x-2 p-2">
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-plus icon-improved"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded" disabled>
<i className="fa-light fa-trash icon-disabled"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-edit icon-improved"></i>
</button>
</div>
);
};
export {
DisabledButtonExamples,
IconExamples,
InlineStyleExamples,
ToolbarExample
};
@@ -0,0 +1,94 @@
/**
* Tailwind Dark Mode Usage Examples for OpenSign
*
* This file demonstrates how to use the new Tailwind utilities
* for better dark mode visibility of buttons and icons.
*/
// Example 1: VS Code-style disabled buttons
const DisabledButtonExamples = () => {
return (
<div>
{/* Option A: Using the VS Code disabled style */}
<button className="op-btn op-btn-primary op-btn-vscode-disabled" disabled>
VS Code Style Disabled Button
</button>
{/* Option B: Using themed disabled style */}
<button className="op-btn btn-themed-disabled">
Themed Disabled Button
</button>
{/* Option C: Conditional styling */}
<button
className={`op-btn op-btn-primary ${isDisabled ? 'op-btn-vscode-disabled' : ''}`}
disabled={isDisabled}
>
Conditional Button
</button>
</div>
);
};
// Example 2: Icon visibility improvements
const IconExamples = () => {
return (
<div>
{/* Theme-aware icons with better visibility */}
<i className="fa-light fa-folder icon-improved"></i>
<i className="fa-light fa-plus icon-muted"></i>
<i className="fa-light fa-trash icon-disabled"></i>
{/* Using CSS variables */}
<i className="fa-light fa-search icon-themed"></i>
<i className="fa-light fa-settings icon-themed-muted"></i>
{/* Gray text that automatically improves in dark mode */}
<span className="text-gray-500">This text is now more visible in dark mode</span>
<span className="text-gray-400">Muted but still readable</span>
</div>
);
};
// Example 3: Using CSS variables in inline styles
const InlineStyleExamples = () => {
return (
<div>
{/* Using CSS variables directly */}
<i
className="fa-light fa-plus"
style={{ color: 'var(--icon-color)' }}
/>
{/* Using the existing JavaScript function */}
<i
className="fa-light fa-minus"
style={{ color: getThemeIconColor() }}
/>
</div>
);
};
// Example 4: Toolbar with improved icons
const ToolbarExample = () => {
return (
<div className="flex space-x-2 p-2">
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-plus icon-improved"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded" disabled>
<i className="fa-light fa-trash icon-disabled"></i>
</button>
<button className="p-2 hover:bg-gray-200 rounded">
<i className="fa-light fa-edit icon-improved"></i>
</button>
</div>
);
};
export {
DisabledButtonExamples,
IconExamples,
InlineStyleExamples,
ToolbarExample
};
@@ -3,19 +3,20 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="The fastest way to sign PDFs & request signatures from others" />
<!-- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> -->
<!-- <link rel="apple-touch-icon" href="/logo192.png" /> -->
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
<title>...Loading</title>
</head>
<body>
<div id="root" style="touch-action:pan-x pan-y;"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>
+8239 -18547
View File
File diff suppressed because it is too large Load Diff
+96 -71
View File
@@ -1,74 +1,91 @@
{
"name": "open_sign",
"version": "0.1.0",
"version": "2.37.0",
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.13.0",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@formkit/auto-animate": "^0.9.0",
"@imgly/background-removal": "^1.7.0",
"@lottiefiles/dotlottie-react": "^0.18.3",
"@mui/icons-material": "^5.15.10",
"@mui/material": "^5.15.10",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.1.6",
"@react-pdf/renderer": "^4.1.6",
"@reduxjs/toolkit": "^2.5.1",
"axios": "^1.7.9",
"css-minimizer-webpack-plugin": "^7.0.0",
"@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.13.5",
"date-fns-tz": "^3.2.0",
"dompurify": "^3.3.1",
"file-saver": "^2.0.5",
"i18next": "^23.16.8",
"i18next-browser-languagedetector": "^8.0.2",
"i18next-http-backend": "^3.0.1",
"highlight.js": "^11.9.0",
"i18next": "^25.8.13",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^3.0.2",
"jszip": "^3.10.1",
"juice": "^11.1.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"nth-check": "^2.1.1",
"parse": "^5.3.0",
"parse": "^8.1.0",
"pdf-lib": "^1.17.1",
"pkijs": "^3.3.3",
"prettier": "^3.8.1",
"print-js": "^1.6.0",
"radix-ui": "^1.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.9",
"react-confetti": "^6.2.2",
"react-cookie": "^7.2.2",
"react-datepicker": "^7.6.0",
"prismjs": "^1.30.0",
"radix-ui": "^1.4.3",
"react": "^19.2.4",
"react-bootstrap": "^2.10.10",
"react-colorful": "^5.6.1",
"react-confetti": "^6.4.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.2.0",
"react-dom": "^19.2.4",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.4.0",
"react-konva": "^18.2.10",
"react-pdf": "^9.2.1",
"react-quill-new": "^3.3.3",
"react-i18next": "^16.5.4",
"react-konva": "^19.2.2",
"react-pdf": "^10.4.1",
"react-redux": "^9.2.0",
"react-rnd": "^10.4.14",
"react-router": "^7.1.5",
"react-scripts": "^5.0.1",
"react-rnd": "^10.5.2",
"react-router": "^7.13.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.10.0",
"react-signature-canvas": "^1.0.7",
"react-syntax-highlighter": "^15.6.1",
"react-select": "^5.10.2",
"react-signature-canvas": "^1.1.0-alpha.2",
"react-timezone-select": "^3.2.8",
"react-tooltip": "^5.28.0",
"react-web-share": "^2.0.2",
"reactour": "^1.19.4",
"react-to-print": "^3.3.0",
"react-tooltip": "^5.30.0",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"regex-parser": "^2.3.0",
"serve": "^14.2.4",
"styled-components": "^5.3.0",
"web-vitals": "^4.2.4",
"ws": "^8.18.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
"regex-parser": "^2.3.1",
"serve": "^14.2.5",
"web-vitals": "^5.1.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 && react-scripts build",
"start-dev": "react-scripts start",
"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",
"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 && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"build-win": "npm run version-win && vite build",
"test": "vitest run",
"test:watch": "vitest",
"release": "standard-version",
"commit": "cz"
},
@@ -96,33 +113,41 @@
}
},
"devDependencies": {
"@babel/core": "^7.26.9",
"@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3",
"@babel/runtime-corejs2": "^7.26.9",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.2.1",
"@babel/core": "^7.29.0",
"@babel/preset-env": "^7.29.0",
"@babel/preset-react": "^7.28.5",
"@babel/runtime-corejs2": "^7.28.6",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^19.2.14",
"@vitejs/plugin-react": "^5.1.4",
"@vitest/ui": "^4.0.18",
"autoprefixer": "^10.4.27",
"babel-loader": "^10.0.0",
"commitizen": "^4.3.1",
"concurrently": "^9.1.2",
"css-loader": "^7.1.2",
"daisyui": "^4.12.23",
"dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0",
"eslint": "^9.20.0",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.4",
"lint-staged": "^15.4.3",
"mini-css-extract-plugin": "^2.9.2",
"postcss": "^8.5.1",
"prettier": "^3.5.0",
"pretty-quick": "^4.0.0",
"tailwindcss": "^3.4.17",
"terser-webpack-plugin": "^5.3.11",
"webpack-cli": "^5.1.4"
"concurrently": "^9.2.1",
"css-loader": "^7.1.4",
"daisyui": "^4.12.24",
"eslint": "^9.39.3",
"eslint-plugin-prettier": "^5.5.5",
"eslint-plugin-react": "^7.37.5",
"jsdom": "^28.1.0",
"lint-staged": "^16.2.7",
"postcss": "^8.5.6",
"pretty-quick": "^4.2.2",
"rollup-plugin-node-polyfills": "^0.2.1",
"tailwindcss": "^3.4.19",
"tmp": "^0.2.5",
"vite": "^7.3.1",
"vite-plugin-svgr": "^4.5.0",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.0.18"
},
"overrides": {
"nth-check": "$nth-check",
"ws": "$ws"
"tmp": "$tmp",
"react": "$react",
"react-dom": "$react-dom"
},
"engines": {
"node": "18 || 20 || 22"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

+601 -61
View File
@@ -1,12 +1,14 @@
{
"header-news": "Neue Funktion: Benutzer des Teams-Plans können jetzt ihre eigenen AWS S3-Buckets für die Dateispeicherung integrieren",
"header-news-btn": "Jetzt einrichten",
"sandbox-news": "Dies ist eine Sandbox-Umgebung. Bitte nicht für produktive Zwecke verwenden.",
"create-account": "Konto erstellen",
"login": "Anmelden",
"language": "Sprache",
"dark-mode": "Dunkelmodus",
"name": "Name",
"phone": "Telefon",
"phone-optional": "optional",
"phone-optional": "Optional",
"email": "E-Mail",
"company": "Unternehmen",
"job-title": "Berufsbezeichnung",
@@ -24,6 +26,8 @@
"Name": "Name",
"Date": "Datum"
},
"folder": "Ordner",
"pdf": "Pdf",
"context-menu": {
"Download": "Herunterladen",
"Rename": "Umbenennen",
@@ -39,8 +43,14 @@
"save": "Speichern",
"cancel": "Abbrechen",
"upgrade-now": "Jetzt upgraden",
"contact-now": "Jetzt kontaktieren",
"upgrade-to": "Upgrade zu",
"plan": "Plan",
"connect": "Verbinden",
"connect-to-g-drive": "Mit Google Drive verbinden",
"reconnect-to-g-drive": "Erneut mit Google Drive verbinden",
"gdrive-info-connect": "Wenn Google Drive verbunden ist, wird das abgeschlossene Dokument im Ordner {{appName}} auf Google Drive gespeichert.",
"subscription-renew-warning": "Ihr Abonnement läuft in {{remainingDays}} Tagen ab. Bitte verlängern Sie Ihr Abonnement.",
"subscribe-card-teamplan": "Entfesseln Sie die volle Kraft der Zusammenarbeit! Erstellen Sie unbegrenzt Organisationen, Teams und Hierarchien. Teilen Sie Vorlagen nahtlos zwischen Teams und weisen Sie benutzerdefinierte Benutzerrollen zu. Optimieren Sie Ihren Workflow noch heute!",
"subscribe-card-plan": "Entsperren Sie Premium-Funktionen ab nur {{premiumPrice}}/Monat. Genießen Sie eine verbesserte Leistung und zahlen Sie nur {{addonPrice}} pro zusätzlichem Credit nach den enthaltenen Premium-Credits.",
"user-name-limit-char": "Um einen Benutzernamen mit weniger als 8 Zeichen zu haben, abonnieren Sie bitte.",
@@ -54,17 +64,18 @@
"welcome": "Willkommen zurück!",
"Login-to-your-account": "Melden Sie sich bei Ihrem Konto an",
"password": "Passwort",
"forgot-password": "Passwort vergessen?",
"forgot-password": "Passwort vergessen",
"loading": "Wird geladen...",
"of": "von",
"sign-SSO": "Mit SSO anmelden",
"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",
@@ -110,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",
@@ -142,6 +154,7 @@
"Quick send": "Schnell senden",
"Edit": "Bearbeiten",
"Share with team": "Mit Team teilen",
"Share with user": "Mit Kollegen teilen",
"Share": "Teilen",
"View": "Ansehen",
"option": "Option",
@@ -151,7 +164,14 @@
"extend-expiry-date": "Ablaufdatum verlängern",
"Duplicate Template": "Vorlage duplizieren",
"Duplicate": "Duplikat",
"daily-mail-quota": "Tägliches E-Mail-Kontingent"
"daily-mail-quota": "Tägliches E-Mail-Kontingent",
"Save as template": "Als Vorlage speichern",
"Fix & resend": "Korrigieren und erneut senden",
"Kiosk Mode": "Kiosk Modus",
"Reset password": "Passwort zurücksetzen",
"View formdata": "Formulardaten ansehen",
"Download all formdata": "Alle Formulardaten herunterladen",
"API credits": "API-Credits"
},
"report-heading": {
"Sr.No": "Nr.",
@@ -173,7 +193,21 @@
"created-date": "Erstellungsdatum",
"Type": "Type",
"Logs": "Protokolle",
"Expiry-date": "Ablaufdatum"
"Expiry-date": "Ablaufdatum",
"Company": "Unternehmen",
"JobTitle": "Berufsbezeichnung",
"Time to complete (Days)": "Zeit zur Fertigstellung (Tage)",
"Auto reminder": "Automatische Erinnerung",
"Remind once in every (Days)": "Erinnere einmal alle (Tage)",
"Enable OTP verification": "OTP-Verifizierung aktivieren",
"Enable Tour": "Tour aktivieren",
"Notify on signatures": "Bei Signaturen benachrichtigen",
"Allow modifications": "Änderungen zulassen",
"Redirect url": "Weiterleitungs-URL",
"Created Date": "Erstellungsdatum",
"Updated Date": "Aktualisierungsdatum",
"Expiry Date": "Ablaufdatum",
"Sent Date": "Sendedatum"
},
"report-help": {
"Draft Documents": "Dies sind Dokumente, die Sie begonnen, aber noch nicht zum Versenden fertiggestellt haben.",
@@ -185,16 +219,15 @@
"Contactbook": "Dies ist eine Liste von Kontakten/Unterzeichnern, die Sie hinzugefügt haben. Diese erscheinen als Vorschläge, wenn Sie neue Unterzeichner hinzufügen möchten.",
"Templates": "Dies ist eine Liste von Vorlagen, die Ihnen zur Verfügung stehen, um Dokumente zu erstellen. Sie können die Schaltfläche 'Verwenden' anklicken, um ein neues Dokument mit einer Vorlage zu erstellen, das Dokument zu ändern und Unterzeichner im nächsten Schritt hinzuzufügen."
},
"form-name": {
"Sign Yourself": "Selbst unterschreiben",
"Request Signatures": "Signaturen anfordern",
"New Template": "Neue Vorlage"
},
"Sign Yourself": "Selbst unterschreiben",
"Request Signatures": "Signaturen anfordern",
"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",
"description": "Beschreibung",
"time-to-complete": "Bearbeitungszeit (Tage)",
"send-in-order": "In der Reihenfolge senden",
@@ -208,7 +241,6 @@
"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",
@@ -237,17 +269,18 @@
"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",
"regenerate-token": "Live-Token neu generieren",
"remove-background": "Hintergrund entfernen",
"generate-token": "Live-Token generieren",
"view-docs": "Dokumentation ansehen",
"generate-token-alert": "Sind Sie sicher, dass Sie das Token neu generieren möchten? Das alte Token wird ablaufen.",
"yes": "Ja",
"copied": "Kopiert",
"something-went-wrong-mssg": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später erneut.",
"something-went-wrong-mssg": "Etwas ist schiefgelaufen, Das Aktualisieren dieser Seite kann das Problem lösen.",
"token-generated": "Token erfolgreich generiert.",
"webhook": "Webhook",
"update-webhook": "Webhook aktualisieren",
@@ -262,7 +295,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",
@@ -271,9 +304,9 @@
"deactivate": "deaktivieren",
"this-user": "diesen Benutzer",
"delete-user": "Benutzer löschen",
"delete": "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.",
@@ -283,6 +316,7 @@
"make-template-public": "Vorlage öffentlich machen",
"make-template-private": "Vorlage privat machen",
"make-template-public-alert": "Sind Sie sicher, dass Sie diese Vorlage öffentlich machen möchten?",
"make-template-private-alert-non": "Sind Sie sicher, dass Sie diese Vorlage privat machen möchten?",
"make-template-private-alert": "Sind Sie sicher, dass Sie diese Vorlage privat machen möchten? Dadurch wird sie aus Ihrem öffentlichen Profil entfernt.",
"public-role": "Öffentliche Rolle",
"public-url": "Öffentliches Profil",
@@ -301,14 +335,14 @@
"send": "Senden",
"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 mindestens eine Rolle zu dieser Vorlage hinzu, um Kopien davon schnell an mehrere Unterzeichner zu senden.",
"quick-send-alert-4": "Das Limit für Schnellsendungen wurde erreicht.",
"quick-send-alert-3": "Bitte fügen Sie diesem template mindestens eine Rolle hinzu.",
"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",
"revoke-document-alert": "Sind Sie sicher, dass Sie dieses Dokument widerrufen möchten?",
"resend-mail": "E-Mail erneut senden",
"resend-mail-help": "Sie können folgende Variablen verwenden, die durch ihre tatsächlichen Werte ersetzt werden: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"resend-mail-help": "Sie können folgende Variablen verwenden, die durch ihre tatsächlichen Werte ersetzt werden: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
"subject": "Betreff",
"body": "Inhalt",
"add-contact": "Kontakt hinzufügen",
@@ -338,7 +372,7 @@
"verify-email-1": "E-Mail verifizieren",
"resend": "Erneut senden",
"contact-details": "Kontaktdetails",
"verify-email": "Bitte verifizieren Sie Ihre E-Mail!",
"verify-email": "Bitte bestätigen Sie Ihre E-Mail, um fortzufahren. Dies ist ein einmaliger Schritt, um die mit Ihrem OpenSign-Konto verknüpfte E-Mail zu bestätigen.",
"send-otp": "OTP senden",
"otp-placeholder": "Verifizierungscode aus der E-Mail eingeben",
"loading-doc": "Dokument wird geladen...",
@@ -352,11 +386,14 @@
"date": "Datum",
"text": "Text",
"text input": "Texteingabe",
"cells": "Zellen",
"checkbox": "Checkbox",
"dropdown": "Dropdown",
"radio button": "Radiobutton",
"image": "Bild",
"email": "E-Mail"
"email": "E-Mail",
"number": "Zahl",
"draw": "Zeichnen"
},
"fields": "Felder",
"recipients": "Empfänger",
@@ -368,6 +405,7 @@
"certificate": "Zertifikat",
"decline": "Ablehnen",
"finish": "Fertigstellen",
"done": "Fertig",
"mail": "E-Mail",
"sign-now": "Jetzt unterzeichnen",
"successfully-signed": "Erfolgreich unterzeichnet!",
@@ -377,6 +415,10 @@
"Email-verified-alert-1": "E-Mail wurde verifiziert.",
"Email-verified-alert-2": "E-Mail wurde bereits verifiziert.",
"upload-stamp-image": "Stempelbild hochladen",
"draw-signature": "Unterschrift zeichnen",
"draw-initials": "Initialen zeichnen",
"enter-text": "Text eingeben",
"enter-widgettype": "{{widgetType}} eingeben",
"draw": "Zeichnen",
"type": "Schreiben",
"color-type": {
@@ -389,27 +431,44 @@
"upload": "Hochladen",
"initial-teb": "Initialen",
"signature-tab": "Unterschrift",
"initial-alert": "Meine Initiale nicht gefunden",
"initial-alert": "Meine Initiale wurde nicht gefunden",
"copy-title": "Widget kopieren zu",
"contact-delete-alert": "Sind Sie sicher, dass Sie diesen Kontakt löschen möchten?",
"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",
"formula": "Formel",
"formula-placeholder": "Erstellen Sie eine Formel mit Zahlen-Widgets",
"formula-no-widgets": "Für diesen Unterzeichner sind noch keine weiteren Zahlen-Widgets vorhanden.",
"supported-operators": "Unterstützte Operatoren:",
"formula-helper": "Klicken Sie auf einen Zahlen-Widget-Chip, um ihn einzufügen (z. B. {{number-1}} + {{number-2}}).",
"formula-error-letter": "Widget-Namen müssen in doppelte geschweifte Klammern gesetzt werden (z. B. {{number-1}}).",
"formula-error-characters": "Außerhalb der Klammern sind nur Zahlen sowie +, -, *, /, (, ) erlaubt.",
"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.",
"decimal-places": "Dezimalstellen",
"number-of-decimal-places": "Anzahl der Dezimalstellen",
"decimal-places-helper": "Auf 0 setzen für ganze Zahlen",
"select": "Auswählen",
"read-only": "Nur lesen",
"read-only": "Ist schreibgeschützt",
"hide-labels": "Labels ausblenden",
"layout": "Layout",
"checkbox": "Checkbox",
"alert": "Warnung",
"zoom-in": "Vergrößern",
@@ -452,20 +511,21 @@
"add-recipients": "Empfänger hinzufügen",
"loading-mssg": "Das könnte etwas Zeit in Anspruch nehmen",
"send-mail": "E-Mail senden",
"signature-field-widget": "Für jeden Benutzer muss mindestens ein Signaturfeld hinzugefügt werden. Sie haben keine Signaturfelder für {{signersName}} hinzugefügt",
"signature-field-widget": "Jeder Benutzer muss mindestens ein Signatur-Widget haben. Sie haben kein Signatur-Widget für {{signersName}} hinzugefügt.",
"placeholder-alert-1": "Bitte stellen Sie sicher, dass für alle Empfänger mindestens ein Signatur-Widget hinzugefügt wurde.",
"placeholder-alert-2": "Bitte bestätigen Sie, dass Sie das Textfeld ausgefüllt haben.",
"placeholder-alert-3": "Sind Sie sicher, dass Sie dieses Dokument zur Unterzeichnung senden möchten?",
"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-alert-5": "Möchten Sie die Dokumente jetzt unterschreiben?",
"placeholder-mail-alert-you": "Nachfolgende Unterzeichner erhalten E-Mails, sobald Sie das Dokument 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",
@@ -473,6 +533,7 @@
"document-alert": "Dokument-Warnung",
"owner-subscription-expired": "Das Abonnement des Besitzers ist abgelaufen.",
"subscription-expired": "Abonnement abgelaufen",
"owner-doesnt-have-paid-plan": "Der Inhaber hat keinen kostenpflichtigen Plan.",
"alert-message": "Warnmeldung",
"document-decline": "Dokument ablehnen",
"decline-alert-1": "Sind Sie sicher, dass Sie dieses Dokument ablehnen möchten?",
@@ -521,7 +582,7 @@
"correct-password": "Bitte korrektes Passwort angeben",
"decrypting-pdf": "PDF wird entschlüsselt, bitte warten...",
"invalid-otp": "Ungültiger OTP",
"user-not-found": "Benutzer nicht gefunden!",
"user-not-found": "Benutzer nicht gefunden",
"enter-otp-alert": "Bitte OTP eingeben!",
"get-verification-code": "Verifizierungscode erhalten",
"get-verification-code-2": "Sie erhalten einen Verifizierungscode per E-Mail",
@@ -529,7 +590,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!",
@@ -566,7 +627,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",
@@ -585,7 +646,8 @@
"tour-mssg": {
"home-layout-1": "Sie haben sich erfolgreich angemeldet! Werfen wir einen Blick darauf.",
"home-layout-2": "Um Dokumente zum Selbstunterzeichnen hochzuladen oder um Unterschriften anderer anzufordern, wählen Sie einfach die entsprechenden Schaltflächen.",
"home-layout-3": "Sie sind bereit, {{appName}} zu verwenden! Wenn Sie Unterstützung benötigen, kontaktieren Sie uns gerne.",
"home-layout-3": "Sie sind bereit, {{appName}} zu verwenden!",
"home-layout-4": "Markiere uns auf",
"generate-token": "Jetzt upgraden, um ein Produktions-API-Token zu generieren.",
"opensign-drive-1": "Klicken Sie auf die Breadcrumb-Links, um einfach durch die Ordnerhierarchie zu navigieren und die Dokumente in jedem Ordner anzuzeigen.",
"opensign-drive-2": "Klicken Sie auf die Schaltfläche Hinzufügen, um einen neuen Ordner oder ein neues Dokument zu erstellen.",
@@ -595,28 +657,26 @@
"opensign-drive-6": "Klicken Sie mit der rechten Maustaste auf ein Dokument, um Optionen wie Herunterladen, Umbenennen, Verschieben und Löschen anzuzeigen. Klicken Sie auf das Dokument, um es zu öffnen.",
"opensign-drive-7": "Klicken Sie mit der rechten Maustaste auf einen Ordner, um Optionen anzuzeigen. Wählen Sie Umbenennen, um den Namen des Ordners zu ändern, oder klicken Sie auf den Ordner, um seinen Inhalt anzuzeigen.",
"pdf-request-file-1": "Liste der Unterzeichner, die das Dokument noch unterschreiben müssen.",
"pdf-request-file-2": "Klicken Sie auf die Platzhalter im Dokument, um zu unterschreiben. Sie sehen dann Optionen, Ihre Unterschrift zu zeichnen, einzugeben oder ein Bild hochzuladen.",
"pdf-request-file-3": "Klicken Sie auf die Schaltflächen Ablehnen oder Fertig stellen, um durch Ihr Dokument zu navigieren. Verwenden Sie das Auslassungsmenü für weitere Optionen, einschließlich der Schaltfläche Herunterladen.",
"pdf-request-file-2": "Klicken Sie auf eines der Felder im Dokument, um zu beginnen. Danach können Sie die erforderlichen Informationen eingeben.",
"pdf-request-file-3": "Sobald Sie alle erforderlichen Felder ausgefüllt haben, klicken Sie auf „Fertigstellen“. Danach können Sie das unterschriebene Dokument herunterladen. Je nach den Einstellungen des Absenders erhalten Sie möglicherweise eine Kopie des abgeschlossenen Dokuments zusammen mit dem Abschlusszertifikat, sobald alle Empfänger unterschrieben haben.",
"pdf-request-file-4": "Liste der Unterzeichner, die das Dokument bereits unterzeichnet haben.",
"pdf-request-file-5": "Sie können auf Automatisch alles unterschreiben klicken, um automatisch an allen für Sie vorgesehenen Stellen zu unterschreiben. Überprüfen Sie das Dokument gründlich, bevor Sie diese Schaltfläche verwenden.",
"pdf-request-file-6": "Bitte füllen Sie die Felder auf Seite {{pagenumbers}} aus, die alle zur einfachen Identifizierung in der gleichen Farbe hervorgehoben sind.",
"placeholder-sign-1": "Wählen Sie einen Empfänger aus dieser Liste aus, um einen Platzhalter hinzuzufügen, an dem er unterschreiben soll. Der Platzhalter wird in der gleichen Farbe wie der Empfängername angezeigt, sobald Sie ihn im Dokument ablegen.",
"placeholder-sign-1": "Wählen Sie einen Empfänger aus dieser Liste, um ein Widget hinzuzufügen. Das Widget wird in derselben Farbe wie der Empfängername angezeigt, sobald Sie es auf das Dokument ziehen.",
"placeholder-sign-2": "Das Klicken auf die Schaltfläche 'Empfänger hinzufügen' ermöglicht es Ihnen, weitere Unterzeichner hinzuzufügen.",
"placeholder-sign-3": "Klicken Sie auf diese Schaltfläche, um weitere Empfänger/Unterzeichner zum Dokument hinzuzufügen.",
"placeholder-sign-4": "Ziehen Sie ein Feld in das Dokument oder klicken Sie darauf, um es hinzuzufügen.",
"placeholder-sign-5": "Der PDF-Inhaltsbereich zeigt bereits die vorhandenen Platzhalter der Vorlage an. Diese Platzhalter entsprechen der Farbe des Empfängernamens, um sie leicht erkennbar zu machen.",
"placeholder-sign-4": "Ziehen oder klicken Sie auf ein Widget, um es dem Dokument hinzuzufügen.",
"placeholder-sign-5": "Der PDF-Inhaltsbereich zeigt bereits die vorhandenen Widgets der Vorlage an. Diese Widgets entsprechen der Farbe des Empfängernamens, um sie leicht erkennbar zu machen.",
"placeholder-sign-6": "Mit einem Klick auf 'Weiter' wird das Dokument gespeichert. Im nächsten Schritt können Sie die E-Mails, die an die Empfänger versendet werden sollen, anpassen oder die Signaturlinks kopieren und diese selbst mit den Empfängern teilen.",
"report-1":"Klicken Sie auf die Schaltfläche „Hinzufügen“, um eine neue Vorlage zu erstellen. Vorlagen sind wiederverwendbare Dokumente, mit denen schnell neue Dokumente mit derselben Struktur und unterschiedlichen Unterzeichnern erstellt werden können. Eine HR-Vorlage für die Einarbeitung könnte beispielsweise vordefinierte Rollen wie „Personalleiter“ und „Neuer Mitarbeiter“ enthalten. Bei jeder Verwendung der Vorlage können Sie die Rolle „Neuer Mitarbeiter“ verschiedenen neuen Mitarbeitern zuweisen, während die Rolle „Personalleiter“ unverändert bleibt. So wird ein nahtloser Einarbeitungsprozess für jeden neuen Mitarbeiter ermöglicht.",
"report-1": "Klicken Sie auf die Schaltfläche „Hinzufügen“, um eine neue Vorlage zu erstellen. Vorlagen sind wiederverwendbare Dokumente, mit denen schnell neue Dokumente mit derselben Struktur und unterschiedlichen Unterzeichnern erstellt werden können. Eine HR-Vorlage für die Einarbeitung könnte beispielsweise vordefinierte Rollen wie „Personalleiter“ und „Neuer Mitarbeiter“ enthalten. Bei jeder Verwendung der Vorlage können Sie die Rolle „Neuer Mitarbeiter“ verschiedenen neuen Mitarbeitern zuweisen, während die Rolle „Personalleiter“ unverändert bleibt. So wird ein nahtloser Einarbeitungsprozess für jeden neuen Mitarbeiter ermöglicht.",
"redirect": "Klicken Sie auf die Schaltfläche 'Verwenden', um ein neues Dokument aus einer bestehenden Vorlage zu erstellen.",
"bulksend": "Um schnell mehrere Dokumente mithilfe einer vorhandenen Vorlage zu versenden, indem Sie einfach die E-Mail-Adressen der Empfänger erstellen, klicken Sie auf die Schaltfläche Massenversand",
"option": "Dieses Menü zeigt weitere Optionen wie Bearbeiten und Löschen. Verwenden Sie die Schaltfläche 'Bearbeiten', um Unterzeichnerrollen hinzuzufügen, Felder zu ändern und Ihre Vorlage zu aktualisieren. Änderungen gelten für alle zukünftigen Dokumente, die aus dieser Vorlage erstellt werden, wirken sich jedoch nicht auf vorhandene Dokumente aus. Verwenden Sie die Schaltfläche 'Löschen', um die Vorlage zu entfernen.",
"signyour-self-1": "Wählen und ziehen Sie Ihre bevorzugten Widgets auf das PDF, um Ihr Dokument vor dem Unterzeichnen anzupassen. Wählen Sie die perfekten Stellen für jede Anpassung aus, um das Dokument an Ihre Bedürfnisse anzupassen.",
"bulksend": "Um schnell mehrere Dokumente mit einer vorhandenen Vorlage zu versenden, geben Sie einfach die E-Mail-Adressen der Empfänger ein und klicken Sie auf die Schaltfläche 'Massenversand'. Sie können bis zu 50 Empfänger erreichen.",
"option": "Dieses Menü zeigt weitere Optionen wie Bearbeiten, Löschen, Umbenennen, Duplizieren, Teilen usw. <1>Klicken Sie hier</1>, um mehr über alle verfügbaren Optionen zu erfahren. <3>Hinweis: Änderungen an einer vorhandenen Vorlage gelten für alle zukünftigen Dokumente, die aus dieser Vorlage erstellt werden, betreffen jedoch nicht bereits versandte Dokumente.</3>",
"signyour-self-1": "Wählen oder ziehen Sie Ihre bevorzugten Widgets auf das PDF, um Ihr Dokument vor der Unterzeichnung anzupassen. Platzieren Sie die Widgets an den perfekten Stellen, um das Dokument an Ihre Bedürfnisse anzupassen.",
"signyour-self-2": "Ziehen Sie ein Widget irgendwo in diesen Bereich. Sie können es später in der Größe ändern und verschieben.",
"template-placeholder-1": "Das Klicken auf die Schaltfläche 'Rolle hinzufügen' ermöglicht es Ihnen, verschiedene Unterzeichnerrollen hinzuzufügen. Sie können Benutzern diese Rollen in den folgenden Schritten zuweisen.",
"template-placeholder-2": "Sobald Rollen hinzugefügt wurden, wählen Sie eine Rolle aus der Liste aus, um einen Platzhalter hinzuzufügen, an dem der Unterzeichner unterschreiben soll. Der Platzhalter wird in der gleichen Farbe wie der Rollenname angezeigt, sobald Sie ihn im Dokument ablegen.",
"template-placeholder-3": "Ziehen Sie ein Feld in das Dokument oder klicken Sie darauf, um es hinzuzufügen.",
"template-placeholder-4": "Ziehen Sie den Platzhalter für eine Rolle an eine beliebige Stelle im Dokument. Denken Sie daran, dass er in der gleichen Farbe wie der Name des Empfängers angezeigt wird, um die Zuordnung zu erleichtern.",
"template-placeholder-5": "Das Klicken auf 'Weiter' speichert die aktuelle Vorlage. Nach dem Speichern werden Sie gefragt, ob Sie ein neues Dokument aus dieser Vorlage erstellen möchten.",
"template-placeholder-2": "Nachdem Sie Rollen hinzugefügt haben, wählen Sie eine aus der Liste, um ein Widget für diesen Empfänger zu platzieren. Sie können entweder auf das Widget klicken oder es auf das Dokument ziehen. Sobald es platziert ist, wird das Widget in derselben Farbe wie die ausgewählte Rolle angezeigt.",
"template-placeholder-3": "Das Klicken auf 'Weiter' speichert die aktuelle Vorlage. Nach dem Speichern werden Sie gefragt, ob Sie ein neues Dokument aus dieser Vorlage erstellen möchten.",
"webhook-1": "Jetzt upgraden, um einen Webhook einzurichten",
"Need your Signature": "Das Klicken auf diese Karte führt Sie zu einer Liste der Dokumente, die auf Ihre Überprüfung warten.",
"Out for signatures": "Das Klicken auf diese Karte führt Sie zu einer Liste der Dokumente, die auf eine Signatur warten.",
@@ -626,13 +686,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",
@@ -646,7 +706,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",
@@ -654,7 +714,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.",
@@ -665,7 +725,7 @@
"public-template-mssg-1": "Um OpenSign in Ihr React- oder Next.js-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
"public-template-mssg-2": "Stellen Sie sicher, dass npm oder yarn in Ihrem Projekt eingerichtet ist. Wenn Sie Yarn verwenden, können Sie npm install durch yarn add @opensign/react ersetzen.",
"public-template-mssg-3": "Benötigen Sie weitere Details oder Beispiele?",
"public-template-mssg-4": "Besuchen Sie die",
"public-template-mssg-4": "Besuchen Sie die ",
"public-template-mssg-5": " npm für die neuesten Updates, detaillierte Dokumentationen und Versionshistorie.",
"public-template-mssg-6": "Bevor Sie diesen Code-Schnipsel verwenden können, müssen Sie diese Vorlage öffentlich machen.",
"public-template-mssg-7": "Bevor Sie einen öffentlichen Link generieren können, müssen Sie diese Vorlage öffentlich machen.",
@@ -674,8 +734,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.",
@@ -713,7 +774,7 @@
"public-tour-message": "Die Vorlage muss öffentlich sein, bevor Sie einen teilbaren Link generieren können.",
"add-user-template": "Sie müssen eine Rolle hinzufügen, bevor Sie Felder dafür hinzufügen können.",
"pdf-uncompatible": "Diese PDF-Datei ist nicht kompatibel, bitte kontaktieren Sie {{appName}}",
"text-field-tour": "Felder vom Typ 'Text' müssen im Voraus ausgefüllt werden, bevor das Dokument versendet wird. Wenn Sie möchten, dass die Unterzeichner Eingaben machen, verwenden Sie das Feld 'Texteingabe'.",
"text-field-tour": "Felder zum Vorabfüllen müssen vor dem Senden des Dokuments ausgefüllt werden. Wenn Sie Eingaben von den Unterzeichnern benötigen, verwenden Sie stattdessen die Felder für Unterzeichner.",
"attach-signer-tour": "Sie müssen einen Unterzeichner jeder Rolle zuweisen. Dies können Sie durch Klicken auf dieses Symbol tun. Sobald Sie einen Unterzeichner auswählen, wird er allen Feldern der zugehörigen Rolle zugewiesen, die in derselben Farbe erscheinen.",
"allowed-signature-types": "Erlaubte Signaturtypen",
"at-least-one-signature-type": "Mindestens ein Signaturtyp sollte aktiviert sein.",
@@ -739,12 +800,15 @@
"delete-page": "Seite löschen",
"merge-pdf": "PDFs zusammenführen",
"add-pages": "Seiten hinzufügen",
"reorder-pages": "Seiten neu anordnen",
"delete-alert": "Einzelne Seite kann nicht gelöscht werden.",
"delete-alert-2": "Sind Sie sicher, dass Sie diese Seite löschen möchten?",
"delete-note": "Hinweis: Sobald Sie diese Seite löschen, kann dies nicht rückgängig gemacht werden.",
"Rotation-alert": "Seite drehen",
"bulk-import": "Massenimport",
"contacts-file": "Kontaktdatei (xlsx, csv)",
"import-guideline": "Laden Sie eine CSV- oder Excel-Datei mit den Spalten Name, Email und optional Phone hoch. Es werden nur die ersten 100 Kontakte importiert.",
"download-sample": "Beispieldatei herunterladen",
"100-records-only": "Derzeit können Sie nur bis zu 100 Datensätze importieren.",
"csv-excel-support-only": "Laden Sie eine Datei in einem der folgenden Formate hoch: CSV, XLSX oder XLS.",
"contact-imported": "{{imported}} Kontakte wurden importiert. {{failed}} Kontakte konnten nicht importiert werden.",
@@ -758,7 +822,7 @@
"agree-p1": "Ich bestätige, dass ich die ",
"agree-p2": "Offenlegung elektronischer Aufzeichnungen und Signaturen",
"agree-p3": "gelesen und verstanden habe und der Verwendung elektronischer Aufzeichnungen und Signaturen zustimme.",
"agrre-button": "Zustimmen & Fortfahren",
"agrre-button": "Ich bestätige und stimme zu, fortzufahren",
"term-cond-title": "Allgemeine Geschäftsbedingungen",
"term-cond-h": "OFFENLEGUNG ELEKTRONISCHER AUFZEICHNUNGEN UND SIGNATUREN",
"term-cond-p1": "Diese Offenlegung elektronischer Aufzeichnungen und Signaturen ('Offenlegung') ist eine Vereinbarung zwischen dem Dokumentersteller ('Sender') und dem Unterzeichner ('Ihnen'), bereitgestellt über die {{appName}} Plattform ('Plattform'). Durch das Signieren von Dokumenten über {{appName}} stimmen Sie den in dieser Offenlegung beschriebenen Bedingungen zu. Bitte lesen Sie sie sorgfältig durch, bevor Sie fortfahren.",
@@ -804,7 +868,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.",
@@ -831,7 +894,7 @@
"initial-type": "Ihre Initialen",
"redirect-url": "Weiterleitungs-URL",
"bulk-send": "Massenversand",
"select-timezone": "Wählen Sie Ihre Zeitzone",
"select-timezone": "Zeitzone",
"current-time": "Aktuelle Uhrzeit",
"email-help": "Aus Sicherheitsgründen dürfen Sie die E-Mail-Adresse nicht ändern. Bitte erstellen Sie ein weiteres kostenloses Konto mit der neuen E-Mail-Adresse.",
"doc-sent": "Dokument erfolgreich gesendet.",
@@ -856,5 +919,482 @@
"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",
"user-created-successfully": "Benutzer erfolgreich erstellt.",
"only-15-reminder-allowed": "Sie können bis zu 15 automatische Erinnerungen festlegen. Wenn zum Beispiel 'TimeToComplete' auf 15 Tage und 'RemindOnceInEvery' auf 1 Tag eingestellt ist, erreichen Sie das maximale Limit von 15 Erinnerungen. Passen Sie Ihre Einstellungen entsprechend an."
}
"only-15-reminder-allowed": "Sie können bis zu 15 automatische Erinnerungen festlegen. Wenn zum Beispiel 'TimeToComplete' auf 15 Tage und 'RemindOnceInEvery' auf 1 Tag eingestellt ist, erreichen Sie das maximale Limit von 15 Erinnerungen. Passen Sie Ihre Einstellungen entsprechend an.",
"rate-your-experience": "Wie war Ihre Erfahrung mit {{appName}}?",
"thanks-for-feedback": "Danke für Ihr Feedback 🙏",
"share-your-feedback": "Teilen Sie Ihr Feedback",
"share-your-review": "Teilen Sie Ihre Bewertung",
"please-select-rating": "Bitte wählen Sie eine Bewertung aus",
"feedback-optional": "Feedback (optional)",
"feedback-saved": "Feedback wurde gespeichert.",
"feedback-save-error": "Feedback konnte nicht gespeichert werden, bitte versuchen Sie es erneut.",
"date-format": "Datumsformat",
"document-deleted": "Das Dokument wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
"save-as-template-?": "Sind Sie sicher, dass Sie dieses Dokument als Vorlage speichern möchten?",
"go-to-manage-templates": "Zu 'Vorlagen verwalten' gehen",
"template-created": "Vorlage erstellt",
"how-would-you-like-to-proceed?": "Wie möchten Sie fortfahren?",
"failed-to-load-refresh-page": "Fehler beim Laden des Dokuments. Bitte versuchen Sie, diese Seite zu aktualisieren.",
"document-has-been-signed": "Das Dokument wurde erfolgreich unterschrieben!",
"document-has-been-signed-by-you": "Das Dokument wurde erfolgreich von Ihnen unterschrieben!",
"participant-completed-signing": "Alle Teilnehmer haben den Signaturprozess abgeschlossen.",
"you-will-receive-email-shortly": "✅ Das war's! Sie erhalten in Kürze eine Bestätigungs-E-Mail.",
"please-provide-templateid": "Bitte geben Sie templateid an",
"this-template-is-not-public": "Dieses template ist nicht öffentlich",
"invalid-templateid": "Ungültige templateid",
"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.",
"description-length-alert": "Die Beschreibung darf höchstens 500 Zeichen lang sein.",
"fix-&-resend-document": "Dokument korrigieren und erneut senden",
"do-you-want-recreate-document?": "Dadurch wird ein Entwurf aus diesem Dokument mit allen vorhandenen Feldern erstellt. Sind Sie sicher, dass Sie dieses Dokument neu erstellen möchten?",
"start-editing": "Bearbeitung starten",
"unsaved-changes-discard-them?": "Sie haben ungespeicherte Änderungen. Verwerfen?",
"yes-discard": "Ja, verwerfen",
"LTV-enabled-signatures": "LTV-fähige Signaturen",
"BETA": "BETA",
"two-factor-authentication": "Zwei-Faktor-Authentifizierung",
"2fa-help-text": "Die Zwei-Faktor-Authentifizierung fügt Ihrem Konto eine zusätzliche Sicherheitsebene hinzu, indem sie mehr als nur ein Passwort zum Anmelden erfordert.",
"2fa-help-bullet1": "Erhöht die Sicherheit, indem sowohl Ihr Passwort als auch ein Bestätigungscode erforderlich sind.",
"2fa-help-bullet2": "Der Bestätigungscode wird von einer Authentifizierungs-App auf Ihrem Gerät generiert.",
"2fa-help-bullet3": "Schützt Ihr Konto, selbst wenn Ihr Passwort kompromittiert wurde.",
"setup-2fa": "2FA einrichten",
"setup-2fa-again": "2FA erneut einrichten",
"2fa-setup-intro": "Schützen Sie Ihr Konto mit der Zwei-Faktor-Authentifizierung. Wenn aktiviert, müssen Sie bei jeder Anmeldung einen Code aus Ihrer Authentifizierungs-App eingeben.",
"scan-qr-code": "QR-Code scannen",
"scan-qr-instructions": "Verwenden Sie eine Authentifizierungs-App wie Google Authenticator, Microsoft Authenticator oder Authy, um diesen QR-Code zu scannen.",
"manual-setup-instructions": "Können Sie den Code nicht scannen? Sie können Ihre Authentifizierungs-App manuell mit diesem geheimen Schlüssel einrichten:",
"secret-key": "Geheimer Schlüssel",
"copied-to-clipboard": "In die Zwischenablage kopiert",
"copy-to-clipboard": "In die Zwischenablage kopieren",
"recovery-codes": "Wiederherstellungscodes",
"recovery-codes-instructions": "Speichern Sie diese Wiederherstellungscodes an einem sicheren Ort. Wenn Sie den Zugriff auf Ihre Authentifizierungs-App verlieren, können Sie einen dieser Einmal-Codes verwenden, um sich anzumelden.",
"download-recovery-codes": "Wiederherstellungscodes herunterladen",
"verification-code": "Bestätigungscode",
"enter-code-from-authenticator-app": "Geben Sie den 6-stelligen Code aus Ihrer Authentifizierungs-App ein",
"verification-code-required": "Bestätigungscode ist erforderlich",
"verification-code-invalid": "Ungültiger Bestätigungscode. Bitte versuchen Sie es erneut.",
"2fa-enabled": "2FA aktiviert",
"2fa-enabled-successfully": "Zwei-Faktor-Authentifizierung erfolgreich aktiviert",
"2fa-setup-complete": "Einrichtung abgeschlossen!",
"2fa-setup-complete-instructions": "Ihr Konto ist jetzt mit der Zwei-Faktor-Authentifizierung geschützt. Sie müssen bei jeder Anmeldung einen Bestätigungscode eingeben.",
"two-factor-verification": "Zwei-Faktor-Verifizierung",
"enter-verification-code-instructions": "Geben Sie den 6-stelligen Bestätigungscode aus Ihrer Authentifizierungs-App ein, um fortzufahren.",
"recovery-code": "Wiederherstellungscode",
"enter-recovery-code-help": "Geben Sie einen Ihrer Wiederherstellungscodes ein",
"recovery-code-required": "Wiederherstellungscode ist erforderlich",
"use-verification-code-instead": "Stattdessen Bestätigungscode verwenden",
"use-recovery-code-instead": "Stattdessen Wiederherstellungscode verwenden",
"regenerate-2fa-remove-existing": "Sind Sie sicher, dass Sie die Zwei-Faktor-Authentifizierung neu generieren möchten? Diese Aktion entfernt Ihre bestehenden Authentifizierungseinstellungen.",
"use-passkey": "Mit Passkey anmelden",
"security-section": "Sicherheit",
"passkey-authentication": "Passkey-Authentifizierung",
"passkey-not-supported": "Ihr Browser oder Gerät unterstützt keine Passkey-Authentifizierung",
"passkey-description": "Passkeys bieten eine stärkere, phishing-resistente Alternative zu Passwörtern. Sie können Ihren Fingerabdruck, Ihre Gesichtserkennung oder Ihre Geräte-PIN verwenden, um sich sicher anzumelden.",
"passkey-tooltip": "Passkeys sind eine einfachere und sicherere Alternative zu Passwörtern. Sie verwenden biometrische Daten wie Fingerabdrücke oder Gesichtserkennung, die bereits auf Ihrem Gerät gespeichert sind.",
"security-section-help": "Verwalten Sie Sicherheitsoptionen, einschließlich Passkeys und Authentifizierungsmethoden, um Ihr Konto zu schützen.",
"passkey-register": "Passkey registrieren",
"passkey-register-another": "Weiteren Passkey registrieren",
"passkey-registered": "Passkey registriert",
"passkey-registering": "Passkey wird registriert...",
"passkey-registered-success": "Passkey erfolgreich registriert!",
"passkey-registration-failed": "Passkey-Registrierung fehlgeschlagen",
"passkey-auth-failed": "Passkey-Authentifizierung fehlgeschlagen",
"passkey-missing-user-info": "Benutzerinformationen erforderlich",
"passkeys-list": "Ihre Passkeys",
"default-passkey": "Ihr Passkey",
"passkey-rename": "Umbenennen",
"passkey-delete": "Löschen",
"passkey-rename-title": "Passkey umbenennen",
"passkey-delete-title": "Passkey löschen",
"passkey-delete-confirm": "Möchten Sie den Passkey \"{{name}}\" wirklich löschen?",
"passkey-name": "Passkey-Name",
"passkey-name-placeholder": "Geben Sie einen beschreibenden Namen für diesen Passkey ein",
"passkey-renamed-success": "Passkey erfolgreich umbenannt",
"passkey-deleted-success": "Passkey erfolgreich gelöscht",
"passkey-rename-failed": "Passkey konnte nicht umbenannt werden",
"passkey-delete-failed": "Passkey konnte nicht gelöscht werden",
"processing": "Wird verarbeitet...",
"today": "Heute",
"yesterday": "Gestern",
"days-ago": "Vor {{days}} Tagen",
"verify-with-passkey": "Mit Passkey verifizieren",
"verify-with-otp": "Mit OTP verifizieren",
"verify-identity": "Identität verifizieren",
"verify-account": "Verifizieren Sie Ihre Identität",
"verification": "Verifizierung",
"passkey-verification-failed": "Passkey-Verifizierung fehlgeschlagen",
"security-auth-help": {
"p1": "Administre la configuración de seguridad de su cuenta para mantener sus datos seguros. OpenSign admite métodos de autenticación avanzados para mejorar la protección de la cuenta.",
"2fa-auth-help": "Agregue una capa adicional de seguridad activando 2FA. Esto requiere ingresar un código de verificación desde una aplicación autenticadora después de su contraseña.",
"passkey-auth-help": "Use claves de acceso para iniciar sesión sin contraseña con verificación biométrica o basada en el dispositivo, proporcionando una seguridad sólida y comodidad."
},
"signer-already-present": "Unterzeichner bereits vorhanden",
"kiosk-sign": "Kiosk-Unterschrift",
"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?",
"review": "Überprüfen",
"next-field": "Nächstes Feld",
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig",
"verify-document": "Dokument verifizieren",
"verify-document-signature": "Dokumentensignatur überprüfen",
"select-pdf-document": "PDF-Dokument auswählen",
"selected-file": "Ausgewählte Datei",
"verify-signature": "Signatur überprüfen",
"verification-status": "Überprüfungsstatus",
"verification-in-progress": "Überprüfung läuft...",
"verification-results-will-appear-here": "Überprüfungsergebnisse werden hier angezeigt",
"please-select-pdf": "Bitte wählen Sie eine gültige PDF-Datei aus",
"please-select-file-to-verify": "Bitte wählen Sie eine Datei zur Überprüfung aus",
"no-signature-found": "Keine Signatur im Dokument gefunden",
"error-verifying-pdf": "Fehler beim Überprüfen der PDF",
"signature-valid-basic": "Signatur ist gültig",
"signature-invalid-basic": "Signatur ist ungültig",
"all-signatures-verified-convincing": "Dokument überprüft: Alle Signaturen wurden erfolgreich validiert.",
"some-signatures-invalid-basic": "Einige Signaturen sind ungültig",
"no-signatures-processed": "Keine Signaturen verarbeitet",
"unnamed-signature-field": "Unbenanntes Signaturfeld",
"error-processing-signature": "Fehler beim Verarbeiten der Signatur",
"signer-info-not-available": "Signaturinformationen nicht verfügbar",
"cert-validity-not-checked": "Gültigkeit des Zertifikats nicht geprüft",
"valid": "Gültig",
"expired-or-not-yet-valid": "Abgelaufen oder noch nicht gültig",
"valid-from": "Gültig von",
"to": "bis",
"signer": "Unterzeichner",
"issuer": "Aussteller",
"not-available": "Nicht verfügbar",
"not-performed": "Nicht durchgeführt",
"missing-acrofield-dict": "Fehlendes Acrofield-Wörterbuch",
"signature-dictionary-not-found-or-invalid": "Signaturwörterbuch nicht gefunden oder ungültig",
"missing-or-invalid-byterange": "Fehlender oder ungültiger ByteRange",
"missing-or-invalid-contents": "Fehlender oder ungültiger Inhalt",
"missing-signature-contents": "Fehlender Signaturinhalt",
"invalid-signature-hex-format": "Ungültiges Signatur-Hex-Format",
"unsupported-signature-format-not-signeddata": "Nicht unterstütztes Signaturformat - nicht SignedData",
"signer-certificate-not-found": "Unterzeichnerzertifikat nicht gefunden",
"no-certificates-in-signature": "Keine Zertifikate in der Signatur",
"no-signer-info-in-pkcs7": "Keine Signaturinformationen in PKCS#7",
"could-not-parse-signer-info": "Signaturinformationen konnten nicht analysiert werden",
"not-calculated": "Nicht berechnet",
"not-found-in-signature": "Nicht in Signatur gefunden",
"readonly-error": "Das schreibgeschützte {{widgetName}}-Widget muss einen Standardwert haben oder kann optional gemacht werden.",
"choose-one": "Wählen Sie eine aus",
"search-templates": "Vorlagen durchsuchen…",
"search-documents": "Dokumente suchen…",
"search-contacts": "Kontakte durchsuchen…",
"edit-draft": "Entwurf bearbeiten",
"add-role-alert": "Bitte fügen Sie mindestens eine Rolle hinzu",
"vertical": "Vertikal",
"horizontal": "Horizontal",
"billing": "Abrechnung",
"console": "Konsole",
"prefill-widget": "Vorausgefüllte Widgets",
"action-prohibited": "Diese Aktion ist für Ihre E-Mail-Domain nicht erlaubt. Bitte wenden Sie sich an Ihren Administrator, um Hilfe zu erhalten.",
"must-have-at-least-one-vacant-role": "Mindestens eine Rolle muss unzugewiesen sein, bevor Sie ein template auf 'public' setzen.",
"remove-duplicate": "Bitte doppelte Option entfernen",
"prefill-bulk-error": "Der Massenversand ist nicht erlaubt, wenn Prefill-Widgets hinzugefügt wurden. Bitte entfernen Sie die Prefill-Widgets, um fortzufahren.",
"session-expired-title": "Sitzung abgelaufen",
"access-denied": "Zugriff verweigert",
"upgrade": "Upgrade",
"do-not-access-app": "Sie haben keinen Zugriff auf diese Anwendung.",
"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",
"atleast-one-recipient-alert": "Bitte fügen Sie mindestens einen Empfänger hinzu!",
"incorrect-password-or-decryption-failed": "Falsches Passwort oder Entschlüsselung fehlgeschlagen.",
"incorrect-password-for-file": "Falsches Passwort für Datei: {{file}}",
"error-uploading-pdf": "Fehler beim Hochladen der PDF.",
"provide-password": "Bitte geben Sie das Passwort an.",
"only-pdf-allowed": "Nur PDF-Dateien sind erlaubt.",
"invalid-username-password-region": "Ungültiger Benutzername/Passwort oder Region.",
"pfx-extension-alert": "Bitte laden Sie eine Datei mit der Endung .pfx hoch.",
"email-already-exist": "E-Mail existiert bereits",
"branding": "Markenbildung",
"branding-help": "Branding ermöglicht White-Labeling für Ihre App",
"custom-sub-domain": "Benutzerdefinierte Subdomain",
"app-name": "App Name",
"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",
"prefill-unfilled-widget": "Die folgenden Pflichtfelder dürfen nicht leer sein: {{emptyWidget}}. Bitte füllen Sie diese aus, um fortzufahren.",
"Dashboard": "Armaturenbrett",
"Analytics": "Analytik",
"Templates": "Vorlagen",
"Need your sign": "Benötigt Ihre Unterschrift",
"In Progress": "In Bearbeitung",
"Completed": "Abgeschlossen",
"Drafts": "Entwürfe",
"Declined": "Abgelehnt",
"Expired": "Abgelaufen",
"Contactbook": "Kontaktbuch",
"My Signature": "Meine Unterschrift",
"API Token": "API-Token",
"Webhook": "Webhook",
"Preferences": "Einstellungen",
"Teams": "Teams",
"Users": "Benutzer",
"Drive": "Drive",
"Branding": "Markenbildung",
"Mail": "Mail",
"Storage": "Speicher",
"Signing certificate": "Signierzertifikat",
"General": "Allgemein",
"Organizations": "Organisationen",
"OrgAdmins": "OrgAdmins",
"Debug Pdf": "PDF debuggen",
"New Document": "Neues Dokument",
"subscription": "Abonnement",
"Draft document": "Dokumententwurf",
"Draft template": "Vorlagenentwurf",
"Public sign": "Öffentliche Signatur",
"Signup": "Registrieren",
"delete-contact": "Kontakt löschen",
"total-records-found": "Gesamtanzahl gefundener Einträge: {{count}}",
"Invalid-records-found": "Ungültige Einträge gefunden: {{records}}",
"previous": "Zurück",
"page-n-of-n": "Seite {{currentPage}} von {{totalPages}}",
"import": "Importieren",
"search": "Suchen",
"viewed-on": "Angesehen am: {{ViewedOn}}",
"signed-on": "Unterschrieben am: {{SignedOn}}",
"hide": "Ausblenden",
"show-more": "Mehr anzeigen",
"browse-or-drag-to-replace-existing-file": "Durchsuchen oder per Drag & Drop eine neue Datei ziehen, um die vorhandene zu ersetzen",
"optional-details": "Optionale Angaben",
"hide-optional-details": "optionale Angaben ausblenden",
"mail-adapter-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um den Mail-Adapter einzurichten.",
"connect-to-mail": "Mit Gmail verbinden",
"custom-smtp": "Benutzerdefiniertes SMTP",
"default-smtp": "{{appName}} Standard-SMTP",
"host": "Host",
"port": "Port",
"sender-email": "Absender-E-Mail",
"username": "Benutzername",
"use-default-mail-adapter": "Möchten Sie wirklich die Standard-Mailserver von {{appName}} verwenden, um Ihre Signaturanfragen zu versenden? Wir empfehlen, Ihre eigenen Gmail- oder SMTP-Server zu verwenden, um die Zustellbarkeit zu verbessern.",
"verification-code-sent-registered-email": "Ein Bestätigungscode wurde an Ihre registrierte E-Mail-Adresse <1>{{useremail}}</1> gesendet. Bitte geben Sie den Code unten ein, um Ihre Einstellungen zu bestätigen.",
"smpt-credentials": "SMTP-Zugangsdaten",
"delete-account": "Konto löschen",
"delete-account-que": "Sind Sie sicher, dass Sie Ihr Konto löschen möchten?",
"delete-account-que-user": "Sie sind dabei, diesen Benutzer und alle zugehörigen Daten dauerhaft zu löschen. Diese Aktion kann nicht rückgängig gemacht werden.",
"user-deleted-successfully": "Benutzer und alle zugehörigen Daten wurden erfolgreich gelöscht.",
"account-deletion-request-sent-via-mail": "Wir haben Ihnen einen Bestätigungslink per E-Mail gesendet. Genehmigen Sie die Anfrage, um die Löschung Ihres Kontos abzuschließen.",
"type-exact-email-delete": "Geben Sie die genaue E-Mail ein, um das Löschen zu aktivieren",
"email-does-not-match": "E-Mail stimmt nicht überein.",
"please-type-to-confirm": "Bitte geben Sie <1>{{userEmail}}</1> ein, um zu bestätigen:",
"email-settings": "E-Mail-Einstellungen",
"email-settings-help": "Um eine bessere Zustellbarkeit Ihrer Signaturanfrage-E-Mails im Posteingang zu gewährleisten, können Sie Ihren eigenen E-Mail-Anbieter verbinden. Wählen Sie eine der folgenden Optionen:",
"connect-to-gmail": "Mit Gmail verbinden: ",
"connect-to-gmail-help": "Verwenden Sie Ihr Gmail-Konto, um Signaturanfrage-E-Mails direkt aus Ihrem eigenen Posteingang zu senden. Dies verbessert die Zustellraten und Zuverlässigkeit.",
"connect-to-smtp": "Benutzerdefiniertes SMTP: ",
"connect-to-smtp-help": "Verwenden Sie Ihren eigenen SMTP-Server, um E-Mails über Ihre Domain zu versenden. Diese Option gibt Ihnen die volle Kontrolle über Ihre E-Mail-Infrastruktur und verbessert die Zustellbarkeit sowie die Markenkonsistenz.",
"connect-to-default": "Wenn Sie möchten, können Sie auch die Standard-Mailserver von {{appName}} verwenden, wir empfehlen jedoch für optimale Ergebnisse die Nutzung Ihrer eigenen.",
"email-settings-redirect-message": "Diese Einstellung wurde von der Konsole zu den Haupteinstellungen → Präferenzen verschoben. Diese Seite wird in zukünftigen Versionen entfernt.",
"go-to-preferences-menu": "Zum Menü 'Einstellungen' gehen",
"document-download-filename-format": "Dateiname-Format für Dokument-Download",
"preview": "Vorschau: ",
"download-filename-format-help": "Wählen Sie, wie heruntergeladene PDFs benannt werden. Ihre Auswahl wird in Ihrem Profil gespeichert und in der gesamten App verwendet.",
"delete-action-prohibited": "Diese Aktion ist nicht erlaubt. Bitte wenden Sie sich an Ihren Administrator, um die Löschung des Kontos zu beantragen.",
"not-verified": "Nicht verifiziert",
"verified": "Verifiziert",
"expires": "Läuft ab",
"fix-resend-error": "Sie können dieses Dokument nicht korrigieren und erneut senden, da es vorab ausgefüllte Widgets enthält.",
"duplicate-template-error": "Sie können diese Vorlage nicht duplizieren, da sie vorab ausgefüllte Widgets enthält.",
"save-as-template-error": "Dieses Dokument kann nicht als Vorlage gespeichert werden, da es vorab ausgefüllte Widgets enthält.",
"redirecting-you-in": "Sie werden in {{redirectTimeLeft}} Sek. weitergeleitet...",
"pdf-tools-tour": "Klicken Sie auf diese Schaltflächen, um Seiten hinzuzufügen, zu löschen, neu anzuordnen, zu drehen und zu zoomen.",
"widgets": "Widgets",
"prefill-tour": "Verwenden Sie diese Option, um Informationen im Voraus einzugeben, bevor Sie das Dokument an Empfänger senden.",
"empty-prefill-error": "Die folgenden Pflichtfelder dürfen nicht leer bleiben:",
"please-fill-out": "Bitte füllen Sie diese aus, um fortzufahren.",
"custom-signing-certificate": "Benutzerdefiniertes Signaturzertifikat",
"signing-certificate-help": "Sie können Ihr eigenes Dokumentensignaturzertifikat hochladen, das zum Signieren aller Ihrer Dokumente sowie Abschlusszertifikate verwendet wird. Die Zertifikatsdatei sollte ein P12-Zertifikat im PFX-Format sein.",
"certificate-file-p12-in-PFX-format": "Zertifikatsdatei (p12-Zertifikat im PFX-Format)",
"password-of-pfx-file": "Passwort der PFX-Datei eingeben",
"update": "Aktualisieren",
"use-default-certificate": "Standardzertifikat von {{appName}} verwenden",
"upgrade-to-team-plan": "Auf Team-Plan upgraden",
"setup-file-storage": "Dateispeicher einrichten",
"save-and-activate": "Speichern & aktivieren",
"logging-out-to-apply-settings": "Sie werden abgemeldet, um neue Einstellungen anzuwenden",
"reconnect-to-default": "Erneut mit {{appName}} verbinden",
"active-file-adapter": "Aktiver Dateiadapter",
"file-adapter-unique-name": "Eindeutiger Name des Dateiadapters",
"unique-name-for-file-adapter": "Eindeutigen Namen für Dateiadapter eingeben",
"storage-provider": "Speicheranbieter",
"enter-bucket-name": "Bucket-Namen eingeben",
"enter-space-name": "Space-Namen eingeben",
"enter-region-of-bucket": "Region des Buckets eingeben",
"enter-region-of-space": "Region des Spaces eingeben",
"enter-access-key": "Access Key eingeben",
"enter-secret-access-key": "Secret Access Key eingeben",
"otp-email": "Wir haben einen Bestätigungscode gesendet",
"save-as-temp-warn": "Hinweis: Dieses Dokument enthält vorausgefüllte Widgets, die automatisch entfernt werden, da sie bereits im Basisdokument integriert sind.",
"edit-document": "Dokument bearbeiten",
"modify": "Ändern",
"merge-certificate-to-pdf": "Zertifikat mit PDF zusammenführen",
"merge-cc-to-pdf-help": {
"p1": "Dies stellt sicher, dass das Abschlusszertifikat im endgültigen PDF-Dokument enthalten ist. Bitte beachten Sie jedoch, dass das Zertifikat nach dem Zusammenführen nicht mehr vom Hauptdokument getrennt werden kann.",
"p2": "Wenn Sie sich entscheiden, nicht zusammenzuführen, wird das Abschlusszertifikat als separate PDF-Datei zusammen mit dem unterzeichneten Dokument bereitgestellt."
},
"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",
"subscribe-to-opensign-msg": "Abonnieren Sie {{appName}} und genießen Sie unbegrenzt kostenlose digitale Signaturen.",
"duplicate-subscribe-msg": "Mit Duplizieren können Sie eine exakte Kopie der ausgewählten Vorlage erstellen, die Sie wiederverwenden oder ändern können, ohne das Original zu beeinflussen.",
"save-as-template-msg": "Speichern Sie dieses Dokument als wiederverwendbare Vorlage, die Sie für zukünftige Dokumente erneut nutzen können.",
"public-credit-alert": "Das Signieren über diesen Link verbraucht Ihre kostenlosen E-Mail-Credits. Als kostenloser Nutzer erhalten Sie 15 E-Mail-Credits pro Monat, um zu verhindern, dass Spammer unsere Systeme missbrauchen. Um höhere Limits und ununterbrochenen Zugriff zu genießen, abonnieren Sie die <1>OpenSign™-Bezahlpläne.</1>",
"know-more-about": "Mehr erfahren über",
"free-unlimited-signatures": "Kostenlose unbegrenzte Signaturen",
"email-quota-updated": "Tägliches E-Mail-Kontingent erfolgreich aktualisiert.",
"password-has-been-reset": "Passwort wurde zurückgesetzt.",
"Unauthorized to reset your own password.": "Nicht berechtigt, Ihr eigenes Passwort zurückzusetzen.",
"User not found or not allowed.": "Benutzer nicht gefunden oder nicht erlaubt.",
"User not found.": "Benutzer nicht gefunden.",
"Admin user tenant not found.": "Admin-Benutzer-Tenant nicht gefunden.",
"User is not authenticated.": "Benutzer ist nicht authentifiziert.",
"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",
"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.",
"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": "Sie können diese Vorlage nicht duplizieren, da sie doppelte Widget-Namen enthält.",
"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."
}
File diff suppressed because it is too large Load Diff
+600 -60
View File
@@ -1,12 +1,14 @@
{
"header-news": "Nueva característica: los usuarios del plan Teams ahora pueden integrar sus propios depósitos de AWS S3 para el almacenamiento de archivos",
"header-news-btn": "Configurar ahora",
"sandbox-news": "Este es un entorno sandbox. Por favor, no lo utilice con fines de producción.",
"create-account": "Crear cuenta",
"login": "Iniciar sesión",
"language": "Idioma",
"dark-mode": "Modo oscuro",
"name": "Nombre",
"phone": "Teléfono",
"phone-optional": "opcional",
"phone-optional": "Opcional",
"email": "Correo",
"company": "Compañía",
"job-title": "Título del puesto",
@@ -24,6 +26,8 @@
"Name": "Nombre",
"Date": "Fecha"
},
"folder": "Carpeta",
"pdf": "Pdf",
"context-menu": {
"Download": "Descargar",
"Rename": "Renombrar",
@@ -39,8 +43,14 @@
"save": "Guardar",
"cancel": "Cancelar",
"upgrade-now": "Mejorar ahora",
"contact-now": "Contactar ahora",
"upgrade-to": "Mejorar a",
"plan": "Plan",
"connect": "Conectar",
"connect-to-g-drive": "Conectar con Google Drive",
"reconnect-to-g-drive": "Volver a conectar con Google Drive",
"gdrive-info-connect": "Cuando Google Drive está conectado, el documento completado se guardará en la carpeta {{appName}} de Google Drive.",
"subscription-renew-warning": "Su suscripción vencerá en {{remainingDays}} días. Por favor, renueve su suscripción.",
"subscribe-card-teamplan": "¡Libera todo el poder de la colaboración! Crea organizaciones, equipos y jerarquías ilimitadas. Comparte plantillas sin problemas entre equipos y asigna funciones de usuario personalizadas. ¡Mejora tu flujo de trabajo hoy mismo!",
"subscribe-card-plan": "Desbloquea funciones premium desde solo {{premiumPrice}}/mes. Disfruta de un rendimiento mejorado y solo {{addonPrice}} por crédito adicional después de tus créditos premium incluidos.",
"user-name-limit-char": "Para tener un nombre de usuario menor a 8 caracteres por favor suscríbete",
@@ -54,17 +64,18 @@
"welcome": "¡Bienvenido de nuevo!",
"Login-to-your-account": "Ingresa a tu cuenta",
"password": "Contraseña",
"forgot-password": "¿Olvidaste la contraseña?",
"forgot-password": "¿Olvidaste la contraseña",
"loading": "Cargando...",
"of": "de",
"sign-SSO": "Ingresar con SSO",
"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",
@@ -110,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",
@@ -142,6 +154,7 @@
"Quick send": "Envío rápido",
"Edit": "Editar",
"Share with team": "Compartir con el equipo",
"Share with user": "Compartir con un colega",
"Share": "Compartir",
"View": "Ver",
"option": "Opción",
@@ -151,7 +164,14 @@
"extend-expiry-date": "Date d'expiration",
"Duplicate Template": "Plantilla duplicada",
"Duplicate": "Duplicada",
"daily-mail-quota": "Cuota diaria de correos electrónicos"
"daily-mail-quota": "Cuota diaria de correos electrónicos",
"Save as template": "Guardar como plantilla",
"Fix & resend": "Corregir y reenviar",
"Kiosk Mode": "Modo Kiosco",
"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º",
@@ -173,7 +193,21 @@
"created-date": "Fecha de creación",
"Type": "Tipo",
"Logs": "Registros",
"Expiry-date": "Date d'expiration"
"Expiry-date": "Date d'expiration",
"Company": "Compañía",
"JobTitle": "Título del puesto",
"Time to complete (Days)": "Tiempo para completar (días)",
"Auto reminder": "Recordatorio automático",
"Remind once in every (Days)": "Recordar una vez cada (días)",
"Enable OTP verification": "Habilitar verificación OTP",
"Enable Tour": "Habilitar recorrido",
"Notify on signatures": "Notificar en las firmas",
"Allow modifications": "Permitir modificaciones",
"Redirect url": "URL de redirección",
"Created Date": "Fecha de creación",
"Updated Date": "Fecha de actualización",
"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.",
@@ -185,16 +219,15 @@
"Contactbook": "Esta es una lista de contactos/firmantes añadidos por ti. Aparecerán como sugerencias cuando intentes añadir firmantes a un nuevo documento.",
"Templates": "Esta es una lista de plantillas que están a tu disposición para crear documentos. Puedes hacer clic en el botón «usar» para crear un nuevo documento utilizando una plantilla, modifica el documento y añade firmantes en el siguiente paso."
},
"form-name": {
"Sign Yourself": "Firmar",
"Request Signatures": "Solicitar firmas",
"New Template": "Nueva plantilla"
},
"Sign Yourself": "Firmar",
"Request Signatures": "Solicitar firmas",
"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",
"description": "Descripción",
"time-to-complete": "Plazo de finalización (días)",
"send-in-order": "Enviar en orden",
@@ -208,7 +241,6 @@
"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",
@@ -234,21 +266,21 @@
"submit": "Enviar",
"reset": "Volver a empezar",
"my-signature": "Mi firma",
"initial-alert": "Mi inicial no encontrada",
"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",
"regenerate-token": "Regenerar token activo",
"remove-background": "Eliminar fondo",
"generate-token": "Generar token activo",
"view-docs": "Ver documentación",
"generate-token-alert": "¿En definitiva quieres regenerar el token? Esto expirará el token antiguo.",
"yes": "Sí",
"copied": "Copiado",
"something-went-wrong-mssg": "Algo salió mal, por favor, intenta de nuevo más tarde.",
"something-went-wrong-mssg": "Un problème est survenu, Actualiser cette page peut résoudre le problème.",
"token-generated": "Token generado exitosamente.",
"webhook": "Webhook",
"update-webhook": "Actualizar webhook",
@@ -263,7 +295,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",
@@ -272,9 +304,9 @@
"deactivate": "desactivar",
"this-user": " este usuario",
"delete-user": "Eliminar usuario",
"delete": "eliminar",
"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.",
@@ -284,6 +316,7 @@
"make-template-public": "Convertir la plantilla en pública",
"make-template-private": "Convertir la plantilla en privada",
"make-template-public-alert": "¿En definitiva quieres convertir esta plantilla en pública?",
"make-template-private-alert-non": "¿Está seguro de que desea hacer este plantilla privado?",
"make-template-private-alert": "¿En definitiva quieres convertir esta plantilla en privada? Esto lo removerá de tu perfil público.",
"public-role": "Rol público",
"public-url": "Perfil publico",
@@ -302,14 +335,14 @@
"send": "Enviar",
"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": "Por favor, añade al menos un rol a esta plantilla para poder hacer un «envío rápido» de copias a varios firmantes.",
"quick-send-alert-4": "El envío rápido ha alcanzado el límite.",
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
"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",
"revoke-document-alert": "¿En definitiva quieres revocar este documento?",
"resend-mail": "Reenviar correo",
"resend-mail-help": "Puedes usar las siguientes variables que serán reemplazadas por sus valores reales:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"resend-mail-help": "Puedes usar las siguientes variables que serán reemplazadas por sus valores reales:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
"subject": "Asunto",
"body": "Cuerpo",
"add-contact": "Agregar contacto",
@@ -339,7 +372,7 @@
"verify-email-1": "Verificar correo",
"resend": "Reenviar",
"contact-details": "Detalles del contacto",
"verify-email": "¡Por favor, verifica tu correo!",
"verify-email": "Por favor verifique su correo electrónico para continuar. Este es un paso único para confirmar el correo electrónico asociado a su cuenta de OpenSign.",
"send-otp": " Enviar OTP",
"otp-placeholder": "Ingresa el código de verificación enviado por correo",
"loading-doc": "Cargando documento...",
@@ -353,11 +386,14 @@
"date": "fecha",
"text": "texto",
"text input": "entrada de texto",
"cells": "células",
"checkbox": "casilla",
"dropdown": "desplegable",
"radio button": "botón de radio",
"image": "imagen",
"email": "correo"
"email": "correo",
"number": "número",
"draw": "Dibujar"
},
"fields": "Campos",
"recipients": "Destinatarios",
@@ -369,6 +405,7 @@
"certificate": "Certificado",
"decline": "Rechazar",
"finish": "Finalizar",
"done": "Hecho",
"mail": "Correo",
"sign-now": "Firmar ahora",
"successfully-signed": "¡Firmado exitosamente!",
@@ -378,6 +415,10 @@
"Email-verified-alert-1": "El correo está verificado.",
"Email-verified-alert-2": "El correo ya ha sido verificado.",
"upload-stamp-image": "Subir imagen del sello",
"draw-signature": "Dibujar firma",
"draw-initials": "Dibujar iniciales",
"enter-text": "Introducir texto",
"enter-widgettype": "Introduzca {{widgetType}}",
"draw": "Dibujar",
"type": "Escribir",
"color-type": {
@@ -390,27 +431,44 @@
"upload": "Subir",
"initial-teb": "Iniciales",
"signature-tab": "Firma",
"your-signature": "Tu firma",
"initial-alert": "Mi inicial no encontrada",
"copy-title": "Copiar widget a",
"contact-delete-alert": "¿En definitiva quieres eliminar este contacto?",
"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",
"formula": "Fórmula",
"formula-placeholder": "Crea una fórmula utilizando widgets numéricos",
"formula-no-widgets": "Todavía no hay otros widgets numéricos asignados a este firmante.",
"supported-operators": "Operadores compatibles:",
"formula-helper": "Haz clic en una ficha de widget numérico para insertarla (p. ej. {{number-1}} + {{number-2}}).",
"formula-error-letter": "Envuelve los nombres de los widgets entre dobles llaves (p. ej. {{number-1}}).",
"formula-error-characters": "Fuera de las llaves solo se permiten números y +, -, *, /, (, ).",
"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.",
"decimal-places": "Decimales",
"number-of-decimal-places": "Número de decimales",
"decimal-places-helper": "Establecer en 0 para números enteros",
"select": "Seleccionar",
"read-only": "Es de solo lectura",
"hide-labels": "Esconder etiquetas",
"layout": "Diseño",
"checkbox": "Casilla",
"alert": "Alerta",
"zoom-in": "Acercar",
@@ -453,25 +511,28 @@
"add-recipients": "Agregar destinatarios",
"loading-mssg": "Esto podría llevar algún tiempo",
"send-mail": "Enviar correo",
"signature-field-widget": "Cada usuario debe tener al menos un widget de firma. No ha añadido un widget de firma para {{signersName}}.",
"placeholder-alert-1": "Por favor, asegúrate de que hay al menos un widget de firma para cada destinatario.",
"placeholder-alert-2": "Por favor, confirma que hayas rellenado el campo de texto.",
"placeholder-alert-3": " ¿En definitiva quieres enviar este documento para ser firmado?",
"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-alert-5": "¿Quieres firmar documentos ahora mismo?",
"placeholder-mail-alert-you": "Los firmantes siguientes recibirán un correo electrónico una vez que usted firme el documento.",
"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",
"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.",
"subscription-expired": "Suscripción expirada",
"alert-message": "Mensaje de alerta",
"document-decline": "Rechazar documento",
@@ -521,7 +582,7 @@
"correct-password": "Por favor, proporciona la contraseña correcta",
"decrypting-pdf": " Desencriptando PDF, por favor, espera...",
"invalid-otp": "OTP inválido",
"user-not-found": "¡Usuario no encontrado!",
"user-not-found": "Usuario no encontrado",
"enter-otp-alert": "¡Por favor, ingresa el OTP!",
"get-verification-code": "Obtener código de verificación",
"get-verification-code-2": "Obtendrás un código de verificación por correo",
@@ -529,7 +590,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!",
@@ -566,7 +627,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",
@@ -585,7 +646,8 @@
"tour-mssg": {
"home-layout-1": "¡Has iniciado sesión exitosamente! Echemos un vistazo.",
"home-layout-2": "Para subir documentos para firmar tú mismo o para solicitar la firma de otros, simplemente selecciona los botones correspondientes.",
"home-layout-3": "¡Ya puedes empezar a utilizar {{appName}}! Si necesitas soporte siéntete libre de contactarnos.",
"home-layout-3": "¡Ya puedes empezar a utilizar {{appName}}!",
"home-layout-4": "Danos una estrella en",
"generate-token": "Mejora ahora para generar un token API de producción.",
"opensign-drive-1": "Haz clic en los enlaces para navegar fácilmente por la estructura jerárquica de carpetas y ver los documentos de cada carpeta.",
"opensign-drive-2": "Haz clic en el botón «Agregar» para crear una nueva carpeta o documento.",
@@ -595,44 +657,42 @@
"opensign-drive-6": "Haz clic con el botón derecho en un documento para ver opciones como «Descargar», «Renombrar», «Mover» y «Eliminar». Haz clic en el documento para abrirlo.",
"opensign-drive-7": "Haz clic con el botón derecho en cualquier carpeta para ver las opciones. Elige «Renombrar» para cambiar el nombre de la carpeta o haz clic en ella para navegar por su contenido.",
"pdf-request-file-1": "Lista de firmantes que todavía tienen que firmar este documento.",
"pdf-request-file-2": "Haz clic en cualquiera de los marcadores de posición que aparecen en el documento para firmar. A continuación, verás opciones para dibujar tu firma, escribirla o subir una imagen .",
"pdf-request-file-3": "Haga clic en los botones Rechazar o Finalizar para navegar por el documento. Utilice el menú de puntos suspensivos para ver opciones adicionales, incluido el botón Descargar.",
"pdf-request-file-2": "Haga clic en cualquiera de los campos que aparecen en el documento para comenzar. Luego podrá completar la información requerida.",
"pdf-request-file-3": "Una vez que haya completado todos los campos obligatorios, haga clic en 'Finalizar'. Luego podrá descargar el documento firmado. Según la configuración del remitente, es posible que reciba una copia del documento completado junto con el certificado de finalización una vez que todos los destinatarios hayan firmado.",
"pdf-request-file-4": "Lista de firmantes que ya han firmado el documento.",
"pdf-request-file-5": "Puedes hacer clic en «Firmar todo automáticamente» para firmar de manera automática en todos los lugares que deban ser firmados por ti. Asegúrate de revisar correctamente el documento antes de hacer clic en ese botón.",
"pdf-request-file-6": "Por favor, complete los campos en la página {{pagenumbers}}, todos resaltados con el mismo color para facilitar la identificación.",
"placeholder-sign-1": "Selecciona un destinatario de esta lista para agregar un marcador de posición dónde este destinatario tendrá que firmar. El marcador de posición aparecerá con el mismo color que el nombre del destinatario una vez lo sueltes en el documento.",
"placeholder-sign-1": "Seleccione un destinatario de esta lista para añadir un widget. El widget aparecerá con el mismo color que el nombre del destinatario una vez que lo coloque en el documento.",
"placeholder-sign-2": "Hacer clic en el botón «Agregar destinatarios» te permitirá añadir más firmantes.",
"placeholder-sign-3": "Haz clic en este botón para agregar más destinatarios/firmantes al documento.",
"placeholder-sign-4": "Arrastra o haz clic en un capo para agregarlo al documento.",
"placeholder-sign-5": "El área de contenido del PDF ya muestra los marcadores de posición existentes en la plantilla. Para tu comodidad, estos marcadores de posición coincidirán con el color del nombre del destinatario, haciéndolos fácilmente identificables.",
"placeholder-sign-4": "Arrastre o haga clic en un widget para añadirlo al documento.",
"placeholder-sign-5": "El área de contenido del PDF ya muestra los widgets existentes de la plantilla. Para su comodidad, estos widgets coincidirán con el color del nombre del destinatario, lo que los hace fácilmente identificables.",
"placeholder-sign-6": "Al hacer clic en 'Siguiente' se guardará el documento. En el siguiente paso, puede personalizar los correos electrónicos que se enviarán a los destinatarios o copiar los enlaces de firma y compartirlos usted mismo con los destinatarios.",
"report-1": "Haz clic en el botón «Agregar» para crear una nueva plantilla. Las plantillas son documentos reusables diseñados para generar rápidamente nuevos documentos con la misma estructura pero diferentes firmantes. Por ejemplo, una plantilla de RR.HH. para el proceso de incorporación podría tener roles predefinidos como «Director de RR.HH.» y «Nuevo empleado». Cada vez que utilices la plantilla, puedes asignar el rol de «Nuevo empleado» a los distintos miembros del personal que se incorporen, mientras que el rol de « Director de RR.HH.» permanece constante, lo que facilita un proceso de incorporación fluido para cada persona contratada.",
"redirect": "Haz clic en el botón «Usar» para crear un nuevo documento desde una plantilla existente.",
"bulksend": "Para enviar rápidamente múltiples documentos utilizando una plantilla existente creando simplemente las direcciones de correo electrónico de los destinatarios, haz clic en el botón 'Envío Masivo'.",
"option": "Este menú revela más opciones como «Editar» y «Eliminar». Usa el botón «Editar» para agregar roles, modificar campos y actualizar tu plantilla. Los cambios aplicarán para todos los documentos futuros que sean creados usando esta plantilla pero no afectarán a los documentos ya existentes. Con el botón «Eliminar» puedes eliminar la plantilla.",
"signyour-self-1": "Para personalizar el documento antes de que sea firmado, selecciona y arrastra al documento los widgets que prefieras. Escoge el espacio perfecto para cada modificación para adaptar el documento a tus necesidades.",
"bulksend": "Para enviar rápidamente varios documentos utilizando una plantilla existente, simplemente introduzca las direcciones de correo electrónico de los destinatarios y haga clic en el botón 'Envío masivo'. Puede enviar hasta 50 destinatarios.",
"option": "Este menú muestra más opciones como Editar, Eliminar, Renombrar, Duplicar, Compartir, etc. <1>Haga clic aquí</1> para leer más sobre todas las opciones disponibles. <3>Nota: Los cambios en una plantilla existente se aplicarán a todos los futuros documentos creados a partir de esa plantilla, pero no afectarán a los documentos que ya se hayan enviado.</3>",
"signyour-self-1": "Seleccione o arrastre sus widgets preferidos al PDF para personalizar su documento antes de firmar. Elija los lugares perfectos para cada widget y adapte el documento a sus necesidades.",
"signyour-self-2": "Arrastra y suelta en cualquier lugar de esta área. Después podrás cambiar el tamaño y moverlo.",
"template-placeholder-1": "Hacer clic en el botón «Agregar rol» te permitirá agregar varios roles de firmantes. Puedes asociar usuarios a cada rol en los siguientes pasos.",
"template-placeholder-2": "Una vez que los roles hayan sido agregados, selecciona un rol de la lista para agregar un marcador de posición donde éste tendrá que firmar. El marcador de posición aparecerá con el mismo color que el nombre del rol una vez lo sueltes en el documento.",
"template-placeholder-3": "Arrastra o haz clic en un capo para agregarlo al documento.",
"template-placeholder-4": "Arrastra el marcador de posición para un rol a cualquier lugar del documento. Recuerda, aparecerá con el mismo color que el nombre del rol para poder referenciarlo fácilmente.",
"template-placeholder-5": "Hacer clic en «Siguiente» guardará la plantilla actual. Luego de guardar, se te consultará si deseas crear un nuevo documento usando esta plantilla.",
"template-placeholder-2": "Después de añadir roles, elija uno de la lista para colocar un widget para ese destinatario. Puede hacer clic en el widget o arrastrarlo al documento. Una vez colocado, el widget se mostrará en el mismo color que el rol seleccionado.",
"template-placeholder-3": "Hacer clic en «Siguiente» guardará la plantilla actual. Luego de guardar, se te consultará si deseas crear un nuevo documento usando esta plantilla.",
"webhook-1": "Mejora ahora para configurar un webhook",
"Need your Signature": "Hacer clic en esta tarjeta te llevará a la lista de documentos que esperan por tu revisión.",
"Out for signatures": "Hacer clic en esta tarjeta te llevará a la lista de documentos que esperan tu firma.",
"Recent signature requests": "Esta es una lista de documentos que esperan por tu firma.",
"Recently sent for signatures": "Esta es una lista de documentos que has enviado a otras partes para que sea firmado.",
"Drafts": "Estos son documentos que has empezado pero que todavía no finalizas para su envío.",
"Drafts": "Estos son documentos que ha comenzado pero que aún no ha finalizado para enviar.",
"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",
@@ -646,7 +706,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",
@@ -654,7 +714,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.",
@@ -665,7 +725,7 @@
"public-template-mssg-1": "Para integrar OpenSign a tu proyecto React o Next.js, simplemente ejecuta los siguientes comandos:",
"public-template-mssg-2": "Asegúrate de tener «npm» o «yarn» configurado en tu proyecto. Si estás usando «yarn», puedes reemplazar «npm install» con «yarn add @opensign/react».",
"public-template-mssg-3": "¿Necesitas más detalles o ejemplos?",
"public-template-mssg-4": "Visita la",
"public-template-mssg-4": "Visita la ",
"public-template-mssg-5": " «npm» para las últimas actualizaciones, documentación detallada e historial de versiones.",
"public-template-mssg-6": "Antes de que puedas usar este fragmento de código, debes convertir esta plantilla en pública.",
"public-template-mssg-7": "Antes de poder generar un enlace público, debes hacer que esta plantilla sea pública.",
@@ -674,8 +734,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.",
@@ -713,7 +774,7 @@
},
"form-title-1": "Configuración del flujo de documentos",
"form-title-2": "Configuración de seguridad",
"text-field-tour": "Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
"text-field-tour": "Los campos de 'Rellenado previo' deben completarse antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice los campos de firmantes.",
"attach-signer-tour": "Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
"allowed-signature-types": "Tipos de firma permitidos",
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma.",
@@ -739,12 +800,15 @@
"delete-page": "eliminar página",
"merge-pdf": "fusionar pdf",
"add-pages": "Agregar páginas",
"reorder-pages": "Reordenar páginas",
"delete-alert": "No se puede eliminar una sola página",
"delete-alert-2": "¿Está seguro de que desea eliminar esta página?",
"delete-note": "Nota: una vez que elimines esta página, no podrás deshacerla",
"Rotation-alert": "Girar página",
"bulk-import": "Importación masiva",
"contacts-file": "Archivo de contactos (xlsx, csv)",
"import-guideline": "Sube un archivo CSV o Excel con las columnas Name, Email y opcionalmente Phone. Solo se importarán los primeros 100 contactos.",
"download-sample": "Descargar archivo de ejemplo",
"100-records-only": "Currently you can only import up to 100 records.",
"csv-excel-support-only": "Cargue un archivo en uno de los siguientes formatos: CSV, XLSX o XLS.",
"contact-imported": "Se importaron {{imported}} contactos. {{failed}} contactos no pudieron importarse.",
@@ -758,7 +822,7 @@
"agree-p1": "Confirmo que he leído y comprendido el ",
"agree-p2": "Registro Electrónico y Divulgación de Firma",
"agree-p3": "y consentimiento para utilizar registros y firmas electrónicas.",
"agrre-button": "Aceptar y continuar",
"agrre-button": "Confirmo y acepto continuar",
"term-cond-title": "Términos y condiciones",
"term-cond-h": "DIVULGACIÓN DE REGISTRO ELECTRÓNICO Y FIRMA",
"term-cond-p1": "Esta Divulgación de Firma y Registro Electrónico ('Divulgación') es un acuerdo entre el Creador del Documento ('Remitente') y el Firmante ('Usted'), facilitado a través de la plataforma {{appName}} ('Plataforma'). Al firmar documentos a través de {{appName}}, usted acepta los términos descritos en esta Divulgación. Léala detenidamente antes de continuar.",
@@ -804,7 +868,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",
@@ -831,7 +894,7 @@
"initial-type": "Tus iniciales",
"redirect-url": "URL de redireccionamiento",
"bulk-send": "envío masivo",
"select-timezone": "Seleccione su zona horaria",
"select-timezone": "Zona horaria",
"current-time": "Hora actual",
"email-help": "No se permite cambiar la dirección de correo electrónico por razones de seguridad. Por favor, cree otra cuenta gratuita utilizando la nueva dirección de correo electrónico.",
"doc-sent": "Documento enviado con éxito.",
@@ -856,5 +919,482 @@
"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",
"user-created-successfully": "Usuario creado con éxito.",
"only-15-reminder-allowed": "Puede configurar hasta 15 recordatorios automáticos. Por ejemplo, si 'TimeToComplete' es de 15 días y 'RemindOnceInEvery' es de 1 día, alcanzará el límite máximo de 15 recordatorios. Ajuste su configuración en consecuencia."
}
"only-15-reminder-allowed": "Puede configurar hasta 15 recordatorios automáticos. Por ejemplo, si 'TimeToComplete' es de 15 días y 'RemindOnceInEvery' es de 1 día, alcanzará el límite máximo de 15 recordatorios. Ajuste su configuración en consecuencia.",
"rate-your-experience": "¿Cómo fue su experiencia con {{appName}}?",
"thanks-for-feedback": "Gracias por su comentario 🙏",
"share-your-feedback": "Comparta sus comentarios",
"share-your-review": "Comparta su reseña",
"please-select-rating": "Por favor seleccione una calificación",
"feedback-optional": "Comentarios (opcional)",
"feedback-saved": "Los comentarios han sido guardados.",
"feedback-save-error": "No se pudieron guardar los comentarios, inténtelo de nuevo.",
"date-format": "Formato de fecha",
"document-deleted": "El documento ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
"save-as-template-?": "¿Está seguro de que desea guardar este documento como plantilla?",
"go-to-manage-templates": "Ir a 'Gestionar plantillas'",
"template-created": "Plantilla creada",
"how-would-you-like-to-proceed?": "¿Cómo le gustaría proceder?",
"failed-to-load-refresh-page": "Error al cargar el documento. Intente actualizar esta página.",
"document-has-been-signed": "¡El documento ha sido firmado con éxito!",
"document-has-been-signed-by-you": "¡El documento ha sido firmado con éxito por usted!",
"participant-completed-signing": "Todos los participantes han completado el proceso de firma.",
"you-will-receive-email-shortly": "✅ ¡Eso es todo! Recibirá un correo de confirmación en breve.",
"please-provide-templateid": "Por favor, proporcione templateid",
"this-template-is-not-public": "Esta template no es pública",
"invalid-templateid": "templateid no válida",
"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.",
"description-length-alert": "La descripción debe tener como máximo 500 caracteres.",
"fix-&-resend-document": "Corregir y reenviar el documento",
"do-you-want-recreate-document?": "Esto creará un borrador a partir de este documento con todos los campos intactos. ¿Está seguro de que desea recrear este documento?",
"start-editing": "Comenzar a editar",
"unsaved-changes-discard-them?": "Tienes cambios sin guardar. ¿Deseas descartarlos?",
"yes-discard": "Sí, descartar",
"LTV-enabled-signatures": "Firmas con LTV habilitado",
"BETA": "BETA",
"two-factor-authentication": "Autenticación de dos factores",
"2fa-help-text": "La autenticación de dos factores agrega una capa adicional de seguridad a su cuenta al requerir más que solo una contraseña para iniciar sesión.",
"2fa-help-bullet1": "Mejora la seguridad al requerir tanto su contraseña como un código de verificación.",
"2fa-help-bullet2": "El código de verificación es generado por una aplicación de autenticación en su dispositivo.",
"2fa-help-bullet3": "Protege su cuenta incluso si su contraseña se ve comprometida.",
"setup-2fa": "Configurar 2FA",
"setup-2fa-again": "Configurar 2FA nuevamente",
"2fa-setup-intro": "Proteja su cuenta con la autenticación de dos factores. Cuando esté habilitada, deberá ingresar un código de su aplicación de autenticación cada vez que inicie sesión.",
"scan-qr-code": "Escanear código QR",
"scan-qr-instructions": "Use una aplicación de autenticación como Google Authenticator, Microsoft Authenticator o Authy para escanear este código QR.",
"manual-setup-instructions": "¿No puede escanear el código? Puede configurar manualmente su aplicación de autenticación utilizando esta clave secreta:",
"secret-key": "Clave secreta",
"copied-to-clipboard": "Copiado al portapapeles",
"copy-to-clipboard": "Copiar al portapapeles",
"recovery-codes": "Códigos de recuperación",
"recovery-codes-instructions": "Guarde estos códigos de recuperación en un lugar seguro. Si pierde el acceso a su aplicación de autenticación, puede usar uno de estos códigos de un solo uso para iniciar sesión.",
"download-recovery-codes": "Descargar códigos de recuperación",
"verification-code": "Código de verificación",
"enter-code-from-authenticator-app": "Ingrese el código de 6 dígitos de su aplicación de autenticación",
"verification-code-required": "Se requiere el código de verificación",
"verification-code-invalid": "Código de verificación inválido. Por favor, inténtelo de nuevo.",
"2fa-enabled": "2FA activado",
"2fa-enabled-successfully": "Autenticación de dos factores habilitada con éxito",
"2fa-setup-complete": "¡Configuración completa!",
"2fa-setup-complete-instructions": "Su cuenta ahora está protegida con autenticación de dos factores. Deberá ingresar un código de verificación cada vez que inicie sesión.",
"two-factor-verification": "Verificación de dos factores",
"enter-verification-code-instructions": "Ingrese el código de verificación de 6 dígitos de su aplicación de autenticación para continuar.",
"recovery-code": "Código de recuperación",
"enter-recovery-code-help": "Ingrese uno de sus códigos de recuperación",
"recovery-code-required": "Se requiere el código de recuperación",
"use-verification-code-instead": "Usar código de verificación en su lugar",
"use-recovery-code-instead": "Usar código de recuperación en su lugar",
"regenerate-2fa-remove-existing": "¿Está seguro de que desea regenerar la autenticación de dos factores? Esta acción eliminará su configuración de autenticación actual.",
"use-passkey": "Iniciar sesión con passkey",
"security-section": "Seguridad",
"passkey-authentication": "Autenticación con passkey",
"passkey-not-supported": "Tu navegador o dispositivo no admite la autenticación con passkey",
"passkey-description": "Las passkeys proporcionan una alternativa más segura y resistente al phishing que las contraseñas. Puedes usar tu huella digital, reconocimiento facial o PIN del dispositivo para iniciar sesión de forma segura.",
"passkey-tooltip": "Las passkeys son una alternativa más simple y segura a las contraseñas. Usan datos biométricos como huellas digitales o reconocimiento facial ya almacenados en tu dispositivo.",
"security-section-help": "Administra las opciones de seguridad, incluidas las passkeys y métodos de autenticación para proteger tu cuenta.",
"passkey-register": "Registrar passkey",
"passkey-register-another": "Registrar otra passkey",
"passkey-registered": "Passkey registrada",
"passkey-registering": "Registrando passkey...",
"passkey-registered-success": "¡Passkey registrada con éxito!",
"passkey-registration-failed": "Fallo al registrar la passkey",
"passkey-auth-failed": "Falló la autenticación con passkey",
"passkey-missing-user-info": "Se requiere información del usuario",
"passkeys-list": "Tus passkeys",
"default-passkey": "Tu passkey",
"passkey-rename": "Renombrar",
"passkey-delete": "Eliminar",
"passkey-rename-title": "Renombrar passkey",
"passkey-delete-title": "Eliminar passkey",
"passkey-delete-confirm": "¿Estás seguro de que deseas eliminar la passkey \"{{name}}\"?",
"passkey-name": "Nombre de la passkey",
"passkey-name-placeholder": "Introduce un nombre descriptivo para esta passkey",
"passkey-renamed-success": "Passkey renombrada con éxito",
"passkey-deleted-success": "Passkey eliminada con éxito",
"passkey-rename-failed": "No se pudo renombrar la passkey",
"passkey-delete-failed": "No se pudo eliminar la passkey",
"processing": "Procesando...",
"today": "Hoy",
"yesterday": "Ayer",
"days-ago": "Hace {{days}} días",
"verify-with-passkey": "Verificar con passkey",
"verify-with-otp": "Verificar con OTP",
"verify-identity": "Verificar identidad",
"verify-account": "Verifique su identidad",
"verification": "Verificación",
"passkey-verification-failed": "La verificación con passkey ha fallado",
"security-auth-help": {
"p1": "Administre la configuración de seguridad de su cuenta para mantener sus datos seguros. OpenSign admite métodos de autenticación avanzados para mejorar la protección de la cuenta.",
"2fa-auth-help": "Agregue una capa adicional de seguridad activando 2FA. Esto requiere ingresar un código de verificación desde una aplicación autenticadora después de su contraseña.",
"passkey-auth-help": "Use claves de acceso para iniciar sesión sin contraseña con verificación biométrica o basada en el dispositivo, proporcionando una seguridad sólida y comodidad."
},
"signer-already-present": "Firmante ya presente",
"kiosk-sign": "Firma en quiosco",
"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?",
"review": "Revisar",
"next-field": "Siguiente campo",
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes",
"verify-document": "Verificar documento",
"verify-document-signature": "Verificar firma del documento",
"select-pdf-document": "Seleccionar documento PDF",
"selected-file": "Archivo seleccionado",
"verify-signature": "Verificar firma",
"verification-status": "Estado de verificación",
"verification-in-progress": "Verificación en curso...",
"verification-results-will-appear-here": "Los resultados de la verificación aparecerán aquí",
"please-select-pdf": "Por favor, seleccione un archivo PDF válido",
"please-select-file-to-verify": "Por favor, seleccione un archivo para verificar",
"no-signature-found": "No se encontró ninguna firma en el documento",
"error-verifying-pdf": "Error al verificar el PDF",
"signature-valid-basic": "La firma es válida",
"signature-invalid-basic": "La firma no es válida",
"all-signatures-verified-convincing": "Documento verificado: Todas las firmas han sido validadas exitosamente.",
"some-signatures-invalid-basic": "Algunas firmas no son válidas",
"no-signatures-processed": "No se procesaron firmas",
"unnamed-signature-field": "Campo de firma sin nombre",
"error-processing-signature": "Error al procesar la firma",
"signer-info-not-available": "Información del firmante no disponible",
"cert-validity-not-checked": "Validez del certificado no verificada",
"valid": "Válido",
"expired-or-not-yet-valid": "Caducado o aún no válido",
"valid-from": "Válido desde",
"to": "hasta",
"signer": "Firmante",
"issuer": "Emisor",
"not-available": "No disponible",
"not-performed": "No realizado",
"missing-acrofield-dict": "Falta el diccionario Acrofield",
"signature-dictionary-not-found-or-invalid": "Diccionario de firmas no encontrado o inválido",
"missing-or-invalid-byterange": "ByteRange faltante o inválido",
"missing-or-invalid-contents": "Contenido faltante o inválido",
"missing-signature-contents": "Falta el contenido de la firma",
"invalid-signature-hex-format": "Formato hexadecimal de firma inválido",
"unsupported-signature-format-not-signeddata": "Formato de firma no compatible - no SignedData",
"signer-certificate-not-found": "Certificado del firmante no encontrado",
"no-certificates-in-signature": "No hay certificados en la firma",
"no-signer-info-in-pkcs7": "No hay información del firmante en PKCS#7",
"could-not-parse-signer-info": "No se pudo analizar la información del firmante",
"not-calculated": "No calculado",
"not-found-in-signature": "No encontrado en la firma",
"readonly-error": "El widget de solo lectura {{widgetName}} debe tener un valor predeterminado o puede hacerlo opcional.",
"choose-one": "Elige uno",
"search-templates": "Buscar plantillas…",
"search-documents": "Buscar documentos…",
"search-contacts": "Buscar contactos…",
"vertical": "Vertical",
"add-role-alert": "Por favor, agregue al menos un rol",
"edit-draft": "Editar borrador",
"horizontal": "Horizontal",
"billing": "Facturación",
"console": "Consola",
"prefill-widget": "Widgets de Relleno Previo",
"action-prohibited": "Esta acción no está permitida para su dominio de correo electrónico. Por favor, contacte con su administrador para obtener ayuda.",
"must-have-at-least-one-vacant-role": "Debe dejar al menos un rol sin asignar antes de establecer un template como 'public'.",
"remove-duplicate": "Por favor, elimina la opción duplicada",
"prefill-bulk-error": "El envío masivo no está permitido cuando se han agregado widgets de pre-rellenado. Por favor, elimine los widgets de pre-rellenado para continuar.",
"session-expired-title": "Sesión expirada",
"access-denied": "Acceso denegado",
"upgrade": "Actualizar",
"do-not-access-app": "No tienes acceso a esta aplicación.",
"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",
"atleast-one-recipient-alert": "¡Por favor agrega al menos un destinatario!",
"incorrect-password-or-decryption-failed": "Contraseña incorrecta o fallo de descifrado.",
"incorrect-password-for-file": "Contraseña incorrecta para el archivo: {{file}}",
"error-uploading-pdf": "Error al subir PDF.",
"provide-password": "Por favor proporciona la contraseña.",
"only-pdf-allowed": "Solo se permiten archivos PDF.",
"invalid-username-password-region": "Usuario/contraseña o región inválidos.",
"pfx-extension-alert": "Por favor sube un archivo con extensión .pfx.",
"email-already-exist": "El correo electrónico ya existe",
"branding": "Marca",
"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": "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",
"prefill-unfilled-widget": "Los siguientes campos obligatorios no pueden quedar vacíos: {{emptyWidget}}. Por favor, complételos para continuar.",
"Dashboard": "Panel",
"Analytics": "Analítica",
"Templates": "Plantillas",
"Need your sign": "Necesitan tu firma",
"In Progress": "En progreso",
"Completed": "Completado",
"Drafts": "Borradores",
"Declined": "Rechazados",
"Expired": "Expirados",
"Contactbook": "Agenda de contactos",
"My Signature": "Mi firma",
"API Token": "Token API",
"Webhook": "Webhook",
"Preferences": "Preferencias",
"Teams": "Equipos",
"Users": "Usuarios",
"Drive": "Drive",
"Branding": "Marca",
"Mail": "Correo",
"Storage": "Almacenamiento",
"Signing certificate": "Certificado de firma",
"General": "General",
"Organizations": "Organizaciones",
"OrgAdmins": "OrgAdmins",
"Debug Pdf": "Depurar PDF",
"New Document": "Nuevo documento",
"subscription": "Suscripción",
"Draft document": "Borrador de documento",
"Draft template": "Borrador de plantilla",
"Public sign": "Firma pública",
"Signup": "Registrarse",
"delete-contact": "Eliminar contacto",
"total-records-found": "Total de registros encontrados: {{count}}",
"Invalid-records-found": "Registros inválidos encontrados: {{records}}",
"previous": "Anterior",
"page-n-of-n": "Página {{currentPage}} de {{totalPages}}",
"import": "Importar",
"search": "Buscar",
"viewed-on": "Visto el: {{ViewedOn}}",
"signed-on": "Firmado el: {{SignedOn}}",
"hide": "Ocultar",
"show-more": "Mostrar más",
"browse-or-drag-to-replace-existing-file": "Busque o arrastre y suelte un nuevo archivo para reemplazar el existente",
"optional-details": "Detalles opcionales",
"hide-optional-details": "ocultar detalles opcionales",
"mail-adapter-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um ein benutzerdefiniertes SMTP einzurichten.",
"connect-to-mail": "Conectar con Gmail",
"custom-smtp": "SMTP personalizado",
"default-smtp": "SMTP predeterminado de {{appName}}",
"host": "Host",
"port": "Puerto",
"sender-email": "Correo del remitente",
"username": "Nombre de usuario",
"use-default-mail-adapter": "¿Está seguro de que desea usar los servidores de correo predeterminados de {{appName}} para enviar solicitudes de firma? Recomendamos usar su propio servidor de Gmail o SMTP para una mejor entregabilidad.",
"verification-code-sent-registered-email": "Se ha enviado un código de verificación a su correo registrado <1>{{useremail}}</1>. Ingrese el código a continuación para confirmar su configuración.",
"smpt-credentials": "Credenciales SMTP",
"delete-account": "Eliminar cuenta",
"delete-account-que": "¿Está seguro de que desea eliminar su cuenta?",
"delete-account-que-user": "Está a punto de eliminar permanentemente este usuario y todos los datos asociados. Esta acción no se puede deshacer.",
"user-deleted-successfully": "Usuario y todos los datos asociados eliminados correctamente.",
"account-deletion-request-sent-via-mail": "Le hemos enviado por correo electrónico un enlace de confirmación. Apruebe la solicitud para completar la eliminación de su cuenta.",
"type-exact-email-delete": "Escriba el correo electrónico exacto para habilitar la eliminación",
"email-does-not-match": "El correo electrónico no coincide.",
"please-type-to-confirm": "Por favor, escriba <1>{{userEmail}}</1> para confirmar:",
"email-settings": "Configuración de correo electrónico",
"email-settings-help": "Para garantizar una mejor entregabilidad en la bandeja de entrada de sus correos electrónicos de solicitud de firma, puede conectar su propio proveedor de correo electrónico. Elija una de las siguientes opciones:",
"connect-to-gmail": "Conectar con Gmail: ",
"connect-to-gmail-help": "Utilice su cuenta de Gmail para enviar correos electrónicos de solicitud de firma directamente desde su propia bandeja de entrada, mejorando la tasa de entrega y la fiabilidad.",
"connect-to-smtp": "SMTP personalizado: ",
"connect-to-smtp-help": "Utilice su propio servidor SMTP para enviar correos electrónicos a través de su dominio. Esta opción le brinda control total sobre su infraestructura de correo electrónico, mejorando la entregabilidad y la coherencia de la marca.",
"connect-to-default": "Si lo prefiere, también puede usar los servidores de correo predeterminados de {{appName}}, aunque recomendamos usar los suyos para obtener mejores resultados.",
"email-settings-redirect-message": "Esta configuración se ha movido de la consola a Configuración del menú principal → Preferencias. Esta página se eliminará en próximas versiones.",
"go-to-preferences-menu": "Ir al menú de preferencias",
"document-download-filename-format": "Formato de nombre de archivo para la descarga del documento",
"preview": "Vista previa: ",
"download-filename-format-help": "Elija cómo se nombran los archivos PDF descargados. Su selección se guarda en su perfil y se usa en toda la aplicación.",
"delete-action-prohibited": "Esta acción no está permitida. Por favor, póngase en contacto con su administrador para solicitar la eliminación de la cuenta.",
"not-verified": "No verificado",
"verified": "Verificado",
"expires": "Expira",
"fix-resend-error": "No puede corregir y reenviar este documento porque contiene widgets predefinidos.",
"duplicate-template-error": "No puede duplicar esta plantilla porque contiene widgets predefinidos.",
"save-as-template-error": "Este documento no se puede guardar como plantilla porque contiene widgets predefinidos.",
"redirecting-you-in": "Redirigiéndolo en {{redirectTimeLeft}} seg...",
"pdf-tools-tour": "Haga clic en estos botones para agregar, eliminar, reorganizar, rotar y acercar/alejar páginas.",
"widgets": "Widgets",
"prefill-tour": "Use esta opción para ingresar información por adelantado antes de enviar el documento a los destinatarios.",
"empty-prefill-error": "Los siguientes campos obligatorios no pueden quedar vacíos:",
"please-fill-out": "Por favor, complételos para continuar.",
"custom-signing-certificate": "Certificado de firma personalizado",
"signing-certificate-help": "Puede cargar su propio certificado de firma de documentos, que se utilizará para firmar todos sus documentos así como los certificados de finalización. El archivo del certificado debe ser un certificado P12 en formato PFX.",
"certificate-file-p12-in-PFX-format": "Archivo de certificado (certificado p12 en formato PFX)",
"password-of-pfx-file": "Ingrese la contraseña del archivo pfx",
"update": "Actualizar",
"use-default-certificate": "Usar el certificado predeterminado de {{appName}}",
"upgrade-to-team-plan": "Actualizar al plan de equipo",
"setup-file-storage": "Configurar almacenamiento de archivos",
"save-and-activate": "Guardar y activar",
"logging-out-to-apply-settings": "Se está cerrando la sesión para aplicar la nueva configuración",
"reconnect-to-default": "Volver a conectar con {{appName}}",
"active-file-adapter": "Adaptador de archivos activo",
"file-adapter-unique-name": "Nombre único del adaptador de archivos",
"unique-name-for-file-adapter": "Ingrese un nombre único para el adaptador de archivos",
"storage-provider": "Proveedor de almacenamiento",
"enter-bucket-name": "Ingrese el nombre del bucket",
"enter-space-name": "Ingrese el nombre del espacio",
"enter-region-of-bucket": "Ingrese la región del bucket",
"enter-region-of-space": "Ingrese la región del espacio",
"enter-access-key": "Ingrese la clave de acceso",
"enter-secret-access-key": "Ingrese la clave secreta de acceso",
"otp-email": "Hemos enviado un código de verificación",
"save-as-temp-warn": "Nota: Este documento incluye widgets prellenados, que se eliminarán automáticamente ya que ya están incorporados en el documento base.",
"edit-document": "Editar documento",
"modify": "Modificar",
"merge-certificate-to-pdf": "Combinar certificado con PDF",
"merge-cc-to-pdf-help": {
"p1": "Esto garantizará que el certificado de finalización esté incluido en el documento PDF final. Sin embargo, tenga en cuenta que una vez combinado, el certificado no se podrá separar del documento principal.",
"p2": "Si elige no combinar, el certificado de finalización se proporcionará como un archivo PDF separado junto con el documento firmado."
},
"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",
"subscribe-to-opensign-msg": "Suscríbase a {{appName}} y disfrute de firmas digitales gratuitas ilimitadas.",
"duplicate-subscribe-msg": "Duplicar le permite crear una copia exacta de la plantilla seleccionada, lo que le permite reutilizarla o modificarla sin afectar al original.",
"save-as-template-msg": "Guarde este documento como una plantilla reutilizable que podrá usar nuevamente para futuros documentos.",
"public-credit-alert": "Firmar a través de este enlace consumirá sus créditos de correo electrónico gratuitos. Como usuario gratuito, se le asignan 15 créditos de correo electrónico por mes para evitar que los spammers abusen de nuestros sistemas. Para disfrutar de límites más altos y acceso ininterrumpido, suscríbase a los planes de pago de <1>OpenSign™.</1>",
"know-more-about": "Saber más sobre",
"free-unlimited-signatures": "¿Firmas ilimitadas gratis",
"email-quota-updated": "La cuota diaria de correos se actualizó correctamente.",
"password-has-been-reset": "La contraseña ha sido restablecida.",
"Unauthorized to reset your own password.": "No está autorizado para restablecer su propia contraseña.",
"User not found or not allowed.": "Usuario no encontrado o no autorizado.",
"User not found.": "Usuario no encontrado.",
"Admin user tenant not found.": "Inquilino de usuario administrador no encontrado.",
"User is not authenticated.": "El usuario no está autenticado.",
"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",
"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.",
"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 puede duplicar esta plantilla porque contiene nombres de widgets duplicados.",
"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}}."
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+606 -66
View File
@@ -1,12 +1,14 @@
{
"header-news": "Nuova funzionalità: Gli utenti del piano Teams possono ora integrare i propri bucket AWS S3 per l'archiviazione dei file",
"header-news-btn": "Configura Ora",
"sandbox-news": "Questo è un ambiente sandbox. Si prega di non utilizzarlo per scopi di produzione.",
"create-account": "Crea Account",
"login": "Accedi",
"language": "Lingua",
"dark-mode": "Modalità scura",
"name": "Nome",
"phone": "Telefono",
"phone-optional": "facoltativo",
"phone-optional": "Opzionale",
"email": "Email",
"company": "Azienda",
"job-title": "Titolo professionale",
@@ -24,6 +26,8 @@
"Name": "Nome",
"Date": "Data"
},
"folder": "Cartella",
"pdf": "Pdf",
"context-menu": {
"Download": "Scarica",
"Rename": "Rinomina",
@@ -39,8 +43,14 @@
"save": "Salva",
"cancel": "Annulla",
"upgrade-now": "Aggiorna ora",
"contact-now": "Contatta ora",
"upgrade-to": "Aggiorna a",
"plan": "Piano",
"connect": "Connetti",
"connect-to-g-drive": "Connetti a Google Drive",
"reconnect-to-g-drive": "Riconnetti a Google Drive",
"gdrive-info-connect": "Quando Google Drive è connesso, il documento completato verrà salvato nella cartella {{appName}} su Google Drive.",
"subscription-renew-warning": "Il tuo abbonamento scadrà tra {{remainingDays}} giorni. Ti preghiamo di rinnovarlo.",
"subscribe-card-teamplan": "Sblocca tutto il potenziale della collaborazione! Crea organizzazioni, team e gerarchie illimitati. Condividi modelli senza problemi tra i team e assegna ruoli personalizzati agli utenti. Migliora il tuo flusso di lavoro oggi stesso!",
"subscribe-card-plan": "Sblocca le funzionalità premium a partire da soli {{premiumPrice}}/mese. Approfitta di prestazioni migliorate e paga solo {{addonPrice}} per ogni credito aggiuntivo dopo quelli inclusi.",
"user-name-limit-char": "Per un nome utente con meno di 8 caratteri, abbonati",
@@ -54,17 +64,18 @@
"welcome": "Bentornato!",
"Login-to-your-account": "Accedi al tuo account",
"password": "Password",
"forgot-password": "Password dimenticata?",
"forgot-password": "Password dimenticata",
"loading": "Caricamento...",
"of": "di",
"sign-SSO": "Accedi con SSO",
"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",
@@ -110,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",
@@ -142,6 +154,7 @@
"Quick send": "Invio rapido",
"Edit": "Modifica",
"Share with team": "Condividi con il team",
"Share with user": "Condividi con un collega",
"Share": "Condividi",
"View": "Visualizza",
"option": "Opzione",
@@ -151,7 +164,14 @@
"extend-expiry-date": "Estendi data di scadenza",
"Duplicate Template": "Duplica modello",
"Duplicate": "Duplica",
"daily-mail-quota": "Quota e-mail giornaliera"
"daily-mail-quota": "Quota e-mail giornaliera",
"Save as template": "Salva come modello",
"Fix & resend": "Correggi e reinvia",
"Kiosk Mode": "Modalità Kiosk",
"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.",
@@ -173,7 +193,21 @@
"created-date": "Data di creazione",
"Type": "Tipo",
"Logs": "Log",
"Expiry-date": "Data di scadenza"
"Expiry-date": "Data di scadenza",
"Company": "Azienda",
"JobTitle": "Titolo professionale",
"Time to complete (Days)": "Tempo per completare (giorni)",
"Auto reminder": "Promemoria automatico",
"Remind once in every (Days)": "Ricorda una volta ogni (giorni)",
"Enable OTP verification": "Abilita verifica OTP",
"Enable Tour": "Abilita tour",
"Notify on signatures": "Notifica sulle firme",
"Allow modifications": "Consenti modifiche",
"Redirect url": "URL di reindirizzamento",
"Created Date": "Data di creazione",
"Updated Date": "Data di aggiornamento",
"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.",
@@ -185,16 +219,15 @@
"Contactbook": "Questa è una lista di contatti/firmatari aggiunti da te. Appariranno come suggerimenti quando provi ad aggiungere firmatari a un nuovo documento.",
"Templates": "Questa è una lista di modelli disponibili per creare documenti. Puoi fare clic sul pulsante 'Usa' per creare un nuovo documento utilizzando un modello, modificare il documento e aggiungere firmatari nel passaggio successivo."
},
"form-name": {
"Sign Yourself": "Firma tu stesso",
"Request Signatures": "Richiedi firme",
"New Template": "Nuovo modello"
},
"Sign Yourself": "Firma tu stesso",
"Request Signatures": "Richiedi firme",
"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",
"description": "Descrizione",
"time-to-complete": "Tempo per completare (giorni)",
"send-in-order": "Invia in ordine",
@@ -208,7 +241,6 @@
"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",
@@ -236,18 +268,19 @@
"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",
"regenerate-token": "Rigenera token live",
"remove-background": "Rimuovi sfondo",
"generate-token": "Genera token live",
"view-docs": "Visualizza documenti",
"generate-token-alert": "Sei sicuro di voler rigenerare il token? Questo invaliderà il vecchio token.",
"yes": "Sì",
"copied": "Copiato",
"something-went-wrong-mssg": "Qualcosa è andato storto, riprova più tardi.",
"something-went-wrong-mssg": "Si è verificato un errore, L'aggiornamento della pagina potrebbe risolvere il problema.",
"token-generated": "Token generato con successo.",
"webhook": "Webhook",
"update-webhook": "Aggiorna Webhook",
@@ -262,7 +295,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",
@@ -271,9 +304,9 @@
"deactivate": "disattiva",
"this-user": "questo utente",
"delete-user": "Elimina utente",
"delete": "elimina",
"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.",
@@ -283,6 +316,7 @@
"make-template-public": "Rendi il modello pubblico",
"make-template-private": "Rendi il modello privato",
"make-template-public-alert": "Sei sicuro di voler rendere pubblico questo modello?",
"make-template-private-alert-non": "Sei sicuro di voler rendere privato questo modello?",
"make-template-private-alert": "Sei sicuro di voler rendere privato questo modello? Questo lo rimuoverà dal tuo profilo pubblico.",
"public-role": "Ruolo pubblico",
"public-url": "Profilo pubblico",
@@ -301,19 +335,19 @@
"send": "Invia",
"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": "Aggiungi almeno un ruolo a questo modello per 'invio rapido' a più firmatari.",
"quick-send-alert-4": "Limite di invio rapido raggiunto.",
"quick-send-alert-3": "Si prega di aggiungere almeno un ruolo a questo template.",
"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",
"revoke-document-alert": "Sei sicuro di voler revocare questo documento?",
"resend-mail": "Reinvia email",
"resend-mail-help": "Puoi usare le seguenti variabili che verranno sostituite con i loro valori effettivi: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"resend-mail-help": "Puoi usare le seguenti variabili che verranno sostituite con i loro valori effettivi: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
"subject": "Oggetto",
"body": "Corpo del messaggio",
"add-contact": "Aggiungi contatto",
"edit-contact": "Modifica contatto",
"add-signer-alert": "Il contatto esiste già! Selezionalo dal menu a tendina Firmatari.",
"add-signer-alert": "Il contatto esiste già! Selezionalo dal menu a tendina 'Firmatari'.",
"record-delete-alert": "Record eliminato con successo!",
"record-revoke-alert": "Record revocato con successo!",
"mail-sent-alert": "Email inviata con successo.",
@@ -338,7 +372,7 @@
"verify-email-1": "Verifica email",
"resend": "Reinvia",
"contact-details": "Dettagli contatto",
"verify-email": "Verifica la tua email!",
"verify-email": "Si prega di verificare l'e-mail per continuare. Questo è un passaggio unico per confermare l'e-mail associata al tuo account OpenSign.",
"send-otp": "Invia OTP",
"otp-placeholder": "Inserisci il codice di verifica ricevuto via email",
"loading-doc": "Caricamento del documento...",
@@ -352,11 +386,14 @@
"date": "data",
"text": "testo",
"text input": "campo di testo",
"cells": "cellule",
"checkbox": "casella di controllo",
"dropdown": "menu a tendina",
"radio button": "pulsante di opzione",
"image": "immagine",
"email": "email"
"email": "email",
"number": "numero",
"draw": "Disegno"
},
"fields": "Campi",
"recipients": "Destinatari",
@@ -368,6 +405,7 @@
"certificate": "Certificato",
"decline": "Rifiuta",
"finish": "Completa",
"done": "Fatto",
"mail": "Email",
"sign-now": "Firma ora",
"successfully-signed": "Firmato con successo!",
@@ -377,6 +415,10 @@
"Email-verified-alert-1": "Email verificata.",
"Email-verified-alert-2": "Email già verificata.",
"upload-stamp-image": "Carica immagine timbro",
"draw-signature": "Disegna la firma",
"draw-initials": "Disegna le iniziali",
"enter-text": "Inserisci testo",
"enter-widgettype": "Inserisci {{widgetType}}",
"draw": "Disegno",
"type": "Digitata",
"color-type": {
@@ -389,27 +431,44 @@
"upload": "Caricamento",
"initial-teb": "Iniziali",
"signature-tab": "Firma",
"your-signature": "La tua firma",
"initial-alert": "La mia iniziale non trovata",
"copy-title": "Copia widget in",
"contact-delete-alert": "Sei sicuro di voler eliminare questo contatto?",
"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",
"formula": "Formula",
"formula-placeholder": "Crea una formula utilizzando i widget numerici",
"formula-no-widgets": "Non ci sono ancora altri widget numerici assegnati a questo firmatario.",
"supported-operators": "Operatori supportati:",
"formula-helper": "Fai clic su una chip di widget numerico per inserirla (es. {{number-1}} + {{number-2}}).",
"formula-error-letter": "Racchiudi i nomi dei widget tra doppie parentesi graffe (es. {{number-1}}).",
"formula-error-characters": "Al di fuori delle parentesi sono consentiti solo numeri e +, -, *, /, (, ).",
"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.",
"decimal-places": "Cifre decimali",
"number-of-decimal-places": "Numero di cifre decimali",
"decimal-places-helper": "Imposta su 0 per numeri interi",
"select": "Seleziona",
"read-only": "È solo lettura",
"read-only": di sola lettura",
"hide-labels": "Nascondi etichette",
"layout": "Layout",
"checkbox": "Casella di controllo",
"alert": "Avviso",
"zoom-in": "Ingrandisci",
@@ -452,26 +511,28 @@
"add-recipients": "Aggiungi destinatari",
"loading-mssg": "Questo potrebbe richiedere del tempo",
"send-mail": "Invia Mail",
"signature-field-widget":"È necessario aggiungere almeno un campo firma per ogni utente. Non hai aggiunto campi firma per {{signersName}}",
"signature-field-widget": "Ogni utente deve avere almeno un widget di firma. Non hai aggiunto un widget di firma per {{signersName}}.",
"placeholder-alert-1": "Assicurati che sia stato aggiunto almeno un widget per la firma per tutti i destinatari.",
"placeholder-alert-2": "Conferma di aver compilato il campo di testo.",
"placeholder-alert-3": "Sei sicuro di voler inviare questo documento per le firme?",
"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-alert-5": "Vuoi firmare i documenti ora?",
"placeholder-mail-alert-you": "I firmatari successivi riceveranno un'email dopo che avrai firmato il documento.",
"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",
"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.",
"subscription-expired": "Abbonamento Scaduto",
"alert-message": "Messaggio di avviso",
"document-decline": "Documento rifiutato",
@@ -521,7 +582,7 @@
"correct-password": "Fornisci la password corretta",
"decrypting-pdf": "Decrittazione PDF, attendi...",
"invalid-otp": "OTP non valido",
"user-not-found": "Utente non trovato!",
"user-not-found": "Utente non trovato",
"enter-otp-alert": "Inserisci l'OTP!",
"get-verification-code": "Ottieni codice di verifica",
"get-verification-code-2": "Riceverai un codice di verifica tramite Email",
@@ -529,7 +590,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!",
@@ -566,7 +627,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",
@@ -585,7 +646,8 @@
"tour-mssg": {
"home-layout-1": "Accesso effettuato con successo! Diamo un'occhiata.",
"home-layout-2": "Per caricare documenti da firmare personalmente o richiedere le firme di altri, seleziona semplicemente i pulsanti corrispondenti.",
"home-layout-3": "Sei pronto per iniziare a usare {{appName}}! Se hai bisogno di supporto, contattaci pure.",
"home-layout-3": "Sei pronto per iniziare a usare {{appName}}!",
"home-layout-4": "Dacci una stella su",
"generate-token": "Esegui l'upgrade ora per generare un token API di produzione.",
"opensign-drive-1": "Fai clic sui collegamenti breadcrumb per navigare facilmente attraverso la gerarchia delle cartelle e visualizzare i documenti in ciascuna cartella.",
"opensign-drive-2": "Fai clic sul pulsante aggiungi per creare una nuova cartella o documento.",
@@ -595,44 +657,42 @@
"opensign-drive-6": "Fai clic con il tasto destro su un documento per vedere opzioni come Scarica, Rinomina, Sposta e Elimina. Fai clic sul documento per aprirlo.",
"opensign-drive-7": "Fai clic con il tasto destro su qualsiasi cartella per vedere le opzioni. Scegli 'Rinomina' per cambiare il nome della cartella o fai clic sulla cartella per navigare nei suoi contenuti.",
"pdf-request-file-1": "Elenco dei firmatari che devono ancora firmare il documento.",
"pdf-request-file-2": "Fai clic su uno dei segnaposto che appaiono sul documento per firmare. Vedrai poi le opzioni per disegnare la tua firma, digitarla o caricare un'immagine.",
"pdf-request-file-3": "Clicca sui pulsanti Rifiuta o Fine per navigare nel tuo documento. Usa il menu con i puntini di sospensione per opzioni aggiuntive, incluso il pulsante Scarica.",
"pdf-request-file-2": "Fai clic su uno qualsiasi dei campi presenti nel documento per iniziare. Potrai quindi compilare le informazioni richieste.",
"pdf-request-file-3": "Dopo aver compilato tutti i campi richiesti, fai clic su 'Fine'. Potrai quindi scaricare il documento firmato. A seconda delle impostazioni del mittente, potresti ricevere una copia del documento completato insieme al certificato di completamento una volta che tutti i destinatari avranno firmato.",
"pdf-request-file-4": "Elenco dei firmatari che hanno già firmato il documento.",
"pdf-request-file-5": "Puoi fare clic su 'Firma Tutto Automaticamente' per firmare automaticamente in tutte le posizioni previste per te. Assicurati di esaminare attentamente il documento prima di fare clic su questo pulsante.",
"pdf-request-file-6": "Si prega di completare i campi alla pagina {{pagenumbers}}, tutti evidenziati con lo stesso colore per una facile identificazione.",
"placeholder-sign-1": "Seleziona un destinatario da questo elenco per aggiungere un segnaposto dove deve firmare. Il segnaposto apparirà dello stesso colore del nome del destinatario una volta posizionato sul documento.",
"placeholder-sign-1": "Seleziona un destinatario da questo elenco per aggiungere un widget. Il widget apparirà con lo stesso colore del nome del destinatario una volta posizionato sul documento.",
"placeholder-sign-2": "Facendo clic sul pulsante 'Aggiungi destinatari' potrai aggiungere più firmatari.",
"placeholder-sign-3": "Fai clic su questo pulsante per aggiungere altri destinatari/firmatari al documento.",
"placeholder-sign-4": "Trascina o fai clic su un campo per aggiungerlo al documento.",
"placeholder-sign-5": "L'area del contenuto PDF visualizza già i segnaposti esistenti del modello. Per tua comodità, questi segnaposti corrisponderanno al colore del nome del destinatario, rendendoli facilmente identificabili.",
"placeholder-sign-4": "Trascina o fai clic su un widget per aggiungerlo al documento.",
"placeholder-sign-5": "L'area di contenuto PDF mostra già i widget esistenti del modello. Per comodità, questi widget corrisponderanno al colore del nome del destinatario, rendendoli facilmente identificabili.",
"placeholder-sign-6": "Facendo clic su 'Invia' il documento verrà salvato. Nel passaggio successivo potrai personalizzare le email da inviare ai destinatari o copiare i link di firma e condividerli direttamente con i destinatari.",
"report-1": "Fai clic sul pulsante 'Aggiungi' per creare un nuovo modello. I modelli sono documenti riutilizzabili progettati per generare rapidamente nuovi documenti con la stessa struttura e firmatari diversi. Ad esempio, un modello HR per l'onboarding potrebbe avere ruoli predefinita come Responsabile HR e Nuovo Dipendente. Ogni volta che usi il modello, puoi assegnare il ruolo Nuovo Dipendente a membri dello staff in arrivo, mentre il ruolo Responsabile HR rimane costante, facilitando un processo di onboarding fluido per ogni nuovo assunto.",
"report-1": "Fai clic sul pulsante 'Aggiungi' per creare un nuovo modello. I modelli sono documenti riutilizzabili progettati per generare rapidamente nuovi documenti con la stessa struttura e firmatari diversi. Ad esempio, un modello HR per l'onboarding potrebbe avere ruoli predefinita come 'Responsabile HR' e 'Nuovo Dipendente'. Ogni volta che usi il modello, puoi assegnare il ruolo 'Nuovo Dipendente' a membri dello staff in arrivo, mentre il ruolo 'Responsabile HR' rimane costante, facilitando un processo di onboarding fluido per ogni nuovo assunto.",
"redirect": "Fai clic sul pulsante 'Usa' per creare un nuovo documento da un modello esistente.",
"bulksend": "Per inviare rapidamente più documenti utilizzando un modello esistente creando semplicemente gli indirizzi e-mail dei destinatari, fai clic sul pulsante 'Invio Multiplo'.",
"option": "Questo menu rivela altre opzioni come Modifica ed Elimina. Usa il pulsante 'Modifica' per aggiungere ruoli di firmatari, modificare i campi e aggiornare il modello. Le modifiche si applicheranno a tutti i futuri documenti creati da questo modello ma non influiranno sui documenti esistenti. Usa il pulsante Elimina per eliminare il modello.",
"signyour-self-1": "Seleziona e trascina i widget preferiti sul PDF per personalizzare il documento prima di firmarlo. Scegli i punti perfetti per ogni modifica per adattare il documento alle tue esigenze.",
"bulksend": "Per inviare rapidamente più documenti utilizzando un modello esistente, basta inserire gli indirizzi e-mail dei destinatari e fare clic sul pulsante 'Invio massivo'. Puoi inviare fino a 50 destinatari.",
"option": "Questo menu mostra ulteriori opzioni come Modifica, Elimina, Rinomina, Duplica, Condividi, ecc. <1>Fai clic qui</1> per saperne di più su tutte le opzioni disponibili. <3>Nota: Le modifiche a un modello esistente si applicheranno a tutti i futuri documenti creati da quel modello, ma non influenzeranno i documenti già inviati.</3>",
"signyour-self-1": "Seleziona o trascina i widget preferiti sul PDF per personalizzare il tuo documento prima della firma. Scegli i punti perfetti per ciascun widget per adattare il documento alle tue esigenze.",
"signyour-self-2": "Trascina e rilascia ovunque in quest'area. Puoi ridimensionarlo e spostarlo in seguito.",
"template-placeholder-1": "Facendo clic sul pulsante 'Aggiungi ruolo' potrai aggiungere vari ruoli di firmatari. Puoi assegnare utenti a ciascun ruolo nei passaggi successivi.",
"template-placeholder-2": "Una volta aggiunti i ruoli, seleziona un ruolo dall'elenco per aggiungere un segnaposto dove deve firmare. Il segnaposto apparirà dello stesso colore del nome del ruolo una volta posizionato sul documento.",
"template-placeholder-3": "Trascina o fai clic su un campo per aggiungerlo al documento.",
"template-placeholder-4": "Trascina il segnaposto per un ruolo ovunque sul documento. Ricorda, apparirà dello stesso colore del nome del destinatario per un facile riferimento.",
"template-placeholder-5": "Facendo clic su 'Avanti' il modello corrente verrà memorizzato. Dopo averlo salvato, ti verrà chiesto di creare un nuovo documento da questo modello, se lo desideri.",
"template-placeholder-2": "Dopo aver aggiunto i ruoli, scegli uno dall'elenco per posizionare un widget per quel destinatario. Puoi fare clic sul widget o trascinarlo nel documento. Una volta posizionato, il widget verrà visualizzato nello stesso colore del ruolo selezionato.",
"template-placeholder-3": "Facendo clic su 'Avanti' il modello corrente verrà memorizzato. Dopo averlo salvato, ti verrà chiesto di creare un nuovo documento da questo modello, se lo desideri.",
"webhook-1": "Esegui l'upgrade ora per configurare il webhook.",
"Need your Signature": "Facendo clic su questa scheda, accederai all'elenco dei documenti in attesa della tua revisione.",
"Out for signatures": "Facendo clic su questa scheda, accederai all'elenco dei documenti in attesa di firma.",
"Recent signature requests": "Questo è un elenco di documenti che aspettano la tua firma.",
"Recently sent for signatures": "Questo è un elenco di documenti che hai inviato ad altre parti per la firma.",
"Drafts": "Questi sono documenti che hai iniziato ma non hai finalizzato per l'invio.",
"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.",
"Drafts": "Questi sono documenti che hai iniziato ma non hai ancora finalizzato per l'invio.",
"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",
@@ -646,7 +706,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",
@@ -654,7 +714,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.",
@@ -665,7 +725,7 @@
"public-template-mssg-1": "Per integrare OpenSign nel tuo progetto React o Next.js, esegui semplicemente il seguente comando:",
"public-template-mssg-2": "Assicurati di avere npm o yarn configurato nel tuo progetto. Se usi Yarn, puoi sostituire npm install con yarn add @opensign/react.",
"public-template-mssg-3": "Hai bisogno di maggiori dettagli o esempi?",
"public-template-mssg-4": "Visita il",
"public-template-mssg-4": "Visita il ",
"public-template-mssg-5": " npm per gli aggiornamenti più recenti, documentazione dettagliata e cronologia delle versioni.",
"public-template-mssg-6": "Prima di poter utilizzare questo frammento di codice, devi rendere questo modello pubblico.",
"public-template-mssg-7": "Prima di poter generare un link pubblico, devi rendere questo modello pubblico.",
@@ -674,8 +734,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.",
@@ -713,11 +774,11 @@
"public-tour-message": "Il modello deve essere pubblico prima di poter generare un link condivisibile.",
"add-user-template": "Devi aggiungere un ruolo prima di poter aggiungere i campi per esso.",
"pdf-uncompatible": "Questo PDF non è compatibile, contatta {{appName}}",
"text-field-tour": "I campi di tipo 'Testo' devono essere compilati in anticipo prima che il documento venga inviato. Se hai bisogno che i firmatari forniscano un input, usa invece il campo 'Testo di input'.",
"text-field-tour": "I campi 'Prefill' devono essere compilati in anticipo prima dell'invio del documento. Se hai bisogno che i firmatari forniscano input, usa invece i campi dei firmatari.",
"attach-signer-tour": "Devi allegare un firmatario a ogni ruolo. Puoi farlo cliccando su questa icona. Una volta selezionato un firmatario, sarà associato a tutti i campi associati a quel ruolo che appariranno dello stesso colore.",
"allowed-signature-types": "Tipi di firma consentiti",
"at-least-one-signature-type": "Almeno un tipo di firma deve essere abilitato.",
"expect-default-one-more-signature-type": "Abilita un tipo di firma aggiuntivo oltre al tipo predefinita.",
"expect-default-one-signature-type": "Abilita un tipo di firma aggiuntivo oltre al tipo predefinita.",
"add-signer-note": "Nota - Questo contatto verrà aggiunto alla tua lista di contatti",
"allowed-signature-types-help": {
"p1": "Questa preferenza di firma controlla le opzioni di firma disponibili per i tuoi firmatari. Solo i tipi di firma che selezioni appariranno nel widget di firma quando un documento viene firmato. Ad esempio, se scegli solo l'opzione 'Disegna' nelle preferenze, il tuo firmatario vedrà solo l'opzione 'Disegna' nel widget di firma, mentre gli altri tre tipi non saranno disponibili.",
@@ -739,12 +800,15 @@
"delete-page": "Elimina pagina",
"merge-pdf": "Unisci PDF",
"add-pages": "Aggiungi pagine",
"reorder-pages": "Riordina pagine",
"delete-alert": "Non è possibile eliminare una singola pagina",
"delete-alert-2": "Sei sicuro di voler eliminare questa pagina?",
"delete-note": "Nota: Una volta eliminata questa pagina, non potrai annullare l'operazione.",
"Rotation-alert": "Ruota pagina",
"bulk-import": "Importazione massiva",
"contacts-file": "File contatti (xlsx, csv)",
"import-guideline": "Carica un file CSV o Excel con le colonne Name, Email e opzionale Phone. Verranno importati solo i primi 100 contatti.",
"download-sample": "Scarica file di esempio",
"100-records-only": "Attualmente puoi importare solo fino a 100 record.",
"csv-excel-support-only": "Carica un file nei seguenti formati: CSV, XLSX o XLS.",
"contact-imported": "{{imported}} contatti importati. {{failed}} contatti non sono stati importati.",
@@ -758,7 +822,7 @@
"agree-p1": "Confermo di aver letto e compreso il",
"agree-p2": "Divulgazione sulle registrazioni e firme elettroniche",
"agree-p3": "e acconsento all'uso di registrazioni e firme elettroniche.",
"agrre-button": "Accetta e Continua",
"agrre-button": "Confermo e accetto di continuare",
"term-cond-title": "Termini e Condizioni",
"term-cond-h": "DIVULGAZIONE SULLE REGISTRAZIONI E FIRME ELETTRONICHE",
"term-cond-p1": "Questa Divulgazione sulle Registrazioni e Firme Elettroniche ('Divulgazione') è un accordo tra il Creatore del Documento ('Mittente') e il Firmatario ('Tu'), facilitato tramite la piattaforma {{appName}} (Piattaforma). Firmando documenti tramite {{appName}}, accetti i termini descritti in questa Divulgazione. Ti preghiamo di leggerla attentamente prima di procedere.",
@@ -804,7 +868,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",
@@ -831,7 +894,7 @@
"initial-type": "Le tue iniziali",
"redirect-url": "URL di reindirizzamento",
"bulk-send": "Invio in blocco",
"select-timezone": "Seleziona il tuo fuso orario",
"select-timezone": "Fuso orario",
"current-time": "Ora corrente",
"email-help": "Non ti è permesso cambiare l'indirizzo email per motivi di sicurezza. Ti preghiamo di creare un altro account gratuito utilizzando il nuovo indirizzo email.",
"doc-sent": "Documento inviato con successo.",
@@ -856,5 +919,482 @@
"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",
"user-created-successfully": "Utente creato con successo.",
"only-15-reminder-allowed": "Puoi impostare fino a 15 promemoria automatici. Ad esempio, se 'TimeToComplete' è di 15 giorni e 'RemindOnceInEvery' è di 1 giorno, raggiungerai il limite massimo di 15 promemoria. Regola le tue impostazioni di conseguenza."
}
"only-15-reminder-allowed": "Puoi impostare fino a 15 promemoria automatici. Ad esempio, se 'TimeToComplete' è di 15 giorni e 'RemindOnceInEvery' è di 1 giorno, raggiungerai il limite massimo di 15 promemoria. Regola le tue impostazioni di conseguenza.",
"rate-your-experience": "Com'è stata la sua esperienza con {{appName}}?",
"thanks-for-feedback": "Grazie per il tuo feedback 🙏",
"share-your-feedback": "Condividi il tuo feedback",
"share-your-review": "Condividi la tua recensione",
"please-select-rating": "Si prega di selezionare una valutazione",
"feedback-optional": "Feedback (opzionale)",
"feedback-saved": "Il feedback è stato salvato.",
"feedback-save-error": "Impossibile salvare il feedback, riprova.",
"date-format": "Formato data",
"document-deleted": "Il documento è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
"save-as-template-?": "Sei sicuro di voler salvare questo documento come modello?",
"go-to-manage-templates": "Vai a 'Gestisci modelli'",
"template-created": "Modello creato",
"how-would-you-like-to-proceed?": "Come desideri procedere?",
"failed-to-load-refresh-page": "Impossibile caricare il documento. Prova ad aggiornare questa pagina.",
"document-has-been-signed": "Il documento è stato firmato con successo!",
"document-has-been-signed-by-you": "Il documento è stato firmato con successo da lei!",
"participant-completed-signing": "Tutti i partecipanti hanno completato il processo di firma.",
"you-will-receive-email-shortly": "✅ È tutto! Riceverà a breve un'e-mail di conferma.",
"please-provide-templateid": "Si prega di fornire templateid",
"this-template-is-not-public": "Questo template non è pubblico",
"invalid-templateid": "templateid non valido",
"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.",
"description-length-alert": " La descrizione può contenere al massimo 500 caratteri.",
"fix-&-resend-document": "Correggi e reinvia il documento",
"do-you-want-recreate-document?": "Questo creerà una bozza da questo documento con tutti i campi intatti. Sei sicuro di voler ricreare questo documento?",
"start-editing": "Inizia a modificare",
"unsaved-changes-discard-them?": "Hai modifiche non salvate. Vuoi scartarle?",
"yes-discard": "Sì, scarta",
"LTV-enabled-signatures": "Firme con LTV abilitato",
"BETA": "BETA",
"two-factor-authentication": "Autenticazione a due fattori",
"2fa-help-text": "L'autenticazione a due fattori aggiunge un ulteriore livello di sicurezza al tuo account richiedendo più di una semplice password per accedere.",
"2fa-help-bullet1": "Aumenta la sicurezza richiedendo sia la password che un codice di verifica.",
"2fa-help-bullet2": "Il codice di verifica viene generato da un'app di autenticazione sul tuo dispositivo.",
"2fa-help-bullet3": "Protegge il tuo account anche se la password è stata compromessa.",
"setup-2fa": "Configura 2FA",
"setup-2fa-again": "Configura nuovamente 2FA",
"2fa-setup-intro": "Proteggi il tuo account con l'autenticazione a due fattori. Quando è attiva, dovrai inserire un codice dall'app di autenticazione ogni volta che accedi.",
"scan-qr-code": "Scansiona il codice QR",
"scan-qr-instructions": "Usa un'app di autenticazione come Google Authenticator, Microsoft Authenticator o Authy per scansionare questo codice QR.",
"manual-setup-instructions": "Non riesci a scansionare il codice? Puoi configurare manualmente l'app usando questa chiave segreta:",
"secret-key": "Chiave segreta",
"copied-to-clipboard": "Copiato negli appunti",
"copy-to-clipboard": "Copia negli appunti",
"recovery-codes": "Codici di recupero",
"recovery-codes-instructions": "Salva questi codici di recupero in un luogo sicuro. Se perdi l'accesso all'app di autenticazione, puoi usarne uno per accedere.",
"download-recovery-codes": "Scarica i codici di recupero",
"verification-code": "Codice di verifica",
"enter-code-from-authenticator-app": "Inserisci il codice a 6 cifre dalla tua app di autenticazione",
"verification-code-required": "È richiesto il codice di verifica",
"verification-code-invalid": "Codice di verifica non valido. Riprova.",
"2fa-enabled": "2FA abilitato",
"2fa-enabled-successfully": "Autenticazione a due fattori attivata con successo",
"2fa-setup-complete": "Configurazione completata!",
"2fa-setup-complete-instructions": "Il tuo account è ora protetto da autenticazione a due fattori. Dovrai inserire un codice ogni volta che accedi.",
"two-factor-verification": "Verifica a due fattori",
"enter-verification-code-instructions": "Inserisci il codice di verifica a 6 cifre dalla tua app di autenticazione per continuare.",
"recovery-code": "Codice di recupero",
"enter-recovery-code-help": "Inserisci uno dei tuoi codici di recupero",
"recovery-code-required": "È richiesto il codice di recupero",
"use-verification-code-instead": "Usa il codice di verifica invece",
"use-recovery-code-instead": "Usa il codice di recupero invece",
"regenerate-2fa-remove-existing": "Sei sicuro di voler rigenerare l'autenticazione a due fattori? Questa azione rimuoverà le impostazioni di autenticazione esistenti.",
"use-passkey": "Accedi con passkey",
"security-section": "Sicurezza",
"passkey-authentication": "Autenticazione passkey",
"passkey-not-supported": "Il tuo browser o dispositivo non supporta l'autenticazione passkey",
"passkey-description": "Le passkey offrono unalternativa più sicura e resistente al phishing rispetto alle password. Puoi usare limpronta digitale, il riconoscimento facciale o il PIN del dispositivo per accedere in sicurezza.",
"passkey-tooltip": "Le passkey sono unalternativa più semplice e sicura alle password. Usano dati biometrici come impronte digitali o riconoscimento facciale già presenti sul tuo dispositivo.",
"security-section-help": "Gestisci le opzioni di sicurezza, incluse passkey e metodi di autenticazione, per proteggere il tuo account.",
"passkey-register": "Registra passkey",
"passkey-register-another": "Registra un'altra passkey",
"passkey-registered": "Passkey registrata",
"passkey-registering": "Registrazione passkey in corso...",
"passkey-registered-success": "Passkey registrata con successo!",
"passkey-registration-failed": "Registrazione della passkey non riuscita",
"passkey-auth-failed": "Autenticazione passkey fallita",
"passkey-missing-user-info": "Informazioni utente richieste",
"passkeys-list": "Le tue passkey",
"default-passkey": "La tua passkey",
"passkey-rename": "Rinomina",
"passkey-delete": "Elimina",
"passkey-rename-title": "Rinomina passkey",
"passkey-delete-title": "Elimina passkey",
"passkey-delete-confirm": "Sei sicuro di voler eliminare la passkey \"{{name}}\"?",
"passkey-name": "Nome della passkey",
"passkey-name-placeholder": "Inserisci un nome descrittivo per questa passkey",
"passkey-renamed-success": "Passkey rinominata con successo",
"passkey-deleted-success": "Passkey eliminata con successo",
"passkey-rename-failed": "Impossibile rinominare la passkey",
"passkey-delete-failed": "Impossibile eliminare la passkey",
"processing": "Elaborazione...",
"today": "Oggi",
"yesterday": "Ieri",
"days-ago": "{{days}} giorni fa",
"verify-with-passkey": "Verifica con passkey",
"verify-with-otp": "Verifica con OTP",
"verify-identity": "Verifica identità",
"verify-account": "Verifica la tua identità",
"verification": "Verifica",
"passkey-verification-failed": "Verifica con passkey fallita",
"security-auth-help": {
"p1": "Gestisci le impostazioni di sicurezza del tuo account per proteggere i tuoi dati. OpenSign supporta metodi di autenticazione avanzati per migliorare la protezione dellaccount.",
"2fa-auth-help": "Aggiungi un ulteriore livello di sicurezza abilitando 2FA. Ti verrà richiesto di inserire un codice di verifica da un'app di autenticazione dopo la password.",
"passkey-auth-help": "Usa le passkey per accedere senza password con verifica biometrica o basata sul dispositivo, garantendo sicurezza elevata e praticità."
},
"signer-already-present": "Firmatario già presente",
"kiosk-sign": "Firma su chiosco",
"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?",
"review": "Rivedere",
"next-field": "Campo successivo",
"required-mssg": "{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
"verify-document": "Verifica documento",
"verify-document-signature": "Verifica firma documento",
"select-pdf-document": "Seleziona documento PDF",
"selected-file": "File selezionato",
"verify-signature": "Verifica firma",
"verification-status": "Stato verifica",
"verification-in-progress": "Verifica in corso...",
"verification-results-will-appear-here": "I risultati della verifica appariranno qui",
"please-select-pdf": "Seleziona un file PDF valido",
"please-select-file-to-verify": "Seleziona un file da verificare",
"no-signature-found": "Nessuna firma trovata nel documento",
"error-verifying-pdf": "Errore durante la verifica del PDF",
"signature-valid-basic": "La firma è valida",
"signature-invalid-basic": "La firma non è valida",
"all-signatures-verified-convincing": "Documento Verificato: Tutte le firme sono state validate con successo.",
"some-signatures-invalid-basic": "Alcune firme non sono valide",
"no-signatures-processed": "Nessuna firma elaborata",
"unnamed-signature-field": "Campo firma senza nome",
"error-processing-signature": "Errore durante l'elaborazione della firma",
"signer-info-not-available": "Informazioni firmatario non disponibili",
"cert-validity-not-checked": "Validità certificato non verificata",
"valid": "Valido",
"expired-or-not-yet-valid": "Scaduto o non ancora valido",
"valid-from": "Valido dal",
"to": "al",
"signer": "Firmatario",
"issuer": "Emittente",
"not-available": "Non disponibile",
"not-performed": "Non eseguito",
"missing-acrofield-dict": "Dizionario Acrofield mancante",
"signature-dictionary-not-found-or-invalid": "Dizionario firme non trovato o non valido",
"missing-or-invalid-byterange": "ByteRange mancante o non valido",
"missing-or-invalid-contents": "Contenuto mancante o non valido",
"missing-signature-contents": "Contenuto firma mancante",
"invalid-signature-hex-format": "Formato esadecimale firma non valido",
"unsupported-signature-format-not-signeddata": "Formato firma non supportato - non SignedData",
"signer-certificate-not-found": "Certificato firmatario non trovato",
"no-certificates-in-signature": "Nessun certificato nella firma",
"no-signer-info-in-pkcs7": "Nessuna informazione firmatario in PKCS#7",
"could-not-parse-signer-info": "Impossibile analizzare le informazioni del firmatario",
"not-calculated": "Non calcolato",
"not-found-in-signature": "Non trovato nella firma",
"readonly-error": "Il widget {{widgetName}} di sola lettura deve avere un valore predefinito oppure può essere reso facoltativo.",
"choose-one": "Scegline uno",
"search-templates": "Cerca modelli…",
"search-documents": "Cerca documenti…",
"search-contacts": "Cerca contatti…",
"add-role-alert": "Si prega di aggiungere almeno un ruolo",
"edit-draft": "Modifica bozza",
"vertical": "Verticale",
"horizontal": "Orizzontale",
"billing": "Fatturazione",
"console": "Console",
"prefill-widget": "Widget Precompilati",
"action-prohibited": "Questa azione non è consentita per il tuo dominio email. Contatta il tuo amministratore per ricevere assistenza.",
"must-have-at-least-one-vacant-role": "Devi lasciare almeno un ruolo non assegnato prima di impostare un template su 'public'.",
"remove-duplicate": "Si prega di rimuovere l'opzione duplicata",
"prefill-bulk-error": "L'invio in blocco non è consentito quando sono stati aggiunti i widget di precompilazione. Si prega di rimuovere i widget di precompilazione per procedere.",
"session-expired-title": "Sessione scaduta",
"access-denied": "Accesso negato",
"upgrade": "Aggiorna",
"do-not-access-app": "Non hai accesso a questa applicazione.",
"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",
"atleast-one-recipient-alert": "Aggiungi almeno un destinatario!",
"incorrect-password-or-decryption-failed": "Password errata o decrittazione non riuscita.",
"incorrect-password-for-file": "Password errata per il file: {{file}}",
"error-uploading-pdf": "Errore durante il caricamento del PDF.",
"provide-password": "Fornisci la password.",
"only-pdf-allowed": "Sono consentiti solo file PDF.",
"invalid-username-password-region": "Nome utente/password o regione non valida.",
"pfx-extension-alert": "Carica un file con estensione .pfx.",
"email-already-exist": "L'email esiste già",
"branding": "Marchio",
"branding-help": "Il branding consente il white labelling della tua app",
"custom-sub-domain": "Sottodominio personalizzato",
"app-name": "Nome dell'app",
"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",
"prefill-unfilled-widget": "I seguenti campi obbligatori non possono essere lasciati vuoti: {{emptyWidget}}. Per favore compilali per procedere.",
"Dashboard": "Dashboard",
"Analytics": "Analitica",
"Templates": "Modelli",
"Need your sign": "Necessita della tua firma",
"In Progress": "In corso",
"Completed": "Completati",
"Drafts": "Bozze",
"Declined": "Rifiutati",
"Expired": "Scaduti",
"Contactbook": "Rubrica",
"My Signature": "La mia firma",
"API Token": "Token API",
"Webhook": "Webhook",
"Preferences": "Preferenze",
"Teams": "Team",
"Users": "Utenti",
"Drive": "Drive",
"Branding": "Marchio",
"Mail": "Posta",
"Storage": "Archiviazione",
"Signing certificate": "Certificato di firma",
"General": "Generale",
"Organizations": "Organizzazioni",
"OrgAdmins": "OrgAdmins",
"Debug Pdf": "Debug PDF",
"New Document": "Nuovo documento",
"subscription": "Abbonamento",
"Draft document": "Bozza di documento",
"Draft template": "Bozza di modello",
"Public sign": "Firma pubblica",
"Signup": "Registrazione",
"delete-contact": "Elimina contatto",
"total-records-found": "Totale record trovati: {{count}}",
"Invalid-records-found": "Record non validi trovati: {{records}}",
"previous": "Precedente",
"page-n-of-n": "Pagina {{currentPage}} di {{totalPages}}",
"import": "Importa",
"search": "Cerca",
"viewed-on": "Visualizzato il: {{ViewedOn}}",
"signed-on": "Firmato il: {{SignedOn}}",
"hide": "Nascondi",
"show-more": "Mostra di più",
"browse-or-drag-to-replace-existing-file": "Sfoglia o trascina un nuovo file per sostituire quello esistente",
"optional-details": "Dettagli facoltativi",
"hide-optional-details": "nascondi dettagli facoltativi",
"mail-adapter-subscription-alert": "Esegui l'upgrade al piano Professional o Team per configurare un SMTP personalizzato.",
"connect-to-mail": "Connetti a Gmail",
"custom-smtp": "SMTP personalizzato",
"default-smtp": "SMTP predefinito di {{appName}}",
"host": "Host",
"port": "Porta",
"sender-email": "Email del mittente",
"username": "Nome utente",
"use-default-mail-adapter": "Sei sicuro di voler usare i server di posta predefiniti di {{appName}} per inviare le richieste di firma? Ti consigliamo di usare i tuoi server Gmail o SMTP per una migliore consegna in posta in arrivo.",
"verification-code-sent-registered-email": "Un codice di verifica è stato inviato alla tua email registrata <1>{{useremail}}</1>. Inserisci il codice qui sotto per confermare le impostazioni.",
"smpt-credentials": "Credenziali SMTP",
"delete-account": "Elimina account",
"delete-account-que": "Sei sicuro di voler eliminare il tuo account?",
"delete-account-que-user": "Stai per eliminare in modo permanente questo utente e tutti i dati associati. Questa azione non può essere annullata.",
"user-deleted-successfully": "Utente e tutti i dati associati eliminati con successo.",
"account-deletion-request-sent-via-mail": "Le abbiamo inviato via e-mail un link di conferma. Approvi la richiesta per completare l'eliminazione del suo account.",
"type-exact-email-delete": "Digita l'e-mail esatta per abilitare l'eliminazione",
"email-does-not-match": "L'e-mail non corrisponde.",
"please-type-to-confirm": "Per favore, digita <1>{{userEmail}}</1> per confermare:",
"email-settings": "Impostazioni e-mail",
"email-settings-help": "Per garantire una migliore consegna nella casella di posta in arrivo delle e-mail di richiesta firma, puoi collegare il tuo provider di posta elettronica. Scegli una delle seguenti opzioni:",
"connect-to-gmail": "Connetti a Gmail",
"connect-to-gmail-help": "Usa il tuo account Gmail per inviare e-mail di richiesta firma direttamente dalla tua casella di posta, migliorando il tasso di consegna e l'affidabilità.",
"connect-to-smtp": "SMTP personalizzato",
"connect-to-smtp-help": "Usa il tuo server SMTP personalizzato per inviare e-mail tramite il tuo dominio. Questa opzione ti offre il pieno controllo sulla tua infrastruttura e-mail, migliorando la consegna e la coerenza del marchio.",
"connect-to-default": "Se preferisci, puoi anche utilizzare i server di posta predefiniti di {{appName}}, anche se consigliamo di usare i tuoi per ottenere risultati ottimali.",
"email-settings-redirect-message": "Questa impostazione è stata spostata dalla console a Impostazioni del menu principale → Preferenze. Questa pagina verrà rimossa nelle prossime versioni.",
"go-to-preferences-menu": "Vai al menu Preferenze",
"document-download-filename-format": "Formato del nome file per il download del documento",
"preview": "Anteprima: ",
"download-filename-format-help": "Scegli come vengono nominati i PDF scaricati. La tua selezione viene salvata nel tuo profilo e utilizzata in tutta l'app.",
"delete-action-prohibited": "Questa azione non è consentita. Si prega di contattare l'amministratore per richiedere l'eliminazione dell'account.",
"not-verified": "Non verificato",
"verified": "Verificato",
"expires": "Scade",
"fix-resend-error": "Non è possibile correggere e reinviare questo documento perché contiene widget precompilati.",
"duplicate-template-error": "Non è possibile duplicare questo modello perché contiene widget precompilati.",
"save-as-template-error": "Questo documento non può essere salvato come modello perché contiene widget precompilati.",
"redirecting-you-in": "Reindirizzamento tra {{redirectTimeLeft}} sec...",
"pdf-tools-tour": "Fare clic su questi pulsanti per aggiungere, eliminare, riorganizzare, ruotare e ingrandire/ridurre le pagine.",
"widgets": "Widget",
"prefill-tour": "Usa questa opzione per inserire le informazioni in anticipo prima di inviare il documento ai destinatari.",
"empty-prefill-error": "I seguenti campi obbligatori non possono essere lasciati vuoti:",
"please-fill-out": "Si prega di compilarli per continuare.",
"custom-signing-certificate": "Certificato di firma personalizzato",
"signing-certificate-help": "Puoi caricare il tuo certificato di firma dei documenti, che verrà utilizzato per firmare tutti i tuoi documenti e i certificati di completamento. Il file del certificato deve essere un certificato P12 in formato PFX.",
"certificate-file-p12-in-PFX-format": "File di certificato (certificato p12 in formato PFX)",
"password-of-pfx-file": "Inserisci la password del file pfx",
"update": "Aggiorna",
"use-default-certificate": "Usa il certificato predefinito di {{appName}}",
"upgrade-to-team-plan": "Aggiorna al piano Team",
"setup-file-storage": "Configura archiviazione file",
"save-and-activate": "Salva e attiva",
"logging-out-to-apply-settings": "Stai effettuando il logout per applicare le nuove impostazioni",
"reconnect-to-default": "Riconnetti a {{appName}}",
"active-file-adapter": "File Adapter attivo",
"file-adapter-unique-name": "Nome univoco del File Adapter",
"unique-name-for-file-adapter": "Inserisci un nome univoco per il File Adapter",
"storage-provider": "Provider di archiviazione",
"enter-bucket-name": "Inserisci il nome del bucket",
"enter-space-name": "Inserisci il nome dello space",
"enter-region-of-bucket": "Inserisci la regione del bucket",
"enter-region-of-space": "Inserisci la regione dello space",
"enter-access-key": "Inserisci l'access key",
"enter-secret-access-key": "Inserisci la secret access key",
"otp-email": "Abbiamo inviato un codice di verifica",
"save-as-temp-warn": "Nota: Questo documento include widget precompilati, che verranno automaticamente rimossi poiché sono già incorporati nel documento di base.",
"edit-document": "Modifica documento",
"modify": "Modifica",
"merge-certificate-to-pdf": "Unisci certificato al PDF",
"merge-cc-to-pdf-help": {
"p1": "Questo garantirà che il certificato di completamento sia incluso nel documento PDF finale. Tuttavia, si noti che una volta unito, il certificato non può essere separato dal documento principale.",
"p2": "Se scegli di non unire, il certificato di completamento sarà fornito come file PDF separato insieme al documento firmato."
},
"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",
"subscribe-to-opensign-msg": "Abbonati a {{appName}} e goditi firme digitali gratuite illimitate.",
"duplicate-subscribe-msg": "Duplicare consente di creare una copia esatta del modello selezionato, permettendoti di riutilizzarlo o modificarlo senza influire sull'originale.",
"save-as-template-msg": "Salva questo documento come modello riutilizzabile che potrai usare di nuovo per documenti futuri.",
"public-credit-alert": "Firmare tramite questo link consumerà i tuoi crediti e-mail gratuiti. Come utente gratuito, ti vengono assegnati 15 crediti e-mail al mese per impedire agli spammer di abusare dei nostri sistemi. Per usufruire di limiti più elevati e accesso ininterrotto, abbonati ai piani a pagamento di <1>OpenSign™.</1>",
"know-more-about": "Scopri di più su",
"free-unlimited-signatures": "Firme illimitate gratuite",
"email-quota-updated": "Quota e-mail giornaliera aggiornata con successo.",
"password-has-been-reset": "La password è stata reimpostata.",
"Unauthorized to reset your own password.": "Non sei autorizzato a reimpostare la tua password.",
"User not found or not allowed.": "Utente non trovato o non autorizzato.",
"User not found.": "Utente non trovato.",
"Admin user tenant not found.": "Tenant utente amministratore non trovato.",
"User is not authenticated.": "Utente non autenticato.",
"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",
"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.",
"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": "Non è possibile duplicare questo modello perché contiene nomi di widget duplicati.",
"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}}."
}
File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

+4
View File
@@ -0,0 +1,4 @@
Name,Email,Phone,Company,JobTitle
John Doe,john@example.com,1234567890,abc firm,dev
Jane Smith,jane@example.com,9876543210,xyz firm,manager
Foo Bar,foo@example.com,5555555555,xyz firm,director
1 Name Email Phone Company JobTitle
2 John Doe john@example.com 1234567890 abc firm dev
3 Jane Smith jane@example.com 9876543210 xyz firm manager
4 Foo Bar foo@example.com 5555555555 xyz firm director
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.
+1
View File
@@ -0,0 +1 @@
import "@testing-library/jest-dom";
-183
View File
@@ -1,183 +0,0 @@
import React, { useState, useEffect, lazy } from "react";
import { Routes, Route, BrowserRouter } from "react-router";
import { pdfjs } from "react-pdf";
import Login from "./pages/Login";
import Form from "./pages/Form";
import Report from "./pages/Report";
import Dashboard from "./pages/Dashboard";
import HomeLayout from "./layout/HomeLayout";
import PageNotFound from "./pages/PageNotFound";
import ValidateRoute from "./primitives/ValidateRoute";
import Validate from "./primitives/Validate";
import TemplatePlaceholder from "./pages/TemplatePlaceholder";
import SignYourSelf from "./pages/SignyourselfPdf";
import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage";
import Loader from "./primitives/Loader";
import UserList from "./pages/UserList";
import { serverUrl_fn } from "./constant/appinfo";
import DocSuccessPage from "./pages/DocSuccessPage";
import ValidateSession from "./primitives/ValidateSession";
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
const ChangePassword = lazy(() => import("./pages/ChangePassword"));
const UserProfile = lazy(() => import("./pages/UserProfile"));
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
const ManageSign = lazy(() => import("./pages/Managesign"));
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
const Preferences = lazy(() => import("./pages/Preferences"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
<div className="flex justify-center items-center h-[100vh]">
<Loader />
</div>
);
};
function App() {
const [isloading, setIsLoading] = useState(true);
useEffect(() => {
handleCredentials();
}, []);
const handleCredentials = () => {
const appId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID
: "opensign";
const baseurl = serverUrl_fn();
try {
localStorage.setItem("baseUrl", `${baseurl}/`);
localStorage.setItem("parseAppId", appId);
setIsLoading(false);
} catch (error) {
console.log("err ", error);
}
};
return (
<div className="bg-base-200">
{isloading ? (
<AppLoader />
) : (
<BrowserRouter>
<Routes>
<Route element={<ValidateRoute />}>
<Route exact path="/" element={<Login />} />
<Route
path="/addadmin"
element={<LazyPage Page={AddAdmin} />}
/>
<Route
path="/upgrade-2.1"
element={<LazyPage Page={UpdateExistUserAdmin} />}
/>
</Route>
<Route element={<Validate />}>
<Route
path="/load/template/:templateId"
element={<TemplatePlaceholder />}
/>
<Route
exact
path="/load/placeholdersign/:docId"
element={<PlaceHolderSign />}
/>
<Route
exact
path="/load/recipientSignPdf/:docId/:contactBookId"
element={<PdfRequestFiles />}
/>
</Route>
<Route
path="/loadmf/signmicroapp/login/:id/:userMail/:contactBookId/:serverUrl"
element={<LazyPage Page={GuestLogin} />}
/>
<Route
path="/login/:id/:userMail/:contactBookId/:serverUrl"
element={<LazyPage Page={GuestLogin} />}
/>
<Route
path="/login/:base64url"
element={<LazyPage Page={GuestLogin} />}
/>
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
<Route
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
<Route
element={
<ValidateSession>
<HomeLayout />
</ValidateSession>
}
>
<Route
path="/changepassword"
element={<LazyPage Page={ChangePassword} />}
/>
<Route path="/form/:id" element={<Form />} />
<Route path="/report/:id" element={<Report />} />
<Route path="/dashboard/:id" element={<Dashboard />} />
<Route
path="/profile"
element={<LazyPage Page={UserProfile} />}
/>
<Route
path="/drive"
element={<LazyPage Page={Opensigndrive} />}
/>
<Route
path="/managesign"
element={<LazyPage Page={ManageSign} />}
/>
<Route
path="/template/:templateId"
element={<TemplatePlaceholder />}
/>
{/* signyouself route with no rowlevel data using docId from url */}
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
{/* draft document route to handle and navigate route page accordiing to document status */}
<Route path="/draftDocument" element={<DraftDocument />} />
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
<Route
path="/placeHolderSign/:docId"
element={<PlaceHolderSign />}
/>
{/* for user signature (need your sign route) with row level data */}
<Route path="/pdfRequestFiles" element={<PdfRequestFiles />} />
{/* for user signature (need your sign route) with no row level data */}
<Route
path="/pdfRequestFiles/:docId"
element={<PdfRequestFiles />}
/>
{/* recipient signature route with no rowlevel data using docId from url */}
<Route
path="/recipientSignPdf/:docId/:contactBookId"
element={<PdfRequestFiles />}
/>
<Route
path="/recipientSignPdf/:docId"
element={<PdfRequestFiles />}
/>
<Route path="/users" element={<UserList />} />
<Route
path="/preferences"
element={<LazyPage Page={Preferences} />}
/>
</Route>
<Route path="/success" element={<DocSuccessPage />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
)}
</div>
);
}
export default App;
+151
View File
@@ -0,0 +1,151 @@
import { useState, useEffect } from "react";
import { lazyWithRetry, hideUpgradeProgress } from "./utils";
import { Routes, Route, BrowserRouter } from "react-router";
import { pdfjs } from "react-pdf";
import Form from "./pages/Form";
import Report from "./pages/Report";
import Dashboard from "./pages/Dashboard";
import HomeLayout from "./layout/HomeLayout";
import PageNotFound from "./pages/PageNotFound";
import ValidateRoute from "./primitives/ValidateRoute";
import Validate from "./primitives/Validate";
import TemplatePlaceholder from "./pages/TemplatePlaceholder";
import SignYourSelf from "./pages/SignyourselfPdf";
import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import Lazy from "./primitives/LazyPage";
import Loader from "./primitives/Loader";
import UserList from "./pages/UserList";
import { serverUrl_fn } from "./constant/appinfo";
import DocSuccessPage from "./pages/DocSuccessPage";
import DragProvider from "./components/DragProivder";
import Title from "./components/Title";
const DebugPdf = lazyWithRetry(() => import("./pages/DebugPdf"));
const ForgetPassword = lazyWithRetry(() => import("./pages/ForgetPassword"));
const GuestLogin = lazyWithRetry(() => import("./pages/GuestLogin"));
const ChangePassword = lazyWithRetry(() => import("./pages/ChangePassword"));
const UserProfile = lazyWithRetry(() => import("./pages/UserProfile"));
const Opensigndrive = lazyWithRetry(() => import("./pages/Opensigndrive"));
const ManageSign = lazyWithRetry(() => import("./pages/Managesign"));
const AddAdmin = lazyWithRetry(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazyWithRetry(
() => import("./pages/UpdateExistUserAdmin")
);
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 (
<div className="flex justify-center items-center h-[100vh]">
<Loader />
</div>
);
};
function App() {
const [isloading, setIsLoading] = useState(true);
useEffect(() => {
// initialize creds
const id = process.env.REACT_APP_APPID ?? "opensign";
localStorage.setItem("parseAppId", id);
localStorage.setItem("baseUrl", `${serverUrl_fn()}/`);
hideUpgradeProgress();
localStorage.removeItem("showUpgradeProgress");
setIsLoading(false);
}, []);
return (
<div className="bg-base-200">
{isloading ? (
<AppLoader />
) : (
<BrowserRouter>
<Title />
<Routes>
<Route element={<ValidateRoute />}>
<Route exact path="/" element={<Lazy Page={Login} />} />
<Route path="/addadmin" element={<Lazy Page={AddAdmin} />} />
<Route
path="/upgrade-2.1"
element={<Lazy Page={UpdateExistUserAdmin} />}
/>
</Route>
<Route element={<Validate />}>
<Route
exact
path="/load/recipientSignPdf/:docId/:contactBookId"
element={<DragProvider Page={PdfRequestFiles} />}
/>
</Route>
<Route
path="/login/:base64url"
element={<Lazy Page={GuestLogin} />}
/>
<Route path="/debugpdf" element={<Lazy Page={DebugPdf} />} />
<Route
path="/forgetpassword"
element={<Lazy Page={ForgetPassword} />}
/>
<Route element={<HomeLayout />}>
<Route path="/users" element={<UserList />} />
<Route
path="/changepassword"
element={<Lazy Page={ChangePassword} />}
/>
<Route path="/form/:id" element={<Form />} />
<Route path="/report/:id" element={<Report />} />
<Route path="/dashboard/:id" element={<Dashboard />} />
<Route path="/profile" element={<Lazy Page={UserProfile} />} />
<Route path="/drive" element={<Lazy Page={Opensigndrive} />} />
<Route path="/managesign" element={<Lazy Page={ManageSign} />} />
<Route
path="/template/:templateId"
element={<DragProvider Page={TemplatePlaceholder} />}
/>
{/* signyouself route with no rowlevel data using docId from url */}
<Route
path="/signaturePdf/:docId"
element={<DragProvider Page={SignYourSelf} />}
/>
{/* draft document route to handle and navigate route page according to document status */}
<Route
path="/draftDocument"
element={<DragProvider Page={DraftDocument} />}
/>
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
<Route
path="/placeHolderSign/:docId"
element={<DragProvider Page={PlaceHolderSign} />}
/>
{/* recipient signature route with no rowlevel data using docId from url */}
<Route
path="/recipientSignPdf/:docId/:contactBookId"
element={<DragProvider Page={PdfRequestFiles} />}
/>
<Route
path="/recipientSignPdf/:docId"
element={<DragProvider Page={PdfRequestFiles} />}
/>
<Route
path="/verify-document"
element={<Lazy Page={VerifyDocument} />}
/>
<Route
path="/preferences"
element={<Lazy Page={Preferences} />}
/>
</Route>
<Route path="/success" element={<DocSuccessPage />} />
<Route path="/emailbuilder" element={<EmailBuilder />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
)}
</div>
);
}
export default App;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

-300
View File
@@ -1,300 +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);
}
}, [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("Please enter a valid email address.");
} 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"
/>
</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"
/>
</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;
-385
View File
@@ -1,385 +0,0 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
import { copytoData, usertimezone } from "../constant/Utils";
import { emailRegex } from "../constant/const";
import { useTranslation } from "react-i18next";
function generatePassword(length) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
const AddUser = (props) => {
const { t } = useTranslation();
const [formdata, setFormdata] = useState({
name: "",
phone: "",
email: "",
team: "",
password: "",
role: ""
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
const role = ["OrgAdmin", "Editor", "User"];
useEffect(() => {
getTeamList();
// eslint-disable-next-line
}, []);
const getTeamList = async () => {
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
const teamRes = await Parse.Cloud.run("getteams", { active: true });
if (teamRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamRes));
setTeamList(_teamRes);
const allUserId =
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
setFormdata((prev) => ({ ...prev, team: allUserId }));
}
};
const checkUserExist = async () => {
try {
const res = await Parse.Cloud.run("getUserDetails", {
email: formdata.email
});
if (res) {
return true;
} else {
return false;
}
} catch (err) {
console.log("err", err);
}
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(formdata.email)) {
alert("Please enter a valid email address.");
} else {
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
setIsFormLoader(true);
const res = await checkUserExist();
if (res) {
props.showAlert("danger", t("user-already-exist"));
setIsFormLoader(false);
} else {
if (localStorage.getItem("TenantId")) {
try {
const extUser = new Parse.Object("contracts_Users");
extUser.set("Name", formdata.name);
if (formdata.phone) {
extUser.set("Phone", formdata.phone);
}
extUser.set("Email", formdata.email);
extUser.set("UserRole", `contracts_${formdata.role}`);
if (formdata?.team) {
extUser.set("TeamIds", [
{
__type: "Pointer",
className: "contracts_Teams",
objectId: formdata.team
}
]);
}
if (localUser && localUser.OrganizationId) {
extUser.set("OrganizationId", {
__type: "Pointer",
className: "contracts_Organizations",
objectId: localUser.OrganizationId.objectId
});
}
if (localUser && localUser.Company) {
extUser.set("Company", localUser.Company);
}
if (localStorage.getItem("TenantId")) {
extUser.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
}
const timezone = usertimezone;
if (timezone) {
extUser.set("Timezone", timezone);
}
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", formdata.name);
_user.set("username", formdata.email);
_user.set("email", formdata.email);
_user.set("password", formdata.password);
if (formdata.phone) {
_user.set("phone", formdata.phone);
}
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
props.showAlert("success", t("user-created-successfully"));
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: formdata.email };
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.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);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
props.showAlert("success", t("user-created-successfully"));
} else {
setIsFormLoader(false);
props.showAlert("danger", t("something-went-wrong-mssg"));
}
}
} catch (err) {
console.log("err", err);
setIsFormLoader(false);
props.showAlert("danger", t("something-went-wrong-mssg"));
}
} else {
props.showAlert("danger", t("something-went-wrong-mssg"));
}
}
}
};
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
if (props.closePopup) {
props.closePopup();
}
};
const handleChange = (event) => {
let { name, value } = event.target;
if (name === "email") {
value = value?.toLowerCase()?.replace(/\s/g, "");
}
setFormdata((prev) => ({ ...prev, [name]: value }));
};
const copytoclipboard = (text) => {
copytoData(text);
props.showAlert("success", t("copied"));
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title={t("add-user")} />
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<div className="w-full mx-auto">
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
required
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"
/>
</div>
<div className="mb-3">
<label className="block text-xs text-gray-700 font-semibold">
{t("password")}
</label>
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
<input
type="text"
name="phone"
placeholder={t("phone-optional")}
value={formdata.phone}
onChange={(e) => handleChange(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("cancel")}
</div>
</div>
</form>
</div>
</div>
);
};
export default AddUser;
+288
View File
@@ -0,0 +1,288 @@
import { useEffect, useState } from "react";
import Parse from "parse";
import Loader from "../primitives/Loader";
import {
copytoData,
usertimezone
} from "../constant/Utils";
import {
emailRegex,
} from "../constant/const";
import {
useTranslation
} from "react-i18next";
import { withSessionValidation } from "../utils";
function generatePassword(length) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
const AddUser = (props) => {
const { t } = useTranslation();
const [formdata, setFormdata] = useState({
name: "",
phone: "",
email: "",
team: "",
password: "",
role: ""
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
const role = ["OrgAdmin", "Editor", "User"];
useEffect(() => {
getTeamList();
// eslint-disable-next-line
}, []);
const getTeamList = async () => {
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
const teamRes = await Parse.Cloud.run("getteams", { active: true });
if (teamRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamRes));
setTeamList(_teamRes);
const allUserId =
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
setFormdata((prev) => ({ ...prev, team: allUserId }));
}
};
const checkUserExist = async () => {
try {
const res = await Parse.Cloud.run("getUserDetails", {
email: formdata.email
});
if (res) {
return true;
} else {
return false;
}
} catch (err) {
console.log("err", err);
}
};
// Define a function to handle form submission
const handleSubmit = withSessionValidation(async (e) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(formdata.email)) {
alert(t("valid-email-alert"));
} else {
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
setIsFormLoader(true);
const res = await checkUserExist();
if (res) {
props.showAlert("danger", t("user-already-exist"));
setIsFormLoader(false);
} else {
if (localStorage.getItem("TenantId")) {
const timezone = usertimezone;
try {
const params = {
name: formdata.name,
email: formdata.email,
phone: formdata.phone,
password: formdata.password,
role: formdata.role,
team: formdata.team,
timezone: timezone,
tenantId: localStorage.getItem("TenantId"),
organization: {
objectId: localUser?.OrganizationId?.objectId,
company: localUser?.Company
},
};
const res = await Parse.Cloud.run("adduser", params);
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find((x) => x.objectId === formdata.team);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
props.showAlert("success", t("user-created-successfully"));
} catch (err) {
console.log("err", err);
setIsFormLoader(false);
props.showAlert("danger", t("something-went-wrong-mssg"));
}
} else {
props.showAlert("danger", t("something-went-wrong-mssg"));
}
}
}
});
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
if (props.closePopup) {
props.closePopup();
}
};
const handleChange = (event) => {
let { name, value } = event.target;
if (name === "email") {
value = value?.toLowerCase()?.replace(/\s/g, "");
}
setFormdata((prev) => ({ ...prev, [name]: value }));
};
const copytoclipboard = (text) => {
copytoData(text);
props.showAlert("success", t("copied"));
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<div className="w-full mx-auto">
<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"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
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"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
required
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-email")}
/>
</div>
<div className="mb-3">
<label className="block text-xs font-semibold">
{t("password")}
</label>
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generated")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs font-semibold"
>
{t("phone")}
</label>
<input
type="text"
name="phone"
placeholder={t("phone-optional")}
value={formdata.phone}
onChange={(e) => handleChange(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("cancel")}
</div>
</div>
</form>
</div>
</div>
);
};
export default AddUser;
-287
View File
@@ -1,287 +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);
useEffect(() => {
signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function to check atleast one signature field exist
const signatureExist = async () => {
setIsDisableBulkSend(false);
const getPlaceholder = props?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.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 = [];
props?.Placeholders?.forEach((element) => {
if (!element.signerObjId) {
users = [
...users,
{
fieldId: element.Id,
email: "",
label: element.Role,
signer: {}
}
];
}
});
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
}
})();
// 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) {
for (const field of item.fields) {
if (!emailRegex.test(field.email)) {
alert(`Invalid email found: ${field.email}`);
return false;
}
}
}
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: props.item.Signers
? [...props.item.Signers, ...existSigner]
: [...existSigner]
});
} else {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
SignatureType: props.signatureType
});
}
});
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>
)}
{!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>{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;
@@ -0,0 +1,62 @@
import { useState, useEffect } from "react";
import ModalUi from "../primitives/ModalUi";
import { useTranslation } from "react-i18next";
const ColumnSelector = ({
isOpen,
allColumns = [],
visibleColumns = [],
columnLabels = {},
onApply,
onClose
}) => {
const { t } = useTranslation();
const [selected, setSelected] = useState(visibleColumns);
const [names, setNames] = useState(columnLabels);
useEffect(() => {
setSelected(visibleColumns);
setNames(columnLabels);
}, [visibleColumns, columnLabels]);
const handleChange = (col) => {
setSelected((prev) =>
prev.includes(col) ? prev.filter((c) => c !== col) : [...prev, col]
);
};
const handleApply = () => {
onApply && onApply(selected, names);
onClose && onClose();
};
return (
<ModalUi isOpen={isOpen} title={t("select-columns")} handleClose={onClose}>
<div className="p-[20px] flex flex-col gap-2">
{allColumns.map((col, i) => (
<div key={col} className="flex justify-between items-center gap-2">
<span className="flex justify-center items-center h-full">
<input
id={col + "_" + i}
type="checkbox"
checked={selected.includes(col)}
onChange={() => handleChange(col)}
className="mb-0 cursor-pointer"
/>
<span className="whitespace-nowrap ml-1">
{t(`report-heading.${col}`, { defaultValue: col })}
</span>
</span>
</div>
))}
<div className="flex justify-start mt-2">
<button onClick={handleApply} className="op-btn op-btn-primary">
{t("apply")}
</button>
</div>
</div>
</ModalUi>
);
};
export default ColumnSelector;
@@ -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;
@@ -0,0 +1,27 @@
import { HTML5Backend } from "react-dnd-html5-backend";
import {
DndProvider,
MouseTransition
} from "react-dnd-multi-backend";
import LazyPage from "../primitives/LazyPage";
import { GuidelinesProvider } from "../context/GuidelinesContext";
const HTML5toTouch = {
backends: [
{
id: "html5",
backend: HTML5Backend,
transition: MouseTransition
}
]
};
export default function DragProvider({ Page, lazy = false }) {
return (
<DndProvider options={HTML5toTouch}>
<GuidelinesProvider>
{lazy ? <LazyPage Page={Page} /> : <Page />}
</GuidelinesProvider>
</DndProvider>
);
}
@@ -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;
@@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import dp from "../assets/images/dp.png";
import FullScreenButton from "./FullScreenButton";
import ThemeToggle from "./ThemeToggle";
import { useNavigate } from "react-router";
import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize";
@@ -11,26 +12,45 @@ import {
} from "../constant/Utils";
import { useTranslation } from "react-i18next";
import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { toggleSidebar } from "../redux/reducers/sidebarReducer";
import { sessionStatus } from "../redux/reducers/userReducer";
const Header = ({ showSidebar, setIsMenu, isConsole }) => {
const Header = ({ isConsole, setIsLoggingOut }) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { width } = useWindowSize();
const dispatch = useDispatch();
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [applogo, setAppLogo] = useState("");
const [isDarkTheme, setIsDarkTheme] = useState();
const toggleDropdown = () => {
setIsOpen(!isOpen);
if (width <= 768) {
setIsMenu(false);
closeSidebar();
};
const closeSidebar = () => {
if (width && width <= 768) {
dispatch(toggleSidebar(false));
}
};
useEffect(() => {
initializeHead();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
closeSidebar();
}, [width]);
const showSidebar = () => {
dispatch(toggleSidebar());
};
async function initializeHead() {
const applogo = await getAppLogo();
if (applogo?.logo) {
@@ -40,13 +60,15 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
setAppLogo(logo);
}
}
const closeDropdown = async () => {
const handleLogout = async () => {
setIsOpen(false);
setIsLoggingOut(true);
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
} finally {
dispatch(sessionStatus(true));
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
@@ -54,6 +76,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
let favicon = localStorage.getItem("favicon");
localStorage.clear();
saveLanguageInLocal(i18n);
@@ -63,7 +86,8 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
localStorage.setItem("favicon", favicon);
setIsLoggingOut(false);
navigate("/");
};
@@ -83,9 +107,30 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
};
}, [isOpen]);
useEffect(() => {
const updateThemeStatus = () => {
const isDarkTheme =
document.documentElement.getAttribute("data-theme") === "opensigndark";
setIsDarkTheme(isDarkTheme);
};
updateThemeStatus();
const observer = new MutationObserver(() => {
updateThemeStatus();
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"]
});
return () => observer.disconnect();
}, []);
return (
<div>
<div className="op-navbar bg-base-100 shadow">
<>
<div className="op-navbar bg-base-100 shadow touch-none">
<div className="flex-none">
<button
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
@@ -95,11 +140,18 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
</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"
src={applogo}
src={
isDarkTheme
? "/static/js/assets/images/logo-dark.png"
: applogo
}
alt="logo"
/>
)}
@@ -145,7 +197,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
</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-52 ${
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"
}`}
>
@@ -170,20 +222,41 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
<i className="fa-light fa-user"></i> {t("profile")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
}}
>
<span>
<i className="fa-light fa-lock"></i>{" "}
{t("change-password")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
navigate("/verify-document");
}}
>
<span>
<i className="fa-light fa-lock"></i>{" "}
{t("change-password")}
<i className="fa-light fa-check-square"></i>{" "}
{t("verify-document")}
</span>
</li>
<li>
<span>
<i className="fa-light fa-moon"></i>
{t("dark-mode")}
<span className="text-[10px] font-semibold bg-base-300 text-base-content px-1 rounded-md">
BETA
</span>
<ThemeToggle />
</span>
</li>
</>
)}
<li onClick={closeDropdown}>
<li onClick={handleLogout}>
<span>
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
{t("log-out")}
@@ -193,7 +266,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
</div>
</div>
</div>
</div>
</>
);
};
@@ -1,7 +1,9 @@
import React from "react";
import { Document, Page } from "react-pdf";
import { Stage, Layer, Rect, Text } from "react-konva";
import { useTranslation } from "react-i18next";
const RenderDebugPdf = (props) => {
const { t } = useTranslation();
return (
<div>
<div className="sticky top-0 p-[10px] z-10 bg-white border-[1px] border-[gray] my-[5px]">
@@ -12,10 +14,9 @@ const RenderDebugPdf = (props) => {
onMouseMove={props.handleMouseMoveDiv}
>
<Document
onLoadError={() => {
props.setPdfLoadFail(false);
}}
loading={"Loading Document.."}
onLoadError={() => props.setPdfLoadFail(false)}
loading={t("loading-doc")}
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
onLoadSuccess={props.pageDetails}
ref={props.pdfRef}
file={props.pdfUrl}
@@ -26,7 +26,7 @@ function RotateAlert(props) {
props.setShowRotateAlert({ status: false, degree: 0 })
}
type="button"
className="op-btn op-btn-ghost shadow-md"
className="op-btn op-btn-ghost text-base-content shadow-md"
>
{t("no")}
</button>
@@ -0,0 +1,41 @@
import { useEffect, useState } from "react";
const ThemeToggle = () => {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
const storedTheme = localStorage.getItem("theme");
if (storedTheme === "dark") {
setIsDark(true);
document.documentElement.setAttribute("data-theme", "opensigndark");
} else {
document.documentElement.setAttribute("data-theme", "opensigncss");
}
}, []);
const handleChange = () => {
const newTheme = !isDark;
setIsDark(newTheme);
if (newTheme) {
document.documentElement.setAttribute("data-theme", "opensigndark");
localStorage.setItem("theme", "dark");
} else {
document.documentElement.setAttribute("data-theme", "opensigncss");
localStorage.setItem("theme", "light");
}
};
return (
<>
<input
id="dark-mode-toggle"
type="checkbox"
className="op-toggle checked:[--tglbg:#3368ff] transition-all checked:bg-white"
checked={isDark}
onChange={handleChange}
/>
</>
);
};
export default ThemeToggle;
-21
View File
@@ -1,21 +0,0 @@
import React from "react";
import { Helmet } from "react-helmet";
function Title({ title, drive }) {
const appName =
"OpenSign™";
return (
<Helmet>
<title>{drive ? title : `${title} - ${appName}`}</title>
<meta name="description" content={`${title} - ${appName}`} />
<link
rel="icon"
type="image/png"
href={localStorage.getItem("fev_Icon")}
sizes="40x40"
/>
</Helmet>
);
}
export default Title;
+81
View File
@@ -0,0 +1,81 @@
import { useLocation, matchPath } from "react-router";
import { useTranslation } from "react-i18next";
import { useMemo } from "react";
import { useManifestUrl } from "../hook/useManifestUrl";
const TITLE_MAP = {
"/": "login",
// Homelayout
"/dashboard/35KBoSgoAK": "Dashboard",
"/form/sHAnZphf69": "Sign Yourself",
"/form/8mZzFxbG1z": "Request Signatures",
"/form/template": "New Template",
"/report/6TeaPr321t": "Templates",
"/report/4Hhwbp482K": "Need your sign",
"/report/1MwEuxLEkF": "In Progress",
"/report/kQUoW4hUXz": "Completed",
"/report/ByHuevtCFY": "Drafts",
"/report/UPr2Fm5WY3": "Declined",
"/report/zNqBHXHsYH": "Expired",
"/report/contacts": "Contactbook",
"/drive": "Drive",
"/managesign": "My Signature",
"/preferences": "Preferences",
"/users": "Users",
"/profile": "profile",
"/changepassword": "change-password",
"/verify-document": "verify-document",
"/signaturePdf/:docId": "Sign Yourself",
"/placeHolderSign/:docId": "Request Signatures",
"/template/:templateId": "New Template",
"/recipientSignPdf/:docId": "Request Signatures",
"/recipientSignPdf/:docId/:contactBookId": "Request Signatures",
"/load/recipientSignPdf/:docId/:contactBookId": "Request Signatures",
// alone
"/debugpdf": "Debug Pdf",
"/forgetpassword": "forgot-password",
"/success": "success",
"/addadmin": "add-admin",
"/upgrade-2.1": "add-admin",
"/draftDocument": "New Document",
"/login/:base64url": "Request Signatures",
};
function resolveTitle(pathname, override) {
if (override) return override;
for (let [pattern, label] of Object.entries(TITLE_MAP)) {
if (matchPath({ path: pattern, end: true }, pathname)) {
return label;
}
}
return "";
}
export default function Title() {
const { pathname, state } = useLocation();
const { t } = useTranslation();
const appName =
"OpenSign™";
const logo = useMemo(() => localStorage.getItem("favicon"), []);
const prefix = useMemo(
() => resolveTitle(pathname, state?.title),
[pathname, state?.title]
);
const title = useMemo(
() => (prefix ? `${t(prefix)} - ${appName}` : appName),
[t, prefix, appName]
);
const manifestUrl = useManifestUrl(appName, logo);
return (
<>
<title>{title}</title>
<meta name="description" content={title} />
{logo && <link rel="icon" type="image/png" href={logo} />}
<link rel="manifest" href={manifestUrl} />
</>
);
}
@@ -0,0 +1,645 @@
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,
} 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 emails = [];
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
};
})
)
);
const signerEmail =
normalizeKey(role?.email) || role?.signerPtr?.Email;
if (signerEmail) emails.push(signerEmail);
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(emails);
// 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");
const isSignExist =
filtered?.length > 0 &&
filtered.every((p) =>
p?.placeHolder?.some((h) =>
h?.pos?.some((x) => x?.type === "signature")
)
);
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()) {
let email = "";
const rowNumber = rowIndex + 1;
const filterFields = item?.fields || [];
for (const field of filterFields) {
// skip prefill
if (field?.label === "prefill") continue;
const params = { email: field.email, row: rowNumber };
if (!field.email) {
alert(t("email-not-found-in-row", { ...params }));
return false;
} else if (!emailRegex.test(field.email)) {
alert(t("invalid-email-found-in-row", { ...params }));
return false;
} else if (email === field.email || emails?.includes(field.email)) {
alert(t("duplicate-email-found-in-row", { ...params }));
return false;
} else {
email = field.email;
}
}
}
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,62 @@
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.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,646 @@
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 CellsInput from "../../shared/fields/CellsInput";
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 regExpression = inputValidation(serverRegex, inputType);
const pattern = useMemo(() => toHtmlPattern(regExpression), [regExpression]);
useEffect(() => {
const response =
widget?.options?.response ?? widget?.options?.defaultValue ?? "";
const isNumber = inputType === "number" && response;
setWord(isNumber ? Number(response) : response);
}, []);
const handleChange = (words) => {
const value = words;
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">
<CellsInput
hint={
widget?.options?.hint ||
t("enter-value", { value: widget?.options?.name })
}
count={count}
value={word}
onChange={handleChange}
only="all"
autoFocus
/>
<input
id={`widget-${widget?.options?.name}-${widget.key}`}
value={word}
required={isRequired}
tabIndex={-1}
onChange={() => {}}
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="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>
);
};
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;
@@ -29,7 +29,7 @@ const DashboardButton = (props) => {
: "cursor-default"
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
>
<div className="flex flex-row items-center">
<div className="flex flex-row items-center text-base-content">
<div className="flex flex-row items-center">
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
<i
@@ -39,7 +39,7 @@ const DashboardButton = (props) => {
></i>
</span>
</div>
<div className="text-lg ml-3 text-base-content">
<div className="text-lg ml-3">
{t(`sidebar.${props.Label}`)}
{props.Label === "Sign yourself" && (
<div className="text-gray-500 text-xs mt-1">
@@ -1,10 +1,11 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import axios from "axios";
import Parse from "parse";
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 {
@@ -148,7 +149,7 @@ const DashboardCard = (props) => {
);
let arr = [];
for (const obj of listData) {
const isSigner = obj.Signers.some(
const isSigner = obj.Signers?.some(
(item) => item.UserId.objectId === currentUser.id
);
if (isSigner) {
@@ -168,31 +169,27 @@ const DashboardCard = (props) => {
}
}
setresponse(arr.length);
setLoading(false);
});
} else {
await axios.get(url, { headers: headers }).then((res) => {
if (res.data.results.length > 0) {
setLoading(false);
if (props.Data.key !== "count") {
setresponse(res.data.results[0][props.Data.key]);
} else {
setresponse(res.data[props.Data.key]);
}
if (res?.data?.[props.Data.key]) {
setresponse(parseInt(res.data[props.Data.key]));
} else if (res?.data?.results?.length > 0) {
setresponse(res.data.results.length);
} else {
setresponse(0);
setLoading(false);
}
});
}
} catch (e) {
console.error("Problem", e);
} finally {
setLoading(false);
}
}
};
});
const filterRender = async () => {
const filterRender = withSessionValidation(async () => {
if (props.FilterData && props.FilterData.queryType === "function") {
setLoading(true);
try {
@@ -270,7 +267,7 @@ const DashboardCard = (props) => {
setLoading(false);
}
}
};
});
const setFormat = (val) => {
switch (props.Format) {
@@ -1,158 +0,0 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import ReportTable from "../../primitives/GetReportDisplay";
import reportJson from "../../json/ReportJson";
import axios from "axios";
import Loader from "../../primitives/Loader";
import { useTranslation } from "react-i18next";
function DashboardReport(props) {
const { t } = useTranslation();
// console.log("props ", props)
const [List, setList] = useState([]);
const [isLoader, setIsLoader] = useState(true);
const [reportName, setReportName] = useState("");
const [actions, setActions] = useState([]);
const [heading, setHeading] = useState([]);
const [isNextRecord, setIsNextRecord] = useState(false);
const [isMoreDocs, setIsMoreDocs] = useState(true);
const abortController = new AbortController();
const docPerPage = 5;
useEffect(() => {
setReportName("");
getReportData(props.Record.reportId);
// Function returned from useEffect is called on unmount
return () => {
setIsLoader(true);
setList([]);
setIsNextRecord(false);
// Here it'll abort the fetch
abortController.abort();
};
// eslint-disable-next-line
}, [props.Record.reportId]);
// below useEffect call when isNextRecord state is true and fetch next record
useEffect(() => {
if (isNextRecord) {
getReportData(props.Record.reportId, List.length, 20);
}
// eslint-disable-next-line
}, [isNextRecord]);
const getReportData = async (id, skipUserRecord = 0, limit = 20) => {
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 };
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
);
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) {
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);
}
} else {
setIsLoader(false);
}
};
return (
<>
{isLoader ? (
<div className="h-[250px] flex justify-center items-center">
<Loader />
</div>
) : (
<>
{reportName ? (
<ReportTable
ReportName={reportName}
List={List}
setList={setList}
actions={actions}
heading={heading}
setIsNextRecord={setIsNextRecord}
isMoreDocs={isMoreDocs}
docPerPage={docPerPage}
/>
) : (
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded">
<div className="text-center">
<p className="text-xl text-black">{t("report-not-found")}</p>
</div>
</div>
)}
</>
)}
</>
);
}
export default DashboardReport;
@@ -0,0 +1,229 @@
import { useEffect, useState, useRef } from "react";
import Parse from "parse";
import DocumentsReport from "../../reports/document/DocumentsReport";
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();
const [List, setList] = useState([]);
const [isLoader, setIsLoader] = useState(true);
const [reportName, setReportName] = useState("");
const [actions, setActions] = useState([]);
const [heading, setHeading] = useState([]);
const [isNextRecord, setIsNextRecord] = useState(false);
const [isMoreDocs, setIsMoreDocs] = useState(true);
const abortController = new AbortController();
const docPerPage = 5;
const [searchTerm, setSearchTerm] = useState("");
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const [isSearchResult, setIsSearchResult] = useState(false);
const debounceTimer = useRef(null);
useEffect(() => {
setReportName("");
setSearchTerm("");
setMobileSearchOpen(false);
getReportData(props.Record.reportId, 0, 20, "");
// Function returned from useEffect is called on unmount
return () => {
setIsLoader(true);
setList([]);
setIsNextRecord(false);
// Here it'll abort the fetch
abortController.abort();
};
// eslint-disable-next-line
}, [props.Record.reportId]);
// below useEffect call when isNextRecord state is true and fetch next record
useEffect(() => {
if (isNextRecord) {
getReportData(props.Record.reportId, List.length, 20, searchTerm);
}
// eslint-disable-next-line
}, [isNextRecord]);
const handleSearchChange = withSessionValidation(async (e) => {
const term = e.target.value.toLowerCase();
setSearchTerm(term);
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
debounceTimer.current = setTimeout(async () => {
try {
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
};
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const res = await axios.post(
url,
{
reportId: props.Record.reportId,
searchTerm: term,
skip: 0,
limit: docPerPage
},
{ headers }
);
const data = res.data?.result || [];
if (!data.error) {
setList(data);
setIsMoreDocs(data.length >= docPerPage);
setIsNextRecord(false);
setIsSearchResult(true);
}
} catch (err) {
console.error("Search error:", err);
}
}, 300);
setIsSearchResult(false);
});
const handleSearchPaste = (e) => {
setTimeout(() => {
handleSearchChange({ target: { value: e.target.value } });
}, 0);
};
useEffect(() => {
return () => {
if (debounceTimer.current) {
clearTimeout(debounceTimer.current);
}
};
}, []);
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
);
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) {
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);
}
} else {
setIsLoader(false);
}
}
);
return (
<>
{isLoader ? (
<div className="h-[250px] flex justify-center items-center">
<Loader />
</div>
) : (
<>
{reportName ? (
<DocumentsReport
ReportName={reportName}
List={List}
setList={setList}
actions={actions}
heading={heading}
setIsNextRecord={setIsNextRecord}
isMoreDocs={isMoreDocs}
docPerPage={docPerPage}
mobileSearchOpen={mobileSearchOpen}
setMobileSearchOpen={setMobileSearchOpen}
searchTerm={searchTerm}
handleSearchChange={handleSearchChange}
handleSearchPaste={handleSearchPaste}
isSearchResult={isSearchResult}
/>
) : (
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded-box">
<div className="text-center text-xl text-base-content">
{t("report-not-found")}
</div>
</div>
)}
</>
)}
</>
);
}
export default DashboardReport;
@@ -1,8 +1,9 @@
import React, { Suspense, lazy } from "react";
import { useState, useEffect, Suspense } from "react";
import { lazyWithRetry } from "../../utils";
import { useTranslation } from "react-i18next";
const DashboardButton = lazy(() => import("./DashboardButton"));
const DashboardCard = lazy(() => import("./DashboardCard"));
const DashboardReport = lazy(() => import("./DashboardReport"));
const DashboardButton = lazyWithRetry(() => import("./DashboardButton"));
const DashboardCard = lazyWithRetry(() => import("./DashboardCard"));
const DashboardReport = lazyWithRetry(() => import("./DashboardReport"));
const buttonList = [
{
label: "Sign yourself",
@@ -19,6 +20,7 @@ const buttonList = [
];
const GetDashboard = (props) => {
const { t } = useTranslation();
const Button = ({ label, redirectId, redirectType, icon }) => (
<DashboardButton
Icon={icon}
@@ -58,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>
@@ -92,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>
);
}

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