Compare commits

..
213 Commits
Author SHA1 Message Date
prafull-opensignlabsandGitHub f1ab35e71e Merge pull request #1340 from OpenSignLabs/mobile-issue
fix: issue of assigned signer gets deselected in mobile view
2024-10-15 10:25:18 +05:30
RaktimaNXG 240d892b94 fix: issue of assigned signer gets deselected in mobile view 2024-10-14 17:09:16 +05:30
AndrewandGitHub 364d96e7f6 Merge pull request #1339 from OpenSignLabs/validation 2024-10-14 16:16:29 +05:30
prafull-opensignlabs 03dfd58ac5 feat: custom PFX functionality 2024-10-14 16:14:54 +05:30
prafull-opensignlabsandGitHub 716023b534 Merge pull request #1323 from OpenSignLabs/react-tour
Fix: resolved text box font size issue during resizing and prevented unnecessary popups from opening when resizing widgets.
2024-10-11 19:15:26 +05:30
RaktimaandGitHub 67b3417ac9 Update Placeholder.js 2024-10-11 19:13:00 +05:30
RaktimaandGitHub f3667f6aaf Update Placeholder.js 2024-10-11 19:07:54 +05:30
AndrewandGitHub 7b35e71f3d Merge pull request #1321 from OpenSignLabs/validation 2024-10-10 17:17:04 +05:30
prafull-opensignlabs 51031878eb Fix 'something went wrong' error when non-existing signer attempts to sign the document 2024-10-10 17:13:22 +05:30
prafull-opensignlabsandGitHub 19d96230cd Merge pull request #1320 from OpenSignLabs/raktima-patch-5
fix: enableTour issue when enableOtp enabled
2024-10-10 14:50:15 +05:30
RaktimaNXG dbee7e2894 fix: enableTour issue when enableOtp enabled 2024-10-10 14:46:59 +05:30
AndrewandGitHub 656241da54 Merge pull request #1318 from OpenSignLabs/validation 2024-10-10 13:33:51 +05:30
prafull-opensignlabs 2a3947ea1d feat: send completed mail to all signers 2024-10-10 12:50:26 +05:30
prafull-opensignlabsandGitHub 9c1b4261dc Merge pull request #1316 from OpenSignLabs/validation
refactor: changes in output of file-adapter's cloud function
2024-10-09 18:41:07 +05:30
prafull-opensignlabs 7c37d8128e refactor: changes in output of fileadapter's cloud function 2024-10-09 18:39:33 +05:30
RaktimaNXG 404785bf29 fix: text box font size issue 2024-10-09 18:05:10 +05:30
prafull-opensignlabsandGitHub dc7fd34b07 Merge pull request #1315 from OpenSignLabs/validation
fix: url is not define
2024-10-09 15:09:09 +05:30
prafull-opensignlabs 86a2975b3c fix: url is not define 2024-10-09 15:06:49 +05:30
prafull-opensignlabsandGitHub f824fc7c81 Merge pull request #1314 from OpenSignLabs/react-tour
fix: issue of enableTour in createDocumentWithTemplate api
2024-10-09 14:39:46 +05:30
RaktimaNXG 244cc30eb6 fix: issue of enableTour in createDocumentWithTemplate api 2024-10-09 14:36:29 +05:30
AndrewandGitHub a833ed8060 Merge pull request #1313 from OpenSignLabs/validation 2024-10-09 13:52:19 +05:30
prafull-opensignlabs 1e869d2d2b fix: handle server down error in add admin for self-hosting 2024-10-09 13:50:20 +05:30
prafull-opensignlabsandGitHub 64df5cfff3 Merge pull request #1312 from OpenSignLabs/validation
fix: handle invalid credentials err while uploading file in form
2024-10-09 11:42:07 +05:30
prafull-opensignlabs d8b2a80e90 fix: handle invalid credentials err while uploading file in form 2024-10-09 11:39:59 +05:30
prafull-opensignlabsandGitHub 10061c061e Merge pull request #1311 from OpenSignLabs/validation
fix: correct migration script
2024-10-09 10:55:04 +05:30
prafull-opensignlabs 6a4124fdf0 fix: migration script 2024-10-09 10:52:38 +05:30
RaktimaandGitHub faaa915163 Merge pull request #1308 from OpenSignLabs/react-tour
fix: resolve duplicate auto-reminder input box issue in request signature form
2024-10-08 21:18:19 +05:30
AndrewandGitHub 3fb911298d Merge pull request #1309 from OpenSignLabs/validation 2024-10-08 20:14:26 +05:30
prafull-opensignlabs 761f81d27c fix: pdf is not loading when custom file storage change 2024-10-08 20:09:38 +05:30
RaktimaNXG d4b7c8ae97 fix: resolve duplicate auto-reminder input box issue in request signature form 2024-10-08 19:02:47 +05:30
prafull-opensignlabsandGitHub 23679e58e1 Merge pull request #1307 from OpenSignLabs/react-tour
fix: enable tour is not working
2024-10-08 16:47:05 +05:30
RaktimaNXG f3e39f18ef fix: enable tour is not working 2024-10-08 16:38:06 +05:30
prafull-opensignlabsandGitHub 55a4cda351 Merge pull request #1302 from OpenSignLabs/react-tour
feat: add enable tour option in request signature and template form
2024-10-08 14:15:19 +05:30
RaktimaNXG f0f270b44d fix: handle form advanced options data for self hosting user 2024-10-08 13:31:30 +05:30
RaktimaNXG 529de2bd90 fix: refactor code 2024-10-08 11:53:06 +05:30
RaktimaNXG 51dd83df09 refactor code 2024-10-08 10:29:14 +05:30
RaktimaNXG 9abe4ffd8e feat: update translation file 2024-10-08 10:27:26 +05:30
RaktimaNXG cdd92f6990 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into react-tour 2024-10-08 10:18:05 +05:30
prafull-opensignlabsandGitHub b08fa9bf60 Merge pull request #1298 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/react-quill-new-3.3.3
build(deps): bump react-quill-new from 3.3.2 to 3.3.3 in /apps/OpenSign
2024-10-08 10:12:20 +05:30
prafull-opensignlabsandGitHub f0bcc3c3e7 Merge pull request #1299 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/aws-sdk/s3-request-presigner-3.665.0
build(deps): bump @aws-sdk/s3-request-presigner from 3.664.0 to 3.665.0 in /apps/OpenSignServer
2024-10-08 10:11:59 +05:30
ZathielandGitHub b9c45f24f5 Merge pull request #1295 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/i18next-23.15.2
build(deps): bump i18next from 23.15.1 to 23.15.2 in /apps/OpenSign
2024-10-07 20:33:06 +02:00
ZathielandGitHub d5f3967558 Merge pull request #1296 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/daisyui-4.12.12
build(deps-dev): bump daisyui from 4.12.11 to 4.12.12 in /apps/OpenSign
2024-10-07 20:32:25 +02:00
ZathielandGitHub d3fccc9b87 Merge pull request #1297 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/eslint-9.12.0
build(deps-dev): bump eslint from 9.11.1 to 9.12.0 in /apps/OpenSign
2024-10-07 20:31:19 +02:00
dependabot[bot]andGitHub 7e6c03faf5 build(deps): bump @aws-sdk/s3-request-presigner in /apps/OpenSignServer
Bumps [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages/s3-request-presigner) from 3.664.0 to 3.665.0.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages/s3-request-presigner/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.665.0/packages/s3-request-presigner)

---
updated-dependencies:
- dependency-name: "@aws-sdk/s3-request-presigner"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 18:28:34 +00:00
ZathielandGitHub 02b2a52280 Merge pull request #1300 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/eslint-9.12.0
build(deps-dev): bump eslint from 9.11.1 to 9.12.0 in /apps/OpenSignServer
2024-10-07 20:28:06 +02:00
ZathielandGitHub 6eb708b155 Merge pull request #1301 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/aws-sdk/client-s3-3.665.0
build(deps): bump @aws-sdk/client-s3 from 3.664.0 to 3.665.0 in /apps/OpenSignServer
2024-10-07 20:26:18 +02:00
prafull-opensignlabsandGitHub 83e6444ddc Merge pull request #1306 from OpenSignLabs/validation
fix: custom file storage not working while create document using template
2024-10-07 16:42:40 +05:30
prafull-opensignlabs 168fe4d680 fix: custom file storage not working while create document using template 2024-10-07 16:40:49 +05:30
prafull-opensignlabsandGitHub 0a10c9a7c9 Merge pull request #1305 from OpenSignLabs/validation
refactor: remove unnecessary code
2024-10-07 14:29:11 +05:30
prafull-opensignlabs 204c530712 refactor: remove unncessary code 2024-10-07 14:22:18 +05:30
RaktimaandGitHub 5e55f0debf Merge branch 'staging' into react-tour 2024-10-07 11:31:58 +05:30
prafull-opensignlabsandGitHub f8bd5a6c86 Merge pull request #1304 from OpenSignLabs/validation
fix: custom file storage not working in use template
2024-10-07 11:29:09 +05:30
prafull-opensignlabs 101e7a9a49 fix: custom file storage not working in use template 2024-10-07 11:27:45 +05:30
RaktimaandGitHub 271e79d6a2 Update translation.json 2024-10-07 10:38:57 +05:30
RaktimaandGitHub 2ca8f8aff8 Update translation.json 2024-10-07 10:38:34 +05:30
RaktimaandGitHub bc6891e746 Update translation.json 2024-10-07 10:38:10 +05:30
RaktimaandGitHub 26aaeccf11 Update translation.json 2024-10-07 10:37:38 +05:30
RaktimaandGitHub 91b512b693 Merge branch 'staging' into react-tour 2024-10-07 10:05:50 +05:30
dependabot[bot]andGitHub 88d7917a84 build(deps): bump @aws-sdk/client-s3 in /apps/OpenSignServer
Bumps [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) from 3.664.0 to 3.665.0.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.665.0/clients/client-s3)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 00:57:25 +00:00
dependabot[bot]andGitHub 7ae0964dd1 build(deps-dev): bump eslint in /apps/OpenSignServer
Bumps [eslint](https://github.com/eslint/eslint) from 9.11.1 to 9.12.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.11.1...v9.12.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 00:57:02 +00:00
dependabot[bot]andGitHub edb0c305dd build(deps): bump react-quill-new from 3.3.2 to 3.3.3 in /apps/OpenSign
Bumps [react-quill-new](https://github.com/VaguelySerious/react-quill) from 3.3.2 to 3.3.3.
- [Changelog](https://github.com/VaguelySerious/react-quill/blob/master/CHANGELOG.md)
- [Commits](https://github.com/VaguelySerious/react-quill/commits)

---
updated-dependencies:
- dependency-name: react-quill-new
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 00:08:54 +00:00
dependabot[bot]andGitHub 0321f35869 build(deps-dev): bump eslint from 9.11.1 to 9.12.0 in /apps/OpenSign
Bumps [eslint](https://github.com/eslint/eslint) from 9.11.1 to 9.12.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.11.1...v9.12.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 00:08:42 +00:00
dependabot[bot]andGitHub 1870f8e90e build(deps-dev): bump daisyui from 4.12.11 to 4.12.12 in /apps/OpenSign
Bumps [daisyui](https://github.com/saadeghi/daisyui) from 4.12.11 to 4.12.12.
- [Release notes](https://github.com/saadeghi/daisyui/releases)
- [Changelog](https://github.com/saadeghi/daisyui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/saadeghi/daisyui/compare/v4.12.11...v4.12.12)

---
updated-dependencies:
- dependency-name: daisyui
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 00:08:30 +00:00
dependabot[bot]andGitHub 6e112b904f build(deps): bump i18next from 23.15.1 to 23.15.2 in /apps/OpenSign
Bumps [i18next](https://github.com/i18next/i18next) from 23.15.1 to 23.15.2.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.15.1...v23.15.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 00:08:12 +00:00
AndrewandGitHub dca8d2533e Merge pull request #1294 from OpenSignLabs/validation 2024-10-04 18:59:29 +05:30
prafull-opensignlabs c57957b56d fix: add error for uncompatible pdf 2024-10-04 18:45:15 +05:30
AndrewandGitHub 1f5ab87653 Merge pull request #1293 from OpenSignLabs/validation 2024-10-04 15:28:55 +05:30
RaktimaNXG d5513e90bd fix: update form advanced option layout design 2024-10-04 15:17:15 +05:30
prafull-opensignlabs 00fcd17285 feat: enable custom file storage for document siging 2024-10-04 12:59:10 +05:30
AndrewandGitHub 356ab4e7cb Merge pull request #1291 from OpenSignLabs/form-layout
fix: mobile view issue with text box widget design for value entry.
2024-10-03 21:36:56 +05:30
AndrewandGitHub 572cb55bf8 Update node version 2024-10-03 21:33:25 +05:30
RaktimaNXG 08b83469e3 fix: text box widgets font size issue 2024-10-03 20:36:34 +05:30
prafull-opensignlabsandGitHub 6939150d2b Merge pull request #1290 from OpenSignLabs/fix-issue
fix: issue of optional widget fields disappear after signing process
2024-10-03 19:25:01 +05:30
RaktimaNXG cf9a4d8a26 fix: issue of optional widgets disappear after signing 2024-10-03 18:34:30 +05:30
RaktimaNXG a5f8e4fc58 fix: updated documents order issue in opensign drive 2024-10-03 16:52:15 +05:30
prafull-opensignlabsandGitHub 775e325503 Merge pull request #1289 from OpenSignLabs/update_spanish
fix: update some phrase of spanish translation and add spanish language dropdown
2024-10-03 15:51:29 +05:30
RaktimaNXG 0c472ba22c fix: update form advanced options design 2024-10-03 14:20:28 +05:30
RaktimaNXG 4c0d15d6ef feat: update spanish translation 2024-10-03 13:40:37 +05:30
AndrewandGitHub 28cea81f61 Merge pull request #1288 from OpenSignLabs/all-contributors/add-1024mb
docs: add 1024mb as a contributor for translation
2024-10-03 13:15:53 +05:30
allcontributors[bot]andGitHub d975dae88f docs: update .all-contributorsrc 2024-10-03 07:45:30 +00:00
allcontributors[bot]andGitHub 30297584d5 docs: update README.md 2024-10-03 07:45:29 +00:00
AndrewandGitHub 1105f8e608 Merge pull request #1285 from 1024mb/spanish-translation
Add Spanish translation
2024-10-03 13:14:28 +05:30
Ángel Beretta eeae097a54 Update the changed strings from the original lang file 2024-10-01 21:40:22 -05:00
1024mbandGitHub 8199e5a253 Merge branch 'OpenSignLabs:staging' into spanish-translation 2024-10-01 21:31:40 -05:00
Ángel Beretta 08c7f4b491 Add Spanish translation 2024-10-01 21:28:57 -05:00
RaktimaNXG 634744c22d fix: text box widgets design to enter value issue in mobile view 2024-10-01 17:58:30 +05:30
prafull-opensignlabsandGitHub 45f4bdd1c5 Merge pull request #1282 from OpenSignLabs/form-layout
fix: document download issue in the disabled OTP public sign flow before signing and subscription issue in the guest login process
2024-10-01 12:46:34 +05:30
RaktimaNXG c8f5c80d5b fix: update tagline maxlength 2024-10-01 12:40:53 +05:30
RaktimaNXG 5556fafa94 fix: document download issue in the disabled OTP public sign flow before signing and subscription issue in the guest login process 2024-10-01 12:20:28 +05:30
prafull-opensignlabsandGitHub 20c21cb964 Merge pull request #1279 from OpenSignLabs/form-layout
fix: unexpected finish button visible after finish signature in disable otp public signing flow
2024-09-30 21:27:43 +05:30
AndrewandGitHub d2de889995 Merge pull request #1278 from OpenSignLabs/validation 2024-09-30 21:26:23 +05:30
RaktimaNXG 05cf3ef7fe fix: unexpected finish button visible after finish signature in disable otp public signing flow 2024-09-30 20:55:43 +05:30
prafull-opensignlabs 6bd8a8fea8 refactor: set fileAcl to none 2024-09-30 19:51:17 +05:30
prafull-opensignlabsandGitHub 4cba1a2fd2 Merge pull request #1277 from OpenSignLabs/form-layout
fix: disable otp issue in public signing flow
2024-09-30 19:38:31 +05:30
RaktimaNXG 86f0622c4a fix: disable otp issue in public signing flow 2024-09-30 19:37:17 +05:30
RaktimaNXG 85ad7ca187 feat: re-design form layout 2024-09-30 17:20:31 +05:30
AndrewandGitHub 879279a382 Merge pull request #1276 from OpenSignLabs/validation 2024-09-30 16:25:31 +05:30
prafull-opensignlabs 74df84e449 feat: add upload option in initials in my signature 2024-09-30 16:23:48 +05:30
ZathielandGitHub 2862ae3302 Merge pull request #1270 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/mongoose-8.7.0 2024-09-30 11:47:27 +02:00
ZathielandGitHub 7d0dfe480c Merge pull request #1271 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/react-bootstrap-2.10.5 2024-09-30 11:47:10 +02:00
ZathielandGitHub c47748ed4d Merge pull request #1272 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/eslint-plugin-react-7.37.0 2024-09-30 11:46:59 +02:00
ZathielandGitHub 396504138c Merge pull request #1274 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/commitizen-4.3.1 2024-09-30 11:46:44 +02:00
ZathielandGitHub 6e067a160c Merge pull request #1268 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/npm_and_yarn-dcddfe2757 2024-09-30 11:46:27 +02:00
RaktimaNXG 66a56757d1 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into react-tour 2024-09-30 12:35:11 +05:30
RaktimaNXG 088ec5580a feat: add enable tour option in request signature and template form 2024-09-30 12:27:33 +05:30
prafull-opensignlabsandGitHub 63b2693fe0 Merge pull request #1275 from OpenSignLabs/validation
fix: enableOTP is not working correctly
2024-09-30 10:44:14 +05:30
prafull-opensignlabs 76a69d8f3b fix: enableOTP is not working correctly 2024-09-30 10:42:31 +05:30
dependabot[bot]andGitHub 5cd0f55cb2 build(deps-dev): bump commitizen from 4.3.0 to 4.3.1 in /apps/OpenSign
Bumps [commitizen](https://github.com/commitizen/cz-cli) from 4.3.0 to 4.3.1.
- [Release notes](https://github.com/commitizen/cz-cli/releases)
- [Commits](https://github.com/commitizen/cz-cli/compare/v4.3.0...v4.3.1)

---
updated-dependencies:
- dependency-name: commitizen
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 01:05:02 +00:00
dependabot[bot]andGitHub db640b04a6 build(deps-dev): bump eslint-plugin-react in /apps/OpenSign
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.36.1 to 7.37.0.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.36.1...v7.37.0)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 01:04:29 +00:00
dependabot[bot]andGitHub 0665bcf30a build(deps): bump react-bootstrap in /apps/OpenSign
Bumps [react-bootstrap](https://github.com/react-bootstrap/react-bootstrap) from 2.10.4 to 2.10.5.
- [Release notes](https://github.com/react-bootstrap/react-bootstrap/releases)
- [Changelog](https://github.com/react-bootstrap/react-bootstrap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-bootstrap/react-bootstrap/compare/v2.10.4...v2.10.5)

---
updated-dependencies:
- dependency-name: react-bootstrap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 01:04:19 +00:00
dependabot[bot]andGitHub 7e8ed42f46 build(deps): bump mongoose from 8.6.3 to 8.7.0 in /apps/OpenSignServer
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.6.3 to 8.7.0.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.6.3...8.7.0)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 00:49:48 +00:00
dependabot[bot]andGitHub 8d7b1a5b9b build(deps): bump rollup in /apps/OpenSign in the npm_and_yarn group
Bumps the npm_and_yarn group in /apps/OpenSign with 1 update: [rollup](https://github.com/rollup/rollup).


Updates `rollup` from 2.79.1 to 2.79.2
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.79.1...v2.79.2)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-28 10:45:46 +00:00
prafull-opensignlabsandGitHub c6829a7d5e Merge pull request #1267 from OpenSignLabs/feat-embed
update the Angular code snippet syntax for use in the @opensign/angular package
2024-09-27 15:17:56 +05:30
RaktimaNXG bef8fbf183 update the Angular code snippet syntax for use in the @opensign/angular package 2024-09-27 14:56:38 +05:30
prafull-opensignlabsandGitHub fca95f09cc Merge pull request #1266 from OpenSignLabs/validation
fix: when pull latest using docker pdf files get remove with useLocal
2024-09-26 17:09:49 +05:30
prafull-opensignlabs 4d6cdf8823 fix: admin exists error 2024-09-26 17:05:06 +05:30
prafull-opensignlabs e34f52dcc5 fix: when pull latest using docker pdf files get remove with useLocal 2024-09-26 17:02:42 +05:30
prafull-opensignlabsandGitHub 91cc4e6f3d Merge pull request #1265 from OpenSignLabs/feat-embed
feat: add copy public URL option in template report to copy public template URL for public signing
2024-09-26 16:20:37 +05:30
RaktimaNXG 429b7dcb0e fix: refactor code 2024-09-26 16:06:37 +05:30
RaktimaNXG 5ffa681a80 feat: add copy public URL option to copy public url 2024-09-26 14:56:25 +05:30
AndrewandGitHub 91364db608 Merge pull request #1263 from OpenSignLabs/validation 2024-09-25 21:24:04 +05:30
prafull-opensignlabs 2433c68f27 fix: capture error message correctly 2024-09-25 21:22:48 +05:30
prafull-opensignlabs 6ea254d9ac fix: show proper error if server down in self-hosting 2024-09-25 21:01:17 +05:30
AndrewandGitHub a4d1df8324 Merge pull request #1262 from OpenSignLabs/validation
feat: add signature and png type image with transparent background in pdf
2024-09-25 17:02:05 +05:30
prafull-opensignlabs ba15611903 feat: add signature and png type image with transparent background in pdf 2024-09-25 17:01:03 +05:30
prafull-opensignlabsandGitHub eda6113792 Merge pull request #1261 from OpenSignLabs/raktima-patch-2
fix: update npm package name of public sign
2024-09-25 12:04:21 +05:30
RaktimaNXG 4f1509c836 fix: handle condition of embed options 2024-09-25 11:53:15 +05:30
prafull-opensignlabsandGitHub dfcf32ef3e Merge pull request #1260 from OpenSignLabs/validation
build(deps): update dependencies
2024-09-25 11:14:53 +05:30
prafull-opensignlabs 4a95257ee2 build(deps): update googleapis package 2024-09-25 11:13:18 +05:30
prafull-opensignlabs ee1e13ef61 build(deps): update dependencies 2024-09-25 11:00:57 +05:30
prafull-opensignlabsandGitHub c9c099ffc5 Merge pull request #1259 from OpenSignLabs/raktima-patch-4
fix: document download & document redirect on wrong route issue in opensign-drive
2024-09-25 10:23:30 +05:30
RaktimaNXG a80f4fb3c9 fix: document download & document redirect on wrong route issue in opensign-drive 2024-09-25 10:21:16 +05:30
prafull-opensignlabsandGitHub 941f3ac47f Merge pull request #1258 from OpenSignLabs/validation
fix: valid file is not attached to mail in signyourself flow
2024-09-25 10:12:11 +05:30
prafull-opensignlabs 80d3200d57 fix: valid file is not attached to mail in signyourself flow 2024-09-25 10:11:01 +05:30
ZathielandGitHub 55926e25d2 Merge pull request #1206 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/eslint-plugin-react-7.36.1
build(deps-dev): bump eslint-plugin-react from 7.35.0 to 7.36.1 in /apps/OpenSign
2024-09-24 19:46:14 +02:00
ZathielandGitHub a31f3ebad7 Merge pull request #1251 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/eslint-9.11.1
build(deps-dev): bump eslint from 9.9.1 to 9.11.1 in /apps/OpenSignServer
2024-09-24 19:44:11 +02:00
RaktimaNXG dc9c32c6ee fix: update npm package name of public sign 2024-09-24 20:19:16 +05:30
dependabot[bot]andGitHub dac3aac2a1 build(deps-dev): bump eslint in /apps/OpenSignServer
Bumps [eslint](https://github.com/eslint/eslint) from 9.9.1 to 9.11.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.9.1...v9.11.1)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-24 14:06:57 +00:00
AndrewandGitHub 40196e823c Merge pull request #1252 from OpenSignLabs/validation
build(deps): bump parse-server from 7.2.0 to 7.3.0-alpha.7 in /apps/OpenSignServer
2024-09-24 19:35:40 +05:30
prafull-opensignlabs 7708b7e938 build(deps): bump googleapis from 7.2.0 to 7.3.0-alpha.7 in /apps/OpenSignServer 2024-09-24 19:34:38 +05:30
prafull-opensignlabsandGitHub 8bbc5a9bc6 Merge pull request #1149 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/babel/runtime-corejs2-7.25.6
build(deps-dev): bump @babel/runtime-corejs2 from 7.25.4 to 7.25.6 in /apps/OpenSign
2024-09-24 19:13:56 +05:30
prafull-opensignlabsandGitHub 4dbdfb1b77 Merge pull request #1238 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/nodemon-3.1.7
build(deps-dev): bump nodemon from 3.1.4 to 3.1.7 in /apps/OpenSignServer
2024-09-24 19:10:12 +05:30
prafull-opensignlabsandGitHub 05c51edce3 Merge pull request #1209 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/aws-sdk-2.1691.0
build(deps): bump aws-sdk from 2.1687.0 to 2.1691.0 in /apps/OpenSignServer
2024-09-24 19:09:24 +05:30
prafull-opensignlabsandGitHub 379e8cd95e Merge pull request #1208 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/react-router-dom-6.26.2
build(deps): bump react-router-dom from 6.26.1 to 6.26.2 in /apps/OpenSign
2024-09-24 19:08:57 +05:30
prafull-opensignlabsandGitHub 9fd00d4427 Merge pull request #1250 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/tailwindcss-3.4.13
build(deps-dev): bump tailwindcss from 3.4.12 to 3.4.13 in /apps/OpenSign
2024-09-24 19:08:27 +05:30
dependabot[bot]andGitHub ca114cf124 build(deps-dev): bump eslint-plugin-react in /apps/OpenSign
Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.35.0 to 7.36.1.
- [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases)
- [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.35.0...v7.36.1)

---
updated-dependencies:
- dependency-name: eslint-plugin-react
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-24 13:37:44 +00:00
prafull-opensignlabsandGitHub 75128784db Merge pull request #1240 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/mongoose-8.6.3
build(deps): bump mongoose from 8.6.1 to 8.6.3 in /apps/OpenSignServer
2024-09-24 19:07:23 +05:30
dependabot[bot]andGitHub 722f0e277e build(deps-dev): bump tailwindcss in /apps/OpenSign
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 3.4.12 to 3.4.13.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.13/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.12...v3.4.13)

---
updated-dependencies:
- dependency-name: tailwindcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-24 13:36:58 +00:00
prafull-opensignlabsandGitHub 7089998966 Merge pull request #1241 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/tailwindcss-3.4.12
build(deps-dev): bump tailwindcss from 3.4.10 to 3.4.12 in /apps/OpenSign
2024-09-24 19:06:28 +05:30
prafull-opensignlabsandGitHub 52211ddfa4 Merge pull request #1242 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/postcss-8.4.47
build(deps-dev): bump postcss from 8.4.45 to 8.4.47 in /apps/OpenSign
2024-09-24 19:05:24 +05:30
AndrewandGitHub 2b8b762d35 Merge pull request #1225 from OpenSignLabs/raktima-patch-2
feat: introduce embedding feature for template
2024-09-24 19:03:11 +05:30
AndrewandGitHub 04c228cd36 Merge pull request #1249 from OpenSignLabs/validation
fix: pagination move on the top records in reports
2024-09-24 18:30:28 +05:30
prafull-opensignlabs 915f8e7700 fix: pagination move on the top records in reports 2024-09-24 18:26:43 +05:30
AndrewandGitHub 218cea00b5 Merge pull request #1247 from OpenSignLabs/validation 2024-09-24 17:29:03 +05:30
prafull-opensignlabs f38f5ea670 fix: file upload get stuck while uploading docx 2024-09-24 17:28:24 +05:30
RaktimaNXG a9c08c8a9f fix: update angular package name 2024-09-24 16:11:58 +05:30
RaktimaNXG 9406c4f718 Merge branch 'raktima-patch-2' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-24 15:01:23 +05:30
AndrewandGitHub 791d8beb06 Merge pull request #1246 from OpenSignLabs/validation 2024-09-24 13:58:51 +05:30
prafull-opensignlabs 57a9f3aca0 fix: document title not get generated when upload image 2024-09-24 13:56:25 +05:30
prafull-opensignlabs ae8bf187c8 fix: not able draw boxes in debug ui on single page pdf 2024-09-24 12:37:50 +05:30
prafull-opensignlabs 88f2ab1f8e fix: close sidebar in mobile view when user click on profile menu from header 2024-09-24 12:19:32 +05:30
AndrewandGitHub 76f5136073 Merge pull request #1245 from OpenSignLabs/validation 2024-09-23 18:10:14 +05:30
prafull-opensignlabs 0f84cd8949 feat: generate document title automatically on the basis file name 2024-09-23 18:09:23 +05:30
AndrewandGitHub adfdf7606f Merge pull request #1244 from OpenSignLabs/validation
feat: add validatesmtp endpoint
2024-09-23 17:01:25 +05:30
prafull-opensignlabs ce671d4294 feat: add validatesmtp endpoint 2024-09-23 17:00:45 +05:30
AndrewandGitHub 2e7cf7a9df Merge pull request #1243 from OpenSignLabs/validation 2024-09-23 11:54:22 +05:30
prafull-opensignlabs f699d93e44 fix: resend mail is not went throught customsmtp 2024-09-23 11:53:15 +05:30
dependabot[bot]andGitHub 531ef4d0b2 build(deps-dev): bump postcss from 8.4.45 to 8.4.47 in /apps/OpenSign
Bumps [postcss](https://github.com/postcss/postcss) from 8.4.45 to 8.4.47.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.45...8.4.47)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 00:38:18 +00:00
dependabot[bot]andGitHub 4c6dbb6690 build(deps-dev): bump tailwindcss in /apps/OpenSign
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 3.4.10 to 3.4.12.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.12/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.10...v3.4.12)

---
updated-dependencies:
- dependency-name: tailwindcss
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 00:35:44 +00:00
dependabot[bot]andGitHub 0beb60d1b5 build(deps): bump mongoose from 8.6.1 to 8.6.3 in /apps/OpenSignServer
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.6.1 to 8.6.3.
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Automattic/mongoose/compare/8.6.1...8.6.3)

---
updated-dependencies:
- dependency-name: mongoose
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 00:20:14 +00:00
dependabot[bot]andGitHub 893c9d7c42 build(deps-dev): bump nodemon in /apps/OpenSignServer
Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.4 to 3.1.7.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v3.1.4...v3.1.7)

---
updated-dependencies:
- dependency-name: nodemon
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 00:19:44 +00:00
AndrewandGitHub b7a22d94db Merge pull request #1236 from OpenSignLabs/validation
refactor: add parenthesis
2024-09-20 20:09:33 +05:30
prafull-opensignlabs 3d854756d1 refactor: add parenthesis 2024-09-20 20:07:17 +05:30
AndrewandGitHub 251e7058f1 Merge pull request #1235 from OpenSignLabs/validation
feat: custom smtp support
2024-09-20 19:59:04 +05:30
prafull-opensignlabs 368eb24217 feat: custom smtp support 2024-09-20 19:57:30 +05:30
AndrewandGitHub f89ef54b82 Merge pull request #1233 from OpenSignLabs/raktima-patch-3
feat: enable dismiss button to hide notification in header
2024-09-20 19:30:15 +05:30
RaktimaNXG b9d9f8772b feat: enable dismiss button to hide notification in header 2024-09-20 18:17:31 +05:30
AndrewandGitHub f2ee03e850 Merge pull request #1232 from OpenSignLabs/raktima-patch-3 2024-09-20 18:11:21 +05:30
RaktimaNXG 92e59a0072 fix: zip file 2024-09-20 17:58:57 +05:30
RaktimaNXG 5394c0b520 refactor code 2024-09-20 16:41:41 +05:30
RaktimaNXG e4a9c6f8c2 fix: remove console 2024-09-20 15:49:50 +05:30
RaktimaNXG e559d037c3 feat: add zip file to dowload pdf and certificate in all download option of completed pdf 2024-09-20 15:33:13 +05:30
RaktimaNXG c9909a0869 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-3 2024-09-20 12:10:44 +05:30
RaktimaNXG 988fc1bbfd fix: error 2024-09-20 12:08:01 +05:30
RaktimaNXG 1831bbcd70 feat: add zip file to download pdf and certificate 2024-09-20 12:04:10 +05:30
prafull-opensignlabsandGitHub ced9bbdbd5 Merge pull request #1230 from OpenSignLabs/fix-Confetti
fix: tour design
2024-09-20 10:15:36 +05:30
RaktimaNXG 15d57d937b fix: tour design 2024-09-19 19:38:25 +05:30
AndrewandGitHub 555979869d Merge pull request #1228 from OpenSignLabs/validation
refactor: provide appName
2024-09-19 19:09:56 +05:30
prafull-opensignlabs 4e6477297e refactor: provide appName 2024-09-19 19:07:54 +05:30
AndrewandGitHub e9e06fc53b Merge pull request #1227 from OpenSignLabs/fix-Confetti 2024-09-19 19:06:46 +05:30
RaktimaNXG ecb2818589 fix: update confetti effect 2024-09-19 19:04:59 +05:30
RaktimaNXG c442689573 fix: remove console 2024-09-19 18:38:32 +05:30
RaktimaNXG 88919f2634 refactor code 2024-09-19 17:47:50 +05:30
AndrewandGitHub 1a1bc41283 Merge pull request #1226 from OpenSignLabs/validation 2024-09-19 17:44:04 +05:30
prafull-opensignlabs 4188caed33 feat: add document logs in in-progress report 2024-09-19 17:42:44 +05:30
RaktimaNXG 50557d8bc5 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-19 17:34:28 +05:30
RaktimaNXG 0f1ef0406e refactor code 2024-09-19 17:19:45 +05:30
prafull-opensignlabsandGitHub ffc742b460 Merge pull request #1224 from OpenSignLabs/raktima-patch-3
fix: ui design in mobile view
2024-09-19 15:57:25 +05:30
RaktimaNXG 0c04cc6ce6 fix: update translation file 2024-09-19 15:27:49 +05:30
RaktimaNXG 4e6dfed838 refactor code 2024-09-19 14:42:13 +05:30
RaktimaNXG fdc0524ad2 fix: ui design in mobile view 2024-09-19 14:35:10 +05:30
RaktimaNXG 9852c87be7 fix: design in mobile view 2024-09-18 18:19:08 +05:30
RaktimaNXG 1268f65516 fix: update angular text 2024-09-18 16:58:04 +05:30
RaktimaNXG 6551b2537a feat: implement angular npm package use case in public template 2024-09-18 16:48:20 +05:30
dependabot[bot]andGitHub b138ccf625 build(deps): bump aws-sdk in /apps/OpenSignServer
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.1687.0 to 2.1691.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Commits](https://github.com/aws/aws-sdk-js/compare/v2.1687.0...v2.1691.0)

---
updated-dependencies:
- dependency-name: aws-sdk
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 00:51:57 +00:00
dependabot[bot]andGitHub 27ff7baa6d build(deps): bump react-router-dom in /apps/OpenSign
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.26.1 to 6.26.2.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.26.2/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-16 00:12:46 +00:00
RaktimaNXG 3fc41f4255 feat : implement credit deduction in the public signature process 2024-09-09 09:59:33 +05:30
RaktimaNXG b20aa33a04 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-06 15:23:31 +05:30
RaktimaNXG a64da4cd85 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-06 09:59:41 +05:30
RaktimaNXG e80bcfc586 refactor code 2024-09-05 18:41:09 +05:30
RaktimaNXG de5b8160a2 fix: add embedding functionality for all template & add CopyTemplateId button in template report list options 2024-09-05 16:40:15 +05:30
RaktimaNXG 83df969751 feat: introduce embedding feature after make template public 2024-09-05 12:56:38 +05:30
dependabot[bot]andGitHub f04fd16c9e build(deps-dev): bump @babel/runtime-corejs2 in /apps/OpenSign
Bumps [@babel/runtime-corejs2](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime-corejs2) from 7.25.4 to 7.25.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.6/packages/babel-runtime-corejs2)

---
updated-dependencies:
- dependency-name: "@babel/runtime-corejs2"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 00:32:16 +00:00
110 changed files with 8199 additions and 258241 deletions
+9
View File
@@ -232,6 +232,15 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "1024mb",
"name": "1024mb",
"avatar_url": "https://avatars.githubusercontent.com/u/9301204?v=4",
"profile": "https://github.com/1024mb",
"contributions": [
"translation"
]
} }
] ]
} }
+2 -1
View File
@@ -147,7 +147,8 @@ 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/VikramNagwal"><img src="https://avatars.githubusercontent.com/u/123088024?v=4?s=100" width="100px;" alt="Vikram"/><br /><sub><b>Vikram</b></sub></a><br /><a href="#code-VikramNagwal" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/VikramNagwal"><img src="https://avatars.githubusercontent.com/u/123088024?v=4?s=100" width="100px;" alt="Vikram"/><br /><sub><b>Vikram</b></sub></a><br /><a href="#code-VikramNagwal" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ugoconsonni"><img src="https://avatars.githubusercontent.com/u/13661702?v=4?s=100" width="100px;" alt="ugoconsonni"/><br /><sub><b>ugoconsonni</b></sub></a><br /><a href="#code-ugoconsonni" title="Code">💻</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/ugoconsonni"><img src="https://avatars.githubusercontent.com/u/13661702?v=4?s=100" width="100px;" alt="ugoconsonni"/><br /><sub><b>ugoconsonni</b></sub></a><br /><a href="#code-ugoconsonni" title="Code">💻</a></td>
<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/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><a href="#security-Zathiel" title="Security">🛡️</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>
</tr> </tr>
</tbody> </tbody>
</table> </table>
+1844 -1396
View File
File diff suppressed because it is too large Load Diff
+34 -29
View File
@@ -4,16 +4,17 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.8.2", "@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.8.9", "@lottiefiles/dotlottie-react": "^0.9.1",
"@pdf-lib/fontkit": "^1.1.1", "@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.1.3", "@radix-ui/themes": "^3.1.4",
"@react-pdf/renderer": "^3.4.4", "@react-pdf/renderer": "^4.0.0",
"@reduxjs/toolkit": "^2.2.7", "@reduxjs/toolkit": "^2.2.8",
"axios": "^1.7.5", "axios": "^1.7.7",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18next": "^23.14.0", "i18next": "^23.16.0",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1", "i18next-http-backend": "^2.6.2",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"nth-check": "^2.1.1", "nth-check": "^2.1.1",
@@ -22,10 +23,10 @@
"print-js": "^1.6.0", "print-js": "^1.6.0",
"radix-ui": "^1.0.1", "radix-ui": "^1.0.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.10.4", "react-bootstrap": "^2.10.5",
"react-confetti": "^6.1.0", "react-confetti": "^6.1.0",
"react-cookie": "^7.2.0", "react-cookie": "^7.2.1",
"react-datepicker": "^7.3.0", "react-datepicker": "^7.4.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^8.0.3", "react-dnd-multi-backend": "^8.0.3",
@@ -33,17 +34,18 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-gtm-module": "^2.0.11", "react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-i18next": "^15.0.1", "react-i18next": "^15.0.3",
"react-konva": "^18.2.10", "react-konva": "^18.2.10",
"react-pdf": "^9.1.0", "react-pdf": "^9.1.1",
"react-quill-new": "^3.3.0", "react-quill-new": "^3.3.3",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"react-rnd": "^10.4.11", "react-rnd": "^10.4.13",
"react-router-dom": "^6.26.1", "react-router-dom": "^6.27.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1", "react-scrollbars-custom": "^4.1.1",
"react-select": "^5.8.0", "react-select": "^5.8.1",
"react-signature-canvas": "^1.0.6", "react-signature-canvas": "^1.0.6",
"react-syntax-highlighter": "^15.5.0",
"react-tooltip": "^5.28.0", "react-tooltip": "^5.28.0",
"react-web-share": "^2.0.2", "react-web-share": "^2.0.2",
"reactour": "^1.19.4", "reactour": "^1.19.4",
@@ -59,7 +61,7 @@
"build-webpack": "webpack --config webpack.config.js", "build-webpack": "webpack --config webpack.config.js",
"build": "npm run version && npm run build-webpack && react-scripts build", "build": "npm run version && npm run build-webpack && react-scripts build",
"build-watch": "webpack --config webpack.config.js --watch", "build-watch": "webpack --config webpack.config.js --watch",
"start-dev": "concurrently \"npm run build-watch\" \"react-scripts start\"", "start-dev": "react-scripts start",
"start": "serve -s build", "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": "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\"", "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\"",
@@ -93,33 +95,36 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.8",
"@babel/preset-env": "^7.25.4", "@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.24.7", "@babel/preset-react": "^7.25.7",
"@babel/runtime-corejs2": "^7.25.4", "@babel/runtime-corejs2": "^7.25.7",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"babel-loader": "^9.1.3", "babel-loader": "^9.2.1",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"commitizen": "^4.3.0", "commitizen": "^4.3.1",
"concurrently": "^8.2.2", "concurrently": "^9.0.1",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"daisyui": "^4.12.10", "daisyui": "^4.12.13",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"dotenv-webpack": "^8.1.0", "dotenv-webpack": "^8.1.0",
"eslint": "^8.57.0", "eslint": "^9.12.0",
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.34.3", "eslint-plugin-react": "^7.37.1",
"lint-staged": "^15.2.10", "lint-staged": "^15.2.10",
"mini-css-extract-plugin": "^2.9.1", "mini-css-extract-plugin": "^2.9.1",
"postcss": "^8.4.45", "postcss": "^8.4.47",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"pretty-quick": "^4.0.0", "pretty-quick": "^4.0.0",
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.13",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
}, },
"overrides": { "overrides": {
"nth-check": "$nth-check", "nth-check": "$nth-check",
"ws": "$ws" "ws": "$ws"
},
"engines": {
"node": "18 || 20 || 22"
} }
} }
@@ -131,7 +131,9 @@
"Share": "Share", "Share": "Share",
"View": "View", "View": "View",
"option": "Option", "option": "Option",
"Embed": "Embed" "Embed": "Embed",
"Copy TemplateId":"Copy TemplateId",
"Copy Public URL":"Copy Public URL"
}, },
"report-heading": { "report-heading": {
"Sr.No": "Sr.No", "Sr.No": "Sr.No",
@@ -151,7 +153,8 @@
"Name": "Name", "Name": "Name",
"Status": "Status", "Status": "Status",
"created-date": "Created Date", "created-date": "Created Date",
"Type": "Type" "Type": "Type",
"Logs": "Logs"
}, },
"report-help": { "report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.", "Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -262,9 +265,10 @@
"make-template-public-alert": "Are you sure you want to make this template public?", "make-template-public-alert": "Are you sure you want to make this template public?",
"make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.", "make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.",
"public-role": "Public role", "public-role": "Public role",
"public-url": "Public URL", "public-url": "Public profile needed",
"public-url-copy-mssg": " Heres your public URL: Copy it or share it with the signer, and you will be able to see all your publicly set templates.", "public-url-copy": "Heres your public URL: ",
"add-public-url-alert": "Please add your public URL, and you will be able to make a public template.", "public-url-copy-mssg":"Copy it or share it with the signer, and you will be able to see all your publicly set templates.",
"add-public-url-alert": "Please complete your OpenSign public profile setup. Once done, youll be able to make templates public. This process only needs to be completed once.",
"share-with-alert": "You cannot share a template if any roles already have contacts assigned. Please remove all contact assignments from the roles before sharing the template.", "share-with-alert": "You cannot share a template if any roles already have contacts assigned. Please remove all contact assignments from the roles before sharing the template.",
"share-with": "Share with", "share-with": "Share with",
"team-not-found": "Team not found", "team-not-found": "Team not found",
@@ -572,9 +576,10 @@
"pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .", "pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .",
"placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.", "placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.",
"placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.", "placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.",
"placeholder-sign-3": "Drag or click on a field to add it to the document.", "placeholder-sign-3": "Click this button to add more recipients/signers to the document.",
"placeholder-sign-4": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.", "placeholder-sign-4": "Drag or click on a field to add it to the document.",
"placeholder-sign-5": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.", "placeholder-sign-5": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-6": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like HR Manager and New Employee. Each time you use the template, you can assign the New Employee role to different incoming staff members, while the HR Manager role remains constant, facilitating a seamless onboarding process for each recruit. ", "report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like HR Manager and New Employee. Each time you use the template, you can assign the New Employee role to different incoming staff members, while the HR Manager role remains constant, facilitating a seamless onboarding process for each recruit. ",
"redirect": "Click the 'Use' button to create a new document from an existing template.", "redirect": "Click the 'Use' button to create a new document from an existing template.",
"bulksend": "To quickly create and send multiple documents using an existing template, click the 'Quick Send' button.", "bulksend": "To quickly create and send multiple documents using an existing template, click the 'Quick Send' button.",
@@ -630,6 +635,19 @@
"reason":"Reason", "reason":"Reason",
"decline-by":"Declined/revoked by", "decline-by":"Declined/revoked by",
"document-declined": "Document declined", "document-declined": "Document declined",
"public-template-mssg-1":"To integrate OpenSign into your React or Next.js project, simply run the following command:",
"public-template-mssg-2" :"Ensure you have npm or yarn set up in your project. If youre using Yarn, you can replace npm install with yarn add @opensign/react.",
"public-template-mssg-3" :"Need more details or examples?",
"public-template-mssg-4": "Visit the",
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
"public-template-mssg-6" :"Before you can use this code snippet, you must make this template public.",
"copy-code":"COPY",
"copied-code":"COPIED",
"Installation":"Installation",
"Usage" :"Usage",
"insufficient-credits":"Insufficient Signing Credits",
"insufficient-credits-mssg":"The owner of this document currently lacks the necessary OpenSign credits for you to sign. Please reach out to the owner if you require further details.",
"angular-npm-mssg-1": "To integrate OpenSign into your Angular project, simply run the following command:",
"quota-mail-info-head":"Monthly request signatures email limit", "quota-mail-info-head":"Monthly request signatures email limit",
"quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.", "quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.",
"quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.", "quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.",
@@ -647,5 +665,23 @@
"p4": "Please choose the option that best suits your document signing requirements." "p4": "Please choose the option that best suits your document signing requirements."
}, },
"advanced-options":"Advanced options", "advanced-options":"Advanced options",
"hide-advanced-options":"Hide Advanced options" "hide-advanced-options":"Hide Advanced options",
"download-files":"Download files",
"download-pdf":"Download Pdf",
"pdf-certificate":"Download Pdf + Certificate",
"document-logs":"Document logs",
"server-down": "Unable to connect to the OpenSign server. If you are self-hosting OpenSign, please ensure that all the steps in the documentation have been followed correctly. If you're running OpenSign locally, you might be accessing it through an incorrect port number.",
"admin-exists": "Admin already exists. Please login to the application using admin credentials in order to manage users.",
"enable-tour":"Enable Tour",
"istourenabled-help":{
"p1":"This will enable a guided tour for signers, providing instructions during the signing process.",
"p2":"This will disable the guided tour, ensuring a faster, uninterrupted signing experience.",
"p3":"Tip: If your signers are familiar with OpenSign and frequently use it, you may want to disable the guided tour for a more seamless experience. Choose the option that best fits your document signing needs."
},
"form-title-1":"Document Flow Setting",
"form-title-2":"Security Setting",
"public-tour-message":"The template needs to be public before you can generate a shareable link.",
"add-user-template": "You need to add a role before you can add fields for it.",
"pdf-uncompatible": "This pdf is not compatible with opensign, please contact <support@opensignlabs.com>"
} }
@@ -0,0 +1,687 @@
{
"header-news": "Nueva funcionalidad: ¡Los perfiles públicos ahora están disponibles! Crea y comparte plantillas públicas fácilmente",
"header-news-btn": "Configurar ahora",
"create-account": "Crear cuenta",
"login": "Iniciar sesión",
"language": "Idioma",
"name": "Nombre",
"phone": "Teléfono",
"phone-optional": "opcional",
"email": "Correo",
"company": "Compañía",
"job-title": "Título del puesto",
"profile": "Perfil",
"log-out": "Cerrar sesión",
"is-email-verified": "El correo está verificado",
"public-profile": "Perfil público",
"tagline": "Eslogan ",
"has-requested-you-to-sign": "ha solicitado que firmes",
"has-requested-you-to-review-and-sign": " ha solicitado que revises y firmes",
"input-required": "Por favor, rellena este campo.",
"sort-order": {
"Ascending": "Ascendente",
"Descending": "Descendente",
"Name": "Nombre",
"Date": "Fecha"
},
"context-menu": {
"Download": "Descargar",
"Rename": "Renombrar",
"Move": "Mover",
"Delete": "Eliminar"
},
"action": "Acción",
"add": "Agregar",
"add-new-folder": "Agregar nueva carpeta",
"disable-documentId": "Desactivar ID de documento",
"edit": "Editar",
"change-password": "Cambiar contraseña",
"save": "Guardar",
"cancel": "Cancelar",
"upgrade-now": "Mejorar ahora",
"upgrade-to": "Mejorar a",
"plan": "Plan",
"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",
"tour-content": "No mostrar esto otra vez",
"pro": "PRO",
"team": "EQUIPO",
"user": "Usuario",
"docs": "Documentación",
"session-expired": "Tu sesión ha expirado.",
"public-profile-help": "Esta es tu URL pública. Cópiala o compártela con el firmante, y podrás ver todas tus plantillas públicas.",
"tagline-help": "Este eslogan aparecerá en tu perfil público de OpenSign, por ejem. https://opensign.me/alex",
"welcome": "¡Bienvenido de nuevo!",
"Login-to-your-account": "Ingresa a tu cuenta",
"password": "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-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",
"term": "Términos de Servicio",
"subscribe-to-opensign": "Suscribirse al boletín de OpenSign",
"register": "Registrarse",
"sidebar": {
"Dashboard": "Panel",
"Sign yourself": "Firma tu mismo",
"Request signatures": "Solicitar firmas",
"Templates": "Plantillas",
"Templates-Children": {
"Create template": "Crear plantilla",
"Manage templates": "Administrar plantillas"
},
"OpenSign™ Drive": "OpenSign™ Drive",
"Reports": "Reportes",
"Reports-Children": {
"Need your sign": "Necesitan tu firma",
"In Progress": "En progreso",
"Completed": "Completado",
"Drafts": "Borradores",
"Declined": "Rechazados",
"Expired": "Expirados",
"Contactbook": "Agenda de contactos"
},
"Settings": "Ajustes",
"Settings-Children": {
"My Signature": "Mi firma",
"API Token": "Token API",
"Webhook": "Webhook",
"Teams": "Equipos",
"Team-update-successfully": "Equipo actualizado correctamente.",
"Users": "Usuarios"
}
},
"dashboard-card": {
"Need your Signature": "Necesitan tu firma",
"Out for signatures": "En espera de ser firmados"
},
"report-name": {
"Draft Documents": "Documentos borradores",
"Need your sign": "Necesitan tu firma",
"In-progress documents": "Documentos en progreso",
"Completed Documents": "Documentos completados",
"Declined Documents": "Documentos rechazados",
"Expired Documents": "Documentos expirados",
"Recently sent for signatures": "Recientemente enviados para ser firmados",
"Recent signature requests": "Solicitudes de firma recientes",
"Drafts": "Borradores",
"Contactbook": "Agenda de contactos",
"Templates": "Plantillas",
"Teams": "Equipos",
"Users": "Usuarios"
},
"btnLabel": {
"sign": "firmar",
"Sign": "FIRMAR",
"Resend": "Reenviar",
"Revoke": "Revocar",
"Delete": "Eliminar",
"Use": "Usar",
"Quick send": "Envío rápido",
"Edit": "Editar",
"Share with team": "Compartir con el equipo",
"Share": "Compartir",
"View": "Ver",
"option": "Opción",
"Embed": "Incrustar",
"Copy TemplateId": "Copiar ID de plantilla",
"Copy Public URL": "Copiar URL pública"
},
"report-heading": {
"Sr.No": "Nº",
"Title": "Título",
"Email": "Correo",
"Phone": "Teléfono",
"File": "Archivo",
"Owner": "Propietario",
"Signers": "Firmantes",
"Note": "Nota",
"Folder": "Carpeta",
"Reason": "Razón",
"Parent Team": "Equipo padre",
"Active": "Activo",
"Role": "Rol",
"Team": "Equipo",
"Name": "Nombre",
"Status": "Estado",
"created-date": "Fecha de creación",
"Type": "Tipo",
"Logs": "Registros"
},
"report-help": {
"Draft Documents": "Estos son documentos que has iniciado pero no has finalizado para su envío.",
"Need your sign": "Esta es una lista de documentos que esperan tu firma.",
"In-progress documents": "Esta es una lista de documentos que has enviado a otras partes para su firma.",
"Completed Documents": "Esta es una lista de documentos en los que todas las partes han firmado.",
"Declined Documents": "Esta es una lista de documentos que han sido rechazados por uno de los firmantes.",
"Expired Documents": "Esta es una lista de documentos que han alcanzado su fecha de caducidad.",
"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"
},
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "archivo seleccionado",
"template-title": "Título de la plantilla",
"document-title": "Título del documento",
"description": "Descripción",
"time-to-complete": "Plazo de finalización (días)",
"send-in-order": "Enviar en orden",
"send-in-order-help": {
"p1": "Elige cómo quieres que se envíen las solicitudes de firma a los firmantes del documento:",
"p2": "Seleccionando esta opción se enviará inicialmente la solicitud de firma al primer firmante. Una vez que el primer firmante complete su parte, el siguiente firmante en la secuencia recibirá la solicitud. Este proceso continúa hasta que todos los firmantes hayan firmado el documento. Este método garantiza que el documento se firme en un orden específico.",
"p3": "Seleccionando esta opción se enviarán los enlaces para la firma a todos los firmantes simultáneamente. Cada firmante puede firmar el documento cuando le convenga, independientemente de si otros firmantes han completado sus firmas. Este método es más rápido, pero no impone ningún orden de firma entre los participantes.",
"p4": "Selecciona la opción que mejor se adapte a las necesidades de tu tramitación de documentos."
},
"no": "No",
"auto-reminder": "Recordatorio automático",
"remind-once": " Recordar una vez cada (Días)",
"next": "Siguiente",
"select-folder": "Seleccionar carpeta",
"OpenSign-drive": "OpenSign™ Drive",
"drive-document-status": {
"Completed": "Completado",
"Declined": "Rechazado",
"Draft": "Borrador",
"Expired": "Expirado",
"In Progress": "En progreso"
},
"select-folder-help": "Si no seleccionas una carpeta, tu documento firmado se guardará en la carpeta principal de OpenSign Drive.",
"no-data": "No se encontraron datos",
"save-here": "Guardar aquí",
"back": "Atrás",
"add-folder": "Añadir carpeta",
"create-folder": "Crear carpeta",
"parent-folder": "Carpeta superior",
"create": "Crear",
"signers": "Firmantes",
"signers-help": "Empieza a escribir el nombre de un contacto para ver los firmantes sugeridos entre tus contactos guardados o añade otros nuevos. Organiza el orden de firma añadiendo firmantes en la secuencia deseada. Utiliza el botón «+» para añadir nuevos firmantes y la «X» para quitar todos. Cada firmante recibirá una solicitud por correo electrónico para que firme el documento según el orden indicado.",
"add-signer": "Añadir firmante",
"contact-not-found": "Contacto no encontrado",
"add-yourself": "Añadirte a ti mismo",
"submit": "Enviar",
"reset": "Volver a empezar",
"my-signature": "Mi firma",
"signature": "Firma",
"upload-image": " Subir imagen",
"clear": "Limpiar",
"upload-signature/Image": "Por favor, sube la firma/imagen",
"initials": "Iniciales",
"API": "API",
"api-token": "Token API",
"regenerate-token": "Regenerar token activo",
"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.",
"token-generated": "Token generado exitosamente.",
"webhook": "Webhook",
"update-webhook": "Actualizar webhook",
"add-webhook": "Agregar webhook",
"webhook-added": "Webhook agregado exitosamente.",
"team-disabled": "Equipo deshabilitado",
"Team enabled": "Equipo habilitado",
"are-you-sure": "¿En definitiva quieres ",
"disable": "deshabilitar",
"enable": "habilitar",
"this-team": " este equipo?",
"edit-team": "Editar equipo",
"name-of-team": "Nombre del equipo",
"prev": "Anterior",
"no-data-avaliable": "No hay datos disponibles",
"add-team": "Agregar equipo",
"page-not-found": "Página no encontrada",
"users-from-teams": "usuarios de equipos",
"user-status": "Estado del usuario",
"activate": "activar",
"deactivate": "desactivar",
"this-user": " este usuario",
"delete-user": "Eliminar usuario",
"delete": "eliminar",
"add-user": "Agregar usuario",
"password-generateed": "La contraseña solo será generada una vez; asegúrate de copiarla.",
"Team status": "Estado del equipo",
"user-deactivated": "Usuario desactivado.",
"user-activated": "Usuario activado.",
"public": "Público",
"download": "Descargar",
"view": "Ver",
"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": "¿En definitiva quieres convertir esta plantilla en privada? Esto lo removerá de tu perfil público.",
"public-role": "Rol público",
"public-url": "Se requiere perfil público",
"public-url-copy": "Aquí está tu URL pública: ",
"public-url-copy-mssg": "Cópiala o compártela con el firmante y podrás ver todas tus plantillas públicas.",
"add-public-url-alert": "Por favor, completa la configuración de tu perfil público OpenSign. Una vez terminado, podrás crear plantillas públicas. Este proceso solo necesita ser completado una vez.",
"share-with-alert": "No puedes compartir una plantilla si alguno de los roles ya tiene contactos asignados. Elimina todas las asignaciones de contactos de los roles antes de compartir la plantilla.",
"share-with": "Compartir con",
"team-not-found": "Equipo no encontrado",
"roles": "Roles",
"delete-document": "Eliminar documento",
"delete-document-alert": "¿En definitiva quieres eliminar este documento?",
"quick-send": "Envío rápido",
"add-new": "Agregar nuevo",
"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.",
"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}}.",
"subject": "Asunto",
"body": "Cuerpo",
"add-contact": "Agregar contacto",
"add-signer-alert": "¡El contacto ya existe!",
"record-delete-alert": "¡Entrada eliminada exitosamente!",
"record-revoke-alert": "¡Entrada revocada exitosamente!",
"mail-sent-alert": "Correo enviado exitosamente.",
"document-sent-alert": "¡Documento enviado exitosamente!",
"template-share-alert": "Plantilla compartida exitosamente.",
"template-public-alert-1": "Has convertido exitosamente la plantilla en pública.",
"template-public-alert-2": "Has convertido exitosamente la plantilla en privada.",
"template-public-alert-3": "Tienes que seleccionar un rol para los firmantes públicos.",
"template-public-alert-4": "Por favor, asegúrate de que has añadido al menos un widget de firma para cada firmante.",
"template-public-alert-5": "Dado que esta plantilla tiene «enviar en orden» habilitado, el rol público debe estar posicionado arriba al inicio.",
"template-public-alert-6": "Por favor, enlaza firmantes a todos los roles excepto al usuario de rol público.",
"template-public-alert-7": "Por favor, asigna al menos un rol público para convertir está plantilla en pública.",
"loader": " Esto podría llevar algún tiempo",
"pages": "Páginas",
"document-signed": "Documento firmado",
"close": "Cerrar",
"validation-alert": "Alerta de validación",
"validate-alert-mssg": " Los datos ingresados no cumplen con los criterios establecidos por la expresión regular.",
"otp-verification": "Verificación OTP",
"enter-otp": "Ingresar OTP",
"verify": "Verificar",
"verify-email-1": "Verificar correo",
"resend": "Reenviar",
"contact-details": "Detalles del contacto",
"verify-email": "¡Por favor, verifica tu correo!",
"send-otp": " Enviar OTP",
"otp-placeholder": "Ingresa el código de verificación enviado por correo",
"loading-doc": "Cargando documento...",
"widgets-name": {
"signature": "firma",
"stamp": "sello",
"initials": "iniciales",
"name": "nombre",
"job title": "título del puesto",
"company": "compañía",
"date": "fecha",
"text": "texto",
"text input": "entrada de texto",
"checkbox": "casilla",
"dropdown": "desplegable",
"radio button": "botón de radio",
"image": "imagen",
"email": "correo"
},
"fields": "Campos",
"recipients": "Destinatarios",
"please-add": "Por favor, agrega un",
"signed-by": "Firmado por",
"more": "más",
"sent": "enviado",
"print": "Imprimir",
"certificate": "Certificado",
"decline": "Rechazar",
"finish": "Finalizar",
"mail": "Correo",
"sign-now": "Firmar ahora",
"successfully-signed": "¡Firmado exitosamente!",
"email-mssg": " Los destinatarios agregados aquí recibirán una copia del documento firmado.",
"email-error-1": " por favor, proporciona una dirección de correo correcta",
"email-error-2": "Solo puedes enviar hasta a 10 destinatarios a la vez.",
"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": "Dibujar",
"type": "Escribir",
"color-type": {
"red": "rojo",
"blue": "azul",
"black": "negro",
"yellow": "amarillo"
},
"my-initials": "Mis iniciales",
"upload": "Subir",
"initial-teb": "Iniciales",
"signature-tab": "Firma",
"your-signature": "Tu firma",
"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",
"apply": "Aplicar",
"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"
},
"options": "Opciones",
"minimun-check": "Chequeo mínimo",
"maximum-check": "Chequeo máximo",
"default-value": "Valor por defecto",
"select": "Seleccionar",
"read-only": "Es de solo lectura",
"hide-labels": "Esconder etiquetas",
"checkbox": "Casilla",
"alert": "Alerta",
"zoom-in": "Acercar",
"zoom-out": "Alejar",
"document-signed-alert": "¡Felicitaciones! 🎉 ¡Este documento ha sido firmado por ti exitosamente!",
"otp-sent-alert": "OTP enviado a tu correo",
"fields-required": "Campos requeridos",
"signature-widget-alert-1": "Por favor, asegúrate de que hay al menos un widget de firma agregado",
"signature-widget-alert-2": "Por favor, asegúrate de que todos los campos están correctamente rellenados y de que cumplen todos los requisitos.",
"encrypted-pdf-alert": "Actualmente los archivos PDF encriptados no están soportados.",
"encrypted-pdf-alert-1": "La contraseña del archivo PFX es inválida.",
"user-not-exist": "El usuario no existe",
"template-signature-field": "Por favor, agrega al menos un campo de firma para todos los roles.",
"template-role-alert": "Por favor, primero agrega roles",
"create-document": "Crear documento",
"template-created-alert": "¿Quieres crear un documento usando la plantilla que acabas de crear?",
"signers-alert": "alerta de firmantes",
"template-creation-alert-1": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
"ok": "OK",
"radio-group": "Grupo de radio",
"dropdown-options": "Opciones del desplegable",
"add-role": "Agregar rol",
"role-ex": " por ejem.: Cliente, RRHH, Director, Administrador, Estudiante, etc.",
"add/choose-signer": "Agregar/Elegir firmante",
"choose-from-contacts": "Elegir desde los contactos",
"select-signer": " Por favor, selecciona el firmante",
"edit-template": "Editar plantilla",
"widget-info": "Información del widget",
"validation": "Validación",
"invalid-default-value": "valor por defecto inválido",
"hint": "Pista",
"color": "color",
"role-help": {
"p1": "¿Qué son los roles de una plantilla?",
"p2": "Empieza por especificar cada uno de los roles necesarios para completar el documento. Piensa en las partes implicadas en el proceso de firma y cuáles son sus responsabilidades. Los roles más habituales son RRHH para los documentos internos, Cliente para los contratos o Proveedor para los convenios comerciales.",
"p3": "¿Por qué preasignar usuarios a algunos roles?",
"p4": "Para los roles que consistentemente involucran al mismo individuo (por ejem., la firma del Gerente en las ofertas de empleo), puedes preasignar un usuario a un rol dentro de una plantilla. Este paso es opcional pero recomendado para la eficiencia y consistencia entre documentos",
"p5": "¿Cuando especifico el usuario asignado a cada rol?",
"p6": "Cuando crees un documento usando tu plantilla, se te pedirá que asignes usuarios a cada rol definido. Si un rol ya tiene un usuario asignado, este será rellenado con anticipación, pero puedes modificarlo según lo necesites antes de enviar el documento."
},
"add-recipients": "Agregar destinatarios",
"loading-mssg": "Esto podría llevar algún tiempo",
"send-mail": "Enviar correo",
"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-alert-5": "¿Quieres firmar documentos ahora mismo?",
"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",
"upgrade-to-customize-email": "Mejora para personalizar el correo",
"sign-url": "URL de firma",
"Mails Sent": "Correos enviados",
"document-alert": "Alerta de documento",
"owner-subscription-expired": "La suscripción del propietario ha expirado.",
"subscription-expired": "Suscripción expirada",
"alert-message": "Mensaje de alerta",
"document-decline": "Rechazar documento",
"decline-alert-1": "¿En definitiva quieres rechazar este documento?",
"decline-alert-2": "¡Has rechazado este documento!",
"decline-alert-3": "No puedes firmar este documento ya que ha sido rechazado/revocado.",
"sign here": "Firma aquí",
"guest-email-alert": "el código de verificación ha sido enviado a tu correo",
"get-otp-alert": "recibirás un código de verificación por correo",
"user-already-exist": "El usuario ya existe.",
"server-error": "¡Error interno del servidor!",
"fill-required-details!": "¡Por favor, rellena los detalles requeridos!",
"sign-up-form": "Formulario de registro",
"sign-up": "Registrarse",
"folder-already-exist!": "¡la carpeta ya existe!",
"please-select-position!": "¡por favor, selecciona una posición!",
"default-sign-alert": "¿En definitiva quieres firmar automáticamente en todos los lugares solicitados?",
"Your-Signature": "Tu firma",
"auto-sign-all": "Firmar todo automáticamente",
"generating-certificate": "Generando certificado",
"pdf-download": "Descargar PDF",
"generate-certificate-alert": " Tu certificado de finalización se está generando. Por favor, espera un momento. Si la descarga no inicia pronto, presiona el botón otra vez.",
"folder-already-exist": "¡La carpeta ya existe!",
"folder-created-successfully": "¡Carpeta creada exitosamente!",
"fill-folder-name": "Por favor, rellena el nombre de la carpeta",
"already-exists-this-username": "¡Un usuario con este nombre de usuario ya existe!",
"verification-code-sent": "¡El correo de verificación ha sido enviado a tu correo!",
"registered-user-successfully": "Usuario registrado exitosamente.",
"role-not-found": "Rol no encontrado.",
"do-not-access": "No tienes acceso, por favor, contacta al administrador.",
"add-admin": "Agregar administrador",
"opensign-setup": "Instalación de OpenSign",
"join-discord": "Únete a nuestro servidor de Discord",
"admin-already-exist": "El administrador ya existe.",
"password-update-alert-1": "Contraseña actualizada exitosamente.",
"password-update-alert-2": "Tu contraseña actual es incorrecta o falta.",
"password-update-alert-3": "Tu contraseña actual es incorrecta o falta.",
"password-update-alert-4": "Tu contraseña y su confirmación no son iguales.",
"current-password": "Contraseña actual",
"new-password": "Contraseña nueva",
"confirm-password": "Confirmar contraseña",
"file-alert-1": "El tamaño del archivo seleccionado es muy grande. Por favor, selecciona un archivo con un tamaño menor a",
"file-alert-2": "Por favor, selecciona un archivo.",
"file-alert-3": "Por favor, espera mientras se sube el documento.",
"enter-pdf-password": "Ingresa la contraseña del PDF",
"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!",
"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",
"enter-verification-code": "Ingresar código de verificación",
"provide-your-details": "Proporciona tus detalles",
"provide-email": "Por favor, proporciona un correo.",
"additional-info": "Información adicional",
"signature-saved-alert": "Firma guardada exitosamente.",
"fill-field": "Por favor, rellena este campo",
"error-template": "Error: ¡Plantilla no encontrada!",
"error": "Error",
"invalid-document": "Error: ¡Documento inválido!",
"document-not-found": "¡Documento no encontrado!",
"document-signed-alert-1": "Has firmado el documento exitosamente. Puedes descargar o imprimir una copia del documento parcialmente firmado. Una copia del documento firmado digitalmente será enviada al propietario mediante correo electrónico una vez haya sido firmado por todos los firmantes.",
"encrypted-pdf-not-support": "Los archivos PDF encriptados no están soportados actualmente.",
"document-signed-alert-4": " ¡Felicitaciones! 🎉 ¡Este documento ha sido firmado exitosamente por todos los participantes!",
"document-signed-alert-5": "Este documento ha sido firmado por todos los firmantes.",
"document-signed-alert-6": "Este documento ha sido rechazado por uno o más destinatarios.",
"document-signed-alert-7": "Este documento ya no está disponible.",
"document-signed-alert-8": "Este documento ya ha sido enviado para ser firmado.",
"yet-to-sign": "Todavía por firmar",
"validation-alert-1": "Los datos ingresados no cumplen con los criterios establecidos por la expresión regular.",
"account-already-exists": "¡La cuenta ya existe!",
"choose-password": "Elegir contraseña",
"enter-password": "Ingresar contraseña",
"sure": "claro",
"subscribed-alert": "¡Ya te has suscrito al plan!",
"monthly": "Mensual",
"yearly-upto": "Anual (hasta 66% de descuento)",
"billed-yearly": "Facturado anualmente",
"billed-monthly": "Facturado mensualmente",
"plansubscription-1": "Alójalo por tu cuenta gratuitamente",
"visit-github": "Visitar GitHub",
"user-already-exist-name": "¡Un usuario con este nombre de usuario ya existe!",
"do-not-access-contact-admin": "No tienes acceso, por favor, contacta a un administrador.",
"filed-required-correctly": "Por favor, rellena correctamente los detalles requeridos.",
"admin-created": "Administrador creado",
"invalid-masterkey": "Llave maestra inválida",
"opensign-Setup": "Instalación de OpenSign",
"master-key": " Llave maestra",
"profile-update-alert": "Perfil actualizado exitosamente.",
"date": "Fecha",
"report-not-found": "Reporte no encontrado",
"please-wait": "por favor, espera",
"select-foler": "Seleccionar carpeta",
"generated-on": "Generado el",
"text-field": "Campo de texto",
"font-size": "Tamaño de la fuente",
"widget-status": {
"Required": "Requerido",
"Optional": "Opcional"
},
"all-right": "Todos los derechos reservados",
"version": "versión",
"social-media": {
"github": "GitHub",
"linked-in": "LinkedIn",
"twitter": "Twitter",
"discord": "Discord"
},
"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 OpenSign! Si necesitas soporte siéntete libre de contactarnos.",
"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.",
"opensign-drive-3": "Ordena tus documentos por «Fecha» o «Nombre» usando este menú.",
"opensign-drive-4": "Haz clic en este menú para mostrar los documentos en vista de lista.",
"opensign-drive-5": "La lista de documentos es mostrada de acuerdo a la opción de ordenamiento seleccionada. Los íconos situados junto a cada documento indican su estado actual.",
"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": "Haz clic en los botones «Atrás», «Rechazar» o «Finalizar» para navegar por el documento. Utiliza el menú de tres puntos para ver otras opciones, como el botón «Descargar».",
"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.",
"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-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-6": "Hacer clic en «Enviar» guardará el documento. En el siguiente paso podrás personalizar los correos que serán enviados a los destinatarios o copiar los enlaces para firmar y compartirlos con los destinatarios por tu cuenta.",
"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 crear y enviar rápidamente múltiples documentos usando una plantilla existente, haz clic en el botón «Envío rápido».",
"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.",
"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.",
"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.",
"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."
},
"enter-email-plaholder": "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",
"Role": "Rol",
"additional-users": "Por favor, compra el complemento Usuarios.",
"Quantity-of-users": "Cantidad de usuarios",
"Price": "Precio",
"Total-price-for-next-time": "Precio total para la próxima vez",
"Proceed": "Proceder",
"Select": "Seleccionar",
"Add-seats": "Agregar plazas",
"user-name-exist": "el nombre de usuario ya existe",
"format": "formato",
"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.",
"additional-credits": "Por favor, compra créditos premium",
"quota-err-quicksend": "Cuota alcanzada, no tienes créditos suficientes.",
"buy-credits": "Comprar créditos premium",
"rotate-right": "Rotar a la derecha",
"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.",
"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.",
"help-api-token": "Este token puede ser usado para acceder a las API de producción en el punto de acceso «{{origin}}/api/v1». Solo puede ser generado con uno de nuestros planes de pago.",
"reason": "Razón",
"decline-by": "Rechazado/revocado por",
"document-declined": "Documento rechazado",
"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-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.",
"copy-code": "COPIAR",
"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.",
"angular-npm-mssg-1": "Para integrar OpenSign a tu proyecto Angular, simplemente ejecuta los siguientes comandos:",
"quota-mail-info-head": "Límite mensual de solicitudes de firma por correo",
"quota-mail-info": "Puedes enviar hasta 15 solicitudes de firma por correo cada mes. Mejora ahora para enviar solicitudes de firma sin límite.",
"quota-mail": "Has alcanzado tu límite de 15 solicitudes de firma por correo para este mes. Mejora ahora para continuar enviando correos.",
"quota-mail-tip": "Consejo: Todavía puedes solciitar la firma de <1>documentos ilimitados</1> compartiendo manualmente los enlaces de solicitud de firma.",
"quota-mail-head": "Cuota alcanzada",
"unauthorized-modal": "No tienes permisos para realizar esta acción, por favor, contacta a {{adminEmail}}.",
"sent-this-month": "Enviados este mes",
"available-seats": "Plazas disponibles",
"buy-users": "Comprar más usuarios",
"isenable-otp": "Habilitar verificación con OTP",
"isenable-otp-help": {
"p1": "¿Te gustaría habilitar el proceso de verificación usando una contraseña de un solo uso (OTP)?",
"p2": "Seleccionar esta opción habilitará la verificación con OTP. Los usuarios recibirán un código de verificación por correo, la cual tendrán que ingresar para poder firmar el documento.",
"p3": "Seleccionar esta opción deshabilitará la verificación con OTP, lo cual permitirá a los usuarios firmar el documento directamente sin pasos adicionales.",
"p4": "Por favor, selecciona la opción que mejor se adapte a tus requisitos para la firma de documentos."
},
"advanced-options": "Opciones avanzadas",
"hide-advanced-options": "Ocultar las opciones avanzadas",
"download-files": "Descargar archivos",
"download-pdf": "Descargar PDF",
"pdf-certificate": "Descargar PDF + Certificado",
"document-logs": "Registros de documentos",
"server-down": "No se pudo conectar al servidor de OpenSign. Si estás alojando OpenSign por tu cuenta, por favor, asegúrate de que todos los pasos de la documentación han sido seguidos correctamente. Si estás ejecutando OpenSign localmente, puedes estar accediendo a OpenSign por un número de puerto incorrecto.",
"admin-exists": "El administrador ya existe. Por favor, inicia sesión en la aplicación usando las credenciales de administrador para poder administrar los usuarios.",
"public-tour-message": "Por favor, convierte la plantilla en pública para copiar la URL pública",
"add-user-template": "Es necesario agregar un rol antes de poder agregarle campos.",
"pdf-uncompatible": "Este pdf no es compatible con opensign, comuníquese con <support@opensignlabs.com>",
"enable-tour": "Habilitar recorrido",
"istourenabled-help":{
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
},
"form-title-1": "Configuración del flujo de documentos",
"form-title-2": "Configuración de seguridad"
}
@@ -136,7 +136,8 @@
"Name": "Nom", "Name": "Nom",
"Status": "Statut", "Status": "Statut",
"created-date": "Date de création", "created-date": "Date de création",
"Type": "Saisir" "Type": "Saisir",
"Logs": "Journaux"
}, },
"btnLabel": { "btnLabel": {
"sign": "Signer", "sign": "Signer",
@@ -150,7 +151,9 @@
"Share": "Partager", "Share": "Partager",
"View": "Voir", "View": "Voir",
"option": "Option", "option": "Option",
"Embed": "Intégrer" "Embed": "Intégrer",
"Copy TemplateId": "Copier l'ID du modèle",
"Copy Public URL":"Copier l'URL publique"
}, },
"report-help": { "report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.", "Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -261,9 +264,10 @@
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?", "make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.", "make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
"public-role": "Rôle public", "public-role": "Rôle public",
"public-url": "URL publique", "public-url": "Profil public requis",
"public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.", "public-url-copy": "Voici votre URL publique : ",
"add-public-url-alert": "Veuillez ajouter votre URL publique et vous pourra rendre public modèle.", "public-url-copy-mssg": "copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
"add-public-url-alert": "Veuillez terminer la configuration de votre profil public OpenSign. Une fois cela fait, vous pourrez rendre les modèles publics. Ce processus ne doit être effectué quune seule fois.",
"share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.", "share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
"share-with": "Partager avec", "share-with": "Partager avec",
"team-not-found": "Équipe introuvable", "team-not-found": "Équipe introuvable",
@@ -571,9 +575,10 @@
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.", "pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.", "placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.",
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.", "placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
"placeholder-sign-3": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.", "placeholder-sign-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
"placeholder-sign-4": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.", "placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"placeholder-sign-5": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.", "placeholder-sign-5": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
"placeholder-sign-6": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue", "report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.", "redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
"bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.", "bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.",
@@ -629,6 +634,19 @@
"reason" :"Raison", "reason" :"Raison",
"decline-by" :"Refusé/révoqué par", "decline-by" :"Refusé/révoqué par",
"document-declined":"document refusé", "document-declined":"document refusé",
"public-template-mssg-1" :"Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
"public-template-mssg-2" :"Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
"public-template-mssg-3" :"Besoin de plus de détails ou d'exemples ?",
"public-template-mssg-4": "Visitez le",
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
"public-template-mssg-6":"Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
"copy-code":"COPIE",
"copied-code":"COPIÉ",
"Installation":"Installation",
"Usage" :"Usage",
"insufficient-credits":"Crédits de signature insuffisants",
"insufficient-credits-mssg" :"Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
"angular-npm-mssg-1" :"Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
"quota-mail-info-head":"Limite mensuelle d'e-mails de signatures de demandes", "quota-mail-info-head":"Limite mensuelle d'e-mails de signatures de demandes",
"quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.", "quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.",
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.", "quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
@@ -646,6 +664,24 @@
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents." "p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
}, },
"advanced-options":"Options avancées", "advanced-options":"Options avancées",
"hide-advanced-options": "Masquer les options avancées" "hide-advanced-options": "Masquer les options avancées",
"download-files" :"Télécharger des fichiers",
"download-pdf" :"Télécharger le PDF",
"pdf-certificate" :"Télécharger Pdf + Certificat",
"document-logs":"Journaux de documents",
"server-down": "Impossible de se connecter au serveur OpenSign. Si vous hébergez vous-même OpenSign, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez OpenSign localement, vous y accédez peut-être via un numéro de port incorrect.",
"admin-exists": "L'administrateur existe déjà. Veuillez vous connecter à l'application en utilisant les informations d'identification de l'administrateur afin de gérer les utilisateurs.",
"enable-tour": "Activer la visite",
"istourenabled-otp-help":{
"p1": "Cela permettra une visite guidée pour les signataires, fournissant des instructions pendant le processus de signature.",
"p2": "Cela désactivera la visite guidée, garantissant ainsi une expérience de signature plus rapide et ininterrompue.",
"p3": "Conseil : si vos signataires connaissent OpenSign et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
},
"form-title-1": "Paramètre du flux de documents",
"form-title-2": "Paramètre de sécurité",
"public-tour-message": "Le modèle doit être public avant que vous puissiez générer un lien partageable.",
"add-user-template":"Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
"pdf-uncompatible":"Ce pdf n'est pas compatible avec opensign, veuillez contacter <support@opensignlabs.com>"
} }
File diff suppressed because one or more lines are too long
Binary file not shown.
+42 -9
View File
@@ -20,6 +20,9 @@ const Header = ({ showSidebar }) => {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const { width } = useWindowSize(); const { width } = useWindowSize();
const current_notification_version = "1"; // Update this with each new feature release
// Get the dismissed version from local storage
const dismissedVersion = localStorage.getItem("notificationDismissedVersion");
const username = localStorage.getItem("username") || ""; const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp; const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
@@ -30,9 +33,15 @@ const Header = ({ showSidebar }) => {
); );
const [emailUsed, setEmailUsed] = useState(0); const [emailUsed, setEmailUsed] = useState(0);
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState(false);
const [showNotification, setShowNotification] = useState(
(!dismissedVersion || dismissedVersion !== current_notification_version) &&
true
);
const toggleDropdown = () => { const toggleDropdown = () => {
setIsOpen(!isOpen); setIsOpen(!isOpen);
if (width <= 768) {
showSidebar();
}
}; };
useEffect(() => { useEffect(() => {
checkSubscription(); checkSubscription();
@@ -58,6 +67,13 @@ const Header = ({ showSidebar }) => {
} catch (err) { } catch (err) {
console.log("err in while fetching monthlyfreeEmails", err); console.log("err in while fetching monthlyfreeEmails", err);
} }
} else {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
} }
} }
@@ -115,17 +131,34 @@ const Header = ({ showSidebar }) => {
const handleMailUsed = () => { const handleMailUsed = () => {
setIsModal(!isModal); setIsModal(!isModal);
}; };
// Handle dismissing the notification
const dismissNotification = () => {
// Store the current version as dismissed in local storage
localStorage.setItem(
"notificationDismissedVersion",
current_notification_version
);
setShowNotification(false);
};
return ( return (
<div> <div>
{isEnableSubscription && ( {isEnableSubscription && showNotification && (
<div className="shadow py-1 text-center bg-[#CAE4FA] text-[14px] p-1"> <div className="flex justify-between items-center shadow py-1 bg-[#CAE4FA] p-1">
{t("header-news")} <div className="text-center text-[14px] ml-auto">
<span {t("header-news")}
className="cursor-pointer font-medium underline text-blue-800" <span
onClick={() => navigate("/profile")} className="cursor-pointer font-medium underline text-blue-800"
onClick={() => navigate("/profile")}
>
{" " + t("header-news-btn") + "."}
</span>
</div>
<div
className="ml-auto mr-1 cursor-pointer"
onClick={() => dismissNotification()}
> >
{" " + t("header-news-btn") + "."} <i className="fa-light fa-xmark"></i>
</span> </div>
</div> </div>
)} )}
<div className="op-navbar bg-base-100 shadow"> <div className="op-navbar bg-base-100 shadow">
@@ -2,13 +2,13 @@ import React, { useState, useEffect, useRef } from "react";
import "../../styles/opensigndrive.css"; import "../../styles/opensigndrive.css";
import axios from "axios"; import axios from "axios";
import * as ContextMenu from "@radix-ui/react-context-menu"; import * as ContextMenu from "@radix-ui/react-context-menu";
import { saveAs } from "file-saver";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Table from "react-bootstrap/Table"; import Table from "react-bootstrap/Table";
import * as HoverCard from "@radix-ui/react-hover-card"; import * as HoverCard from "@radix-ui/react-hover-card";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
import FolderModal from "../shared/fields/FolderModal"; import FolderModal from "../shared/fields/FolderModal";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { handleDownloadPdf } from "../../constant/Utils";
function DriveBody(props) { function DriveBody(props) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -100,16 +100,17 @@ function DriveBody(props) {
//function for navigate user to microapp-signature component //function for navigate user to microapp-signature component
const checkPdfStatus = async (data) => { const checkPdfStatus = async (data) => {
const signerExist = data.Signers && data.Signers; const signerExist = data?.Signers;
const isDecline = data.IsDeclined && data.IsDeclined; const isDecline = data?.IsDeclined;
const isPlaceholder = data.Placeholders && data.Placeholders; const isPlaceholder = data?.Placeholders;
const signedUrl = data.SignedUrl; const signedUrl = data?.SignedUrl;
const isSignYourself = data?.IsSignyourself;
//checking if document has completed and request signature flow //checking if document has completed and request signature flow
if (data?.IsCompleted && signerExist?.length > 0) { if (data?.IsCompleted && signerExist?.length > 0) {
navigate(`/recipientSignPdf/${data.objectId}`); navigate(`/recipientSignPdf/${data.objectId}`);
} }
//checking if document has completed and signyour-self flow //checking if document has completed and signyour-self flow
else if (!signerExist && !isPlaceholder) { else if ((!signerExist && !isPlaceholder) || isSignYourself) {
navigate(`/signaturePdf/${data.objectId}`); navigate(`/signaturePdf/${data.objectId}`);
} }
//checking if document has declined by someone //checking if document has declined by someone
@@ -136,12 +137,10 @@ function DriveBody(props) {
} }
}; };
const handleMenuItemClick = (selectType, data) => { const handleMenuItemClick = async (selectType, data) => {
switch (selectType) { switch (selectType) {
case "Download": { case "Download": {
const pdfName = data && data.Name; await handleDownloadPdf([data]);
const pdfUrl = data && data.SignedUrl ? data.SignedUrl : data.URL;
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
break; break;
} }
case "Rename": { case "Rename": {
@@ -264,12 +263,6 @@ function DriveBody(props) {
setIsOpenMoveModal(false); setIsOpenMoveModal(false);
} }
}; };
const sanitizeFileName = (pdfName) => {
// Replace spaces with underscore
return pdfName.replace(/ /g, "_");
};
const handleEnterPress = (e, data) => { const handleEnterPress = (e, data) => {
if (e.key === "Enter") { if (e.key === "Enter") {
handledRenameDoc(data); handledRenameDoc(data);
@@ -1,11 +1,24 @@
import React from "react"; import React from "react";
function BorderResize({ right, top }) { function BorderResize(props) {
const getHeight = () => {
const height = props.posHeight(props.pos, props.isSignYourself);
if (height > 14) {
return "14px";
} else {
return `${height}px`;
}
};
return ( return (
<div <div
className={`${right ? `-right-[12px]` : "-right-[1px]"} ${ style={{
top ? `-bottom-[11px]` : "-bottom-[1px]" width: getHeight() || "14px",
} absolute inline-block w-[14px] h-[14px] hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`} height: getHeight() || "14px"
}}
className={`${props.right ? `-right-[12px]` : "-right-[2px]"} ${
props.top ? `-bottom-[12px]` : "-bottom-[2px] "
} absolute inline-block hover:cursor-sw-resize border-r-[3px] border-b-[3px] border-[#188ae2]`}
></div> ></div>
); );
} }
@@ -17,7 +17,10 @@ const EditTemplate = ({ template, onSuccess }) => {
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false", SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
AutomaticReminders: template?.AutomaticReminders || false, AutomaticReminders: template?.AutomaticReminders || false,
RemindOnceInEvery: template?.RemindOnceInEvery || 5, RemindOnceInEvery: template?.RemindOnceInEvery || 5,
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false" IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false",
IsTourEnabled: template?.IsTourEnabled
? `${template?.IsTourEnabled}`
: "false"
}); });
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => { useEffect(() => {
@@ -43,6 +46,7 @@ const EditTemplate = ({ template, onSuccess }) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
const isChecked = formData.SendinOrder === "true" ? true : false; const isChecked = formData.SendinOrder === "true" ? true : false;
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
const AutoReminder = formData?.AutomaticReminders || false; const AutoReminder = formData?.AutomaticReminders || false;
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false; const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
let reminderDate = {}; let reminderDate = {};
@@ -56,6 +60,7 @@ const EditTemplate = ({ template, onSuccess }) => {
...formData, ...formData,
SendinOrder: isChecked, SendinOrder: isChecked,
IsEnableOTP: IsEnableOTP, IsEnableOTP: IsEnableOTP,
IsTourEnabled: isTourEnabled,
...reminderDate ...reminderDate
}; };
onSuccess(data); onSuccess(data);
@@ -256,6 +261,59 @@ const EditTemplate = ({ template, onSuccess }) => {
</div> </div>
</div> </div>
)} )}
<div className="text-xs mt-2">
<label className="block">
<span>
{t("enable-tour")}
<a data-tooltip-id="istourenabled-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
</span>
<Tooltip id="istourenabled-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("enable-tour")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("istourenabled-help.p1")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("istourenabled-help.p2")}</span>
</li>
</ol>
</p>
<p>{t("istourenabled-help.p3")}</p>
</div>
</Tooltip>
</label>
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsTourEnabled"
checked={formData.IsTourEnabled === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
<input
type="radio"
value={"false"}
name="IsTourEnabled"
className="op-radio op-radio-xs"
checked={formData.IsTourEnabled === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
<div className="mt-[1rem] flex justify-start"> <div className="mt-[1rem] flex justify-start">
<button type="submit" className="op-btn op-btn-primary"> <button type="submit" className="op-btn op-btn-primary">
{t("submit")} {t("submit")}
@@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import axios from "axios"; import axios from "axios";
import { handleDownloadPdf, handleToPrint } from "../../constant/Utils"; import { handleToPrint } from "../../constant/Utils";
import { themeColor, emailRegex } from "../../constant/const"; import { themeColor, emailRegex } from "../../constant/const";
import Loader from "../../primitives/Loader"; import Loader from "../../primitives/Loader";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
@@ -15,7 +15,8 @@ function EmailComponent({
sender, sender,
setIsAlert, setIsAlert,
extUserId, extUserId,
activeMailAdapter activeMailAdapter,
setIsDownloadModal
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [emailList, setEmailList] = useState([]); const [emailList, setEmailList] = useState([]);
@@ -29,6 +30,27 @@ function EmailComponent({
const pdfName = pdfDetails[0]?.Name; const pdfName = pdfDetails[0]?.Name;
setIsLoading(true); setIsLoading(true);
let sendMail; let sendMail;
const docId = pdfDetails?.[0]?.objectId || "";
const FileAdapterId = pdfDetails?.[0]?.FileAdapterId
? pdfDetails?.[0]?.FileAdapterId
: "";
let presignedUrl = pdfUrl;
try {
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl, docId: docId, fileAdapterId: FileAdapterId },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
presignedUrl = axiosRes.data.result;
} catch (err) {
console.log("err in getsignedurl", err);
}
for (let i = 0; i < emailList.length; i++) { for (let i = 0; i < emailList.length; i++) {
try { try {
const imgPng = const imgPng =
@@ -46,7 +68,7 @@ function EmailComponent({
mailProvider: activeMailAdapter, mailProvider: activeMailAdapter,
extUserId: extUserId, extUserId: extUserId,
pdfName: pdfName, pdfName: pdfName,
url: pdfUrl, url: presignedUrl,
recipient: emailList[i], recipient: emailList[i],
subject: `${sender.name} has signed the doc - ${pdfName}`, subject: `${sender.name} has signed the doc - ${pdfName}`,
from: sender.email, from: sender.email,
@@ -129,6 +151,11 @@ function EmailComponent({
} }
} }
}; };
const handleClose = () => {
setIsEmail(false);
setEmailValue("");
setEmailList([]);
};
return ( return (
<div> <div>
{/* isEmail */} {/* isEmail */}
@@ -165,9 +192,10 @@ function EmailComponent({
)} )}
<button <button
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2" className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
onClick={() => onClick={() => {
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading) handleClose();
} setIsDownloadModal(true);
}}
> >
<i className="fa-light fa-download" aria-hidden="true"></i> <i className="fa-light fa-download" aria-hidden="true"></i>
{t("download")} {t("download")}
@@ -263,11 +291,7 @@ function EmailComponent({
<button <button
type="button" type="button"
className="op-btn op-btn-ghost ml-2" className="op-btn op-btn-ghost ml-2"
onClick={() => { onClick={() => handleClose()}
setIsEmail(false);
setEmailValue("");
setEmailList([]);
}}
> >
{t("close")} {t("close")}
</button> </button>
@@ -0,0 +1,269 @@
import React, { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
import { copytoData } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
function EmbedTab(props) {
const { t } = useTranslation();
const tabName = [
{ title: "React/Next.js", icon: "fa-brands fa-react", color: "#61dafb" },
{ title: "JavaScript", icon: "fa-brands fa-js", color: "#ffd43b" },
{ title: "Angular", icon: "fa-brands fa-angular", color: "#ff5733" }
];
const [activeTab, setActiveTab] = useState(0);
// State to track if the code has been copied
const [isCopied, setIsCopied] = useState(false);
const reactCode = [
{
id: 0,
title: "Installation",
codeString: `
npm install @opensign/react`
},
{
id: 1,
title: "Usage",
codeString: `
import React from "react";
import Opensign from "@opensign/react";
export function App() {
return (
<div className="app">
<Opensign
onLoad={() => console.log("success")}
onLoadError={(error) => console.log(error)}
templateId= "${props.templateId}"
/>
</div>
);
}
`
}
];
const angularCode = [
{
id: 0,
title: "Installation",
codeString: `
npm install @opensign/angular`
},
{
id: 1,
title: "Usage",
codeString: `
import { Component } from '@angular/core';
import {OpensignComponent} from "@opensign/angular"
@Component({
selector:'app-root',
standalone: true,
imports: [OpensignComponent],
template:\`<opensign templateId="${props.templateId}"
(onLoad)="handleLoad()"
(onLoadError)="handleError($event)"
></opensign>\`,
})
export class AppComponent{
handleLoad() {
console.log("success");
}
handleError(error: string) {
console.log(error);
}
}
`
}
];
const jsCodeString = `
<script
src= "https://app.opensignlabs.com/static/js/public-template.bundle.js"
id="opensign-script"
templateId=${props.templateId}
></script>
`;
const handleCopy = (code, ind) => {
copytoData(code);
setIsCopied({ ...isCopied, [ind]: true });
setTimeout(() => setIsCopied(false), 3000);
};
return (
<div className="mt-4 border-t-[1px]">
<div className="flex justify-center items-center mt-2">
<div role="tablist" className="op-tabs op-tabs-bordered">
{tabName.map((tabData, ind) => (
<div
onClick={() => setActiveTab(ind)}
key={ind}
role="tab"
className={`${
activeTab === ind ? "op-tab-active" : ""
} op-tab flex items-center`}
>
<i
className={`${tabData.icon}`}
style={{ color: tabData.color }}
></i>
<span className="ml-1 text-[10px] font-medium md:font-normal md:text-[15px]">
{tabData.title}
</span>
</div>
))}
</div>
</div>
<div>
{activeTab === 0 ? (
<div className="mt-4">
{reactCode.map((data, ind) => {
return (
<div key={ind}>
<p className="font-medium text-[18px]">
{t(`${data.title}`)}
</p>
{ind === 0 && (
<p className="text-[13px] mt-2">
{t("public-template-mssg-1")}
</p>
)}
<div className="relative p-1">
<div
onClick={() => handleCopy(data.codeString, ind)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[ind] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{data.codeString}
</SyntaxHighlighter>
</div>
</div>
);
})}
<p className="font-medium text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
{" "}
{t("public-template-mssg-4")}
<a
href="https://www.npmjs.com/package/@opensign/react"
target="_blank"
rel="noreferrer"
className="cursor-pointer text-blue-700 "
>
{" "}
OpenSign React package{" "}
</a>
{t("public-template-mssg-5")}
</p>
</div>
) : activeTab === 1 ? (
<div className="mt-4">
<div>
{/* <p className="font-medium text-[18px]">{t(`${data.title}`)}</p> */}
<div className="relative p-1">
<div
onClick={() => handleCopy(jsCodeString, 0)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[0] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{jsCodeString}
</SyntaxHighlighter>
</div>
</div>
</div>
) : (
activeTab === 2 && (
<div className="mt-4">
{angularCode.map((data, ind) => {
return (
<div key={ind}>
<p className="font-medium text-[18px]">
{t(`${data.title}`)}
</p>
{ind === 0 && (
<p className="text-[13px] mt-2">
{t("angular-npm-mssg-1")}
</p>
)}
<div className="relative p-1">
<div
onClick={() => handleCopy(data.codeString, ind)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[ind] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{data.codeString}
</SyntaxHighlighter>
</div>
</div>
);
})}
<p className="font-medium text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
{" "}
{t("public-template-mssg-4")}
<a
href="https://www.npmjs.com/package/@opensign/angular"
target="_blank"
rel="noreferrer"
className="cursor-pointer text-blue-700 "
>
{" "}
OpenSign Angular package{" "}
</a>
{t("public-template-mssg-5")}
</p>
</div>
)
)}
</div>
</div>
);
}
export default EmbedTab;
+58 -47
View File
@@ -39,7 +39,8 @@ function Header({
clickOnZoomOut, clickOnZoomOut,
handleRotationFun, handleRotationFun,
isDisableRotate, isDisableRotate,
templateId templateId,
setIsDownloadModal
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const filterPrefill = const filterPrefill =
@@ -94,9 +95,13 @@ function Header({
> >
<DropdownMenu.Item <DropdownMenu.Item
className="DropdownMenuItem" className="DropdownMenuItem"
onClick={() => onClick={() => {
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading) if (isCompleted) {
} setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
> >
<div className="flex flex-row"> <div className="flex flex-row">
<i <i
@@ -209,57 +214,67 @@ function Header({
sideOffset={5} sideOffset={5}
> >
<DropdownMenu.Item <DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer" className="DropdownMenuItem"
onClick={() => onClick={() =>
handleDownloadPdf( handleDownloadPdf(pdfDetails, setIsDownloading)
pdfDetails,
pdfDetails[0]?.URL,
setIsDownloading
)
} }
> >
<i <div className="flex flex-row">
className="fa-light fa-arrow-down mr-[3px]" <i
aria-hidden="true" className="fa-light fa-arrow-down mr-[3px]"
></i> aria-hidden="true"
<span className="font-[500]">{t("download")}</span> ></i>
<span className="font-[500]">
{t("download")}
</span>
</div>
</DropdownMenu.Item> </DropdownMenu.Item>
{!isDisableRotate && ( {!isDisableRotate && (
<> <>
<DropdownMenu.Item <DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer" className="DropdownMenuItem"
onClick={() => handleRotationFun(90)} onClick={() => handleRotationFun(90)}
> >
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i> <div className="flex flex-row">
<span className="font-[500]"> <i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
{t("rotate-right")} <span className="font-[500]">
</span> {t("rotate-right")}
</span>
</div>
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer" className="DropdownMenuItem"
onClick={() => handleRotationFun(-90)} onClick={() => handleRotationFun(-90)}
> >
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i> <div className="flex flex-row">
<span className="font-[500]"> <i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
{t("rotate-left")} <span className="font-[500]">
</span> {t("rotate-left")}
</span>
</div>
</DropdownMenu.Item> </DropdownMenu.Item>
</> </>
)} )}
<DropdownMenu.Item <DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer" className="DropdownMenuItem"
onClick={() => clickOnZoomIn()} onClick={() => clickOnZoomIn()}
> >
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i> <div className="flex flex-row">
<span className="font-[500]">{t("zoom-in")}</span> <i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">{t("zoom-in")}</span>
</div>
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer" className="DropdownMenuItem"
onClick={() => clickOnZoomOut()} onClick={() => clickOnZoomOut()}
> >
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i> <div className="flex flex-row">
<span className="font-[500]">{t("zoom-out")}</span> <i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("zoom-out")}
</span>
</div>
</DropdownMenu.Item> </DropdownMenu.Item>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Portal> </DropdownMenu.Portal>
@@ -372,9 +387,13 @@ function Header({
<button <button
type="button" type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow" className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => onClick={() => {
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading) if (isCompleted) {
} setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
> >
<i <i
className="fa-light fa-download py-[3px]" className="fa-light fa-download py-[3px]"
@@ -399,11 +418,7 @@ function Header({
{templateId && ( {templateId && (
<button <button
onClick={() => onClick={() =>
handleDownloadPdf( handleDownloadPdf(pdfDetails, setIsDownloading)
pdfDetails,
pdfUrl,
setIsDownloading
)
} }
type="button" type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]" className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
@@ -429,11 +444,7 @@ function Header({
type="button" type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow" className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
onClick={() => onClick={() =>
handleDownloadPdf( handleDownloadPdf(pdfDetails, setIsDownloading)
pdfDetails,
pdfUrl,
setIsDownloading
)
} }
> >
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i> <i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
@@ -473,9 +484,9 @@ function Header({
<button <button
type="button" type="button"
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow" className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
onClick={() => onClick={() => {
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading) setIsDownloadModal(true);
} }}
> >
<i className="fa-light fa-download" aria-hidden="true"></i> <i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("download")}</span> <span className="hidden lg:block ml-1">{t("download")}</span>
+41 -13
View File
@@ -331,10 +331,9 @@ function Placeholder(props) {
//condition for only placeholder and template flow //condition for only placeholder and template flow
if (props.data && props?.pos?.type !== textWidget) { if (props.data && props?.pos?.type !== textWidget) {
props.setUniqueId(props?.data?.Id); props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex( const checkIndex = props.xyPostion
(data) => data.Id === props.data.Id .filter((data) => data.Role !== "prefill")
); .findIndex((data) => data.Id === props.data.Id);
props.setIsSelectId(checkIndex || 0); props.setIsSelectId(checkIndex || 0);
} }
//condition to handle in placeholder and template flow for text widget signerId for text widgets i have to set uniqueId in tempSignerId because //condition to handle in placeholder and template flow for text widget signerId for text widgets i have to set uniqueId in tempSignerId because
@@ -342,10 +341,6 @@ function Placeholder(props) {
else if (props.data && props.pos.type === textWidget) { else if (props.data && props.pos.type === textWidget) {
props.setTempSignerId(props.uniqueId); props.setTempSignerId(props.uniqueId);
props.setUniqueId(props?.data?.Id); props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
} }
props.setSignKey(props.pos.key); props.setSignKey(props.pos.key);
props.setWidgetType(props.pos.type); props.setWidgetType(props.pos.type);
@@ -651,6 +646,21 @@ function Placeholder(props) {
} }
} }
}; };
//function to calculate font size
const calculateFont = (size, isMinHeight) => {
const containerScale = getContainerScale(
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const fontSize = (size || 12) * containerScale * props.scale;
//isMinHeight to set text box minimum height
if (isMinHeight) {
return fontSize * 1.5 + "px";
} else {
return fontSize + "px";
}
};
const getCursor = () => { const getCursor = () => {
if (props.data && props.isNeedSign) { if (props.data && props.isNeedSign) {
@@ -725,7 +735,9 @@ function Placeholder(props) {
: props?.pos?.zIndex : props?.pos?.zIndex
? props.pos.zIndex ? props.pos.zIndex
: "5", : "5",
background: props.data ? props.data.blockColor : "rgb(203 233 237)" background: props.data
? props.data?.blockColor + "b0"
: "rgba(203, 233, 237, 0.69)"
}} }}
onDrag={() => { onDrag={() => {
setDraggingEnabled(true); setDraggingEnabled(true);
@@ -743,12 +755,16 @@ function Placeholder(props) {
? "auto" ? "auto"
: props.posHeight(props.pos, props.isSignYourself) : props.posHeight(props.pos, props.isSignYourself)
}} }}
minHeight={calculateFont(props.pos.options?.fontSize, true)}
maxHeight="auto"
onResizeStart={() => { onResizeStart={() => {
setDraggingEnabled(true); setDraggingEnabled(true);
props.setIsResize && props.setIsResize(true); props.setIsResize && props.setIsResize(true);
}} }}
onResizeStop={(e, direction, ref) => { onResizeStop={(e, direction, ref) => {
props.setIsResize && props.setIsResize(false); setTimeout(() => {
props.setIsResize && props.setIsResize(false);
}, 50);
props.handleSignYourselfImageResize && props.handleSignYourselfImageResize &&
props.handleSignYourselfImageResize( props.handleSignYourselfImageResize(
ref, ref,
@@ -783,18 +799,28 @@ function Placeholder(props) {
h: ref.offsetHeight / (props.scale * containerScale) h: ref.offsetHeight / (props.scale * containerScale)
}); });
}} }}
onClick={() => handleOnClickPlaceholder()} onClick={() => !props.isResize && handleOnClickPlaceholder()}
> >
{props.isShowBorder && {props.isShowBorder &&
props.pos.type !== radioButtonWidget && props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" && props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId ? ( props.pos.key === props.selectWidgetId ? (
<BorderResize right={-12} top={-11} /> <BorderResize
right={-12}
top={-11}
pos={props.pos}
posHeight={props.posHeight}
isSignYourself={props.isSignYourself}
/>
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? ( ) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
props.data?.signerObjId === props.signerObjId && props.data?.signerObjId === props.signerObjId &&
props.pos.type !== radioButtonWidget && props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" ? ( props.pos.type !== "checkbox" ? (
<BorderResize /> <BorderResize
posHeight={props.posHeight}
isSignYourself={props.isSignYourself}
pos={props.pos}
/>
) : ( ) : (
<></> <></>
) )
@@ -860,6 +886,7 @@ function Placeholder(props) {
startDate={startDate} startDate={startDate}
handleSaveDate={handleSaveDate} handleSaveDate={handleSaveDate}
xPos={props.xPos} xPos={props.xPos}
calculateFont={calculateFont}
/> />
</div> </div>
) : ( ) : (
@@ -887,6 +914,7 @@ function Placeholder(props) {
startDate={startDate} startDate={startDate}
handleSaveDate={handleSaveDate} handleSaveDate={handleSaveDate}
xPos={props.xPos} xPos={props.xPos}
calculateFont={calculateFont}
/> />
</> </>
)} )}
@@ -29,7 +29,7 @@ function PlaceholderType(props) {
const [textValue, setTextValue] = useState(); const [textValue, setTextValue] = useState();
const [selectedCheckbox, setSelectedCheckbox] = useState([]); const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const years = range(1990, getYear(new Date()) + 16, 1); const years = range(1990, getYear(new Date()) + 16, 1);
const fontSize = (props.pos.options?.fontSize || "12") + "px"; const fontSize = props.calculateFont(props.pos.options?.fontSize);
const fontColor = props.pos.options?.fontColor || "black"; const fontColor = props.pos.options?.fontColor || "black";
const months = [ const months = [
"January", "January",
+10 -1
View File
@@ -65,7 +65,8 @@ function RenderPdf({
fontSize, fontSize,
setFontSize, setFontSize,
fontColor, fontColor,
setFontColor setFontColor,
isResize
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const isMobile = window.innerWidth < 767; const isMobile = window.innerWidth < 767;
@@ -104,6 +105,7 @@ function RenderPdf({
} }
} }
}; };
const posHeight = (pos, signYourself) => { const posHeight = (pos, signYourself) => {
const containerScale = getContainerScale( const containerScale = getContainerScale(
pdfOriginalWH, pdfOriginalWH,
@@ -207,6 +209,7 @@ function RenderPdf({
pageNumber={pageNumber} pageNumber={pageNumber}
ispublicTemplate={ispublicTemplate} ispublicTemplate={ispublicTemplate}
handleUserDetails={handleUserDetails} handleUserDetails={handleUserDetails}
isResize={isResize}
/> />
</React.Fragment> </React.Fragment>
) )
@@ -357,6 +360,7 @@ function RenderPdf({
setFontSize={setFontSize} setFontSize={setFontSize}
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
isResize={isResize}
/> />
</React.Fragment> </React.Fragment>
); );
@@ -417,6 +421,8 @@ function RenderPdf({
setFontSize={setFontSize} setFontSize={setFontSize}
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
isResize={isResize}
setIsResize={setIsResize}
/> />
) )
); );
@@ -548,6 +554,7 @@ function RenderPdf({
setFontSize={setFontSize} setFontSize={setFontSize}
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
isResize={isResize}
/> />
</React.Fragment> </React.Fragment>
); );
@@ -611,6 +618,8 @@ function RenderPdf({
setFontSize={setFontSize} setFontSize={setFontSize}
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
isResize={isResize}
setIsResize={setIsResize}
/> />
</React.Fragment> </React.Fragment>
); );
@@ -6,6 +6,7 @@ function SelectLanguage(props) {
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const languages = [ const languages = [
{ value: "en", text: "English" }, { value: "en", text: "English" },
{ value: "es", text: "Española" },
{ value: "fr", text: "Français" } { value: "fr", text: "Français" }
]; ];
const [lang, setLang] = useState(i18next.language || "en"); const [lang, setLang] = useState(i18next.language || "en");
+1 -1
View File
@@ -596,7 +596,7 @@ function SignPad({
borderRadius: "2px" borderRadius: "2px"
} }
}} }}
backgroundColor="rgb(255, 255, 255)" // backgroundColor="rgb(255, 255, 255)"
onEnd={() => onEnd={() =>
handleSignatureChange(canvasRef.current?.toDataURL()) handleSignatureChange(canvasRef.current?.toDataURL())
} }
@@ -41,9 +41,9 @@ const SuggestionInput = (props) => {
const handleInputChange = async (e) => { const handleInputChange = async (e) => {
const value = e.target.value; const value = e.target.value;
setInputValue(value?.toLowerCase()); setInputValue(value?.toLowerCase()?.replace(/\s/g, ""));
if (props.onChange) { if (props.onChange) {
props.onChange(value); props.onChange(value?.toLowerCase()?.replace(/\s/g, ""));
} }
if (value.trim() === "") { if (value.trim() === "") {
+433 -336
View File
@@ -33,14 +33,15 @@ export const openInNewTab = (url, target) => {
export async function fetchSubscription( export async function fetchSubscription(
extUserId, extUserId,
contactObjId, contactObjId,
isGuestSign = false isGuestSign = false,
isPublic = false
) { ) {
try { try {
const Extand_Class = localStorage.getItem("Extand_Class"); const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class); const extClass = Extand_Class && JSON.parse(Extand_Class);
// console.log("extClass ", extClass); // console.log("extClass ", extClass);
let extUser; let extUser;
if (extClass && extClass.length > 0) { if (extClass && extClass.length > 0 && !isPublic) {
extUser = extClass[0].objectId; extUser = extClass[0].objectId;
} else { } else {
extUser = extUserId; extUser = extUserId;
@@ -54,7 +55,7 @@ export async function fetchSubscription(
}; };
const params = isGuestSign const params = isGuestSign
? { contactId: contactObjId } ? { contactId: contactObjId }
: { extUserId: extUser }; : { extUserId: extUser, ispublic: isPublic };
const tenatRes = await axios.post(url, params, { headers: headers }); const tenatRes = await axios.post(url, params, { headers: headers });
let plan, status, billingDate, adminId; let plan, status, billingDate, adminId;
if (isGuestSign) { if (isGuestSign) {
@@ -709,7 +710,9 @@ export const createDocument = async (
SendinOrder: Doc?.SendinOrder || false, SendinOrder: Doc?.SendinOrder || false,
AutomaticReminders: Doc?.AutomaticReminders || false, AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5), RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5),
IsEnableOTP: Doc?.IsEnableOTP || false IsEnableOTP: Doc?.IsEnableOTP || false,
IsTourEnabled: Doc?.IsTourEnabled || false,
FileAdapterId: Doc?.FileAdapterId || ""
}; };
try { try {
@@ -1085,18 +1088,22 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
const textContent = documentId && `OpenSign™ DocumentId: ${documentId} `; const textContent = documentId && `OpenSign™ DocumentId: ${documentId} `;
const pages = pdfDoc.getPages(); const pages = pdfDoc.getPages();
const page = pages[i]; const page = pages[i];
const getObj = compensateRotation( try {
page.getRotation().angle, const getObj = compensateRotation(
10, page.getRotation().angle,
5, 10,
1, 5,
page.getSize(), 1,
fontSize, page.getSize(),
rgb(0.5, 0.5, 0.5), fontSize,
font, rgb(0.5, 0.5, 0.5),
page font,
); page
page.drawText(textContent, getObj); );
page.drawText(textContent, getObj);
} catch (err) {
console.log("Err in embed docId on page", i + 1, err?.message);
}
} }
}; };
@@ -1320,7 +1327,9 @@ export const multiSignEmbed = async (
); );
pdfDoc.registerFontkit(fontkit); pdfDoc.registerFontkit(fontkit);
const font = await pdfDoc.embedFont(fontBytes, { subset: true }); const font = await pdfDoc.embedFont(fontBytes, { subset: true });
let hasError = false;
for (let item of widgets) { for (let item of widgets) {
if (hasError) break; // Stop the outer loop if an error occurred
const containerScale = getContainerScale( const containerScale = getContainerScale(
pdfOriginalWH, pdfOriginalWH,
item?.pageNumber, item?.pageNumber,
@@ -1333,8 +1342,17 @@ export const multiSignEmbed = async (
if (signyourself) { if (signyourself) {
updateItem = item.pos; updateItem = item.pos;
} else { } else {
// Checking required and optional widget types
// For both required and optional widgets, handle signurl, defaultValue, and response as the widget's data
// If the widget type is checkbox or radio (whether required or optional), we don't need to validate its value.
// Instead, add an empty checkbox/radio, or if a value exists, mark the checkbox/radio as checked.
updateItem = item.pos.filter( updateItem = item.pos.filter(
(data) => data?.options?.status === "required" (data) =>
data?.options?.SignUrl ||
data?.options?.defaultValue ||
data?.options?.response ||
data?.type === "checkbox" ||
data?.type === radioButtonWidget
); );
} }
} else { } else {
@@ -1349,323 +1367,328 @@ export const multiSignEmbed = async (
widgetsPositionArr.map(async (url) => { widgetsPositionArr.map(async (url) => {
let signUrl = url.SignUrl && url.SignUrl; let signUrl = url.SignUrl && url.SignUrl;
if (signUrl) { if (signUrl) {
if (url.ImageType === "image/png") {
//function for convert signature png base64 url to jpeg base64
const newUrl = await convertPNGtoJPEG(signUrl);
signUrl = newUrl;
}
const res = await fetch(signUrl); const res = await fetch(signUrl);
return res.arrayBuffer(); return res.arrayBuffer();
} }
}) })
); );
for (let [id, position] of widgetsPositionArr.entries()) {
widgetsPositionArr.forEach(async (position, id) => { if (hasError) break; // Stop the inner loop if an error occurred
let img; try {
if (["signature", "stamp", "initials", "image"].includes(position.type)) { let img;
if ( if (
(position.ImageType && position.ImageType === "image/png") || ["signature", "stamp", "initials", "image"].includes(position.type)
position.ImageType === "image/jpeg"
) { ) {
img = await pdfDoc.embedJpg(images[id]); if (position.ImageType && position.ImageType === "image/jpeg") {
} else { img = await pdfDoc.embedJpg(images[id]);
img = await pdfDoc.embedPng(images[id]);
}
} else if (!position.type) {
if (
(position.ImageType && position.ImageType === "image/png") ||
position.ImageType === "image/jpeg"
) {
img = await pdfDoc.embedJpg(images[id]);
} else {
img = await pdfDoc.embedPng(images[id]);
}
}
let widgetWidth, widgetHeight;
widgetWidth = placeholderWidth(position);
widgetHeight = placeholderHeight(position);
const xPos = (position) => {
const resizePos = position.xPosition;
//first two condition handle to old data already saved from mobile view which scale point diffrent
if (isMobile && position.isMobile) {
//if pos.isMobile false -- placeholder saved from desktop view then handle position in mobile view divided by scale
const x = resizePos * (position.scale / scale);
return x * scale;
} else if (position.isMobile && position.scale) {
const x = resizePos * position.scale;
return x;
} else {
return resizePos;
}
};
const yPos = (position) => {
const resizePos = position.yPosition;
if (position.isMobile && position.scale) {
if (position.IsResize) {
const y = resizePos * position.scale;
return y;
} else { } else {
const y = resizePos * position.scale; img = await pdfDoc.embedPng(images[id]);
return y;
} }
} else { } else if (!position.type) {
return resizePos; // to handle old widget when only stamp and signature are exists
} if (position.ImageType && position.ImageType === "image/jpeg") {
}; img = await pdfDoc.embedJpg(images[id]);
const color = position?.options?.fontColor; } else {
const updateColorInRgb = getWidgetsFontColor(color); img = await pdfDoc.embedPng(images[id]);
const fontSize = parseInt(position?.options?.fontSize || 13); }
const widgetTypeExist = [ }
textWidget, let widgetWidth, widgetHeight;
textInputWidget, widgetWidth = placeholderWidth(position);
"name", widgetHeight = placeholderHeight(position);
"company", const xPos = (position) => {
"job title", const resizePos = position.xPosition;
"date", //first two condition handle to old data already saved from mobile view which scale point diffrent
"email" if (isMobile && position.isMobile) {
].includes(position.type); //if pos.isMobile false -- placeholder saved from desktop view then handle position in mobile view divided by scale
if (position.type === "checkbox") { const x = resizePos * (position.scale / scale);
let checkboxGapFromTop, isCheck; return x * scale;
let y = yPos(position); } else if (position.isMobile && position.scale) {
const optionsFontSize = fontSize || 13; const x = resizePos * position.scale;
const checkboxSize = fontSize; return x;
const checkboxTextGapFromLeft = fontSize + 5 || 22; } else {
if (position?.options?.values.length > 0) { return resizePos;
position?.options?.values.forEach((item, ind) => {
const checkboxRandomId = "checkbox" + randomId();
if (
position?.options?.response &&
position?.options?.response?.length > 0
) {
isCheck = position?.options?.response?.includes(ind);
} else if (position?.options?.defaultValue) {
isCheck = position?.options?.defaultValue?.includes(ind);
}
const checkbox = form.createCheckBox(checkboxRandomId);
if (ind > 0) {
y = y + checkboxGapFromTop;
} else {
checkboxGapFromTop = fontSize + 5 || 26;
}
if (!position?.options?.isHideLabel) {
// below line of code is used to embed label with radio button in pdf
const optionsPosition = compensateRotation(
page.getRotation().angle,
xPos(position) + checkboxTextGapFromLeft,
y,
1,
page.getSize(),
optionsFontSize,
updateColorInRgb,
font,
page
);
page.drawText(item, optionsPosition);
}
let checkboxObj = {
x: xPos(position),
y: y,
width: checkboxSize,
height: checkboxSize
};
checkboxObj = getWidgetPosition(page, checkboxObj, 1);
checkbox.addToPage(page, checkboxObj);
//applied which checkbox should be checked
if (isCheck) {
checkbox.check();
} else {
checkbox.uncheck();
}
checkbox.enableReadOnly();
});
}
} else if (widgetTypeExist) {
const fontSize = calculateFontSize(
position,
containerScale,
signyourself
);
parseInt(fontSize);
let textContent;
if (position?.options?.response) {
textContent = position.options?.response;
} else if (position?.options?.defaultValue) {
textContent = position?.options?.defaultValue;
}
const fixedWidth = widgetWidth; // Set your fixed width
const isNewOnEnterLineExist = textContent.includes("\n");
// Function to break text into lines based on the fixed width
const NewbreakTextIntoLines = (textContent, width) => {
const lines = [];
let currentLine = "";
for (const word of textContent.split(" ")) {
//get text line width
const lineWidth = font.widthOfTextAtSize(
`${currentLine} ${word}`,
fontSize
);
//check text content line width is less or equal to container width
if (lineWidth <= width) {
currentLine += ` ${word}`;
} else {
lines.push(currentLine.trim());
currentLine = `${word}`;
}
} }
lines.push(currentLine.trim());
return lines;
}; };
const yPos = (position) => {
const resizePos = position.yPosition;
// Function to break text into lines based on when user go next line on press enter button if (position.isMobile && position.scale) {
const breakTextIntoLines = (textContent, width) => { if (position.IsResize) {
const lines = []; const y = resizePos * position.scale;
return y;
for (const word of textContent.split("\n")) { } else {
const lineWidth = font.widthOfTextAtSize(`${word}`, fontSize); const y = resizePos * position.scale;
//checking string length to container width return y;
//if string length is less then container width it means user press enter button
if (lineWidth <= width) {
lines.push(word);
}
//else adjust text content according to width and send it in new line
else {
const newLine = NewbreakTextIntoLines(word, width);
lines.push(...newLine);
} }
} else {
return resizePos;
} }
return lines;
}; };
const color = position?.options?.fontColor;
const updateColorInRgb = getWidgetsFontColor(color);
const fontSize = parseInt(position?.options?.fontSize || 12);
const widgetTypeExist = [
textWidget,
textInputWidget,
"name",
"company",
"job title",
"date",
"email"
].includes(position.type);
if (position.type === "checkbox") {
let checkboxGapFromTop, isCheck;
let y = yPos(position);
const optionsFontSize = fontSize || 13;
const checkboxSize = fontSize;
const checkboxTextGapFromLeft = fontSize + 5 || 22;
if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => {
const checkboxRandomId = "checkbox" + randomId();
//check if text content have `\n` string it means user press enter to go next line and handle condition if (
//else auto adjust text content according to container width position?.options?.response &&
const lines = isNewOnEnterLineExist position?.options?.response?.length > 0
? breakTextIntoLines(textContent, fixedWidth) ) {
: NewbreakTextIntoLines(textContent, fixedWidth); isCheck = position?.options?.response?.includes(ind);
// Set initial y-coordinate for the first line } else if (position?.options?.defaultValue) {
let x = xPos(position); isCheck = position?.options?.defaultValue?.includes(ind);
let y = yPos(position); }
// Embed each line on the page
for (const line of lines) { const checkbox = form.createCheckBox(checkboxRandomId);
const textPosition = compensateRotation(
page.getRotation().angle, if (ind > 0) {
x, y = y + checkboxGapFromTop;
y, } else {
1, checkboxGapFromTop = fontSize + 5 || 26;
page.getSize(), }
fontSize,
updateColorInRgb, if (!position?.options?.isHideLabel) {
font, // below line of code is used to embed label with radio button in pdf
page const optionsPosition = compensateRotation(
page.getRotation().angle,
xPos(position) + checkboxTextGapFromLeft,
y,
1,
page.getSize(),
optionsFontSize,
updateColorInRgb,
font,
page
);
page.drawText(item, optionsPosition);
}
let checkboxObj = {
x: xPos(position),
y: y,
width: checkboxSize,
height: checkboxSize
};
checkboxObj = getWidgetPosition(page, checkboxObj, 1);
checkbox.addToPage(page, checkboxObj);
//applied which checkbox should be checked
if (isCheck) {
checkbox.check();
} else {
checkbox.uncheck();
}
checkbox.enableReadOnly();
});
}
} else if (widgetTypeExist) {
const fontSize = calculateFontSize(
position,
containerScale,
signyourself
); );
page.drawText(line, textPosition); parseInt(fontSize);
y += 18; // Adjust the line height as needed let textContent;
} if (position?.options?.response) {
} else if (position.type === "dropdown") { textContent = position.options?.response;
const fontsize = parseInt(position?.options?.fontSize) || 12; } else if (position?.options?.defaultValue) {
textContent = position?.options?.defaultValue;
}
const fixedWidth = widgetWidth; // Set your fixed width
const isNewOnEnterLineExist = textContent.includes("\n");
const dropdownRandomId = "dropdown" + randomId(); // Function to break text into lines based on the fixed width
const dropdown = form.createDropdown(dropdownRandomId); const NewbreakTextIntoLines = (textContent, width) => {
dropdown.addOptions(position?.options?.values); const lines = [];
if (position?.options?.response) { let currentLine = "";
dropdown.select(position.options?.response);
} else if (position?.options?.defaultValue) {
dropdown.select(position?.options?.defaultValue);
}
// Define the default appearance string
// Example format: `/FontName FontSize Tf 0 g` where:
// - `/FontName` is the name of the font (e.g., `/Helv` for Helvetica)
// - `FontSize` is the size you want to set (e.g., 12)
// - `Tf` specifies the font and size
// - `0 g` sets the text color to black
const defaultAppearance = `/Helv ${fontsize} Tf 0 g`;
// Set the default appearance for the dropdown field
dropdown.acroField.setDefaultAppearance(defaultAppearance);
dropdown.setFontSize(fontsize);
const dropdownObj = {
x: xPos(position),
y: yPos(position),
width: widgetWidth,
height: widgetHeight
};
const dropdownOption = getWidgetPosition(page, dropdownObj, 1);
const dropdownSelected = { ...dropdownOption, font: font };
dropdown.defaultUpdateAppearances(font);
dropdown.addToPage(page, dropdownSelected);
dropdown.enableReadOnly();
} else if (position.type === radioButtonWidget) {
const radioRandomId = "radio" + randomId();
const radioGroup = form.createRadioGroup(radioRandomId);
let radioOptionGapFromTop;
const optionsFontSize = fontSize || 13;
const radioTextGapFromLeft = fontSize + 5 || 20;
const radioSize = fontSize;
let y = yPos(position);
if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => {
if (ind > 0) {
y = y + radioOptionGapFromTop;
} else {
radioOptionGapFromTop = fontSize + 10 || 25;
}
if (!position?.options?.isHideLabel) {
// below line of code is used to embed label with radio button in pdf
const optionsPosition = compensateRotation( for (const word of textContent.split(" ")) {
page.getRotation().angle, //get text line width
xPos(position) + radioTextGapFromLeft, const lineWidth = font.widthOfTextAtSize(
y, `${currentLine} ${word}`,
1, fontSize
page.getSize(),
optionsFontSize,
updateColorInRgb,
font,
page
); );
//check text content line width is less or equal to container width
page.drawText(item, optionsPosition); if (lineWidth <= width) {
currentLine += ` ${word}`;
} else {
lines.push(currentLine.trim());
currentLine = `${word}`;
}
} }
let radioObj = { lines.push(currentLine.trim());
x: xPos(position), return lines;
y: y, };
width: radioSize,
height: radioSize
};
radioObj = getWidgetPosition(page, radioObj, 1); // Function to break text into lines based on when user go next line on press enter button
radioGroup.addOptionToPage(item, page, radioObj); const breakTextIntoLines = (textContent, width) => {
}); const lines = [];
}
if (position?.options?.response) {
radioGroup.select(position.options?.response);
} else if (position?.options?.defaultValue) {
radioGroup.select(position?.options?.defaultValue);
}
radioGroup.enableReadOnly();
} else {
const signature = {
x: xPos(position),
y: yPos(position),
width: widgetWidth,
height: widgetHeight
};
const imageOptions = getWidgetPosition(page, signature, 1); for (const word of textContent.split("\n")) {
page.drawImage(img, imageOptions); const lineWidth = font.widthOfTextAtSize(`${word}`, fontSize);
//checking string length to container width
//if string length is less then container width it means user press enter button
if (lineWidth <= width) {
lines.push(word);
}
//else adjust text content according to width and send it in new line
else {
const newLine = NewbreakTextIntoLines(word, width);
lines.push(...newLine);
}
}
return lines;
};
//check if text content have `\n` string it means user press enter to go next line and handle condition
//else auto adjust text content according to container width
const lines = isNewOnEnterLineExist
? breakTextIntoLines(textContent, fixedWidth)
: NewbreakTextIntoLines(textContent, fixedWidth);
// Set initial y-coordinate for the first line
let x = xPos(position);
let y = yPos(position);
// Embed each line on the page
for (const line of lines) {
const textPosition = compensateRotation(
page.getRotation().angle,
x,
y,
1,
page.getSize(),
fontSize,
updateColorInRgb,
font,
page
);
page.drawText(line, textPosition);
y += 18; // Adjust the line height as needed
}
} else if (position.type === "dropdown") {
const fontsize = parseInt(position?.options?.fontSize) || 12;
const dropdownRandomId = "dropdown" + randomId();
const dropdown = form.createDropdown(dropdownRandomId);
dropdown.addOptions(position?.options?.values);
if (position?.options?.response) {
dropdown.select(position.options?.response);
} else if (position?.options?.defaultValue) {
dropdown.select(position?.options?.defaultValue);
}
// Define the default appearance string
// Example format: `/FontName FontSize Tf 0 g` where:
// - `/FontName` is the name of the font (e.g., `/Helv` for Helvetica)
// - `FontSize` is the size you want to set (e.g., 12)
// - `Tf` specifies the font and size
// - `0 g` sets the text color to black
const defaultAppearance = `/Helv ${fontsize} Tf 0 g`;
// Set the default appearance for the dropdown field
dropdown.acroField.setDefaultAppearance(defaultAppearance);
dropdown.setFontSize(fontsize);
const dropdownObj = {
x: xPos(position),
y: yPos(position),
width: widgetWidth,
height: widgetHeight
};
const dropdownOption = getWidgetPosition(page, dropdownObj, 1);
const dropdownSelected = { ...dropdownOption, font: font };
dropdown.defaultUpdateAppearances(font);
dropdown.addToPage(page, dropdownSelected);
dropdown.enableReadOnly();
} else if (position.type === radioButtonWidget) {
const radioRandomId = "radio" + randomId();
const radioGroup = form.createRadioGroup(radioRandomId);
let radioOptionGapFromTop;
const optionsFontSize = fontSize || 13;
const radioTextGapFromLeft = fontSize + 5 || 20;
const radioSize = fontSize;
let y = yPos(position);
if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => {
if (ind > 0) {
y = y + radioOptionGapFromTop;
} else {
radioOptionGapFromTop = fontSize + 10 || 25;
}
if (!position?.options?.isHideLabel) {
// below line of code is used to embed label with radio button in pdf
const optionsPosition = compensateRotation(
page.getRotation().angle,
xPos(position) + radioTextGapFromLeft,
y,
1,
page.getSize(),
optionsFontSize,
updateColorInRgb,
font,
page
);
page.drawText(item, optionsPosition);
}
let radioObj = {
x: xPos(position),
y: y,
width: radioSize,
height: radioSize
};
radioObj = getWidgetPosition(page, radioObj, 1);
radioGroup.addOptionToPage(item, page, radioObj);
});
}
if (position?.options?.response) {
radioGroup.select(position.options?.response);
} else if (position?.options?.defaultValue) {
radioGroup.select(position?.options?.defaultValue);
}
radioGroup.enableReadOnly();
} else {
const signature = {
x: xPos(position),
y: yPos(position),
width: widgetWidth,
height: widgetHeight
};
const imageOptions = getWidgetPosition(page, signature, 1);
page.drawImage(img, imageOptions);
}
} catch (err) {
console.log("Err in embed widget on page ", pageNo, err?.message);
hasError = true; // Set the flag to stop both loops
break; // Exit inner loop
} }
}); }
}
if (!hasError) {
const pdfBytes = await pdfDoc.saveAsBase64({ useObjectStreams: false });
// console.log("pdf", pdfBytes);
return pdfBytes;
} else {
return {
error:
"This pdf is not compatible with opensign please contact <support@opensignlabs.com>"
};
} }
const pdfBytes = await pdfDoc.saveAsBase64({ useObjectStreams: false });
//console.log("pdf", pdfBytes);
return pdfBytes;
}; };
// function for validating URLs // function for validating URLs
@@ -1935,7 +1958,11 @@ export const getAppLogo = async () => {
} }
} catch (err) { } catch (err) {
console.log("err in getlogo ", err); console.log("err in getlogo ", err);
return { logo: appInfo.applogo, user: "exist" }; if (err?.message?.includes("valid JSON")) {
return { logo: appInfo.applogo, user: "exist", error: "invalid_json" };
} else {
return { logo: appInfo.applogo, user: "exist" };
}
} }
} }
}; };
@@ -2038,31 +2065,45 @@ export const fetchUrl = async (url, pdfName) => {
console.error("Error downloading the file:", error); console.error("Error downloading the file:", error);
} }
}; };
//handle download signed pdf export const getSignedUrl = async (
export const handleDownloadPdf = async (
pdfDetails,
pdfUrl, pdfUrl,
setIsDownloading docId,
fileAdapterId,
templateId
) => { ) => {
const pdfName = pdfDetails[0] && pdfDetails[0].Name; //use only axios here due to public template sign
setIsDownloading("pdf"); const axiosRes = await axios.post(
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : ""; `${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
try { {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl }); url: pdfUrl,
const axiosRes = await axios.post( docId: docId || "",
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`, fileAdapterId: fileAdapterId,
{ url: pdfUrl, docId: docId }, templateId: templateId || ""
{ },
headers: { {
"content-type": "Application/json", headers: {
"X-Parse-Application-Id": localStorage.getItem("parseAppId"), "content-type": "Application/json",
"X-Parse-Session-Token": localStorage.getItem("accesstoken") "X-Parse-Application-Id": localStorage.getItem("parseAppId"),
} "X-Parse-Session-Token": localStorage.getItem("accesstoken")
} }
); }
const url = axiosRes.data.result; );
const url = axiosRes.data.result;
return url;
};
//handle download signed pdf
export const handleDownloadPdf = async (pdfDetails, setIsDownloading) => {
const pdfName = pdfDetails[0] && pdfDetails[0]?.Name;
const pdfUrl = pdfDetails?.[0]?.SignedUrl || pdfDetails?.[0]?.URL;
setIsDownloading && setIsDownloading("pdf");
const docId = pdfDetails?.[0]?.objectId || "";
const fileAdapterId = pdfDetails?.[0]?.FileAdapterId
? pdfDetails?.[0]?.FileAdapterId
: "";
try {
const url = await getSignedUrl(pdfUrl, docId, fileAdapterId);
await fetchUrl(url, pdfName); await fetchUrl(url, pdfName);
setIsDownloading(""); setIsDownloading && setIsDownloading("");
} catch (err) { } catch (err) {
console.log("err in getsignedurl", err); console.log("err in getsignedurl", err);
setIsDownloading(""); setIsDownloading("");
@@ -2083,14 +2124,17 @@ export const handleToPrint = async (
) => { ) => {
event.preventDefault(); event.preventDefault();
setIsDownloading("pdf"); setIsDownloading("pdf");
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : ""; const docId = pdfDetails?.[0]?.objectId || "";
const FileAdapterId = pdfDetails?.[0]?.FileAdapterId
? pdfDetails?.[0]?.FileAdapterId
: "";
try { try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl }); // const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
//`localStorage.getItem("baseUrl")` is also use in public-profile flow for public-sign //`localStorage.getItem("baseUrl")` is also use in public-profile flow for public-sign
//if we give this `appInfo.baseUrl` as a base url then in public-profile it will create base url of it's window.location.origin ex- opensign.me which is not base url //if we give this `appInfo.baseUrl` as a base url then in public-profile it will create base url of it's window.location.origin ex- opensign.me which is not base url
const axiosRes = await axios.post( const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`, `${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl, docId: docId }, { url: pdfUrl, docId: docId, fileAdapterId: FileAdapterId },
{ {
headers: { headers: {
"content-type": "Application/json", "content-type": "Application/json",
@@ -2130,13 +2174,18 @@ export const handleToPrint = async (
//handle download signed pdf //handle download signed pdf
export const handleDownloadCertificate = async ( export const handleDownloadCertificate = async (
pdfDetails, pdfDetails,
setIsDownloading setIsDownloading,
isZip
) => { ) => {
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) { if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try { try {
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl); await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl; const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`); if (isZip) {
return certificateUrl;
} else {
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
}
} catch (err) { } catch (err) {
console.log("err in download in certificate", err); console.log("err in download in certificate", err);
} }
@@ -2162,7 +2211,13 @@ export const handleDownloadCertificate = async (
if (doc?.CertificateUrl) { if (doc?.CertificateUrl) {
await fetch(doc?.CertificateUrl); await fetch(doc?.CertificateUrl);
const certificateUrl = doc?.CertificateUrl; const certificateUrl = doc?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`); if (isZip) {
setIsDownloading("");
return certificateUrl;
} else {
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
}
setIsDownloading(""); setIsDownloading("");
} else { } else {
setIsDownloading("certificate"); setIsDownloading("certificate");
@@ -2462,3 +2517,45 @@ export const handleRotateWarning = (signerPos, pageNumber) => {
return false; return false;
} }
}; };
// `generateTitleFromFilename` to generate Title of document from file name
export function generateTitleFromFilename(filename) {
try {
// Step 1: Trim whitespace
let title = filename.trim();
// Step 2: Remove the file extension (everything after the last '.')
const lastDotIndex = title.lastIndexOf(".");
if (lastDotIndex > 0) {
title = title.substring(0, lastDotIndex);
}
// Step 3: Replace special characters (except Unicode letters, digits, spaces, and hyphens)
title = title.replace(/[^\p{L}\p{N}\s-]/gu, " ");
// Step 4: Replace multiple spaces with a single space
title = title.replace(/\s+/g, " ");
// Step 5: Capitalize first letter of each word (Title Case), handling Unicode characters
title = title.replace(
/\p{L}\S*/gu,
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
);
// Step 6: Restrict length of title (optional, let's say 100 characters)
if (title.length > 100) {
title = title.substring(0, 100).trim();
}
// Step 7: Handle empty or invalid title by falling back to "Untitled Document"
if (!title || title.length === 0) {
return "Untitled Document";
}
return title;
} catch (error) {
// Handle unexpected errors gracefully by returning a default title
console.error("Error generating title from filename:", error);
return "Untitled Document";
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ i18n
interpolation: { interpolation: {
escapeValue: false // Not needed for react as it escapes by default escapeValue: false // Not needed for react as it escapes by default
}, },
whitelist: ["en", "fr"] // List of allowed languages whitelist: ["en", "es", "fr"] // List of allowed languages
}); });
export default i18n; export default i18n;
+47 -20
View File
@@ -1,3 +1,5 @@
import { isEnableSubscription } from "../constant/const";
export default function reportJson(id) { export default function reportJson(id) {
// console.log("json ", json); // console.log("json ", json);
const head = ["Sr.No", "Title", "Note", "Folder", "File", "Owner", "Signers"]; const head = ["Sr.No", "Title", "Note", "Folder", "File", "Owner", "Signers"];
@@ -10,9 +12,43 @@ export default function reportJson(id) {
"Owner", "Owner",
"Signers" "Signers"
]; ];
const iphead = [
"Sr.No",
"Title",
"Note",
"Folder",
"File",
"Status",
"Signers"
];
const contactbook = ["Sr.No", "Name", "Email", "Phone"]; const contactbook = ["Sr.No", "Name", "Email", "Phone"];
const dashboardReportHead = ["Title", "File", "Owner", "Signers"]; const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"]; const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"];
const templateSubAction = isEnableSubscription
? [
{
btnId: "2434",
btnLabel: "Embed",
hoverLabel: "Embed",
btnIcon: "fa-light fa-code",
action: "Embed"
},
{
btnId: "2434",
btnLabel: "Copy TemplateId",
hoverLabel: "Copy TemplateId",
btnIcon: "fa-light fa-copy",
action: "CopyTemplateId"
},
{
btnId: "2434",
btnLabel: "Copy Public URL",
hoverLabel: "Copy Public URL",
btnIcon: "fa-light fa-copy",
action: "CopyPublicURL"
}
]
: [];
switch (id) { switch (id) {
// draft documents report // draft documents report
case "ByHuevtCFY": case "ByHuevtCFY":
@@ -63,7 +99,7 @@ export default function reportJson(id) {
case "1MwEuxLEkF": case "1MwEuxLEkF":
return { return {
reportName: "In-progress documents", reportName: "In-progress documents",
heading: head, heading: iphead,
actions: [ actions: [
{ {
btnId: "8901", btnId: "8901",
@@ -354,13 +390,7 @@ export default function reportJson(id) {
redirectUrl: "template", redirectUrl: "template",
action: "redirect" action: "redirect"
}, },
{ ...templateSubAction,
btnId: "2434",
btnLabel: "Embed",
hoverLabel: "Embed",
btnIcon: "fa-light fa-code",
action: "Embed"
},
{ {
btnId: "1834", btnId: "1834",
btnLabel: "Delete", btnLabel: "Delete",
@@ -382,18 +412,15 @@ export default function reportJson(id) {
if (item.action === "option") { if (item.action === "option") {
// Make a shallow copy of the item // Make a shallow copy of the item
const newItem = { ...item }; const newItem = { ...item };
newItem.subaction = [ //splice method used to add `Share with team` option on second index of list
{ newItem.subaction.splice(1, 0, {
btnId: "1873", btnId: "1873",
btnLabel: "Share with team", btnLabel: "Share with team",
hoverLabel: "Share with team", hoverLabel: "Share with team",
btnIcon: "fa-light fa-share-nodes", btnIcon: "fa-light fa-share-nodes",
redirectUrl: "", redirectUrl: "",
action: "sharewith" action: "sharewith"
}, });
...newItem.subaction
];
return newItem; return newItem;
} }
return item; return item;
+6 -16
View File
@@ -224,23 +224,13 @@ const HomeLayout = () => {
async function checkTourStatus() { async function checkTourStatus() {
const cloudRes = await Parse.Cloud.run("getUserDetails"); const cloudRes = await Parse.Cloud.run("getUserDetails");
const res = { data: cloudRes.toJSON() }; if (cloudRes) {
if (res.data && res.data.TourStatus && res.data.TourStatus.length > 0) { const extUser = JSON.parse(JSON.stringify(cloudRes));
const tourStatus = res.data.TourStatus; localStorage.setItem("Extand_Class", JSON.stringify([extUser]));
// console.log("res ", res.data.TourStatus); const tourStatus = extUser?.TourStatus || [];
setTourStatusArr(tourStatus); setTourStatusArr(tourStatus);
const filteredtourStatus = tourStatus.filter((obj) => obj["loginTour"]); const loginTour = tourStatus.find((obj) => obj.loginTour)?.loginTour;
if (filteredtourStatus.length > 0) { setIsTour(!loginTour);
const loginTour = filteredtourStatus[0]["loginTour"];
// console.log("loginTour", loginTour);
if (loginTour) {
setIsTour(false);
} else {
setIsTour(true);
}
} else {
setIsTour(true);
}
} else { } else {
setIsTour(true); setIsTour(true);
} }
+4 -2
View File
@@ -45,8 +45,10 @@ const AddAdmin = () => {
setState((prev) => ({ ...prev, loading: true })); setState((prev) => ({ ...prev, loading: true }));
try { try {
const app = await getAppLogo(); const app = await getAppLogo();
if (app?.user === "exist") { if (app?.error === "invalid_json") {
setErrMsg(t("admin-already-exist")); setErrMsg(t("server-down"));
} else if (app?.user === "exist") {
setErrMsg(t("admin-exists"));
} }
} catch (err) { } catch (err) {
setErrMsg(t("something-went-wrong-mssg")); setErrMsg(t("something-went-wrong-mssg"));
+1 -1
View File
@@ -74,7 +74,7 @@ const DebugPdf = () => {
const inferPdfType = async (pdf) => { const inferPdfType = async (pdf) => {
try { try {
const firstPage = await pdf.getPage(2); const firstPage = await pdf.getPage(pdf?.numPages > 1 ? 2 : 1);
const scale = 1; const scale = 1;
const { width, height } = firstPage.getViewport({ scale }); const { width, height } = firstPage.getViewport({ scale });
setPdfDimension({ width: width, height: height }); setPdfDimension({ width: width, height: height });
File diff suppressed because it is too large Load Diff
+9 -2
View File
@@ -51,6 +51,7 @@ function Login() {
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState(false);
const [image, setImage] = useState(); const [image, setImage] = useState();
const [isLoginSSO, setIsLoginSSO] = useState(false); const [isLoginSSO, setIsLoginSSO] = useState(false);
const [errMsg, setErrMsg] = useState();
useEffect(() => { useEffect(() => {
checkUserExt(); checkUserExt();
@@ -59,7 +60,9 @@ function Login() {
const checkUserExt = async () => { const checkUserExt = async () => {
const app = await getAppLogo(); const app = await getAppLogo();
if (!isEnableSubscription && app?.user === "not_exist") { if (app?.error === "invalid_json") {
setErrMsg(t("server-down"));
} else if (!isEnableSubscription && app?.user === "not_exist") {
navigate("/addadmin"); navigate("/addadmin");
} }
if (app?.logo) { if (app?.logo) {
@@ -620,7 +623,11 @@ function Login() {
} }
}; };
return ( return errMsg ? (
<div className="h-screen flex justify-center text-center items-center p-4 text-gray-500 text-base">
{errMsg}
</div>
) : (
<div> <div>
<Title title={"Login Page"} /> <Title title={"Login Page"} />
{state.loading && ( {state.loading && (
+111 -60
View File
@@ -2,12 +2,13 @@ import React, { useState, useRef, useEffect } from "react";
import SignatureCanvas from "react-signature-canvas"; import SignatureCanvas from "react-signature-canvas";
import "../styles/managesign.css"; import "../styles/managesign.css";
import "../styles/signature.css"; import "../styles/signature.css";
import { toDataUrl } from "../constant/Utils"; import { generateTitleFromFilename, toDataUrl } from "../constant/Utils";
import Parse from "parse"; import Parse from "parse";
import { SaveFileSize } from "../constant/saveFileSize"; import { SaveFileSize } from "../constant/saveFileSize";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import Loader from "../primitives/Loader"; import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import sanitizeFileName from "../primitives/sanitizeFileName";
const ManageSign = () => { const ManageSign = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [penColor, setPenColor] = useState("blue"); const [penColor, setPenColor] = useState("blue");
@@ -23,9 +24,11 @@ const ManageSign = () => {
const [Initials, setInitials] = useState(""); const [Initials, setInitials] = useState("");
const [isInitials, setIsInitials] = useState(false); const [isInitials, setIsInitials] = useState(false);
const [id, setId] = useState(""); const [id, setId] = useState("");
const [imgInitials, setImgInitials] = useState("");
const canvasRef = useRef(null); const canvasRef = useRef(null);
const imageRef = useRef(null); const imageRef = useRef(null);
const initailsRef = useRef(null); const initailsRef = useRef(null);
const imgInitialsRef = useRef(null);
useEffect(() => { useEffect(() => {
fetchUserSign(); fetchUserSign();
// eslint-disable-next-line // eslint-disable-next-line
@@ -46,14 +49,23 @@ const ManageSign = () => {
if (signRes) { if (signRes) {
const res = signRes.toJSON(); const res = signRes.toJSON();
setId(res.objectId); setId(res.objectId);
setSignName(res?.SignatureName); if (res?.SignatureName) {
setImage(res.ImageURL); const sanitizename = generateTitleFromFilename(res?.SignatureName);
const replaceSpace = sanitizeFileName(sanitizename);
setSignName(replaceSpace);
}
setImage(res?.ImageURL);
if (res && res.Initials) { if (res && res.Initials) {
setInitials(res.Initials); // setInitials(res.Initials);
setIsInitials(true); setIsInitials(true);
setImgInitials(res?.Initials);
} }
} else { } else {
setSignName(User?.get("name") || ""); if (User?.get("name")) {
const sanitizename = generateTitleFromFilename(User?.get("name"));
const replaceSpace = sanitizeFileName(sanitizename);
setSignName(replaceSpace);
}
} }
setIsLoader(false); setIsLoader(false);
} catch (err) { } catch (err) {
@@ -86,8 +98,14 @@ const ManageSign = () => {
if (initailsRef.current) { if (initailsRef.current) {
initailsRef.current.clear(); initailsRef.current.clear();
} }
if (imgInitialsRef.current) {
imgInitialsRef.current.value = "";
}
setImgInitials("");
setInitials(""); setInitials("");
setIsValue(true); if (image) {
setIsValue(true);
}
setIsInitials(false); setIsInitials(false);
}; };
@@ -101,40 +119,54 @@ const ManageSign = () => {
const base64Img = await toDataUrl(file); const base64Img = await toDataUrl(file);
setImage(base64Img); setImage(base64Img);
setIsValue(true); setIsValue(true);
} else {
setImage("");
setIsValue(false);
} }
}; };
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
const isUrl = image.includes("https"); const isUrl = image?.includes("https") || image?.includes("http");
if (!isvalue) { if (!isvalue) {
setWarning(true); setWarning(true);
setTimeout(() => setWarning(false), 1000); setTimeout(() => setWarning(false), 1000);
} else { } else {
setIsLoader(true); setIsLoader(true);
const replaceSpace = signName.replace(/ /g, "_"); const sanitizename = generateTitleFromFilename(signName);
const replaceSpace = sanitizeFileName(sanitizename);
let file; let file;
if (signature) { if (signature) {
file = base64StringtoFile(signature, `${replaceSpace}_sign.png`); file = base64StringtoFile(signature, `${replaceSpace}_sign.png`);
} else { } else {
if (!isUrl) { if (image && !isUrl) {
file = base64StringtoFile(image, `${replaceSpace}__sign.png`); file = base64StringtoFile(image, `${replaceSpace}__sign.png`);
} }
} }
let imgUrl; let imgUrl;
if (!isUrl) { if (file && !isUrl) {
imgUrl = await uploadFile(file); imgUrl = await uploadFile(file);
} else { } else {
imgUrl = image; imgUrl = image;
} }
let initialsUrl = ""; const isInitialsUrl =
const isInitialsUrl = Initials.includes("https"); imgInitials?.includes("https") || imgInitials?.includes("http");
if (!isInitialsUrl && Initials) {
const initialsImg = base64StringtoFile( let initialFile;
Initials, if (Initials) {
`${replaceSpace}_initials.png` initialFile = base64StringtoFile(Initials, `${replaceSpace}_sign.png`);
); } else {
initialsUrl = await uploadFile(initialsImg); if (imgInitials && !isInitialsUrl) {
initialFile = base64StringtoFile(
imgInitials,
`${replaceSpace}__sign.png`
);
}
}
let initialsUrl;
if (initialFile && !isInitialsUrl) {
initialsUrl = await uploadFile(initialFile);
} else {
initialsUrl = imgInitials;
} }
if (imgUrl) { if (imgUrl) {
await saveEntry({ await saveEntry({
@@ -146,7 +178,7 @@ const ManageSign = () => {
} }
}; };
function base64StringtoFile(base64String, filename) { function base64StringtoFile(base64String, filename) {
var arr = base64String.split(","), let arr = base64String.split(","),
mime = arr[0].match(/:(.*?);/)[1], mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), bstr = atob(arr[1]),
n = bstr.length, n = bstr.length,
@@ -175,31 +207,24 @@ const ManageSign = () => {
const saveEntry = async (obj) => { const saveEntry = async (obj) => {
const signCls = "contracts_Signature"; const signCls = "contracts_Signature";
const User = Parse.User.current().id; const User = Parse?.User?.current()?.id;
const userId = { __type: "Pointer", className: "_User", objectId: User }; const userId = { __type: "Pointer", className: "_User", objectId: User };
if (id) { if (id) {
try { try {
const updateSign = new Parse.Object(signCls); const updateSign = new Parse.Object(signCls);
updateSign.id = id; updateSign.id = id;
updateSign.set("Initials", obj.initialsUrl ? obj.initialsUrl : ""); updateSign.set("Initials", obj.initialsUrl ? obj.initialsUrl : "");
updateSign.set("ImageURL", obj.url); updateSign.set("ImageURL", obj.url ? obj.url : "");
updateSign.set("SignatureName", obj.name); updateSign.set("SignatureName", obj.name);
updateSign.set("UserId", userId); updateSign.set("UserId", userId);
const res = await updateSign.save(); const res = await updateSign.save();
setIsLoader(false); setIsAlert({ type: "success", message: t("signature-saved-alert") });
setIsAlert({
type: "success",
message: t("signature-saved-alert")
});
setTimeout(() => setIsAlert({}), 2000);
return res; return res;
} catch (err) { } catch (err) {
console.log(err); console.log(err);
setIsAlert({ type: "danger", message: `${err.message}` });
} finally {
setIsLoader(false); setIsLoader(false);
setIsAlert({
type: "danger",
message: `${err.message}`
});
setTimeout(() => setIsAlert({}), 2000); setTimeout(() => setIsAlert({}), 2000);
} }
} else { } else {
@@ -210,20 +235,13 @@ const ManageSign = () => {
updateSign.set("SignatureName", obj.name); updateSign.set("SignatureName", obj.name);
updateSign.set("UserId", userId); updateSign.set("UserId", userId);
const res = await updateSign.save(); const res = await updateSign.save();
setIsLoader(false); setIsAlert({ type: "success", message: t("signature-saved-alert") });
setIsAlert({
type: "success",
message: t("signature-saved-alert")
});
setTimeout(() => setIsAlert({}), 2000);
return res; return res;
} catch (err) { } catch (err) {
console.log(err); console.log(err);
setIsAlert({ type: "success", message: `${err.message}` });
} finally {
setIsLoader(false); setIsLoader(false);
setIsAlert({
type: "success",
message: `${err.message}`
});
setTimeout(() => setIsAlert({}), 2000); setTimeout(() => setIsAlert({}), 2000);
} }
} }
@@ -234,7 +252,29 @@ const ManageSign = () => {
}; };
const handleInitialsChange = () => { const handleInitialsChange = () => {
setInitials(initailsRef.current.toDataURL()); setInitials(initailsRef.current.toDataURL());
setIsValue(true); if (image || signature) {
setIsValue(true);
}
};
const handleUploadInitials = () => {
imgInitialsRef.current.click();
};
const onImgInitialsChange = async (event) => {
if (initailsRef.current) {
initailsRef.current.clear();
}
setInitials("");
if (event.target.files && event.target.files[0]) {
const file = event.target.files[0];
const base64Img = await toDataUrl(file);
setImgInitials(base64Img);
if (image || signature) {
setIsValue(true);
}
} else {
setImgInitials("");
setIsValue(false);
}
}; };
return ( return (
<div className="relative h-full bg-base-100 text-base-content flex shadow-md rounded-box overflow-auto"> <div className="relative h-full bg-base-100 text-base-content flex shadow-md rounded-box overflow-auto">
@@ -281,7 +321,7 @@ const ManageSign = () => {
height: "180px", height: "180px",
className: "signatureCanvas rounded-box" className: "signatureCanvas rounded-box"
}} }}
backgroundColor="rgb(255, 255, 255)" // backgroundColor="rgb(255, 255, 255)"
onEnd={() => onEnd={() =>
handleSignatureChange(canvasRef.current.toDataURL()) handleSignatureChange(canvasRef.current.toDataURL())
} }
@@ -332,7 +372,7 @@ const ManageSign = () => {
className="op-link" className="op-link"
onClick={() => handleUploadBtn()} onClick={() => handleUploadBtn()}
> >
{t("upload-image")} {t("upload")}
</div> </div>
<div <div
type="button" type="button"
@@ -356,17 +396,21 @@ const ManageSign = () => {
<span className="font-medium select-none flex mb-[10px] pl-[10px]"> <span className="font-medium select-none flex mb-[10px] pl-[10px]">
{t("initials")} {t("initials")}
</span> </span>
<input
type="file"
onChange={onImgInitialsChange}
className="filetype"
accept="image/*"
ref={imgInitialsRef}
hidden
/>
<div> <div>
{isInitials ? ( {imgInitials ? (
<div className="intialSignature relative border-[1px] border-[#888] rounded-box overflow-hidden"> <div className="intialSignature relative border-[1px] border-[#888] rounded-box overflow-hidden">
<img <img
alt="inititals" alt="inititals"
src={Initials} src={imgInitials}
style={{ className="w-[100%] h-[100%] object-contain"
width: "100%",
height: "100%",
objectFit: "contain"
}}
/> />
</div> </div>
) : ( ) : (
@@ -376,10 +420,8 @@ const ManageSign = () => {
canvasProps={{ canvasProps={{
className: "intialSignature rounded-box" className: "intialSignature rounded-box"
}} }}
backgroundColor="rgb(255, 255, 255)" // backgroundColor="rgb(255, 255, 255)"
onEnd={() => onEnd={() => handleInitialsChange()}
handleInitialsChange(initailsRef.current.toDataURL())
}
dotSize={1} dotSize={1}
/> />
)} )}
@@ -402,6 +444,7 @@ const ManageSign = () => {
? "2px solid black" ? "2px solid black"
: "2px solid white" : "2px solid white"
}} }}
className="fa-light fa-pen-nib"
onClick={() => { onClick={() => {
if (key === 0) { if (key === 0) {
setInitialPen("blue"); setInitialPen("blue");
@@ -412,7 +455,6 @@ const ManageSign = () => {
} }
}} }}
key={key} key={key}
className="fa-light fa-pen-nib"
width={20} width={20}
height={20} height={20}
></i> ></i>
@@ -421,13 +463,22 @@ const ManageSign = () => {
</div> </div>
)} )}
</div> </div>
<div> <div className="flex flex-row gap-1">
<div <div
type="button" type="button"
className="op-link text-sm md:text-base mr-1" className="op-link text-sm md:text-base mr-1"
onClick={() => handleClearInitials()} onClick={() => handleUploadInitials()}
> >
{t("clear")} {t("upload")}
</div>
<div>
<div
type="button"
className="op-link text-sm md:text-base mr-1"
onClick={() => handleClearInitials()}
>
{t("clear")}
</div>
</div> </div>
</div> </div>
</div> </div>
+6 -15
View File
@@ -499,22 +499,13 @@ function Opensigndrive() {
//function to use check tour status of open sign drive //function to use check tour status of open sign drive
async function checkTourStatus() { async function checkTourStatus() {
const cloudRes = await Parse.Cloud.run("getUserDetails"); const cloudRes = await Parse.Cloud.run("getUserDetails");
const res = { data: cloudRes.toJSON() }; if (cloudRes) {
if (res.data && res.data.TourStatus && res.data.TourStatus.length > 0) { const extUser = JSON.parse(JSON.stringify(cloudRes));
const tourStatus = res.data.TourStatus; localStorage.setItem("Extand_Class", JSON.stringify([extUser]));
const tourStatus = extUser?.TourStatus || [];
setTourStatusArr(tourStatus); setTourStatusArr(tourStatus);
const filteredtourStatus = tourStatus.filter((obj) => obj["driveTour"]); const driveTour = tourStatus.find((obj) => obj.driveTour)?.driveTour;
if (filteredtourStatus.length > 0) { setIsTour(!driveTour);
const driveTour = filteredtourStatus[0]["driveTour"];
// console.log("loginTour", loginTour);
if (driveTour) {
setIsTour(false);
} else {
setIsTour(true);
}
} else {
setIsTour(true);
}
} else { } else {
setIsTour(true); setIsTour(true);
} }
+297 -218
View File
@@ -28,7 +28,6 @@ import {
contractUsers, contractUsers,
handleSendOTP, handleSendOTP,
contactBook, contactBook,
handleDownloadPdf,
handleToPrint, handleToPrint,
handleDownloadCertificate, handleDownloadCertificate,
getDefaultSignature, getDefaultSignature,
@@ -36,21 +35,22 @@ import {
onClickZoomOut, onClickZoomOut,
fetchUrl fetchUrl
} from "../constant/Utils"; } from "../constant/Utils";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import Header from "../components/pdf/PdfHeader"; import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf"; import RenderPdf from "../components/pdf/RenderPdf";
import PdfDeclineModal from "../primitives/PdfDeclineModal";
import Title from "../components/Title"; import Title from "../components/Title";
import DefaultSignature from "../components/pdf/DefaultSignature"; import DefaultSignature from "../components/pdf/DefaultSignature";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Loader from "../primitives/Loader";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import SignerListComponent from "../components/pdf/SignerListComponent"; import SignerListComponent from "../components/pdf/SignerListComponent";
import VerifyEmail from "../components/pdf/VerifyEmail"; import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom"; import PdfZoom from "../components/pdf/PdfZoom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
import Loader from "../primitives/Loader";
import PdfDeclineModal from "../primitives/PdfDeclineModal";
function PdfRequestFiles(props) { function PdfRequestFiles(props) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -73,7 +73,7 @@ function PdfRequestFiles(props) {
const [selectWidgetId, setSelectWidgetId] = useState(""); const [selectWidgetId, setSelectWidgetId] = useState("");
const [otpLoader, setOtpLoader] = useState(false); const [otpLoader, setOtpLoader] = useState(false);
const [isCelebration, setIsCelebration] = useState(false); const [isCelebration, setIsCelebration] = useState(false);
const [requestSignTour, setRequestSignTour] = useState(false); const [requestSignTour, setRequestSignTour] = useState(true);
const [tourStatus, setTourStatus] = useState([]); const [tourStatus, setTourStatus] = useState([]);
const [isLoading, setIsLoading] = useState({ const [isLoading, setIsLoading] = useState({
isLoad: true, isLoad: true,
@@ -129,13 +129,14 @@ function PdfRequestFiles(props) {
const [contact, setContact] = useState({ name: "", phone: "", email: "" }); const [contact, setContact] = useState({ name: "", phone: "", email: "" });
const [isOtp, setIsOtp] = useState(false); const [isOtp, setIsOtp] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [res, setRes] = useState({}); const [publicRes, setPublicRes] = useState({});
const [documentId, setDocumentId] = useState(""); const [documentId, setDocumentId] = useState("");
const [isPublicContact, setIsPublicContact] = useState(false); const [isPublicContact, setIsPublicContact] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState(""); const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
const [plancode, setPlanCode] = useState(""); const [plancode, setPlanCode] = useState("");
const isHeader = useSelector((state) => state.showHeader); const isHeader = useSelector((state) => state.showHeader);
const divRef = useRef(null); const divRef = useRef(null);
const [isDownloadModal, setIsDownloadModal] = useState(false);
const isMobile = window.innerWidth < 767; const isMobile = window.innerWidth < 767;
@@ -243,7 +244,13 @@ function PdfRequestFiles(props) {
}; };
async function checkIsSubscribed(extUserId, contactId) { async function checkIsSubscribed(extUserId, contactId) {
const isGuestSign = isGuestSignFlow || false; const isGuestSign = isGuestSignFlow || false;
const res = await fetchSubscription(extUserId, contactId, isGuestSign); const isPublic = props.templateId ? true : false;
const res = await fetchSubscription(
extUserId,
contactId,
isGuestSign,
isPublic
);
const plan = res.plan; const plan = res.plan;
const billingDate = res?.billingDate; const billingDate = res?.billingDate;
const status = res?.status; const status = res?.status;
@@ -277,6 +284,7 @@ function PdfRequestFiles(props) {
} }
} }
//function for get document details for perticular signer with signer'object id //function for get document details for perticular signer with signer'object id
//whenever change anything in this function check react/angular packages also in plan js
const getTemplateDetails = async () => { const getTemplateDetails = async () => {
try { try {
const params = { templateId: props.templateId, ispublic: true }; const params = { templateId: props.templateId, ispublic: true };
@@ -291,19 +299,22 @@ function PdfRequestFiles(props) {
} }
} }
); );
const documentData = const documentData = templateDeatils?.data?.result
templateDeatils.data && templateDeatils.data.result ? [templateDeatils?.data?.result]
? [templateDeatils.data.result] : [];
: [];
if (documentData && documentData[0]?.error) { if (documentData && documentData[0]?.error) {
props?.setTemplateStatus({ props?.setTemplateStatus &&
status: "Invalid" props?.setTemplateStatus({
}); status: "Invalid"
});
throw new Error("error: Invalid TemplateId");
} else if (documentData && documentData.length > 0) { } else if (documentData && documentData.length > 0) {
if (documentData[0]?.IsPublic) { if (documentData[0]?.IsPublic) {
props?.setTemplateStatus({ //handle condition when someone use plan js then setTemplateStatus is not supporting
status: "Success" props?.setTemplateStatus &&
}); props?.setTemplateStatus({
status: "Success"
});
const url = const url =
documentData[0] && documentData[0] &&
(documentData[0]?.SignedUrl || documentData[0]?.URL); (documentData[0]?.SignedUrl || documentData[0]?.URL);
@@ -347,22 +358,34 @@ function PdfRequestFiles(props) {
isLoad: false isLoad: false
}); });
} else { } else {
props?.setTemplateStatus({ props?.setTemplateStatus &&
status: "Private" props?.setTemplateStatus({
}); status: "Private"
});
setIsLoading(false);
setHandleError(t("something-went-wrong-mssg"));
console.error("error: TemplateId is not public");
return;
} }
} else { } else {
props?.setTemplateStatus({ props?.setTemplateStatus &&
status: "Invalid" props?.setTemplateStatus({
}); status: "Invalid"
});
setIsLoading(false);
setHandleError(t("something-went-wrong-mssg"));
console.error("error: Invalid TemplateId");
return;
} }
} catch (err) { } catch (err) {
console.log("err in get template details ", err); setIsLoading(false);
if (err?.response?.data?.code === 101) { if (err?.response?.data?.code === 101) {
setHandleError(t("error-template")); setHandleError(t("error-template"));
} else { } else {
setHandleError(t("something-went-wrong-mssg")); setHandleError(t("something-went-wrong-mssg"));
} }
console.error("error: Invalid TemplateId");
return;
} }
}; };
//function for get document details for perticular signer with signer'object id //function for get document details for perticular signer with signer'object id
@@ -400,13 +423,15 @@ function PdfRequestFiles(props) {
const expireUpdateDate = new Date(expireDate).getTime(); const expireUpdateDate = new Date(expireDate).getTime();
const currDate = new Date().getTime(); const currDate = new Date().getTime();
const getSigners = documentData[0].Signers; const getSigners = documentData[0].Signers;
const isTourEnabled =
documentData[0]?.IsTourEnabled === true ? true : false;
const getCurrentSigner = getSigners?.find( const getCurrentSigner = getSigners?.find(
(data) => data.UserId.objectId === jsonSender?.objectId (data) => data.UserId.objectId === jsonSender?.objectId
); );
currUserId = getCurrentSigner?.objectId currUserId = getCurrentSigner?.objectId
? getCurrentSigner.objectId ? getCurrentSigner.objectId
: contactBookId || ""; : contactBookId || signerObjectId || "";
if (isEnableSubscription) { if (isEnableSubscription) {
await checkIsSubscribed( await checkIsSubscribed(
documentData[0]?.ExtUserPtr?.objectId, documentData[0]?.ExtUserPtr?.objectId,
@@ -575,7 +600,8 @@ function PdfRequestFiles(props) {
checkAlreadySign || checkAlreadySign ||
!currUserId || !currUserId ||
declined || declined ||
currDate > expireUpdateDate currDate > expireUpdateDate ||
!isTourEnabled
) { ) {
setRequestSignTour(true); setRequestSignTour(true);
} else { } else {
@@ -603,6 +629,8 @@ function PdfRequestFiles(props) {
tourData?.some((data) => data?.requestSign) || false; tourData?.some((data) => data?.requestSign) || false;
setTourStatus(tourData); setTourStatus(tourData);
setRequestSignTour(checkTourRequest); setRequestSignTour(checkTourRequest);
} else {
setRequestSignTour(false);
} }
} catch (err) { } catch (err) {
console.log("err while getting tourstatus", err); console.log("err while getting tourstatus", err);
@@ -624,6 +652,8 @@ function PdfRequestFiles(props) {
); );
setTourStatus(tourData); setTourStatus(tourData);
setRequestSignTour(checkTourRequest[0]?.requestSign || false); setRequestSignTour(checkTourRequest[0]?.requestSign || false);
} else {
setRequestSignTour(false);
} }
} else if (res?.length === 0) { } else if (res?.length === 0) {
const res = await contactBook(currUserId); const res = await contactBook(currUserId);
@@ -640,6 +670,8 @@ function PdfRequestFiles(props) {
); );
setTourStatus(tourData); setTourStatus(tourData);
setRequestSignTour(checkTourRequest[0]?.requestSign || false); setRequestSignTour(checkTourRequest[0]?.requestSign || false);
} else {
setRequestSignTour(false);
} }
} else if (res.length === 0) { } else if (res.length === 0) {
setHandleError(t("user-not-exist")); setHandleError(t("user-not-exist"));
@@ -884,163 +916,171 @@ function PdfRequestFiles(props) {
); );
// console.log("pdfte", pdfBytes); // console.log("pdfte", pdfBytes);
//get ExistUserPtr object id of user class to get tenantDetails //get ExistUserPtr object id of user class to get tenantDetails
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId; if (!pdfBytes?.error) {
let activeMailAdapter = const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
pdfDetails?.[0]?.ExtUserPtr?.active_mail_adapter; let activeMailAdapter =
pdfDetails?.[0]?.ExtUserPtr?.active_mail_adapter;
//function for call to embed signature in pdf and get digital signature pdf //function for call to embed signature in pdf and get digital signature pdf
const resSign = await signPdfFun( const resSign = await signPdfFun(
pdfBytes, pdfBytes,
documentId, documentId,
signerObjectId, signerObjectId,
objectId, objectId,
isSubscribed, isSubscribed,
activeMailAdapter, activeMailAdapter,
widgets widgets
);
if (resSign && resSign.status === "success") {
setPdfUrl(resSign.data);
setIsSigned(true);
setSignedSigners([]);
setUnSignedSigners([]);
getDocumentDetails(documentId, true);
const index = pdfDetails?.[0]?.Signers.findIndex(
(x) => x.objectId === signerObjectId
); );
const newIndex = index + 1; if (resSign && resSign.status === "success") {
const usermail = { setPdfUrl(resSign.data);
Email: pdfDetails?.[0]?.Placeholders[newIndex]?.email || "" setIsSigned(true);
}; setSignedSigners([]);
const user = usermail?.Email setUnSignedSigners([]);
? usermail getDocumentDetails(documentId, true);
: pdfDetails?.[0]?.Signers[newIndex]; const index = pdfDetails?.[0]?.Signers.findIndex(
if (sendmail !== "false" && sendInOrder) { (x) => x.objectId === signerObjectId
const requestBody = pdfDetails?.[0]?.RequestBody; );
const requestSubject = pdfDetails?.[0]?.RequestSubject; const newIndex = index + 1;
if (user) { const usermail = {
const expireDate = pdfDetails?.[0].ExpiryDate.iso; Email: pdfDetails?.[0]?.Placeholders[newIndex]?.email || ""
const newDate = new Date(expireDate); };
const localExpireDate = newDate.toLocaleDateString( const user = usermail?.Email
"en-US", ? usermail
{ : pdfDetails?.[0]?.Signers[newIndex];
day: "numeric", if (sendmail !== "false" && sendInOrder) {
month: "long", const requestBody = pdfDetails?.[0]?.RequestBody;
year: "numeric" const requestSubject = pdfDetails?.[0]?.RequestSubject;
} if (user) {
); const expireDate = pdfDetails?.[0].ExpiryDate.iso;
let senderEmail = pdfDetails?.[0].ExtUserPtr.Email; const newDate = new Date(expireDate);
let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone; const localExpireDate = newDate.toLocaleDateString(
const senderName = `${pdfDetails?.[0].ExtUserPtr.Name}`; "en-US",
{
day: "numeric",
month: "long",
year: "numeric"
}
);
let senderEmail = pdfDetails?.[0].ExtUserPtr.Email;
let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone;
const senderName = `${pdfDetails?.[0].ExtUserPtr.Name}`;
try { try {
const imgPng = const imgPng =
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png"; "https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem( let url = `${localStorage.getItem(
"baseUrl" "baseUrl"
)}functions/sendmailv3`; )}functions/sendmailv3`;
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": "X-Parse-Application-Id":
localStorage.getItem("parseAppId"), localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken") sessionToken: localStorage.getItem("accesstoken")
};
const objectId = user?.objectId;
const hostUrl = window.location.origin;
//encode this url value `${pdfDetails?.[0].objectId}/${user.Email}/${objectId}` to base64 using `btoa` function
let encodeBase64;
if (objectId) {
encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}/${objectId}`
);
} else {
encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}`
);
}
const hostPublicUrl = isStaging
? "https://staging-app.opensignlabs.com"
: "https://app.opensignlabs.com";
let signPdf = props?.templateId
? `${hostPublicUrl}/login/${encodeBase64}`
: `${hostUrl}/login/${encodeBase64}`;
const openSignUrl =
"https://www.opensignlabs.com/contact-us";
const orgName = pdfDetails[0]?.ExtUserPtr.Company
? pdfDetails[0].ExtUserPtr.Company
: "";
const themeBGcolor = themeColor;
let replaceVar;
if (requestBody && requestSubject && isSubscribed) {
const replacedRequestBody = requestBody.replace(
/"/g,
"'"
);
const htmlReqBody =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
replacedRequestBody +
"</body> </html>";
const variables = {
document_title: pdfDetails?.[0].Name,
sender_name: senderName,
sender_mail: senderEmail,
sender_phone: senderPhone,
receiver_name: user.Name,
receiver_email: user.Email,
receiver_phone: user.Phone,
expiry_date: localExpireDate,
company_name: orgName,
signing_url: `<a href=${signPdf}>Sign here</a>`
}; };
replaceVar = replaceMailVaribles( const objectId = user?.objectId;
requestSubject, const hostUrl = window.location.origin;
htmlReqBody, //encode this url value `${pdfDetails?.[0].objectId}/${user.Email}/${objectId}` to base64 using `btoa` function
variables let encodeBase64;
); if (objectId) {
} encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}/${objectId}`
);
} else {
encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}`
);
}
const hostPublicUrl = isStaging
? "https://staging-app.opensignlabs.com"
: "https://app.opensignlabs.com";
let signPdf = props?.templateId
? `${hostPublicUrl}/login/${encodeBase64}`
: `${hostUrl}/login/${encodeBase64}`;
const openSignUrl =
"https://www.opensignlabs.com/contact-us";
const orgName = pdfDetails[0]?.ExtUserPtr.Company
? pdfDetails[0].ExtUserPtr.Company
: "";
const themeBGcolor = themeColor;
let replaceVar;
if (requestBody && requestSubject && isSubscribed) {
const replacedRequestBody = requestBody.replace(
/"/g,
"'"
);
const htmlReqBody =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
replacedRequestBody +
"</body> </html>";
let params = { const variables = {
mailProvider: activeMailAdapter, document_title: pdfDetails?.[0].Name,
extUserId: extUserId, sender_name: senderName,
recipient: user.Email, sender_mail: senderEmail,
subject: requestSubject sender_phone: senderPhone,
? replaceVar?.subject receiver_name: user.Name,
: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign "${pdfDetails?.[0].Name}"`, receiver_email: user.Email,
from: senderEmail, receiver_phone: user.Phone,
plan: plancode, expiry_date: localExpireDate,
html: requestBody company_name: orgName,
? replaceVar?.body signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
: "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'=> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" + };
imgPng + replaceVar = replaceMailVaribles(
" height='50' style='padding: 20px,width:170px,height:40px' /></div> <div style=' padding: 2px;font-family: system-ui;background-color:" + requestSubject,
themeBGcolor + htmlReqBody,
";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " + variables
pdfDetails?.[0].ExtUserPtr.Name + );
" has requested you to review and sign <strong> " + }
pdfDetails?.[0].Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" + let params = {
senderEmail + mailProvider: activeMailAdapter,
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " + extUserId: extUserId,
orgName + recipient: user.Email,
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + subject: requestSubject
localExpireDate + ? replaceVar?.subject
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + : `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign "${pdfDetails?.[0].Name}"`,
signPdf + from: senderEmail,
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + plan: plancode,
senderEmail + html: requestBody
" directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " + ? replaceVar?.body
openSignUrl + : "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'=> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
" target=_blank>here</a>.</p> </div></div></body> </html>" imgPng +
}; " height='50' style='padding: 20px,width:170px,height:40px' /></div> <div style=' padding: 2px;font-family: system-ui;background-color:" +
await axios.post(url, params, { headers: headers }); themeBGcolor +
} catch (error) { ";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " +
console.log("error", error); pdfDetails?.[0].ExtUserPtr.Name +
" has requested you to review and sign <strong> " +
pdfDetails?.[0].Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
senderEmail +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a target=_blank href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
senderEmail +
" directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
openSignUrl +
" target=_blank>here</a>.</p> </div></div></body> </html>"
};
await axios.post(url, params, { headers: headers });
} catch (error) {
console.log("error", error);
}
} }
} }
} else {
setIsUiLoading(false);
setIsAlert({ isShow: true, alertMessage: resSign.message });
} }
} else { } else {
setIsUiLoading(false); setIsUiLoading(false);
setIsAlert({ isShow: true, alertMessage: resSign.message }); setIsAlert({
header: t("error"),
isShow: true,
alertMessage: t("pdf-uncompatible")
});
} }
} catch (err) { } catch (err) {
setIsUiLoading(false); setIsUiLoading(false);
@@ -1286,7 +1326,11 @@ function PdfRequestFiles(props) {
.catch((err) => { .catch((err) => {
console.log("error updating field is decline ", err); console.log("error updating field is decline ", err);
setIsUiLoading(false); setIsUiLoading(false);
alert(t("something-went-wrong-mssg")); setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}); });
}; };
//function to add default signature for all requested placeholder of sign //function to add default signature for all requested placeholder of sign
@@ -1483,15 +1527,45 @@ function PdfRequestFiles(props) {
); );
if (userRes?.data?.result) { if (userRes?.data?.result) {
setRes(userRes.data.result); setPublicRes(userRes.data.result);
await SendOtp(); const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
if (isEnableOTP) {
await SendOtp();
} else {
setIsPublicContact(false);
setIsPublicTemplate(false);
setDocumentId(userRes.data?.result?.docId);
const contactId = userRes.data.result?.contactId;
setSignerObjectId(contactId);
}
} else { } else {
console.log("error in public-sign to create user details"); console.log("error in public-sign to create user details");
alert(t("something-went-wrong-mssg")); setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
} }
} catch (e) { } catch (e) {
console.log("e", e); console.log("e", e);
// setIsLoader(false); if (
e?.response?.data?.error === "Insufficient Credit" ||
e?.response?.data?.error === "Plan expired"
) {
handleCloseOtp();
setIsAlert({
title: t("insufficient-credits"),
isShow: true,
alertMessage: t("insufficient-credits-mssg")
});
} else {
handleCloseOtp();
setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
} }
}; };
@@ -1505,7 +1579,7 @@ function PdfRequestFiles(props) {
const SendOtp = async () => { const SendOtp = async () => {
try { try {
const params = { email: contact.email, docId: res.docId }; const params = { email: contact.email, docId: publicRes?.docId };
const Otp = await axios.post( const Otp = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/SendOTPMailV1`, `${localStorage.getItem("baseUrl")}/functions/SendOTPMailV1`,
params, params,
@@ -1523,7 +1597,11 @@ function PdfRequestFiles(props) {
} }
} catch (error) { } catch (error) {
console.log("error in verify otp in public-sign", error); console.log("error in verify otp in public-sign", error);
alert(t("something-went-wrong-mssg")); setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
} }
}; };
@@ -1567,17 +1645,13 @@ function PdfRequestFiles(props) {
JSON.stringify(contractUserDetails) JSON.stringify(contractUserDetails)
); );
} }
localStorage.setItem("username", _user.name); localStorage.setItem("username", _user.name);
localStorage.setItem("accesstoken", _user.sessionToken); localStorage.setItem("accesstoken", _user.sessionToken);
setLoading(false); setLoading(false);
// navigate(`/load/recipientSignPdf/${res?.docId}/${res?.contactId}`);
// document.getElementById("my_modal").close();
setIsPublicContact(false); setIsPublicContact(false);
setIsPublicTemplate(false); setIsPublicTemplate(false);
setIsLoading({ isLoad: false }); setIsLoading({ isLoad: false });
setDocumentId(res?.docId); setDocumentId(publicRes?.docId);
getDocumentDetails(res?.docId);
} }
} catch (error) { } catch (error) {
console.log("err ", error); console.log("err ", error);
@@ -1658,6 +1732,8 @@ function PdfRequestFiles(props) {
<Confetti <Confetti
width={window.innerWidth} width={window.innerWidth}
height={window.innerHeight} height={window.innerHeight}
recycle={false} // Prevents confetti from repeating
gravity={0.1} // Adjust the gravity to control the speed
/> />
</div> </div>
)} )}
@@ -1670,33 +1746,13 @@ function PdfRequestFiles(props) {
: "auto" : "auto"
}} }}
className={`${ className={`${
props.templateId && "m-1 border-[0.5px] border-gray-300" (props.templateId || isGuestSignFlow) &&
"mx-2 border-[0.5px] border-gray-300"
} relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300`} } relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300`}
> >
{!requestSignTour && {!requestSignTour &&
signerObjectId && signerObjectId &&
requestSignTourFunction()} requestSignTourFunction()}
<ModalUi
isOpen={isAlert.isShow}
title={t("alert-message")}
handleClose={() =>
setIsAlert({ isShow: false, alertMessage: "" })
}
>
<div className="h-full p-[20px]">
<p>{isAlert.alertMessage}</p>
<button
onClick={() =>
setIsAlert({ isShow: false, alertMessage: "" })
}
type="button"
className="op-btn op-btn-primary mt-3 px-4"
>
{t("ok")}
</button>
</div>
</ModalUi>
<Tour <Tour
showNumber={false} showNumber={false}
showNavigation={false} showNavigation={false}
@@ -1993,13 +2049,13 @@ function PdfRequestFiles(props) {
<button <button
type="button" type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-primary" className="font-[500] text-[13px] mr-[5px] op-btn op-btn-primary"
onClick={() => onClick={() => {
handleDownloadPdf( setIsCompleted((prev) => ({
pdfDetails, ...prev,
pdfUrl, isModal: false
setIsDownloading }));
) setIsDownloadModal(true);
} }}
> >
<i <i
className="fa-light fa-download" className="fa-light fa-download"
@@ -2079,6 +2135,7 @@ function PdfRequestFiles(props) {
clickOnZoomOut={clickOnZoomOut} clickOnZoomOut={clickOnZoomOut}
isDisableRotate={true} isDisableRotate={true}
templateId={props.templateId} templateId={props.templateId}
setIsDownloadModal={setIsDownloadModal}
/> />
<div ref={divRef} data-tut="pdfArea" className="h-[95%]"> <div ref={divRef} data-tut="pdfArea" className="h-[95%]">
@@ -2205,6 +2262,28 @@ function PdfRequestFiles(props) {
</button> </button>
</div> </div>
</ModalUi> </ModalUi>
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal}
pdfDetails={pdfDetails}
isDocId={true}
/>
<ModalUi
isOpen={isAlert.isShow}
title={isAlert?.title || t("alert-message")}
handleClose={() => setIsAlert({ isShow: false, alertMessage: "" })}
>
<div className="h-full p-[20px]">
<p>{isAlert.alertMessage}</p>
<button
onClick={() => setIsAlert({ isShow: false, alertMessage: "" })}
type="button"
className="op-btn op-btn-primary mt-3 px-4"
>
{t("close")}
</button>
</div>
</ModalUi>
</> </>
)} )}
</DndProvider> </DndProvider>
+17 -17
View File
@@ -11,8 +11,6 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import WidgetComponent from "../components/pdf/WidgetComponent"; import WidgetComponent from "../components/pdf/WidgetComponent";
import Tour from "reactour"; import Tour from "reactour";
import { useLocation, useParams } from "react-router-dom"; import { useLocation, useParams } from "react-router-dom";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import SignerListPlace from "../components/pdf/SignerListPlace"; import SignerListPlace from "../components/pdf/SignerListPlace";
import Header from "../components/pdf/PdfHeader"; import Header from "../components/pdf/PdfHeader";
import { RWebShare } from "react-web-share"; import { RWebShare } from "react-web-share";
@@ -45,23 +43,25 @@ import {
import RenderPdf from "../components/pdf/RenderPdf"; import RenderPdf from "../components/pdf/RenderPdf";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy"; import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import LinkUserModal from "../primitives/LinkUserModal";
import Title from "../components/Title"; import Title from "../components/Title";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption"; import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import WidgetNameModal from "../components/pdf/WidgetNameModal"; import WidgetNameModal from "../components/pdf/WidgetNameModal";
import { SaveFileSize } from "../constant/saveFileSize"; import { SaveFileSize } from "../constant/saveFileSize";
import { EmailBody } from "../components/pdf/EmailBody"; import { EmailBody } from "../components/pdf/EmailBody";
import Upgrade from "../primitives/Upgrade";
import Alert from "../primitives/Alert";
import Loader from "../primitives/Loader";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import PdfZoom from "../components/pdf/PdfZoom"; import PdfZoom from "../components/pdf/PdfZoom";
import LottieWithLoader from "../primitives/DotLottieReact";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert"; import RotateAlert from "../components/RotateAlert";
import Loader from "../primitives/Loader";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import LinkUserModal from "../primitives/LinkUserModal";
import QuotaCard from "../primitives/QuotaCard"; import QuotaCard from "../primitives/QuotaCard";
import LottieWithLoader from "../primitives/DotLottieReact";
import Alert from "../primitives/Alert";
import Upgrade from "../primitives/Upgrade";
function PlaceHolderSign() { function PlaceHolderSign() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -901,7 +901,7 @@ function PlaceHolderSign() {
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
if (signerPos?.length > 0) { if (signerPos?.length > 0 && !pdfDetails?.[0]?.IsCompleted) {
autosavedetails(); autosavedetails();
} }
}, 2000); }, 2000);
@@ -1047,7 +1047,7 @@ function PlaceHolderSign() {
type="button" type="button"
className="flex flex-row items-center op-link op-link-primary" className="flex flex-row items-center op-link op-link-primary"
> >
<i className="fa-light fa-link" aria-hidden="true"></i> <i className="fa-light fa-copy" />
<span className=" hidden md:block ml-1 ">{t("copy-link")}</span> <span className=" hidden md:block ml-1 ">{t("copy-link")}</span>
</button> </button>
<RWebShare data={{ url: data.url, title: t("sign-url") }}> <RWebShare data={{ url: data.url, title: t("sign-url") }}>
@@ -1121,7 +1121,7 @@ function PlaceHolderSign() {
receiver_phone: signerMail[i]?.Phone || "", receiver_phone: signerMail[i]?.Phone || "",
expiry_date: localExpireDate, expiry_date: localExpireDate,
company_name: orgName, company_name: orgName,
signing_url: `<a href=${signPdf}>Sign here</a>` signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
}; };
replaceVar = replaceMailVaribles( replaceVar = replaceMailVaribles(
requestSubject, requestSubject,
@@ -1155,7 +1155,7 @@ function PlaceHolderSign() {
orgName + orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expire on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expire on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate + localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a target=_blank href=" +
signPdf + signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
senderEmail + senderEmail +
@@ -1250,7 +1250,7 @@ function PlaceHolderSign() {
selector: '[data-tut="addWidgets"]', selector: '[data-tut="addWidgets"]',
content: () => ( content: () => (
<TourContentWithBtn <TourContentWithBtn
message={t("tour-mssg.placeholder-sign-3")} message={t("tour-mssg.placeholder-sign-4")}
isChecked={handleDontShow} isChecked={handleDontShow}
/> />
), ),
@@ -1261,19 +1261,18 @@ function PlaceHolderSign() {
selector: '[data-tut="pdfArea"]', selector: '[data-tut="pdfArea"]',
content: () => ( content: () => (
<TourContentWithBtn <TourContentWithBtn
message={t("tour-mssg.placeholder-sign-4")} message={t("tour-mssg.placeholder-sign-5")}
isChecked={handleDontShow} isChecked={handleDontShow}
/> />
), ),
position: "top", position: "top",
style: { fontSize: "13px" } style: { fontSize: "13px" }
}, },
{ {
selector: '[data-tut="headerArea"]', selector: '[data-tut="headerArea"]',
content: () => ( content: () => (
<TourContentWithBtn <TourContentWithBtn
message={t("tour-mssg.placeholder-sign-5")} message={t("tour-mssg.placeholder-sign-6")}
isChecked={handleDontShow} isChecked={handleDontShow}
/> />
), ),
@@ -2059,6 +2058,7 @@ function PlaceHolderSign() {
setSignerPos={setSignerPos} setSignerPos={setSignerPos}
containerWH={containerWH} containerWH={containerWH}
setIsResize={setIsResize} setIsResize={setIsResize}
isResize={isResize}
setZIndex={setZIndex} setZIndex={setZIndex}
setIsPageCopy={setIsPageCopy} setIsPageCopy={setIsPageCopy}
signersdata={signersdata} signersdata={signersdata}
+37 -17
View File
@@ -4,7 +4,6 @@ import "../styles/signature.css";
import Parse from "parse"; import Parse from "parse";
import Confetti from "react-confetti"; import Confetti from "react-confetti";
import axios from "axios"; import axios from "axios";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage"; import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { DndProvider } from "react-dnd"; import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend"; import { HTML5Backend } from "react-dnd-html5-backend";
@@ -42,21 +41,23 @@ import {
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Tour from "reactour"; import Tour from "reactour";
import Signedby from "../components/pdf/Signedby"; import Signedby from "../components/pdf/Signedby";
import HandleError from "../primitives/HandleError";
import Header from "../components/pdf/PdfHeader"; import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf"; import RenderPdf from "../components/pdf/RenderPdf";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy"; import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Title from "../components/Title"; import Title from "../components/Title";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption"; import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import TextFontSetting from "../components/pdf/TextFontSetting"; import TextFontSetting from "../components/pdf/TextFontSetting";
import VerifyEmail from "../components/pdf/VerifyEmail"; import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom"; import PdfZoom from "../components/pdf/PdfZoom";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert"; import RotateAlert from "../components/RotateAlert";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
import Loader from "../primitives/Loader";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
//For signYourself inProgress section signer can add sign and complete doc sign. //For signYourself inProgress section signer can add sign and complete doc sign.
function SignYourSelf() { function SignYourSelf() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -130,6 +131,8 @@ function SignYourSelf() {
const [pdfRotateBase64, setPdfRotatese64] = useState(""); const [pdfRotateBase64, setPdfRotatese64] = useState("");
const [isRotate, setIsRotate] = useState({ status: false, degree: 0 }); const [isRotate, setIsRotate] = useState({ status: false, degree: 0 });
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: true }); const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: true });
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [isResize, setIsResize] = useState(false);
const divRef = useRef(null); const divRef = useRef(null);
const nodeRef = useRef(null); const nodeRef = useRef(null);
const [, drop] = useDrop({ const [, drop] = useDrop({
@@ -556,7 +559,7 @@ function SignYourSelf() {
}; };
useEffect(() => { useEffect(() => {
const timer = setTimeout(() => { const timer = setTimeout(() => {
if (xyPostion?.length > 0) { if (xyPostion?.length > 0 && !pdfDetails?.[0]?.IsCompleted) {
autosavedetails(); autosavedetails();
} }
}, 2000); }, 2000);
@@ -615,12 +618,8 @@ function SignYourSelf() {
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl; const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
let checkDefaultSigned = let checkDefaultSigned =
requiredWidgets[i]?.options?.defaultValue; requiredWidgets[i]?.options?.defaultValue;
if (!checkSignUrl) { if (!checkSignUrl && !checkDefaultSigned && !showAlert) {
if (!checkDefaultSigned) { showAlert = true;
if (!showAlert) {
showAlert = true;
}
}
} }
} }
} }
@@ -647,10 +646,9 @@ function SignYourSelf() {
return; return;
} else { } else {
setIsUiLoading(true); setIsUiLoading(true);
const existingPdfBytes = pdfArrayBuffer;
// Load a PDFDocument from the existing PDF bytes // Load a PDFDocument from the existing PDF bytes
try { try {
const pdfDoc = await PDFDocument.load(existingPdfBytes); const pdfDoc = await PDFDocument.load(pdfArrayBuffer);
const isSignYourSelfFlow = true; const isSignYourSelfFlow = true;
const extUserPtr = pdfDetails[0].ExtUserPtr; const extUserPtr = pdfDetails[0].ExtUserPtr;
const HeaderDocId = extUserPtr?.HeaderDocId; const HeaderDocId = extUserPtr?.HeaderDocId;
@@ -669,8 +667,15 @@ function SignYourSelf() {
); );
// console.log("pdf", pdfBytes); // console.log("pdf", pdfBytes);
//function for call to embed signature in pdf and get digital signature pdf //function for call to embed signature in pdf and get digital signature pdf
if (pdfBytes) { if (!pdfBytes?.error) {
await signPdfFun(pdfBytes, documentId); await signPdfFun(pdfBytes, documentId);
} else {
setIsUiLoading(false);
setIsAlert({
header: t("error"),
isShow: true,
alertMessage: t("pdf-uncompatible")
});
} }
} catch (err) { } catch (err) {
setIsUiLoading(false); setIsUiLoading(false);
@@ -781,7 +786,7 @@ function SignYourSelf() {
const handleStop = (event, dragElement) => { const handleStop = (event, dragElement) => {
setFontSize(); setFontSize();
setFontColor(); setFontColor();
if (isDragging && dragElement) { if (!isResize && isDragging && dragElement) {
event.preventDefault(); event.preventDefault();
const containerScale = getContainerScale( const containerScale = getContainerScale(
pdfOriginalWH, pdfOriginalWH,
@@ -1190,7 +1195,12 @@ function SignYourSelf() {
)} )}
{isCelebration && ( {isCelebration && (
<div className="relative z-[1000]"> <div className="relative z-[1000]">
<Confetti width={window.innerWidth} height={window.innerHeight} /> <Confetti
width={window.innerWidth}
height={window.innerHeight}
recycle={false} // Prevents confetti from repeating
gravity={0.1} // Adjust the gravity to control the speed
/>
</div> </div>
)} )}
<div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300"> <div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300">
@@ -1327,6 +1337,7 @@ function SignYourSelf() {
setIsAlert={setIsAlert} setIsAlert={setIsAlert}
extUserId={extUserId} extUserId={extUserId}
activeMailAdapter={activeMailAdapter} activeMailAdapter={activeMailAdapter}
setIsDownloadModal={setIsDownloadModal}
/> />
{/* pdf header which contain funish back button */} {/* pdf header which contain funish back button */}
<Header <Header
@@ -1346,6 +1357,7 @@ function SignYourSelf() {
clickOnZoomIn={clickOnZoomIn} clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut} clickOnZoomOut={clickOnZoomOut}
widgetsDetails={xyPostion} widgetsDetails={xyPostion}
setIsDownloadModal={setIsDownloadModal}
/> />
<div ref={divRef} data-tut="reactourSecond" className="h-full"> <div ref={divRef} data-tut="reactourSecond" className="h-full">
{containerWH?.width && containerWH?.height && ( {containerWH?.width && containerWH?.height && (
@@ -1391,6 +1403,8 @@ function SignYourSelf() {
setFontSize={setFontSize} setFontSize={setFontSize}
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
isResize={isResize}
setIsResize={setIsResize}
/> />
)} )}
</div> </div>
@@ -1450,6 +1464,12 @@ function SignYourSelf() {
setIsRotate={setIsRotate} setIsRotate={setIsRotate}
handleRemoveWidgets={handleRemoveWidgets} handleRemoveWidgets={handleRemoveWidgets}
/> />
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal}
pdfDetails={pdfDetails}
isDocId={true}
/>
<TextFontSetting <TextFontSetting
isTextSetting={isTextSetting} isTextSetting={isTextSetting}
setIsTextSetting={setIsTextSetting} setIsTextSetting={setIsTextSetting}
@@ -9,8 +9,6 @@ import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd"; import { useDrag, useDrop } from "react-dnd";
import WidgetComponent from "../components/pdf/WidgetComponent"; import WidgetComponent from "../components/pdf/WidgetComponent";
import Tour from "reactour"; import Tour from "reactour";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import SignerListPlace from "../components/pdf/SignerListPlace"; import SignerListPlace from "../components/pdf/SignerListPlace";
import Header from "../components/pdf/PdfHeader"; import Header from "../components/pdf/PdfHeader";
import WidgetNameModal from "../components/pdf/WidgetNameModal"; import WidgetNameModal from "../components/pdf/WidgetNameModal";
@@ -36,18 +34,21 @@ import {
import RenderPdf from "../components/pdf/RenderPdf"; import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css"; import "../styles/AddUser.css";
import Title from "../components/Title"; import Title from "../components/Title";
import LinkUserModal from "../primitives/LinkUserModal";
import EditTemplate from "../components/pdf/EditTemplate"; import EditTemplate from "../components/pdf/EditTemplate";
import ModalUi from "../primitives/ModalUi";
import AddRoleModal from "../components/pdf/AddRoleModal"; import AddRoleModal from "../components/pdf/AddRoleModal";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy"; import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption"; import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import Parse from "parse"; import Parse from "parse";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import PdfZoom from "../components/pdf/PdfZoom"; import PdfZoom from "../components/pdf/PdfZoom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert"; import RotateAlert from "../components/RotateAlert";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import LinkUserModal from "../primitives/LinkUserModal";
const TemplatePlaceholder = () => { const TemplatePlaceholder = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -507,7 +508,7 @@ const TemplatePlaceholder = () => {
const tourAddRole = [ const tourAddRole = [
{ {
selector: '[data-tut="reactourAddbtn"]', selector: '[data-tut="reactourAddbtn"]',
content: "You need to add a role before you can add fields for it. ", content: t("add-user-template"),
position: "top", position: "top",
style: { fontSize: "13px" } style: { fontSize: "13px" }
} }
@@ -780,6 +781,7 @@ const TemplatePlaceholder = () => {
RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery), RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery),
NextReminderDate: pdfDetails[0]?.NextReminderDate, NextReminderDate: pdfDetails[0]?.NextReminderDate,
IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false, IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false,
IsTourEnabled: pdfDetails[0]?.IsTourEnabled === true ? true : false,
URL: pdfUrl URL: pdfUrl
}; };
const updateTemplate = new Parse.Object("contracts_Template"); const updateTemplate = new Parse.Object("contracts_Template");
@@ -1598,6 +1600,7 @@ const TemplatePlaceholder = () => {
setFontSize={setFontSize} setFontSize={setFontSize}
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
isResize={isResize}
/> />
)} )}
</div> </div>
@@ -15,6 +15,7 @@ const UpdateExistUserAdmin = () => {
const [isAlert, setIsAlert] = useState({ type: "danger", msg: "" }); const [isAlert, setIsAlert] = useState({ type: "danger", msg: "" });
useEffect(() => { useEffect(() => {
checkIsAdminExist(); checkIsAdminExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const checkIsAdminExist = async () => { const checkIsAdminExist = async () => {
@@ -22,7 +23,7 @@ const UpdateExistUserAdmin = () => {
const isAdminExist = await Parse.Cloud.run("checkadminexist"); const isAdminExist = await Parse.Cloud.run("checkadminexist");
if (isAdminExist !== "not_exist") { if (isAdminExist !== "not_exist") {
// console.log("isAdminExist ", isAdminExist); // console.log("isAdminExist ", isAdminExist);
setErrMsg("Admin already exists."); setErrMsg(t("admin-exists"));
} }
} catch (err) { } catch (err) {
console.log("Err in checkadminexist", err); console.log("Err in checkadminexist", err);
@@ -52,7 +53,7 @@ const UpdateExistUserAdmin = () => {
} else if (err.code === 101) { } else if (err.code === 101) {
setIsAlert((prev) => ({ ...prev, msg: t("user-not-found") })); setIsAlert((prev) => ({ ...prev, msg: t("user-not-found") }));
} else if (err.code === 137) { } else if (err.code === 137) {
setIsAlert((prev) => ({ ...prev, msg: t("admin-already-exist") })); setIsAlert((prev) => ({ ...prev, msg: t("admin-exists") }));
} else { } else {
setErrMsg(t("something-went-wrong-mssg")); setErrMsg(t("something-went-wrong-mssg"));
} }
+2 -2
View File
@@ -596,7 +596,7 @@ function UserProfile() {
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm" className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() => copytoclipboard()} onClick={() => copytoclipboard()}
> >
<i className="fa-light fa-link"></i>{" "} <i className="fa-light fa-copy" />
</button> </button>
</div> </div>
</div> </div>
@@ -611,7 +611,7 @@ function UserProfile() {
<div className="flex flex-row md:items-center gap-1"> <div className="flex flex-row md:items-center gap-1">
{editmode || !extendUser?.[0]?.Tagline ? ( {editmode || !extendUser?.[0]?.Tagline ? (
<input <input
maxLength={40} maxLength={100}
onChange={handleOnchangeTagLine} onChange={handleOnchangeTagLine}
value={tagLine} value={tagLine}
disabled={!editmode} disabled={!editmode}
+2 -2
View File
@@ -25,8 +25,8 @@ const Alert = ({ children, type, className }) => {
: "z-[1000] fixed top-20 left-1/2 transform -translate-x-1/2 text-sm" : "z-[1000] fixed top-20 left-1/2 transform -translate-x-1/2 text-sm"
} `} } `}
> >
<div className={`op-alert ${textcolor}`}> <div className={`op-alert ${textcolor} flex justify-center`}>
<span className="ml-3">{children}</span> <span className="px-1">{children}</span>
</div> </div>
</div> </div>
) )
@@ -0,0 +1,138 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import ModalUi from "./ModalUi";
import {
getSignedUrl,
handleDownloadCertificate,
handleDownloadPdf,
sanitizeFileName
} from "../constant/Utils";
import Loader from "./Loader";
import JSZip from "jszip";
import { saveAs } from "file-saver";
function DownloadPdfZip(props) {
const { t } = useTranslation();
const [selectType, setSelectType] = useState(1);
const [isDownloading, setIsDownloading] = useState(false);
const downloadType = [
{ id: 1, label: t("download-pdf") },
{ id: 2, label: t("pdf-certificate") }
];
const handleDownload = async () => {
if (selectType === 1) {
await handleDownloadPdf(props.pdfDetails, setIsDownloading);
setSelectType(1);
props.setIsDownloadModal(false);
} else if (selectType === 2) {
setIsDownloading("pdf");
const zip = new JSZip();
const pdfDetails = props.pdfDetails;
const pdfName = pdfDetails?.[0]?.Name || "Document";
const pdfUrl = pdfDetails?.[0]?.SignedUrl || "";
try {
// Fetch the first PDF (Signed Document)
const docId = pdfDetails?.[0]?.objectId || "";
const fileAdapterId = pdfDetails?.[0]?.FileAdapterId
? pdfDetails?.[0]?.FileAdapterId
: "";
console.log("pdfDetails?.[0] ", pdfDetails?.[0]);
const signedUrl = await getSignedUrl(pdfUrl, docId, fileAdapterId);
console.log("signedUrl ", signedUrl);
const pdf1Response = await fetch(signedUrl);
if (!pdf1Response.ok) {
throw new Error(`Failed to fetch PDF: ${signedUrl}`);
}
const pdf1Blob = await pdf1Response.blob();
const isZip = true;
// Fetch the Certificate (or generate its URL dynamically)
const certificateUrl = await handleDownloadCertificate(
pdfDetails,
setIsDownloading,
isZip
);
const pdf2Response = await fetch(certificateUrl);
if (!pdf2Response.ok) {
throw new Error(`Failed to fetch certificate PDF: ${certificateUrl}`);
}
const pdf2Blob = await pdf2Response.blob();
// Add files to ZIP
zip.file(
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`,
pdf1Blob
);
zip.file("Certificate_signed_by_OpenSign™.pdf", pdf2Blob);
// Generate the ZIP and trigger download
const zipBlob = await zip.generateAsync({ type: "blob" });
saveAs(
zipBlob,
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.zip`
);
setSelectType(1);
props.setIsDownloadModal(false);
setIsDownloading("");
} catch (error) {
console.error("Error creating ZIP file:", error);
}
}
};
return (
<ModalUi
isOpen={props.isDownloadModal}
title={t("download-files")}
handleClose={() => props.setIsDownloadModal(false)}
>
<div className="p-[20px] h-full">
{downloadType.map((data, ind) => {
return (
<label
key={ind}
className="flex items-center gap-1 mb-2 cursor-pointer"
>
<input
className="mr-[8px] op-radio op-radio-xs"
type="radio"
value={data.id}
onChange={() => setSelectType(data.id)}
checked={selectType === data.id}
/>
{data.label}
</label>
);
})}
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<button
onClick={() => handleDownload()}
type="submit"
className="op-btn op-btn-primary"
>
{t("download")}
</button>
</div>
{isDownloading === "pdf" && (
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<ModalUi
isOpen={isDownloading === "certificate"}
title={
isDownloading === "certificate"
? t("generating-certificate")
: t("pdf-download")
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
{isDownloading === "certificate"}{" "}
<p>{t("generate-certificate-alert")}</p>
</div>
</ModalUi>
</ModalUi>
);
}
export default DownloadPdfZip;
+256 -194
View File
@@ -14,6 +14,7 @@ import {
checkIsSubscribed, checkIsSubscribed,
copytoData, copytoData,
fetchUrl, fetchUrl,
getSignedUrl,
replaceMailVaribles replaceMailVaribles
} from "../constant/Utils"; } from "../constant/Utils";
import Confetti from "react-confetti"; import Confetti from "react-confetti";
@@ -30,6 +31,8 @@ import SubscribeCard from "./SubscribeCard";
import { validplan } from "../json/plansArr"; import { validplan } from "../json/plansArr";
import { serverUrl_fn } from "../constant/appinfo"; import { serverUrl_fn } from "../constant/appinfo";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import DownloadPdfZip from "./DownloadPdfZip";
import EmbedTab from "../components/pdf/EmbedTab";
const ReportTable = (props) => { const ReportTable = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -69,7 +72,11 @@ const ReportTable = (props) => {
const [publicUserName, setIsPublicUserName] = useState(""); const [publicUserName, setIsPublicUserName] = useState("");
const [isViewShare, setIsViewShare] = useState({}); const [isViewShare, setIsViewShare] = useState({});
const [isSubscribe, setIsSubscribe] = useState(true); const [isSubscribe, setIsSubscribe] = useState(true);
const [isModal, setIsModal] = useState({});
const [reason, setReason] = useState(""); const [reason, setReason] = useState("");
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [isEmbed, setIsEmbed] = useState(false);
const [isPublicTour, setIsPublicTour] = useState();
const Extand_Class = localStorage.getItem("Extand_Class"); const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class); const extClass = Extand_Class && JSON.parse(Extand_Class);
const startIndex = (currentPage - 1) * props.docPerPage; const startIndex = (currentPage - 1) * props.docPerPage;
@@ -267,7 +274,8 @@ const ReportTable = (props) => {
SendinOrder: Doc?.SendinOrder || false, SendinOrder: Doc?.SendinOrder || false,
AutomaticReminders: Doc?.AutomaticReminders || false, AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5, RemindOnceInEvery: Doc?.RemindOnceInEvery || 5,
IsEnableOTP: Doc?.IsEnableOTP || false IsEnableOTP: Doc?.IsEnableOTP || false,
FileAdapterId: Doc?.FileAdapterId || ""
}; };
try { try {
const res = await axios.post( const res = await axios.post(
@@ -365,6 +373,21 @@ const ReportTable = (props) => {
setIsShareWith({ [item.objectId]: true }); setIsShareWith({ [item.objectId]: true });
} else if (act.action === "Embed") { } else if (act.action === "Embed") {
handleEmbedFunction(item); handleEmbedFunction(item);
} else if (act.action === "CopyTemplateId") {
copyTemplateId(item.objectId);
} else if (act.action === "CopyPublicURL") {
const isPublic = item?.IsPublic;
if (isPublic) {
let publicUrl = "";
if (isStaging) {
publicUrl = `https://staging.opensign.me/publicsign?templateid=${item.objectId}`;
} else {
publicUrl = `https://opensign.me/publicsign?templateid=${item.objectId}`;
}
copyTemplateId(publicUrl);
} else {
setIsPublicTour({ [item.objectId]: true });
}
} }
}; };
// Get current list // Get current list
@@ -580,25 +603,15 @@ const ReportTable = (props) => {
async function checkTourStatus() { async function checkTourStatus() {
const cloudRes = await Parse.Cloud.run("getUserDetails"); const cloudRes = await Parse.Cloud.run("getUserDetails");
const res = { data: cloudRes.toJSON() }; if (cloudRes) {
if (res.data && res.data.TourStatus && res.data.TourStatus.length > 0) { const extUser = JSON.parse(JSON.stringify(cloudRes));
const tourStatus = res.data.TourStatus; localStorage.setItem("Extand_Class", JSON.stringify([extUser]));
// console.log("res ", res.data.TourStatus); const tourStatus = extUser?.TourStatus || [];
setTourStatusArr(tourStatus); setTourStatusArr(tourStatus);
const filteredtourStatus = tourStatus.filter( const templateTour = tourStatus.find(
(obj) => obj["templateTour"] (obj) => obj.templateTour
); )?.templateTour;
if (filteredtourStatus.length > 0) { setIsTour(!templateTour);
const templateTour = filteredtourStatus[0]["templateTour"];
if (templateTour) {
setIsTour(false);
} else {
setIsTour(true);
}
} else {
setIsTour(true);
}
} else { } else {
setIsTour(true); setIsTour(true);
} }
@@ -648,10 +661,24 @@ const ReportTable = (props) => {
setActLoader({ [`${item.objectId}`]: true }); setActLoader({ [`${item.objectId}`]: true });
const url = item?.SignedUrl || item?.URL || ""; const url = item?.SignedUrl || item?.URL || "";
const pdfName = item?.Name || "exported_file"; const pdfName = item?.Name || "exported_file";
const isCompleted = item?.IsCompleted || false;
const templateId = props?.ReportName === "Templates" && item.objectId;
const docId = props?.ReportName !== "Templates" && item.objectId;
const fileAdapterId = item?.FileAdapterId ? item?.FileAdapterId : "";
if (url) { if (url) {
try { try {
const signedUrl = await Parse.Cloud.run("getsignedurl", { url: url }); if (isCompleted) {
await fetchUrl(signedUrl, pdfName); setIsDownloadModal({ [item.objectId]: true });
} else {
const signedUrl = await getSignedUrl(
url,
docId,
fileAdapterId,
templateId
);
await fetchUrl(signedUrl, pdfName);
}
setActLoader({}); setActLoader({});
} catch (err) { } catch (err) {
console.log("err in getsignedurl", err); console.log("err in getsignedurl", err);
@@ -684,7 +711,7 @@ const ReportTable = (props) => {
receiver_phone: userDetails?.Phone || "", receiver_phone: userDetails?.Phone || "",
expiry_date: localExpireDate, expiry_date: localExpireDate,
company_name: doc.ExtUserPtr.Company, company_name: doc.ExtUserPtr.Company,
signing_url: `<a href=${signPdf}>Sign here</a>` signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
}; };
const res = replaceMailVaribles(subject, "", variables); const res = replaceMailVaribles(subject, "", variables);
@@ -713,7 +740,7 @@ const ReportTable = (props) => {
receiver_phone: userDetails?.Phone || "", receiver_phone: userDetails?.Phone || "",
expiry_date: localExpireDate, expiry_date: localExpireDate,
company_name: doc.ExtUserPtr.Company, company_name: doc.ExtUserPtr.Company,
signing_url: `<a href=${signPdf}>Sign here</a>` signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
}; };
const res = replaceMailVaribles("", body, variables); const res = replaceMailVaribles("", body, variables);
@@ -754,7 +781,7 @@ const ReportTable = (props) => {
receiver_phone: user?.signerPtr?.Phone || "", receiver_phone: user?.signerPtr?.Phone || "",
expiry_date: localExpireDate, expiry_date: localExpireDate,
company_name: doc?.ExtUserPtr?.Company || "", company_name: doc?.ExtUserPtr?.Company || "",
signing_url: `<a href=${signPdf}>Sign here</a>` signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
}; };
const subject = const subject =
@@ -777,7 +804,7 @@ const ReportTable = (props) => {
sessionToken: localStorage.getItem("accesstoken") sessionToken: localStorage.getItem("accesstoken")
}; };
let params = { let params = {
mailProvider: doc?.ExtUserPtr?.active_mail_adapter, mailProvider: doc?.ExtUserPtr?.active_mail_adapter || "",
extUserId: doc?.ExtUserPtr?.objectId, extUserId: doc?.ExtUserPtr?.objectId,
recipient: userDetails?.Email, recipient: userDetails?.Email,
subject: mail.subject, subject: mail.subject,
@@ -1112,12 +1139,25 @@ const ReportTable = (props) => {
} }
}; };
const handleEmbedFunction = async (item) => { const handleEmbedFunction = async (item) => {
setIsEmbed(true);
setIsPublicProfile({ setIsPublicProfile({
[item.objectId]: props.isPublic[item.objectId] [item.objectId]: true
}); });
let extendUser = JSON.parse(localStorage.getItem("Extand_Class")); let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
setIsPublicUserName(extendUser[0]?.UserName || ""); setIsPublicUserName(extendUser[0]?.UserName || "");
}; };
const publicTourConfig = [
{
selector: '[data-tut="IsPublic"]',
content: t("public-tour-message"),
position: "top",
style: { fontSize: "13px" }
}
];
const closePublicTour = () => {
setIsPublicTour();
};
return ( return (
<div className="relative"> <div className="relative">
{Object.keys(actLoader)?.length > 0 && ( {Object.keys(actLoader)?.length > 0 && (
@@ -1125,21 +1165,40 @@ const ReportTable = (props) => {
<Loader /> <Loader />
</div> </div>
)} )}
<div className="p-2 w-full overflow-auto bg-base-100 text-base-content op-card shadow-lg"> <div className="p-2 w-full overflow-hidden bg-base-100 text-base-content op-card shadow-lg">
{isCelebration && ( {isCelebration && (
<div className="relative z-[1000]"> <div className="relative z-[1000]">
<Confetti width={window.innerWidth} height={window.innerHeight} /> <Confetti
width={window.innerWidth}
height={window.innerHeight}
recycle={false} // Prevents confetti from repeating
gravity={0.1} // Adjust the gravity to control the speed
/>
</div> </div>
)} )}
{isAlert && <Alert type={alertMsg.type}>{alertMsg.message}</Alert>} {isAlert && <Alert type={alertMsg.type}>{alertMsg.message}</Alert>}
{props.tourData && props.ReportName === "Templates" && ( {props.tourData && props.ReportName === "Templates" && (
<Tour <>
onRequestClose={closeTour} <Tour
steps={props.tourData} onRequestClose={closeTour}
isOpen={isTour} steps={props.tourData}
// rounded={5} isOpen={isTour}
closeWithMask={false} // rounded={5}
/> closeWithMask={false}
/>
{isPublicTour && (
<Tour
showNumber={false}
showNavigation={false}
showNavigationNumber={false}
onRequestClose={closePublicTour}
steps={publicTourConfig}
isOpen={true}
rounded={5}
closeWithMask={false}
/>
)}
</>
)} )}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]"> <div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<div className="font-light"> <div className="font-light">
@@ -1168,8 +1227,8 @@ const ReportTable = (props) => {
</div> </div>
<div <div
className={`${ className={`${
isDashboard && props.List?.length > 0 ? "h-[317px]" : "h-full" isDashboard && props.List?.length > 0 ? "min-h-[317px]" : "h-full"
} w-full`} } overflow-auto w-full`}
> >
<table className="op-table border-collapse w-full "> <table className="op-table border-collapse w-full ">
<thead className="text-[14px]"> <thead className="text-[14px]">
@@ -1205,48 +1264,50 @@ const ReportTable = (props) => {
</td> </td>
<td className="px-4 py-2 ">{item?.Email || "-"}</td> <td className="px-4 py-2 ">{item?.Email || "-"}</td>
<td className="px-4 py-2">{item?.Phone || "-"}</td> <td className="px-4 py-2">{item?.Phone || "-"}</td>
<td className="px-3 py-2 text-white grid grid-cols-2"> <td className="px-3 py-2">
{props.actions?.length > 0 && <div className="text-base-content min-w-max flex flex-row gap-x-2 gap-y-1 justify-start items-center">
props.actions.map((act, index) => ( {props.actions?.length > 0 &&
<button props.actions.map((act, index) => (
key={index} <button
onClick={() => handleActionBtn(act, item)} key={index}
title={t(`btnLabel.${act.hoverLabel}`)} onClick={() => handleActionBtn(act, item)}
className={`${ title={t(`btnLabel.${act.hoverLabel}`)}
act?.btnColor ? act.btnColor : "" className={`${
} op-btn op-btn-sm`} act?.btnColor ? act.btnColor : ""
} op-btn op-btn-sm`}
>
<i className={act.btnIcon}></i>
</button>
))}
{isDeleteModal[item.objectId] && (
<ModalUi
isOpen
title={"Delete Contact"}
handleClose={handleClose}
> >
<i className={act.btnIcon}></i> <div className="m-[20px]">
</button> <div className="text-lg font-normal text-black">
))} {t("contact-delete-alert")}
{isDeleteModal[item.objectId] && ( </div>
<ModalUi <hr className="bg-[#ccc] mt-4 " />
isOpen <div className="flex items-center mt-3 gap-2 text-white">
title={"Delete Contact"} <button
handleClose={handleClose} onClick={() => handleDelete(item)}
> className="op-btn op-btn-primary"
<div className="m-[20px]"> >
<div className="text-lg font-normal text-black"> {t("yes")}
{t("contact-delete-alert")} </button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div> </div>
<hr className="bg-[#ccc] mt-4 " /> </ModalUi>
<div className="flex items-center mt-3 gap-2 text-white"> )}
<button </div>
onClick={() => handleDelete(item)}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
)}
</td> </td>
</tr> </tr>
) : ( ) : (
@@ -1288,9 +1349,55 @@ const ReportTable = (props) => {
{item?.URL ? t("download") : "-"} {item?.URL ? t("download") : "-"}
</button> </button>
</td> </td>
<td className="px-4 py-2"> {props.ReportName === "In-progress documents" ? (
{formatRow(item?.ExtUserPtr)} <td className="px-4 py-2">
</td> <button
onClick={() =>
item?.AuditTrail?.length > 0 &&
setIsModal({ [item?.objectId]: true })
}
className={`${
item?.AuditTrail?.length
? "border-green-400"
: "cursor-default op-border-primary"
} focus:outline-none w-[60px] border-[2px] text-[12px] rounded-full md:self-center`}
>
{item?.AuditTrail?.length ? "VIEWED" : "SENT"}
</button>
{isModal[item.objectId] && (
<ModalUi
isOpen
title={t("document-logs")}
handleClose={() => setIsModal({})}
>
{item?.AuditTrail?.map((x, i) => (
<div
key={i}
className="pl-3 first:mt-2 text-sm font-medium flex flex-col md:flex-row items-start md:gap-4 border-t-[1px] border-gray-600"
>
<div className="py-2 break-all font-bold md:text-[12px] md:col-span-2 w-full md:w-[210px]">
{x?.UserPtr?.Email || "-"}
</div>
<button className="px-2 cursor-default border-[2px] text-[12px] border-green-400 rounded-full md:self-center">
{x?.Activity?.toUpperCase() || "-"}
</button>
<div className=" text-[12px] py-2">
{x?.Activity === "Signed"
? new Date(x?.SignedOn)?.toUTCString()
: new Date(
x?.ViewedOn
)?.toUTCString() || "-"}
</div>
</div>
))}
</ModalUi>
)}
</td>
) : (
<td className="px-4 py-2">
{formatRow(item?.ExtUserPtr)}
</td>
)}
<td className="px-4 py-2"> <td className="px-4 py-2">
{!item?.IsSignyourself && item?.Placeholders ? ( {!item?.IsSignyourself && item?.Placeholders ? (
<button <button
@@ -1307,17 +1414,17 @@ const ReportTable = (props) => {
isEnableSubscription && ( isEnableSubscription && (
<td className=" pl-[20px] py-2"> <td className=" pl-[20px] py-2">
{props.ReportName === "Templates" && ( {props.ReportName === "Templates" && (
<div className="flex flex-row"> <div
<label className="flex flex-row "
className={ data-tut="IsPublic"
"cursor-pointer relative inline-flex items-center mb-0" >
} <label className="cursor-pointer relative inline-flex items-center mb-0">
>
<input <input
checked={props.isPublic?.[item.objectId]} checked={props.isPublic?.[item.objectId]}
onChange={(e) => onChange={(e) => {
handlePublicChange(e, item) setIsPublicTour();
} handlePublicChange(e, item);
}}
type="checkbox" type="checkbox"
value="" value=""
className="sr-only peer" className="sr-only peer"
@@ -1388,25 +1495,30 @@ const ReportTable = (props) => {
<ModalUi <ModalUi
isOpen isOpen
title={t("public-url")} title={t("public-url")}
handleClose={() => setIsPublicProfile({})} handleClose={() => {
setIsPublicProfile({});
setIsEmbed(false);
}}
> >
<div className="m-[20px]"> <div className="m-[20px]">
{publicUserName ? ( {isEmbed && !item?.IsPublic ? (
<>
<p>{t("public-template-mssg-6")}</p>
<EmbedTab templateId={item.objectId} />
</>
) : publicUserName ? (
<div className="font-normal text-black"> <div className="font-normal text-black">
<p>{t("public-url-copy-mssg")}</p> <span>{t("public-url-copy")}</span>
<div className=" flex items-center justify-between gap-3 mt-2 p-[2px] "> <div className=" flex items-center justify-between gap-3 p-[2px] ">
<div className="w-[280px] whitespace-nowrap overflow-hidden text-ellipsis"> <div className="w-[280px] whitespace-nowrap overflow-hidden text-ellipsis">
<span className="font-bold"> <a
{t("public-url")} : rel="noreferrer"
</span> target="_blank"
<span href={publicUrl()}
onClick={() => className="ml-[2px] underline underline-offset-2 cursor-pointer text-blue-800"
copytoProfileLink()
}
className="ml-[2px] underline underline-offset-2 cursor-pointer"
> >
{publicUrl()} {publicUrl()}
</span> </a>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<RWebShare <RWebShare
@@ -1423,12 +1535,12 @@ const ReportTable = (props) => {
</button> </button>
</RWebShare> </RWebShare>
<button <button
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm" className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm md:w-[100px]"
onClick={() => onClick={() =>
copytoProfileLink() copytoProfileLink()
} }
> >
<i className="fa-light fa-link"></i>{" "} <i className="fa-light fa-copy" />
<span className="hidden md:inline-block"> <span className="hidden md:inline-block">
{copied["publicprofile"] {copied["publicprofile"]
? t("copied") ? t("copied")
@@ -1437,36 +1549,10 @@ const ReportTable = (props) => {
</button> </button>
</div> </div>
</div> </div>
<div className=" flex items-center justify-between gap-3 mt-2 p-[2px] "> <p className="text-[13px] mt-[5px]">
<div> {t("public-url-copy-mssg")}
<span className="font-bold"> </p>
{t("templateid")} : <EmbedTab templateId={item.objectId} />
</span>
<span
onClick={() =>
copyTemplateId(item.objectId)
}
className="underline ml-[2px] underline-offset-2 w-[150px] md:w-[350px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer"
>
{item.objectId}
</span>
</div>
<div className="flex items-center gap-2">
<button
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() =>
copyTemplateId(item.objectId)
}
>
<i className="fa-light fa-link"></i>{" "}
<span className="hidden md:inline-block">
{copied["templateid"]
? t("copied")
: t("copy")}
</span>
</button>
</div>
</div>
</div> </div>
) : ( ) : (
<div className="font-normal text-black"> <div className="font-normal text-black">
@@ -1475,7 +1561,7 @@ const ReportTable = (props) => {
className="mt-3 op-btn op-btn-primary" className="mt-3 op-btn op-btn-primary"
onClick={() => navigate("/profile")} onClick={() => navigate("/profile")}
> >
{t("add")} {t("Proceed")}
</button> </button>
</div> </div>
)} )}
@@ -1485,7 +1571,7 @@ const ReportTable = (props) => {
</td> </td>
)} )}
<td className="px-2 py-2"> <td className="px-2 py-2">
<div className="text-base-content flex flex-row gap-x-2 gap-y-1 justify-start items-center"> <div className="text-base-content min-w-max flex flex-row gap-x-2 gap-y-1 justify-start items-center">
{props.actions?.length > 0 && {props.actions?.length > 0 &&
props.actions.map((act, index) => props.actions.map((act, index) =>
props.ReportName === "Templates" ? ( props.ReportName === "Templates" ? (
@@ -1526,65 +1612,33 @@ const ReportTable = (props) => {
{isOption[item.objectId] && {isOption[item.objectId] &&
act.action === "option" && ( act.action === "option" && (
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow bg-base-100 text-base-content rounded-box"> <ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow bg-base-100 text-base-content rounded-box">
{act.subaction?.map((subact) => {act.subaction?.map((subact) => (
props.isPublic[ <li
item.objectId key={subact.btnId}
] ? ( onClick={() =>
<li handleActionBtn(
key={subact.btnId} subact,
onClick={() => item
handleActionBtn( )
subact, }
item title={t(
) `btnLabel.${subact.btnLabel}`
} )}
title={t( >
`btnLabel.${subact.btnLabel}` <span>
)} <i
> className={`${subact.btnIcon} mr-1.5`}
<span> ></i>
<i {subact.btnLabel && (
className={`${subact.btnIcon} mr-1.5`} <span className="text-[13px] capitalize font-medium">
></i> {t(
{subact.btnLabel && ( `btnLabel.${subact.btnLabel}`
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
)}
</span>
</li>
) : (
subact.action !== "Embed" && (
<li
key={subact.btnId}
onClick={() =>
handleActionBtn(
subact,
item
)
}
title={t(
`btnLabel.${subact.btnLabel}`
)}
>
<span>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
)} )}
</span> </span>
</li> )}
) </span>
) </li>
)} ))}
</ul> </ul>
)} )}
</div> </div>
@@ -1905,7 +1959,7 @@ const ReportTable = (props) => {
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm" className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() => copytoclipboard(share)} onClick={() => copytoclipboard(share)}
> >
<i className="fa-light fa-link"></i>{" "} <i className="fa-light fa-copy" />
{copied[share.email] {copied[share.email]
? t("copied") ? t("copied")
: t("copy")} : t("copy")}
@@ -2059,6 +2113,14 @@ const ReportTable = (props) => {
</div> </div>
</ModalUi> </ModalUi>
)} )}
{isDownloadModal[item.objectId] && (
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal[item.objectId]}
pdfDetails={[item]}
isDocId={false}
/>
)}
</td> </td>
</tr> </tr>
) )
@@ -24,7 +24,7 @@ export default function TourContentWithBtn({ message, isChecked, video }) {
></iframe> ></iframe>
</div> </div>
)} )}
<label className="flex items-center justify-center mb-0"> <label className="flex items-center justify-center mt-3 mb-0">
<input <input
type="checkbox" type="checkbox"
className="op-checkbox op-checkbox-xs mr-1" className="op-checkbox op-checkbox-xs mr-1"
+18 -9
View File
@@ -11,28 +11,37 @@ import { isPublicStaging } from "../constant/const";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.mjs`; pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.mjs`;
// Wrapper component to manage props // Wrapper component to manage props
const PublicScriptFileWrapper = ({ initialProps }) => { const PublicScriptFileWrapper = () => {
const [props, setProps] = useState(initialProps); const [props, setProps] = useState("");
// Make the setProps function globally accessible
useEffect(() => { useEffect(() => {
// Assigns the function 'setProps' to the global 'window' object as 'updatePublicTemplateProps'.
// This allows 'setProps' to be accessed and called globally across different scripts or components,
// enabling dynamic updates to the public template's properties.
window.updatePublicTemplateProps = setProps; window.updatePublicTemplateProps = setProps;
const publicScript = document.getElementById("opensign-script");
// condition for using plan js load opensign-script then get templateId
if (publicScript) {
// Get the custom templateId attribute
const templateId = publicScript.getAttribute("templateId");
if (templateId) {
setProps({ templateId: templateId });
} else {
const error = "error: TemplateId is missing";
throw new Error(error);
}
}
return () => { return () => {
window.updatePublicTemplateProps = undefined; window.updatePublicTemplateProps = undefined;
}; };
}, []); }, []);
return <PdfRequestFiles {...props} />; return <PdfRequestFiles {...props} />;
}; };
const appId = isPublicStaging ? "opensign" : "legadranaxn"; const appId = isPublicStaging ? "opensign" : "legadranaxn";
//"opensign"
const serverUrl = isPublicStaging const serverUrl = isPublicStaging
? "https://staging-app.opensignlabs.com/api/app" ? "https://staging-app.opensignlabs.com/api/app"
: "https://app.opensignlabs.com/api/app"; : "https://app.opensignlabs.com/api/app";
// "https://staging-app.opensignlabs.com/api/app"
localStorage.setItem("baseUrl", `${serverUrl}/`); localStorage.setItem("baseUrl", `${serverUrl}/`);
localStorage.setItem("parseAppId", appId); localStorage.setItem("parseAppId", appId);
@@ -58,7 +67,7 @@ const root = ReactDOM.createRoot(document.getElementById("script-component"));
root.render( root.render(
<div data-theme="opensigncss"> <div data-theme="opensigncss">
<Provider store={store}> <Provider store={store}>
<PublicScriptFileWrapper initialProps={{ text: "templateId" }} /> <PublicScriptFileWrapper />
</Provider> </Provider>
</div> </div>
); );
+5 -1
View File
@@ -2,6 +2,7 @@ import i18n from "i18next";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import en from "./locales/en/translation.json"; import en from "./locales/en/translation.json";
import es from "./locales/es/translation.json";
import fr from "./locales/fr/translation.json"; import fr from "./locales/fr/translation.json";
i18n i18n
@@ -12,6 +13,9 @@ i18n
en: { en: {
translation: en translation: en
}, },
es: {
translation: es
},
fr: { fr: {
translation: fr translation: fr
} }
@@ -27,7 +31,7 @@ i18n
interpolation: { interpolation: {
escapeValue: false // React already does escaping escapeValue: false // React already does escaping
}, },
whitelist: ["en", "fr"] // List of allowed languages whitelist: ["en", "es", "fr"] // List of allowed languages
}); });
export default i18n; export default i18n;
@@ -131,7 +131,9 @@
"Share": "Share", "Share": "Share",
"View": "View", "View": "View",
"option": "Option", "option": "Option",
"Embed": "Embed" "Embed": "Embed",
"Copy TemplateId": "Copy TemplateId",
"Copy Public URL": "Copy Public URL"
}, },
"report-heading": { "report-heading": {
"Sr.No": "Sr.No", "Sr.No": "Sr.No",
@@ -151,7 +153,8 @@
"Name": "Name", "Name": "Name",
"Status": "Status", "Status": "Status",
"created-date": "Created Date", "created-date": "Created Date",
"Type": "Type" "Type": "Type",
"Logs": "Logs"
}, },
"report-help": { "report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.", "Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -262,9 +265,10 @@
"make-template-public-alert": "Are you sure you want to make this template public?", "make-template-public-alert": "Are you sure you want to make this template public?",
"make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.", "make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.",
"public-role": "Public role", "public-role": "Public role",
"public-url": "Public URL", "public-url": "Public profile needed",
"public-url-copy-mssg": " Heres your public URL: Copy it or share it with the signer, and you will be able to see all your publicly set templates.", "public-url-copy": "Heres your public URL: ",
"add-public-url-alert": "Please add your public URL, and you will be able to make a public template.", "public-url-copy-mssg": "Copy it or share it with the signer, and you will be able to see all your publicly set templates.",
"add-public-url-alert": "Please complete your OpenSign public profile setup. Once done, youll be able to make templates public. This process only needs to be completed once.",
"share-with-alert": "You cannot share a template if any roles already have contacts assigned. Please remove all contact assignments from the roles before sharing the template.", "share-with-alert": "You cannot share a template if any roles already have contacts assigned. Please remove all contact assignments from the roles before sharing the template.",
"share-with": "Share with", "share-with": "Share with",
"team-not-found": "Team not found", "team-not-found": "Team not found",
@@ -572,9 +576,10 @@
"pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .", "pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .",
"placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.", "placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.",
"placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.", "placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.",
"placeholder-sign-3": "Drag or click on a field to add it to the document.", "placeholder-sign-3": "Click this button to add more recipients/signers to the document.",
"placeholder-sign-4": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.", "placeholder-sign-4": "Drag or click on a field to add it to the document.",
"placeholder-sign-5": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.", "placeholder-sign-5": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-6": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like HR Manager and New Employee. Each time you use the template, you can assign the New Employee role to different incoming staff members, while the HR Manager role remains constant, facilitating a seamless onboarding process for each recruit. ", "report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like HR Manager and New Employee. Each time you use the template, you can assign the New Employee role to different incoming staff members, while the HR Manager role remains constant, facilitating a seamless onboarding process for each recruit. ",
"redirect": "Click the 'Use' button to create a new document from an existing template.", "redirect": "Click the 'Use' button to create a new document from an existing template.",
"bulksend": "To quickly create and send multiple documents using an existing template, click the 'Quick Send' button.", "bulksend": "To quickly create and send multiple documents using an existing template, click the 'Quick Send' button.",
@@ -627,6 +632,22 @@
"regenerate-test-token": "Regenerate Test Token", "regenerate-test-token": "Regenerate Test Token",
"help-test-token": "This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once youve completed your testing, you can upgrade to one of our paid plans to generate a production token.", "help-test-token": "This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once youve completed your testing, you can upgrade to one of our paid plans to generate a production token.",
"help-api-token": "This token can be used to access the production APIs at the {{origin}}/api/v1 endpoint. It can only be generated on one of our paid plans.", "help-api-token": "This token can be used to access the production APIs at the {{origin}}/api/v1 endpoint. It can only be generated on one of our paid plans.",
"reason": "Reason",
"decline-by": "Declined/revoked by",
"document-declined": "Document declined",
"public-template-mssg-1": "To integrate OpenSign into your React or Next.js project, simply run the following command:",
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If youre using Yarn, you can replace npm install with yarn add @opensign/react.",
"public-template-mssg-3": "Need more details or examples?",
"public-template-mssg-4": "Visit the",
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
"public-template-mssg-6": "Before you can use this code snippet, you must make this template public.",
"copy-code": "COPY",
"copied-code": "COPIED",
"Installation": "Installation",
"Usage": "Usage",
"insufficient-credits": "Insufficient Signing Credits",
"insufficient-credits-mssg": "The owner of this document currently lacks the necessary OpenSign credits for you to sign. Please reach out to the owner if you require further details.",
"angular-npm-mssg-1": "To integrate OpenSign into your Angular project, simply run the following command:",
"quota-mail-info-head": "Monthly request signatures email limit", "quota-mail-info-head": "Monthly request signatures email limit",
"quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.", "quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.",
"quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.", "quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.",
@@ -644,5 +665,23 @@
"p4": "Please choose the option that best suits your document signing requirements." "p4": "Please choose the option that best suits your document signing requirements."
}, },
"advanced-options": "Advanced options", "advanced-options": "Advanced options",
"hide-advanced-options": "Hide Advanced options" "hide-advanced-options": "Hide Advanced options",
"download-files": "Download files",
"download-pdf": "Download Pdf",
"pdf-certificate": "Download Pdf + Certificate",
"document-logs": "Document logs",
"server-down": "Unable to connect to the OpenSign server. If you are self-hosting OpenSign, please ensure that all the steps in the documentation have been followed correctly. If you're running OpenSign locally, you might be accessing it through an incorrect port number.",
"admin-exists": "Admin already exists. Please login to the application using admin credentials in order to manage users.",
"enable-tour": "Enable Tour",
"istourenabled-help": {
"p1": "This will enable a guided tour for signers, providing instructions during the signing process.",
"p2": "This will disable the guided tour, ensuring a faster, uninterrupted signing experience.",
"p3": "Tip: If your signers are familiar with OpenSign and frequently use it, you may want to disable the guided tour for a more seamless experience. Choose the option that best fits your document signing needs."
},
"form-title-1": "Document Flow Setting",
"form-title-2": "Security Setting",
"public-tour-message": "The template needs to be public before you can generate a shareable link.",
"add-user-template": "You need to add a role before you can add fields for it.",
"pdf-uncompatible": "This pdf is not compatible with opensign, please contact <support@opensignlabs.com>"
} }
@@ -0,0 +1,686 @@
{
"header-news": "Nueva funcionalidad: ¡Los perfiles públicos ahora están disponibles! Crea y comparte plantillas públicas fácilmente",
"header-news-btn": "Configurar ahora",
"create-account": "Crear cuenta",
"login": "Iniciar sesión",
"language": "Idioma",
"name": "Nombre",
"phone": "Teléfono",
"phone-optional": "opcional",
"email": "Correo",
"company": "Compañía",
"job-title": "Título del puesto",
"profile": "Perfil",
"log-out": "Cerrar sesión",
"is-email-verified": "El correo está verificado",
"public-profile": "Perfil público",
"tagline": "Eslogan ",
"has-requested-you-to-sign": "ha solicitado que firmes",
"has-requested-you-to-review-and-sign": " ha solicitado que revises y firmes",
"input-required": "Por favor, rellena este campo.",
"sort-order": {
"Ascending": "Ascendente",
"Descending": "Descendente",
"Name": "Nombre",
"Date": "Fecha"
},
"context-menu": {
"Download": "Descargar",
"Rename": "Renombrar",
"Move": "Mover",
"Delete": "Eliminar"
},
"action": "Acción",
"add": "Agregar",
"add-new-folder": "Agregar nueva carpeta",
"disable-documentId": "Desactivar ID de documento",
"edit": "Editar",
"change-password": "Cambiar contraseña",
"save": "Guardar",
"cancel": "Cancelar",
"upgrade-now": "Mejorar ahora",
"upgrade-to": "Mejorar a",
"plan": "Plan",
"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",
"tour-content": "No mostrar esto otra vez",
"pro": "PRO",
"team": "EQUIPO",
"user": "Usuario",
"docs": "Documentación",
"session-expired": "Tu sesión ha expirado.",
"public-profile-help": "Esta es tu URL pública. Cópiala o compártela con el firmante, y podrás ver todas tus plantillas públicas.",
"tagline-help": "Este eslogan aparecerá en tu perfil público de OpenSign, por ejem. https://opensign.me/alex",
"welcome": "¡Bienvenido de nuevo!",
"Login-to-your-account": "Ingresa a tu cuenta",
"password": "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-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",
"term": "Términos de Servicio",
"subscribe-to-opensign": "Suscribirse al boletín de OpenSign",
"register": "Registrarse",
"sidebar": {
"Dashboard": "Panel",
"Sign yourself": "Firma tu mismo",
"Request signatures": "Solicitar firmas",
"Templates": "Plantillas",
"Templates-Children": {
"Create template": "Crear plantilla",
"Manage templates": "Administrar plantillas"
},
"OpenSign™ Drive": "OpenSign™ Drive",
"Reports": "Reportes",
"Reports-Children": {
"Need your sign": "Necesitan tu firma",
"In Progress": "En progreso",
"Completed": "Completado",
"Drafts": "Borradores",
"Declined": "Rechazados",
"Expired": "Expirados",
"Contactbook": "Agenda de contactos"
},
"Settings": "Ajustes",
"Settings-Children": {
"My Signature": "Mi firma",
"API Token": "Token API",
"Webhook": "Webhook",
"Teams": "Equipos",
"Team-update-successfully": "Equipo actualizado correctamente.",
"Users": "Usuarios"
}
},
"dashboard-card": {
"Need your Signature": "Necesitan tu firma",
"Out for signatures": "En espera de ser firmados"
},
"report-name": {
"Draft Documents": "Documentos borradores",
"Need your sign": "Necesitan tu firma",
"In-progress documents": "Documentos en progreso",
"Completed Documents": "Documentos completados",
"Declined Documents": "Documentos rechazados",
"Expired Documents": "Documentos expirados",
"Recently sent for signatures": "Recientemente enviados para ser firmados",
"Recent signature requests": "Solicitudes de firma recientes",
"Drafts": "Borradores",
"Contactbook": "Agenda de contactos",
"Templates": "Plantillas",
"Teams": "Equipos",
"Users": "Usuarios"
},
"btnLabel": {
"sign": "firmar",
"Sign": "FIRMAR",
"Resend": "Reenviar",
"Revoke": "Revocar",
"Delete": "Eliminar",
"Use": "Usar",
"Quick send": "Envío rápido",
"Edit": "Editar",
"Share with team": "Compartir con el equipo",
"Share": "Compartir",
"View": "Ver",
"option": "Opción",
"Embed": "Incrustar",
"Copy TemplateId": "Copiar ID de plantilla",
"Copy Public URL": "Copiar URL pública"
},
"report-heading": {
"Sr.No": "Nº",
"Title": "Título",
"Email": "Correo",
"Phone": "Teléfono",
"File": "Archivo",
"Owner": "Propietario",
"Signers": "Firmantes",
"Note": "Nota",
"Folder": "Carpeta",
"Reason": "Razón",
"Parent Team": "Equipo padre",
"Active": "Activo",
"Role": "Rol",
"Team": "Equipo",
"Name": "Nombre",
"Status": "Estado",
"created-date": "Fecha de creación",
"Type": "Tipo",
"Logs": "Registros"
},
"report-help": {
"Draft Documents": "Estos son documentos que has iniciado pero no has finalizado para su envío.",
"Need your sign": "Esta es una lista de documentos que esperan tu firma.",
"In-progress documents": "Esta es una lista de documentos que has enviado a otras partes para su firma.",
"Completed Documents": "Esta es una lista de documentos en los que todas las partes han firmado.",
"Declined Documents": "Esta es una lista de documentos que han sido rechazados por uno de los firmantes.",
"Expired Documents": "Esta es una lista de documentos que han alcanzado su fecha de caducidad.",
"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"
},
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "archivo seleccionado",
"template-title": "Título de la plantilla",
"document-title": "Título del documento",
"description": "Descripción",
"time-to-complete": "Plazo de finalización (días)",
"send-in-order": "Enviar en orden",
"send-in-order-help": {
"p1": "Elige cómo quieres que se envíen las solicitudes de firma a los firmantes del documento:",
"p2": "Seleccionando esta opción se enviará inicialmente la solicitud de firma al primer firmante. Una vez que el primer firmante complete su parte, el siguiente firmante en la secuencia recibirá la solicitud. Este proceso continúa hasta que todos los firmantes hayan firmado el documento. Este método garantiza que el documento se firme en un orden específico.",
"p3": "Seleccionando esta opción se enviarán los enlaces para la firma a todos los firmantes simultáneamente. Cada firmante puede firmar el documento cuando le convenga, independientemente de si otros firmantes han completado sus firmas. Este método es más rápido, pero no impone ningún orden de firma entre los participantes.",
"p4": "Selecciona la opción que mejor se adapte a las necesidades de tu tramitación de documentos."
},
"no": "No",
"auto-reminder": "Recordatorio automático",
"remind-once": " Recordar una vez cada (Días)",
"next": "Siguiente",
"select-folder": "Seleccionar carpeta",
"OpenSign-drive": "OpenSign™ Drive",
"drive-document-status": {
"Completed": "Completado",
"Declined": "Rechazado",
"Draft": "Borrador",
"Expired": "Expirado",
"In Progress": "En progreso"
},
"select-folder-help": "Si no seleccionas una carpeta, tu documento firmado se guardará en la carpeta principal de OpenSign Drive.",
"no-data": "No se encontraron datos",
"save-here": "Guardar aquí",
"back": "Atrás",
"add-folder": "Añadir carpeta",
"create-folder": "Crear carpeta",
"parent-folder": "Carpeta superior",
"create": "Crear",
"signers": "Firmantes",
"signers-help": "Empieza a escribir el nombre de un contacto para ver los firmantes sugeridos entre tus contactos guardados o añade otros nuevos. Organiza el orden de firma añadiendo firmantes en la secuencia deseada. Utiliza el botón «+» para añadir nuevos firmantes y la «X» para quitar todos. Cada firmante recibirá una solicitud por correo electrónico para que firme el documento según el orden indicado.",
"add-signer": "Añadir firmante",
"contact-not-found": "Contacto no encontrado",
"add-yourself": "Añadirte a ti mismo",
"submit": "Enviar",
"reset": "Volver a empezar",
"my-signature": "Mi firma",
"signature": "Firma",
"upload-image": " Subir imagen",
"clear": "Limpiar",
"upload-signature/Image": "Por favor, sube la firma/imagen",
"initials": "Iniciales",
"API": "API",
"api-token": "Token API",
"regenerate-token": "Regenerar token activo",
"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.",
"token-generated": "Token generado exitosamente.",
"webhook": "Webhook",
"update-webhook": "Actualizar webhook",
"add-webhook": "Agregar webhook",
"webhook-added": "Webhook agregado exitosamente.",
"team-disabled": "Equipo deshabilitado",
"Team enabled": "Equipo habilitado",
"are-you-sure": "¿En definitiva quieres ",
"disable": "deshabilitar",
"enable": "habilitar",
"this-team": " este equipo?",
"edit-team": "Editar equipo",
"name-of-team": "Nombre del equipo",
"prev": "Anterior",
"no-data-avaliable": "No hay datos disponibles",
"add-team": "Agregar equipo",
"page-not-found": "Página no encontrada",
"users-from-teams": "usuarios de equipos",
"user-status": "Estado del usuario",
"activate": "activar",
"deactivate": "desactivar",
"this-user": " este usuario",
"delete-user": "Eliminar usuario",
"delete": "eliminar",
"add-user": "Agregar usuario",
"password-generateed": "La contraseña solo será generada una vez; asegúrate de copiarla.",
"Team status": "Estado del equipo",
"user-deactivated": "Usuario desactivado.",
"user-activated": "Usuario activado.",
"public": "Público",
"download": "Descargar",
"view": "Ver",
"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": "¿En definitiva quieres convertir esta plantilla en privada? Esto lo removerá de tu perfil público.",
"public-role": "Rol público",
"public-url": "Se requiere perfil público",
"public-url-copy": "Aquí está tu URL pública: ",
"public-url-copy-mssg": "Cópiala o compártela con el firmante y podrás ver todas tus plantillas públicas.",
"add-public-url-alert": "Por favor, completa la configuración de tu perfil público OpenSign. Una vez terminado, podrás crear plantillas públicas. Este proceso solo necesita ser completado una vez.",
"share-with-alert": "No puedes compartir una plantilla si alguno de los roles ya tiene contactos asignados. Elimina todas las asignaciones de contactos de los roles antes de compartir la plantilla.",
"share-with": "Compartir con",
"team-not-found": "Equipo no encontrado",
"roles": "Roles",
"delete-document": "Eliminar documento",
"delete-document-alert": "¿En definitiva quieres eliminar este documento?",
"quick-send": "Envío rápido",
"add-new": "Agregar nuevo",
"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.",
"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}}.",
"subject": "Asunto",
"body": "Cuerpo",
"add-contact": "Agregar contacto",
"add-signer-alert": "¡El contacto ya existe!",
"record-delete-alert": "¡Entrada eliminada exitosamente!",
"record-revoke-alert": "¡Entrada revocada exitosamente!",
"mail-sent-alert": "Correo enviado exitosamente.",
"document-sent-alert": "¡Documento enviado exitosamente!",
"template-share-alert": "Plantilla compartida exitosamente.",
"template-public-alert-1": "Has convertido exitosamente la plantilla en pública.",
"template-public-alert-2": "Has convertido exitosamente la plantilla en privada.",
"template-public-alert-3": "Tienes que seleccionar un rol para los firmantes públicos.",
"template-public-alert-4": "Por favor, asegúrate de que has añadido al menos un widget de firma para cada firmante.",
"template-public-alert-5": "Dado que esta plantilla tiene «enviar en orden» habilitado, el rol público debe estar posicionado arriba al inicio.",
"template-public-alert-6": "Por favor, enlaza firmantes a todos los roles excepto al usuario de rol público.",
"template-public-alert-7": "Por favor, asigna al menos un rol público para convertir está plantilla en pública.",
"loader": " Esto podría llevar algún tiempo",
"pages": "Páginas",
"document-signed": "Documento firmado",
"close": "Cerrar",
"validation-alert": "Alerta de validación",
"validate-alert-mssg": " Los datos ingresados no cumplen con los criterios establecidos por la expresión regular.",
"otp-verification": "Verificación OTP",
"enter-otp": "Ingresar OTP",
"verify": "Verificar",
"verify-email-1": "Verificar correo",
"resend": "Reenviar",
"contact-details": "Detalles del contacto",
"verify-email": "¡Por favor, verifica tu correo!",
"send-otp": " Enviar OTP",
"otp-placeholder": "Ingresa el código de verificación enviado por correo",
"loading-doc": "Cargando documento...",
"widgets-name": {
"signature": "firma",
"stamp": "sello",
"initials": "iniciales",
"name": "nombre",
"job title": "título del puesto",
"company": "compañía",
"date": "fecha",
"text": "texto",
"text input": "entrada de texto",
"checkbox": "casilla",
"dropdown": "desplegable",
"radio button": "botón de radio",
"image": "imagen",
"email": "correo"
},
"fields": "Campos",
"recipients": "Destinatarios",
"please-add": "Por favor, agrega un",
"signed-by": "Firmado por",
"more": "más",
"sent": "enviado",
"print": "Imprimir",
"certificate": "Certificado",
"decline": "Rechazar",
"finish": "Finalizar",
"mail": "Correo",
"sign-now": "Firmar ahora",
"successfully-signed": "¡Firmado exitosamente!",
"email-mssg": " Los destinatarios agregados aquí recibirán una copia del documento firmado.",
"email-error-1": " por favor, proporciona una dirección de correo correcta",
"email-error-2": "Solo puedes enviar hasta a 10 destinatarios a la vez.",
"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": "Dibujar",
"type": "Escribir",
"color-type": {
"red": "rojo",
"blue": "azul",
"black": "negro",
"yellow": "amarillo"
},
"my-initials": "Mis iniciales",
"upload": "Subir",
"initial-teb": "Iniciales",
"signature-tab": "Firma",
"your-signature": "Tu firma",
"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",
"apply": "Aplicar",
"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"
},
"options": "Opciones",
"minimun-check": "Chequeo mínimo",
"maximum-check": "Chequeo máximo",
"default-value": "Valor por defecto",
"select": "Seleccionar",
"read-only": "Es de solo lectura",
"hide-labels": "Esconder etiquetas",
"checkbox": "Casilla",
"alert": "Alerta",
"zoom-in": "Acercar",
"zoom-out": "Alejar",
"document-signed-alert": "¡Felicitaciones! 🎉 ¡Este documento ha sido firmado por ti exitosamente!",
"otp-sent-alert": "OTP enviado a tu correo",
"fields-required": "Campos requeridos",
"signature-widget-alert-1": "Por favor, asegúrate de que hay al menos un widget de firma agregado",
"signature-widget-alert-2": "Por favor, asegúrate de que todos los campos están correctamente rellenados y de que cumplen todos los requisitos.",
"encrypted-pdf-alert": "Actualmente los archivos PDF encriptados no están soportados.",
"encrypted-pdf-alert-1": "La contraseña del archivo PFX es inválida.",
"user-not-exist": "El usuario no existe",
"template-signature-field": "Por favor, agrega al menos un campo de firma para todos los roles.",
"template-role-alert": "Por favor, primero agrega roles",
"create-document": "Crear documento",
"template-created-alert": "¿Quieres crear un documento usando la plantilla que acabas de crear?",
"signers-alert": "alerta de firmantes",
"template-creation-alert-1": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
"ok": "OK",
"radio-group": "Grupo de radio",
"dropdown-options": "Opciones del desplegable",
"add-role": "Agregar rol",
"role-ex": " por ejem.: Cliente, RRHH, Director, Administrador, Estudiante, etc.",
"add/choose-signer": "Agregar/Elegir firmante",
"choose-from-contacts": "Elegir desde los contactos",
"select-signer": " Por favor, selecciona el firmante",
"edit-template": "Editar plantilla",
"widget-info": "Información del widget",
"validation": "Validación",
"invalid-default-value": "valor por defecto inválido",
"hint": "Pista",
"color": "color",
"role-help": {
"p1": "¿Qué son los roles de una plantilla?",
"p2": "Empieza por especificar cada uno de los roles necesarios para completar el documento. Piensa en las partes implicadas en el proceso de firma y cuáles son sus responsabilidades. Los roles más habituales son RRHH para los documentos internos, Cliente para los contratos o Proveedor para los convenios comerciales.",
"p3": "¿Por qué preasignar usuarios a algunos roles?",
"p4": "Para los roles que consistentemente involucran al mismo individuo (por ejem., la firma del Gerente en las ofertas de empleo), puedes preasignar un usuario a un rol dentro de una plantilla. Este paso es opcional pero recomendado para la eficiencia y consistencia entre documentos",
"p5": "¿Cuando especifico el usuario asignado a cada rol?",
"p6": "Cuando crees un documento usando tu plantilla, se te pedirá que asignes usuarios a cada rol definido. Si un rol ya tiene un usuario asignado, este será rellenado con anticipación, pero puedes modificarlo según lo necesites antes de enviar el documento."
},
"add-recipients": "Agregar destinatarios",
"loading-mssg": "Esto podría llevar algún tiempo",
"send-mail": "Enviar correo",
"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-alert-5": "¿Quieres firmar documentos ahora mismo?",
"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",
"upgrade-to-customize-email": "Mejora para personalizar el correo",
"sign-url": "URL de firma",
"Mails Sent": "Correos enviados",
"document-alert": "Alerta de documento",
"owner-subscription-expired": "La suscripción del propietario ha expirado.",
"subscription-expired": "Suscripción expirada",
"alert-message": "Mensaje de alerta",
"document-decline": "Rechazar documento",
"decline-alert-1": "¿En definitiva quieres rechazar este documento?",
"decline-alert-2": "¡Has rechazado este documento!",
"decline-alert-3": "No puedes firmar este documento ya que ha sido rechazado/revocado.",
"sign here": "Firma aquí",
"guest-email-alert": "el código de verificación ha sido enviado a tu correo",
"get-otp-alert": "recibirás un código de verificación por correo",
"user-already-exist": "El usuario ya existe.",
"server-error": "¡Error interno del servidor!",
"fill-required-details!": "¡Por favor, rellena los detalles requeridos!",
"sign-up-form": "Formulario de registro",
"sign-up": "Registrarse",
"folder-already-exist!": "¡la carpeta ya existe!",
"please-select-position!": "¡por favor, selecciona una posición!",
"default-sign-alert": "¿En definitiva quieres firmar automáticamente en todos los lugares solicitados?",
"Your-Signature": "Tu firma",
"auto-sign-all": "Firmar todo automáticamente",
"generating-certificate": "Generando certificado",
"pdf-download": "Descargar PDF",
"generate-certificate-alert": " Tu certificado de finalización se está generando. Por favor, espera un momento. Si la descarga no inicia pronto, presiona el botón otra vez.",
"folder-already-exist": "¡La carpeta ya existe!",
"folder-created-successfully": "¡Carpeta creada exitosamente!",
"fill-folder-name": "Por favor, rellena el nombre de la carpeta",
"already-exists-this-username": "¡Un usuario con este nombre de usuario ya existe!",
"verification-code-sent": "¡El correo de verificación ha sido enviado a tu correo!",
"registered-user-successfully": "Usuario registrado exitosamente.",
"role-not-found": "Rol no encontrado.",
"do-not-access": "No tienes acceso, por favor, contacta al administrador.",
"add-admin": "Agregar administrador",
"opensign-setup": "Instalación de OpenSign",
"join-discord": "Únete a nuestro servidor de Discord",
"admin-already-exist": "El administrador ya existe.",
"password-update-alert-1": "Contraseña actualizada exitosamente.",
"password-update-alert-2": "Tu contraseña actual es incorrecta o falta.",
"password-update-alert-3": "Tu contraseña actual es incorrecta o falta.",
"password-update-alert-4": "Tu contraseña y su confirmación no son iguales.",
"current-password": "Contraseña actual",
"new-password": "Contraseña nueva",
"confirm-password": "Confirmar contraseña",
"file-alert-1": "El tamaño del archivo seleccionado es muy grande. Por favor, selecciona un archivo con un tamaño menor a",
"file-alert-2": "Por favor, selecciona un archivo.",
"file-alert-3": "Por favor, espera mientras se sube el documento.",
"enter-pdf-password": "Ingresa la contraseña del PDF",
"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!",
"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",
"enter-verification-code": "Ingresar código de verificación",
"provide-your-details": "Proporciona tus detalles",
"provide-email": "Por favor, proporciona un correo.",
"additional-info": "Información adicional",
"signature-saved-alert": "Firma guardada exitosamente.",
"fill-field": "Por favor, rellena este campo",
"error-template": "Error: ¡Plantilla no encontrada!",
"error": "Error",
"invalid-document": "Error: ¡Documento inválido!",
"document-not-found": "¡Documento no encontrado!",
"document-signed-alert-1": "Has firmado el documento exitosamente. Puedes descargar o imprimir una copia del documento parcialmente firmado. Una copia del documento firmado digitalmente será enviada al propietario mediante correo electrónico una vez haya sido firmado por todos los firmantes.",
"encrypted-pdf-not-support": "Los archivos PDF encriptados no están soportados actualmente.",
"document-signed-alert-4": " ¡Felicitaciones! 🎉 ¡Este documento ha sido firmado exitosamente por todos los participantes!",
"document-signed-alert-5": "Este documento ha sido firmado por todos los firmantes.",
"document-signed-alert-6": "Este documento ha sido rechazado por uno o más destinatarios.",
"document-signed-alert-7": "Este documento ya no está disponible.",
"document-signed-alert-8": "Este documento ya ha sido enviado para ser firmado.",
"yet-to-sign": "Todavía por firmar",
"validation-alert-1": "Los datos ingresados no cumplen con los criterios establecidos por la expresión regular.",
"account-already-exists": "¡La cuenta ya existe!",
"choose-password": "Elegir contraseña",
"enter-password": "Ingresar contraseña",
"sure": "claro",
"subscribed-alert": "¡Ya te has suscrito al plan!",
"monthly": "Mensual",
"yearly-upto": "Anual (hasta 66% de descuento)",
"billed-yearly": "Facturado anualmente",
"billed-monthly": "Facturado mensualmente",
"plansubscription-1": "Alójalo por tu cuenta gratuitamente",
"visit-github": "Visitar GitHub",
"user-already-exist-name": "¡Un usuario con este nombre de usuario ya existe!",
"do-not-access-contact-admin": "No tienes acceso, por favor, contacta a un administrador.",
"filed-required-correctly": "Por favor, rellena correctamente los detalles requeridos.",
"admin-created": "Administrador creado",
"invalid-masterkey": "Llave maestra inválida",
"opensign-Setup": "Instalación de OpenSign",
"master-key": " Llave maestra",
"profile-update-alert": "Perfil actualizado exitosamente.",
"date": "Fecha",
"report-not-found": "Reporte no encontrado",
"please-wait": "por favor, espera",
"select-foler": "Seleccionar carpeta",
"generated-on": "Generado el",
"text-field": "Campo de texto",
"font-size": "Tamaño de la fuente",
"widget-status": {
"Required": "Requerido",
"Optional": "Opcional"
},
"all-right": "Todos los derechos reservados",
"version": "versión",
"social-media": {
"github": "GitHub",
"linked-in": "LinkedIn",
"twitter": "Twitter",
"discord": "Discord"
},
"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 OpenSign! Si necesitas soporte siéntete libre de contactarnos.",
"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.",
"opensign-drive-3": "Ordena tus documentos por «Fecha» o «Nombre» usando este menú.",
"opensign-drive-4": "Haz clic en este menú para mostrar los documentos en vista de lista.",
"opensign-drive-5": "La lista de documentos es mostrada de acuerdo a la opción de ordenamiento seleccionada. Los íconos situados junto a cada documento indican su estado actual.",
"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": "Haz clic en los botones «Atrás», «Rechazar» o «Finalizar» para navegar por el documento. Utiliza el menú de tres puntos para ver otras opciones, como el botón «Descargar».",
"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.",
"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-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-6": "Hacer clic en «Enviar» guardará el documento. En el siguiente paso podrás personalizar los correos que serán enviados a los destinatarios o copiar los enlaces para firmar y compartirlos con los destinatarios por tu cuenta.",
"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 crear y enviar rápidamente múltiples documentos usando una plantilla existente, haz clic en el botón «Envío rápido».",
"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.",
"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.",
"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.",
"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."
},
"enter-email-plaholder": "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",
"Role": "Rol",
"additional-users": "Por favor, compra el complemento Usuarios.",
"Quantity-of-users": "Cantidad de usuarios",
"Price": "Precio",
"Total-price-for-next-time": "Precio total para la próxima vez",
"Proceed": "Proceder",
"Select": "Seleccionar",
"Add-seats": "Agregar plazas",
"user-name-exist": "el nombre de usuario ya existe",
"format": "formato",
"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.",
"additional-credits": "Por favor, compra créditos premium",
"quota-err-quicksend": "Cuota alcanzada, no tienes créditos suficientes.",
"buy-credits": "Comprar créditos premium",
"rotate-right": "Rotar a la derecha",
"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.",
"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.",
"help-api-token": "Este token puede ser usado para acceder a las API de producción en el punto de acceso «{{origin}}/api/v1». Solo puede ser generado con uno de nuestros planes de pago.",
"reason": "Razón",
"decline-by": "Rechazado/revocado por",
"document-declined": "Documento rechazado",
"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-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.",
"copy-code": "COPIAR",
"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.",
"angular-npm-mssg-1": "Para integrar OpenSign a tu proyecto Angular, simplemente ejecuta los siguientes comandos:",
"quota-mail-info-head": "Límite mensual de solicitudes de firma por correo",
"quota-mail-info": "Puedes enviar hasta 15 solicitudes de firma por correo cada mes. Mejora ahora para enviar solicitudes de firma sin límite.",
"quota-mail": "Has alcanzado tu límite de 15 solicitudes de firma por correo para este mes. Mejora ahora para continuar enviando correos.",
"quota-mail-tip": "Consejo: Todavía puedes solciitar la firma de <1>documentos ilimitados</1> compartiendo manualmente los enlaces de solicitud de firma.",
"quota-mail-head": "Cuota alcanzada",
"unauthorized-modal": "No tienes permisos para realizar esta acción, por favor, contacta a {{adminEmail}}.",
"sent-this-month": "Enviados este mes",
"available-seats": "Plazas disponibles",
"buy-users": "Comprar más usuarios",
"isenable-otp": "Habilitar verificación con OTP",
"isenable-otp-help": {
"p1": "¿Te gustaría habilitar el proceso de verificación usando una contraseña de un solo uso (OTP)?",
"p2": "Seleccionar esta opción habilitará la verificación con OTP. Los usuarios recibirán un código de verificación por correo, la cual tendrán que ingresar para poder firmar el documento.",
"p3": "Seleccionar esta opción deshabilitará la verificación con OTP, lo cual permitirá a los usuarios firmar el documento directamente sin pasos adicionales.",
"p4": "Por favor, selecciona la opción que mejor se adapte a tus requisitos para la firma de documentos."
},
"advanced-options": "Opciones avanzadas",
"hide-advanced-options": "Ocultar las opciones avanzadas",
"download-files": "Descargar archivos",
"download-pdf": "Descargar PDF",
"pdf-certificate": "Descargar PDF + Certificado",
"document-logs": "Registros de documentos",
"server-down": "No se pudo conectar al servidor de OpenSign. Si estás alojando OpenSign por tu cuenta, por favor, asegúrate de que todos los pasos de la documentación han sido seguidos correctamente. Si estás ejecutando OpenSign localmente, puedes estar accediendo a OpenSign por un número de puerto incorrecto.",
"admin-exists": "El administrador ya existe. Por favor, inicia sesión en la aplicación usando las credenciales de administrador para poder administrar los usuarios.",
"public-tour-message": "Por favor, convierte la plantilla en pública para copiar la URL pública",
"add-user-template": "Es necesario agregar un rol antes de poder agregarle campos.",
"pdf-uncompatible": "Este pdf no es compatible con opensign, comuníquese con <support@opensignlabs.com>",
"enable-tour": "Habilitar recorrido",
"istourenabled-help":{
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
},
"form-title-1": "Configuración del flujo de documentos",
"form-title-2": "Configuración de seguridad"
}
@@ -136,7 +136,8 @@
"Name": "Nom", "Name": "Nom",
"Status": "Statut", "Status": "Statut",
"created-date": "Date de création", "created-date": "Date de création",
"Type": "Saisir" "Type": "Saisir",
"Logs": "Journaux"
}, },
"btnLabel": { "btnLabel": {
"sign": "Signer", "sign": "Signer",
@@ -150,7 +151,9 @@
"Share": "Partager", "Share": "Partager",
"View": "Voir", "View": "Voir",
"option": "Option", "option": "Option",
"Embed": "Intégrer" "Embed": "Intégrer",
"Copy TemplateId": "Copier l'ID du modèle",
"Copy Public URL": "Copier l'URL publique"
}, },
"report-help": { "report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.", "Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -261,9 +264,10 @@
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?", "make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.", "make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
"public-role": "Rôle public", "public-role": "Rôle public",
"public-url": "URL publique", "public-url": "Profil public requis",
"public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.", "public-url-copy": "Voici votre URL publique : ",
"add-public-url-alert": "Veuillez ajouter votre URL publique et vous pourra rendre public modèle.", "public-url-copy-mssg": "copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
"add-public-url-alert": "Veuillez terminer la configuration de votre profil public OpenSign. Une fois cela fait, vous pourrez rendre les modèles publics. Ce processus ne doit être effectué quune seule fois.",
"share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.", "share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
"share-with": "Partager avec", "share-with": "Partager avec",
"team-not-found": "Équipe introuvable", "team-not-found": "Équipe introuvable",
@@ -571,9 +575,10 @@
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.", "pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.", "placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.",
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.", "placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
"placeholder-sign-3": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.", "placeholder-sign-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
"placeholder-sign-4": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.", "placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"placeholder-sign-5": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.", "placeholder-sign-5": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
"placeholder-sign-6": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue", "report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.", "redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
"bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.", "bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.",
@@ -626,10 +631,26 @@
"regenerate-test-token": "Régénérer le jeton de test", "regenerate-test-token": "Régénérer le jeton de test",
"help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à lun de nos forfaits payants pour générer un jeton de production.", "help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à lun de nos forfaits payants pour générer un jeton de production.",
"help-api-token": "Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.", "help-api-token": "Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
"reason": "Raison",
"decline-by": "Refusé/révoqué par",
"document-declined": "document refusé",
"public-template-mssg-1": "Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
"public-template-mssg-2": "Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
"public-template-mssg-3": "Besoin de plus de détails ou d'exemples ?",
"public-template-mssg-4": "Visitez le",
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
"public-template-mssg-6": "Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
"copy-code": "COPIE",
"copied-code": "COPIÉ",
"Installation": "Installation",
"Usage": "Usage",
"insufficient-credits": "Crédits de signature insuffisants",
"insufficient-credits-mssg": "Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
"angular-npm-mssg-1": "Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
"quota-mail-info-head": "Limite mensuelle d'e-mails de signatures de demandes", "quota-mail-info-head": "Limite mensuelle d'e-mails de signatures de demandes",
"quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.", "quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.",
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.", "quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
"quota-mail-tip": "Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.", "quota-mail-tip-tip": "Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
"quota-mail-head": "Quota atteint", "quota-mail-head": "Quota atteint",
"unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.", "unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
"sent-this-month": "envoyé ce mois-ci", "sent-this-month": "envoyé ce mois-ci",
@@ -643,5 +664,22 @@
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents." "p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
}, },
"advanced-options": "Options avancées", "advanced-options": "Options avancées",
"hide-advanced-options": "Masquer les options avancées" "hide-advanced-options": "Masquer les options avancées",
"download-files": "Télécharger des fichiers",
"download-pdf": "Télécharger le PDF",
"pdf-certificate": "Télécharger Pdf + Certificat",
"document-logs": "Journaux de documents",
"server-down": "Impossible de se connecter au serveur OpenSign. Si vous hébergez vous-même OpenSign, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez OpenSign localement, vous y accédez peut-être via un numéro de port incorrect.",
"admin-exists": "L'administrateur existe déjà. Veuillez vous connecter à l'application en utilisant les informations d'identification de l'administrateur afin de gérer les utilisateurs.",
"enable-tour": "Activer la visite",
"istourenabled-otp-help":{
"p1": "Cela permettra une visite guidée pour les signataires, fournissant des instructions pendant le processus de signature.",
"p2": "Cela désactivera la visite guidée, garantissant ainsi une expérience de signature plus rapide et ininterrompue.",
"p3": "Conseil : si vos signataires connaissent OpenSign et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
},
"form-title-1": "Paramètre du flux de documents",
"form-title-2": "Paramètre de sécurité",
"public-tour-message": "Le modèle doit être public avant que vous puissiez générer un lien partageable.",
"add-user-template": "Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
"pdf-uncompatible": "Ce pdf n'est pas compatible avec opensign, veuillez contacter <support@opensignlabs.com>"
} }
-1
View File
@@ -4,7 +4,6 @@
border: 1px solid black; border: 1px solid black;
background: white; background: white;
border-radius: 5px; border-radius: 5px;
animation: inAnimation 2s ease-in-out;
z-index: 1; z-index: 1;
} }
+1
View File
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
dotenv.config(); dotenv.config();
export const cloudServerUrl = 'http://localhost:8080/app'; export const cloudServerUrl = 'http://localhost:8080/app';
export const appName = process.env.APP_NAME || 'OpenSign™';
export function customAPIurl() { export function customAPIurl() {
const url = new URL(cloudServerUrl); const url = new URL(cloudServerUrl);
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin; return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
@@ -53,7 +53,7 @@ async function sendMail(doc, signer) {
receiver_phone: signer?.Phone || '', receiver_phone: signer?.Phone || '',
expiry_date: localExpireDate, expiry_date: localExpireDate,
company_name: doc?.ExtUserPtr?.Company || '', company_name: doc?.ExtUserPtr?.Company || '',
signing_url: `<a href=${signPdf}>Sign here</a>`, signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`,
}; };
const mail = replaceMailVaribles(subject, body, variables); const mail = replaceMailVaribles(subject, body, variables);
@@ -88,7 +88,7 @@ export default async function autoReminder(request, response) {
docQuery.exists('Signers'); docQuery.exists('Signers');
docQuery.exists('SignedUrl'); docQuery.exists('SignedUrl');
docQuery.descending('createdAt'); docQuery.descending('createdAt');
docQuery.include('Signers,AuditTrail.UserPtr,ExtUserPtr'); docQuery.include('Signers,AuditTrail.UserPtr,ExtUserPtr,ExtUserPtr.TenantId');
docQuery.notEqualTo('IsCompleted', true); docQuery.notEqualTo('IsCompleted', true);
docQuery.notEqualTo('IsDeclined', true); docQuery.notEqualTo('IsDeclined', true);
docQuery.notEqualTo('IsArchive', true); docQuery.notEqualTo('IsArchive', true);
@@ -8,6 +8,7 @@ import saveInvoice from './saveInvoice.js';
import savePayments from './savePayments.js'; import savePayments from './savePayments.js';
import gooogleauth from './googleauth.js'; import gooogleauth from './googleauth.js';
import autoReminder from './autoReminder.js'; import autoReminder from './autoReminder.js';
import validateSmtp from './validateSmtp.js';
export const app = express(); export const app = express();
dotenv.config(); dotenv.config();
@@ -22,3 +23,4 @@ app.post('/saveinvoice', saveInvoice);
app.post('/savepayment', savePayments); app.post('/savepayment', savePayments);
app.post('/googleauth', gooogleauth); app.post('/googleauth', gooogleauth);
app.post('/sendreminder', autoReminder); app.post('/sendreminder', autoReminder);
app.post('/validatesmtp', validateSmtp);
@@ -10,7 +10,6 @@ import getContact from './routes/getContact.js';
import deleteContact from './routes/deleteContact.js'; import deleteContact from './routes/deleteContact.js';
import getContactList from './routes/getContactList.js'; import getContactList from './routes/getContactList.js';
import draftDocument from './routes/draftDocument.js'; import draftDocument from './routes/draftDocument.js';
import createTemplate from './routes/createTemplate.js';
import getTemplate from './routes/getTemplate.js'; import getTemplate from './routes/getTemplate.js';
import deletedTemplate from './routes/deleteTemplate.js'; import deletedTemplate from './routes/deleteTemplate.js';
import getTemplatetList from './routes/getTemplateList.js'; import getTemplatetList from './routes/getTemplateList.js';
@@ -81,9 +80,6 @@ app.get('/documentlist/:doctype', getDocumentList);
// create Template with co-ordinate // create Template with co-ordinate
app.post('/createtemplate', createTemplatewithCoordinate); app.post('/createtemplate', createTemplatewithCoordinate);
// create Template
app.post('/drafttemplate', createTemplate);
// create Template with binary // create Template with binary
app.post('/createtemplatewithbinary', upload.array('file', 1), createTemplatewithCoordinate); app.post('/createtemplatewithbinary', upload.array('file', 1), createTemplatewithCoordinate);
@@ -91,6 +91,7 @@ export default async function createDocumentWithTemplate(request, response) {
if (totalcredits > 0) { if (totalcredits > 0) {
const templateQuery = new Parse.Query('contracts_Template'); const templateQuery = new Parse.Query('contracts_Template');
templateQuery.include('ExtUserPtr'); templateQuery.include('ExtUserPtr');
templateQuery.include('ExtUserPtr.TenantId');
const templateRes = await templateQuery.get(templateId, { useMasterKey: true }); const templateRes = await templateQuery.get(templateId, { useMasterKey: true });
if (templateRes) { if (templateRes) {
const template = JSON.parse(JSON.stringify(templateRes)); const template = JSON.parse(JSON.stringify(templateRes));
@@ -196,8 +197,13 @@ export default async function createDocumentWithTemplate(request, response) {
if (TimeToCompleteDays) { if (TimeToCompleteDays) {
object.set('TimeToCompleteDays', TimeToCompleteDays); object.set('TimeToCompleteDays', TimeToCompleteDays);
} }
const enableOTP = request.body?.enableOTP === true ? true : false; const enableOTP = request.body?.enableOTP;
const IsEnableOTP = enableOTP || template?.IsEnableOTP || false; const IsEnableOTP =
enableOTP !== undefined ? enableOTP : template?.IsEnableOTP || false;
const enableTour = request.body?.enableTour;
const isTourEnabled =
enableTour !== undefined ? enableTour : template?.IsTourEnabled || false;
object.set('IsTourEnabled', isTourEnabled);
object.set('IsEnableOTP', IsEnableOTP); object.set('IsEnableOTP', IsEnableOTP);
object.set('CreatedBy', template.CreatedBy); object.set('CreatedBy', template.CreatedBy);
object.set('ExtUserPtr', { object.set('ExtUserPtr', {
@@ -208,6 +214,9 @@ export default async function createDocumentWithTemplate(request, response) {
if (folderId) { if (folderId) {
object.set('Folder', folderPtr); object.set('Folder', folderPtr);
} }
if (template?.FileAdapterId) {
object.set('FileAdapterId', template?.FileAdapterId);
}
const newACL = new Parse.ACL(); const newACL = new Parse.ACL();
newACL.setPublicReadAccess(false); newACL.setPublicReadAccess(false);
newACL.setPublicWriteAccess(false); newACL.setPublicWriteAccess(false);
@@ -268,7 +277,7 @@ export default async function createDocumentWithTemplate(request, response) {
orgName + orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate + localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a target=_blank href=" +
signPdf + signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender + sender +
@@ -315,6 +324,7 @@ export default async function createDocumentWithTemplate(request, response) {
from: sender, from: sender,
html: html, html: html,
extUserId: template.ExtUserPtr.objectId, extUserId: template.ExtUserPtr.objectId,
mailProvider: template?.ExtUserPtr?.active_mail_adapter || '',
}; };
sendMail = await axios.post(url, params, { headers: headers }); sendMail = await axios.post(url, params, { headers: headers });
} catch (error) { } catch (error) {
@@ -8,6 +8,7 @@ import {
sanitizeFileName, sanitizeFileName,
cloudServerUrl, cloudServerUrl,
} from '../../../../Utils.js'; } from '../../../../Utils.js';
import uploadFileToS3 from '../../../parsefunction/uploadFiletoS3.js';
// `sendDoctoWebhook` is used to send res data of document on webhook // `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) { async function sendDoctoWebhook(doc, WebhookUrl, userId) {
@@ -64,6 +65,7 @@ export default async function createDocumentwithCoordinate(request, response) {
const sendInOrder = request.body.sendInOrder || false; const sendInOrder = request.body.sendInOrder || false;
const TimeToCompleteDays = request.body.timeToCompleteDays || 15; const TimeToCompleteDays = request.body.timeToCompleteDays || 15;
const IsEnableOTP = request.body?.enableOTP === true ? true : false; const IsEnableOTP = request.body?.enableOTP === true ? true : false;
const isTourEnabled = request.body?.enableTour || false;
// console.log('fileData ', fileData); // console.log('fileData ', fileData);
const protocol = customAPIurl(); const protocol = customAPIurl();
const baseUrl = new URL(process.env.PUBLIC_URL); const baseUrl = new URL(process.env.PUBLIC_URL);
@@ -87,6 +89,7 @@ export default async function createDocumentwithCoordinate(request, response) {
}; };
const extUsers = new Parse.Query('contracts_Users'); const extUsers = new Parse.Query('contracts_Users');
extUsers.equalTo('UserId', userPtr); extUsers.equalTo('UserId', userPtr);
extUsers.include('TenantId');
const extUser = await extUsers.first({ useMasterKey: true }); const extUser = await extUsers.first({ useMasterKey: true });
const subscription = new Parse.Query('contracts_Subscriptions'); const subscription = new Parse.Query('contracts_Subscriptions');
@@ -114,6 +117,7 @@ export default async function createDocumentwithCoordinate(request, response) {
.status(400) .status(400)
.json({ error: 'Please add at least one signature widget for all signers' }); .json({ error: 'Please add at least one signature widget for all signers' });
} }
const parseExtUser = JSON.parse(JSON.stringify(extUser));
let fileUrl; let fileUrl;
if (request.files?.[0]) { if (request.files?.[0]) {
const base64 = fileData?.toString('base64'); const base64 = fileData?.toString('base64');
@@ -126,23 +130,29 @@ export default async function createDocumentwithCoordinate(request, response) {
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const filename = sanitizeFileName(`${name}.pdf`); const filename = sanitizeFileName(`${name}.pdf`);
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf'); let adapter = {};
await file.save({ useMasterKey: true }); const ActiveFileAdapter = parseExtUser?.TenantId?.ActiveFileAdapter || '';
fileUrl = file.url(); if (ActiveFileAdapter) {
adapter =
parseExtUser?.TenantId?.FileAdapters?.find(x => (x.id = ActiveFileAdapter)) || {};
}
if (adapter?.id) {
const filedata = Buffer.from(base64File, 'base64');
// `uploadFileToS3` is used to save document in user's file storage
fileUrl = await uploadFileToS3(filedata, filename, 'application/pdf', adapter);
} else {
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
await file.save({ useMasterKey: true });
fileUrl = file.url();
}
const buffer = Buffer.from(base64File, 'base64'); const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
const extUser = await contractsUser.first({ useMasterKey: true });
const parseExtUser = JSON.parse(JSON.stringify(extUser));
const extUserPtr = { const extUserPtr = {
__type: 'Pointer', __type: 'Pointer',
className: 'contracts_Users', className: 'contracts_Users',
objectId: extUser.id, objectId: extUser.id,
}; };
const object = new Parse.Object('contracts_Document'); const object = new Parse.Object('contracts_Document');
object.set('Name', name); object.set('Name', name);
@@ -164,7 +174,11 @@ export default async function createDocumentwithCoordinate(request, response) {
object.set('TimeToCompleteDays', TimeToCompleteDays); object.set('TimeToCompleteDays', TimeToCompleteDays);
} }
object.set('IsEnableOTP', IsEnableOTP); object.set('IsEnableOTP', IsEnableOTP);
object.set('IsTourEnabled', isTourEnabled);
object.set('IsSendMail', send_email); object.set('IsSendMail', send_email);
if (parseExtUser?.TenantId?.ActiveFileAdapter) {
object.set('FileAdapterId', parseExtUser?.TenantId?.ActiveFileAdapter);
}
let contact = []; let contact = [];
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let parseSigners; let parseSigners;
@@ -325,7 +339,7 @@ export default async function createDocumentwithCoordinate(request, response) {
orgName + orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate + localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a target=_blank href=" +
signPdf + signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender + sender +
@@ -371,6 +385,7 @@ export default async function createDocumentwithCoordinate(request, response) {
from: sender, from: sender,
html: html, html: html,
extUserId: extUser.id, extUserId: extUser.id,
mailProvider: parseExtUser?.active_mail_adapter || '',
}; };
await axios.post(url, params, { headers: headers }); await axios.post(url, params, { headers: headers });
@@ -1,140 +0,0 @@
import { saveFileUsage } from '../../../../Utils.js';
export default async function createTemplate(request, response) {
const name = request.body?.title;
const note = request.body?.note;
const description = request.body?.description;
const signers = request.body?.signers;
const folderId = request.body?.folderId;
const base64File = request.body.file;
const SendinOrder = request.body.sendInOrder || false;
const fileData = request.files?.[0] ? request.files[0].buffer : null;
const baseUrl = new URL(process.env.PUBLIC_URL);
const isEnableOTP = request.body?.enableOTP === true ? true : false;
try {
const reqToken = request.headers['x-api-token'];
if (!reqToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
}
const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', reqToken);
tokenQuery.include('userId');
const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) {
// Valid Token then proceed request
const parseUser = JSON.parse(JSON.stringify(token));
const userPtr = {
__type: 'Pointer',
className: '_User',
objectId: parseUser.userId.objectId,
};
let fileUrl;
if (base64File) {
const file = new Parse.File(`${name}.pdf`, {
base64: base64File,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
const extUser = await contractsUser.first({ useMasterKey: true });
const extUserPtr = { __type: 'Pointer', className: 'contracts_Users', objectId: extUser.id };
const folderPtr = { __type: 'Pointer', className: 'contracts_Template', objectId: folderId };
const object = new Parse.Object('contracts_Template');
object.set('Name', name);
if (note) {
object.set('Note', note);
}
if (description) {
object.set('Description', description);
}
object.set('URL', fileUrl);
object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr);
object.set('IsEnableOTP', isEnableOTP);
if (SendinOrder) {
object.set('SendinOrder', SendinOrder);
}
if (signers && signers.length > 0) {
let parseSigners;
if (base64File) {
parseSigners = signers;
} else {
parseSigners = JSON.parse(signers);
}
let createContactUrl = protocol + '/v1/createcontact';
let contact = [];
for (const obj of parseSigners) {
const body = {
name: obj?.name || '',
email: obj?.email || '',
phone: obj?.phone || '',
};
try {
const res = await axios.post(createContactUrl, body, {
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
});
// console.log('res ', res.data);
contact.push({
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: res.data?.objectId,
});
} catch (err) {
// console.log('err ', err.response);
if (err?.response?.data?.objectId) {
contact.push({
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: err.response.data?.objectId,
});
}
}
}
object.set('Signers', contact);
}
if (folderId) {
object.set('Folder', folderPtr);
}
const newACL = new Parse.ACL();
newACL.setPublicReadAccess(false);
newACL.setPublicWriteAccess(false);
newACL.setReadAccess(userPtr.objectId, true);
newACL.setWriteAccess(userPtr.objectId, true);
object.setACL(newACL);
const res = await object.save(null, { useMasterKey: true });
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_draft_template',
properties: { response_code: 200 },
});
}
return response.json({
objectId: res.id,
url: baseUrl.origin + '/template/' + res.id,
});
} else {
return response.status(405).json({ error: 'Invalid API Token!' });
}
} catch (err) {
console.log('err ', err);
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
}
}
@@ -6,6 +6,7 @@ import {
formatWidgetOptions, formatWidgetOptions,
sanitizeFileName, sanitizeFileName,
} from '../../../../Utils.js'; } from '../../../../Utils.js';
import uploadFileToS3 from '../../../parsefunction/uploadFiletoS3.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000); const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function createTemplatewithCoordinate(request, response) { export default async function createTemplatewithCoordinate(request, response) {
@@ -18,6 +19,7 @@ export default async function createTemplatewithCoordinate(request, response) {
const fileData = request.files?.[0] ? request.files[0].buffer : null; const fileData = request.files?.[0] ? request.files[0].buffer : null;
const SendinOrder = request.body.sendInOrder || false; const SendinOrder = request.body.sendInOrder || false;
const isEnableOTP = request.body?.enableOTP === true ? true : false; const isEnableOTP = request.body?.enableOTP === true ? true : false;
const isTourEnabled = request.body?.enableTour || false;
// console.log('fileData ', fileData); // console.log('fileData ', fileData);
const protocol = customAPIurl(); const protocol = customAPIurl();
@@ -40,6 +42,11 @@ export default async function createTemplatewithCoordinate(request, response) {
objectId: parseUser.userId.objectId, objectId: parseUser.userId.objectId,
}; };
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
contractsUser.include('TenantId');
const extUser = await contractsUser.first({ useMasterKey: true });
const parseExtUser = JSON.parse(JSON.stringify(extUser));
let fileUrl; let fileUrl;
if (request.files?.[0]) { if (request.files?.[0]) {
const base64 = fileData?.toString('base64'); const base64 = fileData?.toString('base64');
@@ -52,24 +59,31 @@ export default async function createTemplatewithCoordinate(request, response) {
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const filename = sanitizeFileName(`${name}.pdf`); const filename = sanitizeFileName(`${name}.pdf`);
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf'); let adapter = {};
await file.save({ useMasterKey: true }); const ActiveFileAdapter = parseExtUser?.TenantId?.ActiveFileAdapter || '';
fileUrl = file.url(); if (ActiveFileAdapter) {
adapter =
parseExtUser?.TenantId?.FileAdapters?.find(x => (x.id = ActiveFileAdapter)) || {};
}
if (adapter?.id) {
const filedata = Buffer.from(base64File, 'base64');
// `uploadFileToS3` is used to save document in user's file storage
fileUrl = await uploadFileToS3(filedata, filename, 'application/pdf', adapter);
} else {
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
await file.save({ useMasterKey: true });
fileUrl = file.url();
}
const buffer = Buffer.from(base64File, 'base64'); const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
const extUser = await contractsUser.first({ useMasterKey: true });
const extUserPtr = { const extUserPtr = {
__type: 'Pointer', __type: 'Pointer',
className: 'contracts_Users', className: 'contracts_Users',
objectId: extUser.id, objectId: extUser.id,
}; };
const object = new Parse.Object('contracts_Template'); const object = new Parse.Object('contracts_Template');
object.set('Name', name); object.set('Name', name);
if (note) { if (note) {
object.set('Note', note); object.set('Note', note);
} }
@@ -83,6 +97,7 @@ export default async function createTemplatewithCoordinate(request, response) {
object.set('CreatedBy', userPtr); object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr); object.set('ExtUserPtr', extUserPtr);
object.set('IsEnableOTP', isEnableOTP); object.set('IsEnableOTP', isEnableOTP);
object.set('IsTourEnabled', isTourEnabled);
let contact = []; let contact = [];
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let parseSigners; let parseSigners;
@@ -186,6 +201,9 @@ export default async function createTemplatewithCoordinate(request, response) {
objectId: folderId, objectId: folderId,
}); });
} }
if (parseExtUser?.TenantId?.ActiveFileAdapter) {
object.set('FileAdapterId', parseExtUser?.TenantId?.ActiveFileAdapter);
}
const newACL = new Parse.ACL(); const newACL = new Parse.ACL();
newACL.setPublicReadAccess(false); newACL.setPublicReadAccess(false);
newACL.setPublicWriteAccess(false); newACL.setPublicWriteAccess(false);
@@ -20,6 +20,7 @@ export default async function deleteDocument(request, response) {
const Document = new Parse.Query('contracts_Document'); const Document = new Parse.Query('contracts_Document');
Document.equalTo('objectId', request.params.document_id); Document.equalTo('objectId', request.params.document_id);
Document.equalTo('CreatedBy', userPtr); Document.equalTo('CreatedBy', userPtr);
Document.include('ExtUserPtr.TenantId');
const res = await Document.first({ useMasterKey: true }); const res = await Document.first({ useMasterKey: true });
if (res) { if (res) {
const isArchive = res.get('IsArchive'); const isArchive = res.get('IsArchive');
@@ -22,6 +22,7 @@ export default async function deleteFolder(request, response) {
obj.equalTo('objectId', folderId); obj.equalTo('objectId', folderId);
obj.equalTo('IsArchive', true); obj.equalTo('IsArchive', true);
obj.equalTo('CreatedBy', userPtr); obj.equalTo('CreatedBy', userPtr);
obj.include('ExtUserPtr.TenantId');
const isFolderDeleted = await obj.first({ useMasterKey: true }); const isFolderDeleted = await obj.first({ useMasterKey: true });
if (!isFolderDeleted) { if (!isFolderDeleted) {
const folder = new Parse.Query('contracts_Document'); const folder = new Parse.Query('contracts_Document');
@@ -34,6 +35,7 @@ export default async function deleteFolder(request, response) {
folder.notEqualTo('IsArchive', true); folder.notEqualTo('IsArchive', true);
folder.equalTo('CreatedBy', userPtr); folder.equalTo('CreatedBy', userPtr);
folder.equalTo('Type', 'Folder'); folder.equalTo('Type', 'Folder');
folder.include('ExtUserPtr.TenantId');
const isSubItems = await folder.first({ useMasterKey: true }); const isSubItems = await folder.first({ useMasterKey: true });
// console.log('isSubItems ', isSubItems); // console.log('isSubItems ', isSubItems);
if (isSubItems) { if (isSubItems) {
@@ -19,6 +19,7 @@ export default async function deletedTemplate(request, response) {
const template = new Parse.Query('contracts_Template'); const template = new Parse.Query('contracts_Template');
template.equalTo('objectId', request.params.template_id); template.equalTo('objectId', request.params.template_id);
template.equalTo('CreatedBy', userPtr); template.equalTo('CreatedBy', userPtr);
template.include('ExtUserPtr.TenantId');
const res = await template.first({ useMasterKey: true }); const res = await template.first({ useMasterKey: true });
if (res) { if (res) {
const isArchive = res.get('IsArchive'); const isArchive = res.get('IsArchive');
@@ -13,6 +13,7 @@ export default async function draftDocument(request, response) {
const fileData = request.files?.[0] ? request.files[0].buffer : null; const fileData = request.files?.[0] ? request.files[0].buffer : null;
const SendinOrder = request.body.sendInOrder || false; const SendinOrder = request.body.sendInOrder || false;
const isEnableOTP = request.body?.enableOTP === true ? true : false; const isEnableOTP = request.body?.enableOTP === true ? true : false;
const isTourEnabled = request.body?.enableTour || false;
// console.log('fileData ', fileData); // console.log('fileData ', fileData);
const protocol = customAPIurl(); const protocol = customAPIurl();
@@ -86,6 +87,7 @@ export default async function draftDocument(request, response) {
object.set('ExtUserPtr', extUserPtr); object.set('ExtUserPtr', extUserPtr);
object.set('IsSendMail', send_email); object.set('IsSendMail', send_email);
object.set('IsEnableOTP', isEnableOTP); object.set('IsEnableOTP', isEnableOTP);
object.set('IsTourEnabled', isTourEnabled);
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let parseSigners; let parseSigners;
if (base64File) { if (base64File) {
@@ -24,6 +24,7 @@ export default async function getDocument(request, response) {
Document.include('Folder'); Document.include('Folder');
Document.include('ExtUserPtr'); Document.include('ExtUserPtr');
Document.include('Placeholders.signerPtr'); Document.include('Placeholders.signerPtr');
Document.include('ExtUserPtr.TenantId');
const res = await Document.first({ useMasterKey: true }); const res = await Document.first({ useMasterKey: true });
if (res) { if (res) {
const document = JSON.parse(JSON.stringify(res)); const document = JSON.parse(JSON.stringify(res));
@@ -65,6 +66,7 @@ export default async function getDocument(request, response) {
enableOTP: document?.IsEnableOTP || false, enableOTP: document?.IsEnableOTP || false,
createdAt: document.createdAt, createdAt: document.createdAt,
updatedAt: document.updatedAt, updatedAt: document.updatedAt,
enableTour: document?.IsTourEnabled || false,
}); });
} else { } else {
if (request.posthog) { if (request.posthog) {
@@ -62,7 +62,7 @@ export default async function getDocumentList(request, response) {
'X-Parse-Application-Id': appId, 'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': process.env.MASTER_KEY, 'X-Parse-Master-Key': process.env.MASTER_KEY,
}; };
const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys},Placeholders&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr,Placeholders.signerPtr`; const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys},Placeholders&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr,Placeholders.signerPtr,ExtUserPtr.TenantId`;
try { try {
const res = await axios.get(url, { headers: headers }); const res = await axios.get(url, { headers: headers });
if (res.data && res.data.results.length > 0) { if (res.data && res.data.results.length > 0) {
@@ -103,6 +103,7 @@ export default async function getDocumentList(request, response) {
enableOTP: x?.IsEnableOTP || false, enableOTP: x?.IsEnableOTP || false,
createdAt: x.createdAt, createdAt: x.createdAt,
updatedAt: x.updatedAt, updatedAt: x.updatedAt,
enableTour: x?.IsTourEnabled || false,
})); }));
return response.json({ result: updateRes }); return response.json({ result: updateRes });
} else { } else {
@@ -25,7 +25,7 @@ export default async function getFolder(request, response) {
folderCls.notEqualTo('IsArchive', true); folderCls.notEqualTo('IsArchive', true);
folderCls.descending('createdAt'); folderCls.descending('createdAt');
folderCls.include('Folder'); folderCls.include('Folder');
folderCls.include('ExtUserPtr.TenantId');
const res = await folderCls.first({ useMasterKey: true }); const res = await folderCls.first({ useMasterKey: true });
if (res) { if (res) {
const parseRes = JSON.parse(JSON.stringify(res)); const parseRes = JSON.parse(JSON.stringify(res));
@@ -35,6 +35,7 @@ export default async function getFolderList(request, response) {
folderCls.doesNotExist('Folder'); folderCls.doesNotExist('Folder');
} }
folderCls.descending('createdAt'); folderCls.descending('createdAt');
folderCls.include('ExtUserPtr.TenantId');
folderCls.limit(limit); folderCls.limit(limit);
folderCls.skip(skip); folderCls.skip(skip);
const res = await folderCls.find({ useMasterKey: true }); const res = await folderCls.find({ useMasterKey: true });
@@ -25,6 +25,7 @@ export default async function getTemplate(request, response) {
Template.include('Folder'); Template.include('Folder');
Template.include('ExtUserPtr'); Template.include('ExtUserPtr');
Template.include('Placeholders.signerPtr'); Template.include('Placeholders.signerPtr');
Template.include('ExtUserPtr.TenantId');
const res = await Template.first({ useMasterKey: true }); const res = await Template.first({ useMasterKey: true });
if (res) { if (res) {
const template = JSON.parse(JSON.stringify(res)); const template = JSON.parse(JSON.stringify(res));
@@ -64,6 +65,7 @@ export default async function getTemplate(request, response) {
enableOTP: template?.IsEnableOTP || false, enableOTP: template?.IsEnableOTP || false,
createdAt: template.createdAt, createdAt: template.createdAt,
updatedAt: template.updatedAt, updatedAt: template.updatedAt,
enableTour: template?.IsTourEnabled || false,
}); });
} else { } else {
if (request.posthog) { if (request.posthog) {
@@ -53,7 +53,7 @@ export default async function getTemplatetList(request, response) {
'X-Parse-Application-Id': appId, 'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': process.env.MASTER_KEY, 'X-Parse-Master-Key': process.env.MASTER_KEY,
}; };
const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys}&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr,Placeholders.signerPtr`; const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys}&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr,Placeholders.signerPtr,ExtUserPtr.TenantId`;
const res = await axios.get(url, { headers: headers }); const res = await axios.get(url, { headers: headers });
if (res.data && res.data.results.length > 0) { if (res.data && res.data.results.length > 0) {
if (request.posthog) { if (request.posthog) {
@@ -91,6 +91,7 @@ export default async function getTemplatetList(request, response) {
enableOTP: template?.IsEnableOTP || false, enableOTP: template?.IsEnableOTP || false,
createdAt: template.createdAt, createdAt: template.createdAt,
updatedAt: template.updatedAt, updatedAt: template.updatedAt,
enableTour: template?.IsTourEnabled || false,
})); }));
return response.json({ result: updateRes }); return response.json({ result: updateRes });
@@ -35,12 +35,13 @@ export default async function resendMail(request, response) {
docQuery.notEqualTo('IsArchive', true); docQuery.notEqualTo('IsArchive', true);
docQuery.greaterThanOrEqualTo('ExpiryDate', new Date()); docQuery.greaterThanOrEqualTo('ExpiryDate', new Date());
docQuery.exists('SignedUrl'); docQuery.exists('SignedUrl');
docQuery.include('ExtUserPtr.TenantId');
const resDoc = await docQuery.first({ useMasterKey: true }); const resDoc = await docQuery.first({ useMasterKey: true });
// console.log("resDoc ",resDoc) // console.log("resDoc ",resDoc)
if (resDoc) { if (resDoc) {
const _resDoc = resDoc.toJSON(); const _resDoc = resDoc.toJSON();
const contact = _resDoc.Signers.find(x => x.Email === userMail); const contact = _resDoc.Signers.find(x => x.Email === userMail);
const activeMailAdapter = _resDoc?.ExtUserPtr?.active_mail_adapter || '';
if (contact) { if (contact) {
try { try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
@@ -85,7 +86,7 @@ export default async function resendMail(request, response) {
orgName + orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate + localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a target=_blank href=" +
signPdf + signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender + sender +
@@ -131,6 +132,7 @@ export default async function resendMail(request, response) {
from: sender, from: sender,
html: html, html: html,
extUserId: _resDoc.ExtUserPtr.objectId, extUserId: _resDoc.ExtUserPtr.objectId,
mailProvider: activeMailAdapter,
}; };
const res = await axios.post(url, params, { headers: headers }); const res = await axios.post(url, params, { headers: headers });
@@ -10,7 +10,7 @@ export default async function updateDocument(request, response) {
const token = await tokenQuery.first({ useMasterKey: true }); const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) { if (token !== undefined) {
// Valid Token then proceed request // Valid Token then proceed request
const allowedKeys = ['name', 'note', 'description', 'folderId', 'enableOTP']; const allowedKeys = ['name', 'note', 'description', 'folderId', 'enableOTP', 'enableTour'];
const objectKeys = Object.keys(request.body); const objectKeys = Object.keys(request.body);
const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0; const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
const parseUser = JSON.parse(JSON.stringify(token)); const parseUser = JSON.parse(JSON.stringify(token));
@@ -23,6 +23,7 @@ export default async function updateDocument(request, response) {
const document = new Parse.Query('contracts_Document'); const document = new Parse.Query('contracts_Document');
document.equalTo('objectId', request.params.document_id); document.equalTo('objectId', request.params.document_id);
document.equalTo('CreatedBy', userPtr); document.equalTo('CreatedBy', userPtr);
document.include('ExtUserPtr.TenantId');
const res = await document.first({ useMasterKey: true }); const res = await document.first({ useMasterKey: true });
if (res) { if (res) {
const isArchive = res.get('IsArchive'); const isArchive = res.get('IsArchive');
@@ -48,10 +49,12 @@ export default async function updateDocument(request, response) {
objectId: request?.body?.folderId, objectId: request?.body?.folderId,
}); });
} }
if (request.body?.enableOTP !== undefined) {
if (request.body?.enableOTP) {
updateQuery.set('IsEnableOTP', request.body?.enableOTP); updateQuery.set('IsEnableOTP', request.body?.enableOTP);
} }
if (request.body?.enableTour !== undefined) {
updateQuery.set('IsTourEnabled', request.body?.enableTour);
}
const updatedRes = await updateQuery.save(null, { useMasterKey: true }); const updatedRes = await updateQuery.save(null, { useMasterKey: true });
if (updatedRes) { if (updatedRes) {
if (request.posthog) { if (request.posthog) {
@@ -25,7 +25,7 @@ export default async function updateFolder(request, response) {
folderCls.equalTo('objectId', folderId); folderCls.equalTo('objectId', folderId);
folderCls.equalTo('Type', 'Folder'); folderCls.equalTo('Type', 'Folder');
folderCls.notEqualTo('IsArchive', true); folderCls.notEqualTo('IsArchive', true);
folderCls.include('ExtUserPtr.TenantId');
const res = await folderCls.first({ useMasterKey: true }); const res = await folderCls.first({ useMasterKey: true });
if (res) { if (res) {
const updateFolder = new Parse.Object('contracts_Document'); const updateFolder = new Parse.Object('contracts_Document');
@@ -10,7 +10,7 @@ export default async function updateTemplate(request, response) {
const token = await tokenQuery.first({ useMasterKey: true }); const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) { if (token !== undefined) {
// Valid Token then proceed request // Valid Token then proceed request
const allowedKeys = ['name', 'note', 'description', 'folderId', 'enableOTP']; const allowedKeys = ['name', 'note', 'description', 'folderId', 'enableOTP', 'enableTour'];
const objectKeys = Object.keys(request.body); const objectKeys = Object.keys(request.body);
const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0; const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
const parseUser = JSON.parse(JSON.stringify(token)); const parseUser = JSON.parse(JSON.stringify(token));
@@ -23,6 +23,7 @@ export default async function updateTemplate(request, response) {
const template = new Parse.Query('contracts_Template'); const template = new Parse.Query('contracts_Template');
template.equalTo('objectId', request.params.template_id); template.equalTo('objectId', request.params.template_id);
template.equalTo('CreatedBy', userPtr); template.equalTo('CreatedBy', userPtr);
template.include('ExtUserPtr.TenantId');
const res = await template.first({ useMasterKey: true }); const res = await template.first({ useMasterKey: true });
if (res) { if (res) {
const isArchive = res.get('IsArchive'); const isArchive = res.get('IsArchive');
@@ -48,9 +49,12 @@ export default async function updateTemplate(request, response) {
objectId: request?.body?.folderId, objectId: request?.body?.folderId,
}); });
} }
if (request.body?.enableOTP) { if (request.body?.enableOTP !== undefined) {
updateQuery.set('IsEnableOTP', request.body?.enableOTP); updateQuery.set('IsEnableOTP', request.body?.enableOTP);
} }
if (request.body?.enableTour !== undefined) {
updateQuery.set('IsTourEnabled', request.body?.enableTour);
}
const updatedRes = await updateQuery.save(null, { useMasterKey: true }); const updatedRes = await updateQuery.save(null, { useMasterKey: true });
if (updatedRes) { if (updatedRes) {
if (request.posthog) { if (request.posthog) {
@@ -0,0 +1,72 @@
import { createTransport } from 'nodemailer';
import { appName } from '../../Utils.js';
// Online Javascript Editor for free
// Write, Edit and Run your Javascript code using JS Online Compiler
function generateOtpWithSum10() {
let otp = [];
let sum = 0;
// First, generate 3 random digits (0-9) and add them to the sum
for (let i = 0; i < 3; i++) {
let digit = Math.floor(Math.random() * 10);
otp.push(digit);
sum += digit;
}
// Calculate the last digit to ensure the sum equals 10
let lastDigit = 10 - sum;
// If the last digit is not valid (e.g., greater than 9), regenerate
if (lastDigit >= 0 && lastDigit <= 9) {
otp.push(lastDigit);
} else {
return generateOtpWithSum10(); // Recursively generate again
}
return otp.join('');
}
export default async function validateSmtp(request, response) {
const host = request.body.host;
const port = request.body?.port?.toString();
const username = request.body.username;
const password = request.body.password;
const secure = request.body?.secure;
const email = request.body?.email;
const otp = generateOtpWithSum10();
// console.log('Generated OTP:', otp);
try {
const smtpsecure = secure || port !== '465' ? false : true;
const transporterSMTP = createTransport({
host: host,
port: port,
secure: smtpsecure,
auth: { user: username, pass: password },
});
const from = appName;
const mailsender = username;
const messageParams = {
from: from + ' <' + mailsender + '>',
to: email,
subject: `Your ${appName} SMTP credentials verification code`,
text: 'mail',
html:
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>SMTP Verification Code</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your verification code is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
otp +
'</p></div> </div> </div></body></html>',
};
const res = await transporterSMTP.sendMail(messageParams);
console.log('custom smtp transporter res: ', res?.response);
if (!res.err) {
response.status(200).json({ message: 'success' });
}
} catch (err) {
console.log('err ', err);
const code = err?.responseCode || 400;
const message = err?.response || 'failed!';
response.status(code).json({ error: message });
}
}
+14
View File
@@ -68,6 +68,13 @@ import BuyCredits from './parsefunction/BuyCredits.js';
import getContact from './parsefunction/getContact.js'; import getContact from './parsefunction/getContact.js';
import updateContactTour from './parsefunction/updateContactTour.js'; import updateContactTour from './parsefunction/updateContactTour.js';
import declinedocument from './parsefunction/declinedocument.js'; import declinedocument from './parsefunction/declinedocument.js';
import addcustomsmtp from './parsefunction/addcustomsmtp.js';
import deactivateMailAdapter from './parsefunction/deactivateMailAdapter.js';
import addFileAdapter from './parsefunction/addFileAdapter.js';
import saveToFileAdapter from './parsefunction/saveToFileAdapter.js';
import getFileAdapter from './parsefunction/getFileAdapter.js';
import addPfxFile from './parsefunction/addPfxFile.js';
import getPfxFile from './parsefunction/getPfxFile.js';
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic. // This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
@@ -148,3 +155,10 @@ Parse.Cloud.define('buycredits', BuyCredits);
Parse.Cloud.define('getcontact', getContact); Parse.Cloud.define('getcontact', getContact);
Parse.Cloud.define('updatecontacttour', updateContactTour); Parse.Cloud.define('updatecontacttour', updateContactTour);
Parse.Cloud.define('declinedoc', declinedocument); Parse.Cloud.define('declinedoc', declinedocument);
Parse.Cloud.define('addsmtp', addcustomsmtp);
Parse.Cloud.define('deactivatemailadapter', deactivateMailAdapter);
Parse.Cloud.define('savetofileadapter', saveToFileAdapter);
Parse.Cloud.define('addfileadapter', addFileAdapter);
Parse.Cloud.define('getfileadapter', getFileAdapter);
Parse.Cloud.define('addpfx', addPfxFile);
Parse.Cloud.define('getpfx', getPfxFile);
@@ -2,21 +2,30 @@ import { useLocal } from '../../Utils.js';
import getPresignedUrl from './getSignedUrl.js'; import getPresignedUrl from './getSignedUrl.js';
async function DocumentAfterFind(request) { async function DocumentAfterFind(request) {
if (useLocal !== 'true') { if (request.objects.length === 1) {
if (request.objects.length === 1) { if (request.objects) {
if (request.objects) { const obj = request.objects[0];
const obj = request.objects[0]; const FileAdapterId = obj?.get('FileAdapterId') || '';
if (FileAdapterId || useLocal !== 'true') {
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl'); const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
const Url = obj?.get('URL') && obj?.get('URL'); const Url = obj?.get('URL') && obj?.get('URL');
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl'); const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
let fileAdapter = {};
if (FileAdapterId) {
const tenantId = obj?.get('ExtUserPtr')?.get('TenantId');
if (tenantId) {
const _tenantId = JSON.parse(JSON.stringify(obj?.get('ExtUserPtr')?.get('TenantId')));
fileAdapter = _tenantId?.FileAdapters?.find(x => x.id === FileAdapterId) || {};
}
}
if (SignedUrl) { if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl)); obj.set('SignedUrl', getPresignedUrl(SignedUrl, fileAdapter));
} }
if (Url) { if (Url) {
obj.set('URL', getPresignedUrl(Url)); obj.set('URL', getPresignedUrl(Url, fileAdapter));
} }
if (certificateUrl) { if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl)); obj.set('CertificateUrl', getPresignedUrl(certificateUrl, fileAdapter));
} }
return [obj]; return [obj];
} }
@@ -12,6 +12,7 @@ async function DocumentAftersave(request) {
const ExpiryDate = new Date(createdAt); const ExpiryDate = new Date(createdAt);
ExpiryDate.setDate(ExpiryDate.getDate() + TimeToCompleteDays); ExpiryDate.setDate(ExpiryDate.getDate() + TimeToCompleteDays);
const documentQuery = new Parse.Query('contracts_Document'); const documentQuery = new Parse.Query('contracts_Document');
documentQuery.include('ExtUserPtr.TenantId');
const updateQuery = await documentQuery.get(request.object.id, { useMasterKey: true }); const updateQuery = await documentQuery.get(request.object.id, { useMasterKey: true });
updateQuery.set('ExpiryDate', ExpiryDate); updateQuery.set('ExpiryDate', ExpiryDate);
if (!originIp) { if (!originIp) {
@@ -30,6 +31,7 @@ async function DocumentAftersave(request) {
const ExpiryDate = new Date(createdAt); const ExpiryDate = new Date(createdAt);
ExpiryDate.setDate(ExpiryDate.getDate() + TimeToCompleteDays); ExpiryDate.setDate(ExpiryDate.getDate() + TimeToCompleteDays);
const documentQuery = new Parse.Query('contracts_Document'); const documentQuery = new Parse.Query('contracts_Document');
documentQuery.include('ExtUserPtr.TenantId');
const updateQuery = await documentQuery.get(request.object.id, { useMasterKey: true }); const updateQuery = await documentQuery.get(request.object.id, { useMasterKey: true });
updateQuery.set('ExpiryDate', ExpiryDate); updateQuery.set('ExpiryDate', ExpiryDate);
if (!originIp) { if (!originIp) {
@@ -76,6 +78,7 @@ async function DocumentAftersave(request) {
// console.log(objId) // console.log(objId)
const Query = new Parse.Query('contracts_Document'); const Query = new Parse.Query('contracts_Document');
Query.include('Signers'); Query.include('Signers');
Query.include('ExtUserPtr.TenantId');
Query.include('CreatedBy'); Query.include('CreatedBy');
const updateACL = await Query.get(objId, { useMasterKey: true }); const updateACL = await Query.get(objId, { useMasterKey: true });
const res = JSON.parse(JSON.stringify(updateACL)); const res = JSON.parse(JSON.stringify(updateACL));
@@ -116,6 +119,7 @@ async function DocumentAftersave(request) {
// console.log(objId) // console.log(objId)
const Query = new Parse.Query('contracts_Document'); const Query = new Parse.Query('contracts_Document');
Query.include('CreatedBy'); Query.include('CreatedBy');
Query.include('ExtUserPtr.TenantId');
const updateACL = await Query.get(objId, { useMasterKey: true }); const updateACL = await Query.get(objId, { useMasterKey: true });
const res = JSON.parse(JSON.stringify(updateACL)); const res = JSON.parse(JSON.stringify(updateACL));
// console.log("res"); // console.log("res");
@@ -20,6 +20,7 @@ async function GetPublicTemplate(request) {
templatQuery.descending('updatedAt'); templatQuery.descending('updatedAt');
templatQuery.equalTo('IsPublic', true); templatQuery.equalTo('IsPublic', true);
templatQuery.notEqualTo('IsArchive', true); templatQuery.notEqualTo('IsArchive', true);
templatQuery.include('ExtUserPtr.TenantId');
const getTemplate = await templatQuery.find({ useMasterKey: true }); const getTemplate = await templatQuery.find({ useMasterKey: true });
const extcls = new Parse.Query('contracts_Users'); const extcls = new Parse.Query('contracts_Users');
extcls.equalTo('Email', user.get('email')); extcls.equalTo('Email', user.get('email'));
@@ -22,7 +22,7 @@ export default async function GetTemplate(request) {
template.include('ExtUserPtr'); template.include('ExtUserPtr');
template.include('Signers'); template.include('Signers');
template.include('CreatedBy'); template.include('CreatedBy');
template.include('ExtUserPtr.TenantId');
const extUserQuery = new Parse.Query('contracts_Users'); const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('Email', userRes.data.email); extUserQuery.equalTo('Email', userRes.data.email);
extUserQuery.include('TeamIds'); extUserQuery.include('TeamIds');
@@ -48,12 +48,15 @@ export default async function GetTemplate(request) {
template.include('ExtUserPtr'); template.include('ExtUserPtr');
template.include('Signers'); template.include('Signers');
template.include('CreatedBy'); template.include('CreatedBy');
template.include('ExtUserPtr.TenantId');
} }
} }
const res = await template.first({ useMasterKey: true }); const res = await template.first({ useMasterKey: true });
if (res) { if (res) {
// console.log("res ",res) const templateRes = JSON.parse(JSON.stringify(res));
return res; delete templateRes?.ExtUserPtr?.TenantId?.FileAdapters;
delete templateRes?.ExtUserPtr?.TenantId?.PfxFile;
return templateRes;
} else { } else {
return { error: "You don't have access of this document!" }; return { error: "You don't have access of this document!" };
} }
@@ -69,10 +72,14 @@ export default async function GetTemplate(request) {
template.include('ExtUserPtr'); template.include('ExtUserPtr');
template.include('Signers'); template.include('Signers');
template.include('CreatedBy'); template.include('CreatedBy');
template.include('ExtUserPtr.TenantId');
const res = await template.first({ useMasterKey: true }); const res = await template.first({ useMasterKey: true });
// console.log("res ", res) // console.log("res ", res)
if (res) { if (res) {
return res; const templateRes = JSON.parse(JSON.stringify(res));
delete templateRes?.ExtUserPtr?.TenantId?.FileAdapters;
delete templateRes?.ExtUserPtr?.TenantId?.PfxFile;
return templateRes;
} else { } else {
return { error: "You don't have access of this document!" }; return { error: "You don't have access of this document!" };
} }
@@ -31,7 +31,13 @@ const saveRoleContact = async contact => {
contactQuery.set('CreatedBy', contact.CreatedBy); contactQuery.set('CreatedBy', contact.CreatedBy);
contactQuery.set('UserId', contact.UserId); contactQuery.set('UserId', contact.UserId);
contactQuery.set('UserRole', 'contracts_Guest'); contactQuery.set('UserRole', 'contracts_Guest');
contactQuery.set('TenantId', contact.TenantId); if (contact?.TenantId) {
contactQuery.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: contact.TenantId,
});
}
contactQuery.set('IsDeleted', false); contactQuery.set('IsDeleted', false);
const acl = new Parse.ACL(); const acl = new Parse.ACL();
acl.setReadAccess(contact.CreatedBy.objectId, true); acl.setReadAccess(contact.CreatedBy.objectId, true);
@@ -55,7 +61,7 @@ const createDocumentFromTemplate = async (template, existContact, index) => {
object.set('Description', template?.Description); object.set('Description', template?.Description);
object.set('Note', template?.Note); object.set('Note', template?.Note);
object.set('TimeToCompleteDays', template?.TimeToCompleteDays || 15); object.set('TimeToCompleteDays', template?.TimeToCompleteDays || 15);
object.set('SendinOrder', template?.SendinOrder); object.set('SendinOrder', template?.SendinOrder || false);
object.set('AutomaticReminders', template?.AutomaticReminders || false); object.set('AutomaticReminders', template?.AutomaticReminders || false);
object.set('RemindOnceInEvery', template?.RemindOnceInEvery || 5); object.set('RemindOnceInEvery', template?.RemindOnceInEvery || 5);
object.set('URL', template?.URL); object.set('URL', template?.URL);
@@ -63,6 +69,8 @@ const createDocumentFromTemplate = async (template, existContact, index) => {
object.set('ExtUserPtr', template?.ExtUserPtr); object.set('ExtUserPtr', template?.ExtUserPtr);
object.set('OriginIp', template?.OriginIp || ''); object.set('OriginIp', template?.OriginIp || '');
object.set('IsEnableOTP', template?.IsEnableOTP || false); object.set('IsEnableOTP', template?.IsEnableOTP || false);
object.set('IsTourEnabled', template?.IsTourEnabled || false);
object.set('FileAdapterId', template?.FileAdapterId || '');
let signers = template?.Signers || []; let signers = template?.Signers || [];
const signerobj = { const signerobj = {
__type: 'Pointer', __type: 'Pointer',
@@ -97,7 +105,7 @@ const sendMailToAllSigners = async docId => {
try { try {
//get document details that recenlty created from public template //get document details that recenlty created from public template
const docQuery = new Parse.Query('contracts_Document'); const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr'); docQuery.include('ExtUserPtr,ExtUserPtr.TenantId');
docQuery.include('Signers'); docQuery.include('Signers');
const docRes = await docQuery.get(docId, { useMasterKey: true }); const docRes = await docQuery.get(docId, { useMasterKey: true });
const Doc = JSON.parse(JSON.stringify(docRes)); const Doc = JSON.parse(JSON.stringify(docRes));
@@ -168,7 +176,7 @@ const sendMailToAllSigners = async docId => {
receiver_phone: signerMail[i]?.Phone || '', receiver_phone: signerMail[i]?.Phone || '',
expiry_date: localExpireDate, expiry_date: localExpireDate,
company_name: orgName, company_name: orgName,
signing_url: `<a href=${signPdf}>Sign here</a>`, signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`,
}; };
replaceVar = replaceMailVaribles(requestSubject, htmlReqBody, variables); replaceVar = replaceMailVaribles(requestSubject, htmlReqBody, variables);
} }
@@ -222,6 +230,25 @@ const sendMailToAllSigners = async docId => {
} }
}; };
const deductcount = async _resSub => {
try {
const subscriptionCls = new Parse.Object('contracts_Subscriptions');
subscriptionCls.id = _resSub.objectId;
const allowedCredits = _resSub?.AllowedCredits || 0;
const addonCredits = _resSub?.AddonCredits || 0;
if (allowedCredits > 0) {
const updateAllowedcredits = allowedCredits - 1 || 0;
subscriptionCls.set('AllowedCredits', updateAllowedcredits);
} else {
const updateAddonCredits = addonCredits > 0 ? addonCredits - 1 : 0;
subscriptionCls.set('AddonCredits', updateAddonCredits);
}
await subscriptionCls.save(null, { useMasterKey: true });
} catch (err) {
console.log('Err in deductcount in PublicUserLinkContacttoDoc cloud function', err);
}
};
// `PublicUserLinkContactToDoc` cloud function is used to create contact, add this contact in contracts_Guest role and // `PublicUserLinkContactToDoc` cloud function is used to create contact, add this contact in contracts_Guest role and
// create new document from template and save contact pointer in placeholder, signers and ACL of Document // create new document from template and save contact pointer in placeholder, signers and ACL of Document
export default async function PublicUserLinkContactToDoc(req) { export default async function PublicUserLinkContactToDoc(req) {
@@ -235,132 +262,170 @@ export default async function PublicUserLinkContactToDoc(req) {
// Execute the query to get the template with the specified 'templateid' // Execute the query to get the template with the specified 'templateid'
const docQuery = new Parse.Query('contracts_Template'); const docQuery = new Parse.Query('contracts_Template');
docQuery.include('ExtUserPtr'); docQuery.include('ExtUserPtr');
docQuery.include('ExtUserPtr.TenantId');
const tempRes = await docQuery.get(templateid, { useMasterKey: true }); const tempRes = await docQuery.get(templateid, { useMasterKey: true });
// Check if the template was found; if not, throw an error indicating the template was not found // Check if the template was found; if not, throw an error indicating the template was not found
if (!tempRes) { if (!tempRes) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found.'); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found.');
} }
const _tempRes = JSON.parse(JSON.stringify(tempRes)); const _tempRes = JSON.parse(JSON.stringify(tempRes));
const Placeholders = _tempRes?.Placeholders || []; const subscription = new Parse.Query('contracts_Subscriptions');
let index; subscription.equalTo('TenantId', {
if (role) { __type: 'Pointer',
index = Placeholders?.findIndex(x => x.Role && x.Role === role); className: 'partners_Tenant',
} else { objectId: _tempRes?.ExtUserPtr?.TenantId?.objectId,
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, ''); });
} subscription.include('ExtUserPtr');
if (index !== -1) { const date = new Date();
// Execute the query to check if a contact already exists in the 'contracts_Contactbook' class subscription.greaterThanOrEqualTo('Next_billing_date', date);
const contactCls = new Parse.Query('contracts_Contactbook'); const resSub = await subscription.first({ useMasterKey: true });
contactCls.equalTo('Email', email); if (resSub) {
contactCls.equalTo('CreatedBy', _tempRes.CreatedBy); const _resSub = JSON.parse(JSON.stringify(resSub));
contactCls.notEqualTo('IsDeleted', true); const allowedCredits = _resSub?.AllowedCredits || 0;
const existContact = await contactCls.first({ useMasterKey: true }); const addonCredits = _resSub?.AddonCredits || 0;
if (existContact) { const totalcredits = allowedCredits + addonCredits;
const template_json = JSON.parse(JSON.stringify(tempRes)); if (totalcredits > 0) {
//update contact in placeholder, signers and update ACl in provide document const Placeholders = _tempRes?.Placeholders || [];
const docRes = await createDocumentFromTemplate(template_json, existContact, index); let index;
if (docRes) { if (role) {
//condition will execute only if sendInOrder will be false for send email to all signers at a time. index = Placeholders?.findIndex(x => x.Role && x.Role === role);
if (!template_json?.SendinOrder) { } else {
await sendMailToAllSigners(docRes.id); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, '');
}
return { contactId: existContact.id, docId: docRes.id };
} }
} else { if (index !== -1) {
// Execute the query to check if a user already exists in the 'contracts_Users' class // Execute the query to check if a contact already exists in the 'contracts_Contactbook' class
const extUserQuery = new Parse.Query('contracts_Users'); const contactCls = new Parse.Query('contracts_Contactbook');
extUserQuery.equalTo('Email', email); contactCls.equalTo('Email', email);
const extUser = await extUserQuery.first({ useMasterKey: true }); contactCls.equalTo('CreatedBy', _tempRes.CreatedBy);
if (extUser) { contactCls.notEqualTo('IsDeleted', true);
const _extUser = JSON.parse(JSON.stringify(extUser)); const existContact = await contactCls.first({ useMasterKey: true });
const contact = { if (existContact) {
UserId: _extUser.UserId, const template_json = JSON.parse(JSON.stringify(tempRes));
Name: _extUser.Name, //update contact in placeholder, signers and update ACl in provide document
Email: email, const docRes = await createDocumentFromTemplate(template_json, existContact, index);
Phone: _extUser?.Phone ? _extUser.Phone : '', if (docRes) {
CreatedBy: _tempRes.CreatedBy, await deductcount(_resSub);
TenantId: _tempRes.ExtUserPtr.TenantId, //condition will execute only if sendInOrder will be false for send email to all signers at a time.
}; if (!template_json?.SendinOrder) {
const template_json = JSON.parse(JSON.stringify(tempRes)); await sendMailToAllSigners(docRes.id);
// if user present on platform create contact on the basis of extended user details }
const contactRes = await saveRoleContact(contact); return { contactId: existContact.id, docId: docRes.id };
const docRes = await createDocumentFromTemplate(template_json, contactRes, index);
if (docRes) {
//condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id);
} }
return { contactId: contactRes.id, docId: docRes.id }; } else {
} // Execute the query to check if a user already exists in the 'contracts_Users' class
} else if (name) { const extUserQuery = new Parse.Query('contracts_Users');
try { extUserQuery.equalTo('Email', email);
// Execute the query to check if a user already exists in the '_User' class const extUser = await extUserQuery.first({ useMasterKey: true });
const userQuery = new Parse.Query(Parse.User); if (extUser) {
userQuery.equalTo('email', email); const _extUser = JSON.parse(JSON.stringify(extUser));
const userRes = await userQuery.first({ useMasterKey: true });
if (userRes) {
const contact = { const contact = {
UserId: { __type: 'Pointer', className: '_User', objectId: userRes.id }, UserId: _extUser.UserId,
Name: name, Name: _extUser.Name,
Email: email, Email: email,
Phone: phone, Phone: _extUser?.Phone ? _extUser.Phone : '',
CreatedBy: _tempRes.CreatedBy, CreatedBy: _tempRes.CreatedBy,
TenantId: _tempRes.ExtUserPtr.TenantId, TenantId: _tempRes.ExtUserPtr?.TenantId?.objectId,
}; };
const template_json = JSON.parse(JSON.stringify(tempRes)); const template_json = JSON.parse(JSON.stringify(tempRes));
// Create new contract on the basis provided contact details by user and userId from _User class // if user present on platform create contact on the basis of extended user details
const contactRes = await saveRoleContact(contact); const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(template_json, contactRes, index); const docRes = await createDocumentFromTemplate(template_json, contactRes, index);
if (docRes) { if (docRes) {
await deductcount(_resSub);
//condition will execute only if sendInOrder will be false for send email to all signers at a time. //condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) { if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id); await sendMailToAllSigners(docRes.id);
} }
return { contactId: contactRes.id, docId: docRes.id }; return { contactId: contactRes.id, docId: docRes.id };
} }
} else if (name) {
try {
// Execute the query to check if a user already exists in the '_User' class
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('email', email);
const userRes = await userQuery.first({ useMasterKey: true });
if (userRes) {
const contact = {
UserId: { __type: 'Pointer', className: '_User', objectId: userRes.id },
Name: name,
Email: email,
Phone: phone,
CreatedBy: _tempRes.CreatedBy,
TenantId: _tempRes.ExtUserPtr?.TenantId?.objectId,
};
const template_json = JSON.parse(JSON.stringify(tempRes));
// Create new contract on the basis provided contact details by user and userId from _User class
const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(
template_json,
contactRes,
index
);
if (docRes) {
await deductcount(_resSub);
//condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id);
}
return { contactId: contactRes.id, docId: docRes.id };
}
} else {
// create new user in _User class on the basis of details provide by user
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 newUserRes = await _user.save();
const contact = {
UserId: { __type: 'Pointer', className: '_User', objectId: newUserRes.id },
Name: name,
Email: email,
Phone: phone,
CreatedBy: _tempRes.CreatedBy,
TenantId: _tempRes.ExtUserPtr?.TenantId?.objectId,
};
const template_json = JSON.parse(JSON.stringify(tempRes));
// Create new contract on the basis provided contact details by user and userId from _User class
const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(
template_json,
contactRes,
index
);
if (docRes) {
await deductcount(_resSub);
//condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id);
}
return { contactId: contactRes.id, docId: docRes.id };
}
}
} catch (err) {
console.log('Err', err);
}
} else { } else {
// create new user in _User class on the basis of details provide by user throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
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 newUserRes = await _user.save();
const contact = {
UserId: { __type: 'Pointer', className: '_User', objectId: newUserRes.id },
Name: name,
Email: email,
Phone: phone,
CreatedBy: _tempRes.CreatedBy,
TenantId: _tempRes.ExtUserPtr.TenantId,
};
const template_json = JSON.parse(JSON.stringify(tempRes));
// Create new contract on the basis provided contact details by user and userId from _User class
const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(template_json, contactRes, index);
if (docRes) {
//condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id);
}
return { contactId: contactRes.id, docId: docRes.id };
}
} }
} catch (err) {
console.log('Err', err);
} }
} else { } else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Please provide required parameters!'
);
} }
} else {
throw new Parse.Error(Parse.Error.REQUEST_LIMIT_EXCEEDED, 'Insufficient Credit');
} }
} else { } else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Please provide required parameters!'); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Plan expired');
} }
} else { } else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found.'); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found.');
@@ -1,4 +1,4 @@
import { smtpenable, updateMailCount } from '../../Utils.js'; import { appName, smtpenable, updateMailCount } from '../../Utils.js';
async function getDocument(docId) { async function getDocument(docId) {
try { try {
const query = new Parse.Query('contracts_Document'); const query = new Parse.Query('contracts_Document');
@@ -7,6 +7,7 @@ async function getDocument(docId) {
query.include('CreatedBy'); query.include('CreatedBy');
query.include('Signers'); query.include('Signers');
query.include('AuditTrail.UserPtr'); query.include('AuditTrail.UserPtr');
query.include('ExtUserPtr.TenantId');
query.include('Placeholders'); query.include('Placeholders');
query.notEqualTo('IsArchive', true); query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true }); const res = await query.first({ useMasterKey: true });
@@ -28,12 +29,12 @@ async function sendMailOTPv1(request) {
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER; const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
try { try {
await Parse.Cloud.sendEmail({ await Parse.Cloud.sendEmail({
from: 'Opensign™' + ' <' + mailsender + '>', from: appName + ' <' + mailsender + '>',
recipient: recipient, recipient: recipient,
subject: 'Your OpenSign™ OTP', subject: `Your ${appName} OTP`,
text: 'This email is a test.', text: 'This email is a test.',
html: html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for OpenSign™ verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>" + `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for ${appName} verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
code + code +
'</p></div> </div> </div></body></html>', '</p></div> </div> </div></body></html>',
}); });
@@ -2,23 +2,31 @@ import { useLocal } from '../../Utils.js';
import getPresignedUrl from './getSignedUrl.js'; import getPresignedUrl from './getSignedUrl.js';
async function TemplateAfterFind(request) { async function TemplateAfterFind(request) {
if (useLocal !== 'true') { if (request.objects.length === 1) {
if (request.objects.length === 1) { if (request.objects) {
if (request.objects) { const obj = request.objects[0];
const obj = request.objects[0]; const FileAdapterId = obj?.get('FileAdapterId') || '';
if (FileAdapterId || useLocal !== 'true') {
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl'); const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
const Url = obj?.get('URL') && obj?.get('URL'); const Url = obj?.get('URL') && obj?.get('URL');
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl'); const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
let fileAdapter = {};
if (FileAdapterId) {
const tenantId = obj?.get('ExtUserPtr')?.get('TenantId');
if (tenantId) {
const _tenantId = JSON.parse(JSON.stringify(obj?.get('ExtUserPtr')?.get('TenantId')));
fileAdapter = _tenantId?.FileAdapters?.find(x => x.id === FileAdapterId) || {};
}
}
if (SignedUrl) { if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl)); obj.set('SignedUrl', getPresignedUrl(SignedUrl, fileAdapter));
} }
if (Url) { if (Url) {
obj.set('URL', getPresignedUrl(Url)); obj.set('URL', getPresignedUrl(Url, fileAdapter));
} }
if (certificateUrl) { if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl)); obj.set('CertificateUrl', getPresignedUrl(certificateUrl, fileAdapter));
} }
return [obj]; return [obj];
} }
} }
@@ -44,6 +44,7 @@ export default async function TemplateAfterSave(request) {
const Query = new Parse.Query('contracts_Template'); const Query = new Parse.Query('contracts_Template');
Query.include('Signers'); Query.include('Signers');
Query.include('CreatedBy'); Query.include('CreatedBy');
Query.include('ExtUserPtr.TenantId');
const updateACL = await Query.get(objId, { useMasterKey: true }); const updateACL = await Query.get(objId, { useMasterKey: true });
const res = JSON.parse(JSON.stringify(updateACL)); const res = JSON.parse(JSON.stringify(updateACL));
// console.log("res"); // console.log("res");
@@ -85,6 +86,7 @@ export default async function TemplateAfterSave(request) {
const Query = new Parse.Query('contracts_Template'); const Query = new Parse.Query('contracts_Template');
Query.include('CreatedBy'); Query.include('CreatedBy');
Query.include('ExtUserPtr.TenantId');
const updateACL = await Query.get(objId, { useMasterKey: true }); const updateACL = await Query.get(objId, { useMasterKey: true });
const res = JSON.parse(JSON.stringify(updateACL)); const res = JSON.parse(JSON.stringify(updateACL));
// console.log("res"); // console.log("res");
@@ -51,7 +51,10 @@ export default async function UpdateExistUserAsAdmin(request) {
extClsQuery.notEqualTo('IsDisabled', true); extClsQuery.notEqualTo('IsDisabled', true);
const extAdminRes = await extClsQuery.find({ useMasterKey: true }); const extAdminRes = await extClsQuery.find({ useMasterKey: true });
if (extAdminRes && extAdminRes.length === 1 && extAdminRes?.[0]?.get('OrganizationId')) { if (extAdminRes && extAdminRes.length === 1 && extAdminRes?.[0]?.get('OrganizationId')) {
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Admin already exist.'); throw new Parse.Error(
Parse.Error.DUPLICATE_VALUE,
'Admin already exists. Please login to the application using admin credentials in order to manage users.'
);
} else { } else {
const extCls = new Parse.Query('contracts_Users'); const extCls = new Parse.Query('contracts_Users');
extCls.equalTo('Email', email); extCls.equalTo('Email', email);
@@ -0,0 +1,142 @@
// `generateId` is used to unique Id for fileAdapter
function generateId(length) {
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
// `getExtUser` get ext user details
async function getExtUser(request) {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
extUserCls.include('TenantId');
return await extUserCls.first({ useMasterKey: true });
}
// `saveTenant` save file adapter details in tenant class
async function saveTenant(tenantId, fileAdapters, activeAdapter) {
const tenantCls = new Parse.Object('partners_Tenant');
tenantCls.id = tenantId;
if (fileAdapters?.length > 0) {
tenantCls.set('FileAdapters', fileAdapters);
}
if (activeAdapter) {
tenantCls.set('ActiveFileAdapter', activeAdapter);
} else {
tenantCls.unset('ActiveFileAdapter');
}
return await tenantCls.save(null, { useMasterKey: true });
}
// `updateTenantSchema` is used add FileAdapter in protected fields
async function updateTenantSchema() {
const tenantSchema = new Parse.Schema('partners_Tenant');
const currentSchema = await tenantSchema.get();
let clp = currentSchema.classLevelPermissions;
// Public permission ("*")
const role = '*';
if (!clp.protectedFields || Object.keys(clp.protectedFields).length === 0) {
// Initialize protectedFields if it doesn't exist
clp.protectedFields = { [role]: [] };
}
// save FileAdapters field is in protectedFields if not exists
if (!clp.protectedFields[role]?.includes('FileAdapters')) {
clp.protectedFields[role].push('FileAdapters');
// Update the class schema with the modified CLP
tenantSchema.setCLP(clp);
await tenantSchema.update();
}
}
export default async function addFileAdapter(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
const fileAdapterName = request.params.fileAdapterName;
const bucketName = request.params.bucketName;
const region = request.params.region;
const endpoint = request.params.endpoint;
const baseUrl = request.params.baseUrl;
const accessKeyId = request.params.accessKeyId;
const secretAccessKey = request.params.secretAccessKey;
const adapter = request.params.fileAdapter;
if (fileAdapterName && accessKeyId && secretAccessKey && adapter) {
try {
const extUser = await getExtUser(request);
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
const tenantId = extUser?.get('TenantId')?.id;
// assign existing file adapters or empty array
let fileAdapters = _extUser?.TenantId?.FileAdapters || [];
const uniqueId = generateId(10);
let id = extUser?.get('TenantId')?.id + '_' + uniqueId;
const index = fileAdapters?.findIndex(x => x.fileAdapterName === fileAdapterName);
if (index !== -1) {
// If an object with the same fileAdapterName exists, update it
if (bucketName || region || endpoint || baseUrl) {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'Cannot update bucketName, region, endpoint, baseUrl.'
);
} else {
const adapterConfig = { accessKeyId: accessKeyId, secretAccessKey: secretAccessKey };
fileAdapters[index] = { ...fileAdapters[index], ...adapterConfig };
id = fileAdapters[index].id;
}
} else {
if (bucketName && region && endpoint && baseUrl) {
// If the object with the given fileAdapterName doesn't exist, add a new one
fileAdapters.push({
id: id,
fileAdapterName: fileAdapterName,
fileAdapter: adapter,
bucketName: bucketName,
region: region,
endpoint: endpoint,
baseUrl: baseUrl,
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
});
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide all parameters.');
}
}
const updateTenant = await saveTenant(tenantId, fileAdapters, id);
await updateTenantSchema();
const ActiveFileAdapter = adapter === 'opensign' ? 'opensign' : id;
return { ActiveFileAdapter: ActiveFileAdapter, updateAt: updateTenant.updatedAt };
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
console.log('Err in add custom file adapter', err);
const code = err.code || 400;
const msg = err.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
} else if (adapter === 'opensign') {
try {
const extUser = await getExtUser(request);
if (extUser) {
const tenantId = extUser?.get('TenantId')?.id;
const updateTenant = await saveTenant(tenantId);
await updateTenantSchema();
return { ActiveFileAdapter: 'opensign', updateAt: updateTenant.updatedAt };
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
console.log('Err in add custom file adapter', err);
const code = err.code || 400;
const msg = err.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide all parameters.');
}
}
@@ -0,0 +1,58 @@
// `updateTenantSchema` is used add FileAdapter in protected fields
async function updateTenantSchema() {
const tenantSchema = new Parse.Schema('partners_Tenant');
const currentSchema = await tenantSchema.get();
let clp = currentSchema.classLevelPermissions;
// Public permission ("*")
const role = '*';
if (!clp.protectedFields || Object.keys(clp.protectedFields).length === 0) {
// Initialize protectedFields if it doesn't exist
clp.protectedFields = { [role]: [] };
}
// save PfxFile field is in protectedFields if not exists
if (!clp.protectedFields[role]?.includes('PfxFile')) {
clp.protectedFields[role].push('PfxFile');
// Update the class schema with the modified CLP
tenantSchema.setCLP(clp);
await tenantSchema.update();
}
}
export default async function addPfxFile(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
const pfxBase64 = request.params.pfxBase64;
const title = request.params.title;
const password = request.params.password;
const provider = request.params.provider;
if (provider === 'opensign' || (pfxBase64 && password)) {
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
extUserCls.include('TenantId');
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const tenantCls = new Parse.Object('partners_Tenant');
tenantCls.id = extUser?.get('TenantId')?.id;
if (provider === 'opensign') {
tenantCls.unset('PfxFile');
} else {
tenantCls.set('PfxFile', { title: title, password: password, base64: pfxBase64 });
}
const updateTenant = await tenantCls.save(null, { useMasterKey: true });
await updateTenantSchema();
return { updateAt: updateTenant.updatedAt };
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
console.log('Err in add custom file adapter', err);
const code = err.code || 400;
const msg = err.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide all parameters.');
}
}
@@ -0,0 +1,33 @@
export default async function addcustomsmtp(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
const host = request.params.host;
const port = request.params.port;
const username = request.params.username;
const password = request.params.password;
if (host && port && username && password) {
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const extUserCls = new Parse.Object('contracts_Users');
extUserCls.id = extUser.id;
extUserCls.set('SmtpConfig', { host, port, username, password });
extUserCls.set('active_mail_adapter', 'smtp');
const updateExtUser = await extUserCls.save(null, { useMasterKey: true });
// console.log('updateExtUser ', updateExtUser);
return updateExtUser.updatedAt;
}
return extUser;
} catch (err) {
console.log('Err in add custom smtp', err);
const code = err.code || 400;
const msg = err.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide all parameters.');
}
}
@@ -9,6 +9,7 @@ export default async function callWebhook(request) {
const appId = process.env.APP_ID; const appId = process.env.APP_ID;
try { try {
const docQuery = new Parse.Query('contracts_Document'); const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr.TenantId');
const docRes = await docQuery.get(docId, { useMasterKey: true }); const docRes = await docQuery.get(docId, { useMasterKey: true });
const isEnableOTP = docRes?.get('IsEnableOTP') || false; const isEnableOTP = docRes?.get('IsEnableOTP') || false;
let userId; let userId;
@@ -74,6 +74,7 @@ async function sendMail(document, sessionToken) {
const orgName = document.ExtUserPtr.Company ? document.ExtUserPtr.Company : ''; const orgName = document.ExtUserPtr.Company ? document.ExtUserPtr.Company : '';
const themeBGcolor = '#47a3ad'; const themeBGcolor = '#47a3ad';
let params = { let params = {
mailProvider: document?.ExtUserPtr?.active_mail_adapter || '',
extUserId: document.ExtUserPtr.objectId, extUserId: document.ExtUserPtr.objectId,
recipient: objectId ? existSigner?.Email : signerMail[i].email, recipient: objectId ? existSigner?.Email : signerMail[i].email,
subject: `${document.ExtUserPtr.Name} has requested you to sign "${document.Name}"`, subject: `${document.ExtUserPtr.Name} has requested you to sign "${document.Name}"`,
@@ -94,7 +95,7 @@ async function sendMail(document, sessionToken) {
orgName + orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate + localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a target=_blank href=" +
signPdf + signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender + sender +
@@ -200,6 +201,8 @@ export default async function createBatchDocs(request) {
OriginIp: Ip, OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate }, DocSentAt: { __type: 'Date', iso: isoDate },
IsEnableOTP: x?.IsEnableOTP || false, IsEnableOTP: x?.IsEnableOTP || false,
IsTourEnabled: x?.IsTourEnabled || false,
FileAdapterId: x?.FileAdapterId || '',
}, },
}; };
}); });
@@ -0,0 +1,24 @@
export default async function deactivateMailAdapter(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const extUserCls = new Parse.Object('contracts_Users');
extUserCls.id = extUser.id;
extUserCls.unset('active_mail_adapter');
const updateExtUser = await extUserCls.save(null, { useMasterKey: true });
// console.log('updateExtUser ', updateExtUser);
return updateExtUser.updatedAt;
}
return extUser;
} catch (err) {
console.log('Err in add custom smtp', err);
const code = err.code || 400;
const msg = err.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
}
@@ -9,6 +9,7 @@ export default async function declinedocument(request) {
try { try {
const docCls = new Parse.Query('contracts_Document'); const docCls = new Parse.Query('contracts_Document');
docCls.include('ExtUserPtr.TenantId');
const updateDoc = await docCls.get(docId, { useMasterKey: true }); const updateDoc = await docCls.get(docId, { useMasterKey: true });
if (updateDoc) { if (updateDoc) {
const isEnableOTP = updateDoc?.get('IsEnableOTP') || false; const isEnableOTP = updateDoc?.get('IsEnableOTP') || false;
@@ -11,6 +11,7 @@ export default async function getDocument(request) {
const query = new Parse.Query('contracts_Document'); const query = new Parse.Query('contracts_Document');
query.equalTo('objectId', docId); query.equalTo('objectId', docId);
query.include('ExtUserPtr'); query.include('ExtUserPtr');
query.include('ExtUserPtr.TenantId');
query.include('CreatedBy'); query.include('CreatedBy');
query.include('Signers'); query.include('Signers');
query.include('AuditTrail.UserPtr'); query.include('AuditTrail.UserPtr');
@@ -20,8 +21,11 @@ export default async function getDocument(request) {
const res = await query.first({ useMasterKey: true }); const res = await query.first({ useMasterKey: true });
if (res) { if (res) {
const IsEnableOTP = res?.get('IsEnableOTP') || false; const IsEnableOTP = res?.get('IsEnableOTP') || false;
const document = JSON.parse(JSON.stringify(res));
delete document.ExtUserPtr.TenantId.FileAdapters;
delete document?.ExtUserPtr?.TenantId?.PfxFile;
if (!IsEnableOTP) { if (!IsEnableOTP) {
return res; return document;
} else { } else {
if (request?.headers?.['sessiontoken']) { if (request?.headers?.['sessiontoken']) {
try { try {
@@ -34,7 +38,7 @@ export default async function getDocument(request) {
const userId = userRes.data && userRes.data?.objectId; const userId = userRes.data && userRes.data?.objectId;
const acl = res.getACL(); const acl = res.getACL();
if (userId && acl && acl.getReadAccess(userId)) { if (userId && acl && acl.getReadAccess(userId)) {
return res; return document;
} else { } else {
return { error: "You don't have access of this document!" }; return { error: "You don't have access of this document!" };
} }
@@ -18,9 +18,9 @@ export default async function getDrive(request) {
if (userId) { if (userId) {
let url; let url;
if (docId) { if (docId) {
url = `${classUrl}?where={"Folder":{"__type":"Pointer","className":"contracts_Document","objectId":"${docId}"},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"${userId}"},"IsArchive":{"$ne":true}}&include=ExtUserPtr,Signers,Folder&skip=${skip}&limit=${limit}`; url = `${classUrl}?where={"Folder":{"__type":"Pointer","className":"contracts_Document","objectId":"${docId}"},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"${userId}"},"IsArchive":{"$ne":true}}&include=ExtUserPtr,ExtUserPtr.TenantId,Signers,Folder&order=-updatedAt&skip=${skip}&limit=${limit}`;
} else { } else {
url = `${classUrl}?where={"Folder":{"$exists":false},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"${userId}"},"IsArchive":{"$ne":true}}&include=ExtUserPtr,Signers&skip=${skip}&limit=${limit}`; url = `${classUrl}?where={"Folder":{"$exists":false},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"${userId}"},"IsArchive":{"$ne":true}}&include=ExtUserPtr,ExtUserPtr.TenantId,Signers&order=-updatedAt&skip=${skip}&limit=${limit}`;
} }
try { try {
const res = await axios.get(url, { const res = await axios.get(url, {
@@ -0,0 +1,25 @@
export default async function getFileAdapter(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
extUserCls.include('TenantId');
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
const FileAdapters =
_extUser?.TenantId?.FileAdapters?.length > 0 ? _extUser?.TenantId?.FileAdapters : [];
const ActiveFileAdapter = _extUser?.TenantId?.ActiveFileAdapter || 'opensign';
return { ActiveFileAdapter: ActiveFileAdapter, FileAdapters: FileAdapters };
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
console.log('Err in add custom file adapter', err);
const code = err.code || 400;
const msg = err.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
}
@@ -0,0 +1,25 @@
export default async function getPfxFile(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
extUserCls.include('TenantId');
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
const title = _extUser?.TenantId?.PfxFile?.title || '';
const pfxBase64 = _extUser?.TenantId?.PfxFile?.base64 || '';
const password = _extUser?.TenantId?.PfxFile?.password || '';
return { title: title, password: password, base64: pfxBase64 };
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
console.log('Err in add custom file adapter', err);
const code = err.code || 400;
const msg = err.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
}
@@ -52,11 +52,7 @@ export default async function getReport(request) {
} else { } else {
strParams = JSON.stringify({ strParams = JSON.stringify({
...params, ...params,
CreatedBy: { CreatedBy: { __type: 'Pointer', className: '_User', objectId: userId },
__type: 'Pointer',
className: '_User',
objectId: userId,
},
}); });
} }
} }
@@ -66,7 +62,7 @@ export default async function getReport(request) {
'X-Parse-Application-Id': appId, 'X-Parse-Application-Id': appId,
'X-Parse-Master-Key': masterKey, 'X-Parse-Master-Key': masterKey,
}; };
const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys}&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr,Placeholders.signerPtr`; const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys}&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr,Placeholders.signerPtr,ExtUserPtr.TenantId`;
const res = await axios.get(url, { headers: headers }); const res = await axios.get(url, { headers: headers });
if (res.data && res.data.results) { if (res.data && res.data.results) {
return res.data.results; return res.data.results;
@@ -1,12 +1,12 @@
import AWS from 'aws-sdk'; import AWS from 'aws-sdk';
import { useLocal } from '../../Utils.js'; import { useLocal } from '../../Utils.js';
export default function getPresignedUrl(url) { export default function getPresignedUrl(url, adapter) {
const credentials = { const credentials = {
accessKeyId: process.env.DO_ACCESS_KEY_ID, accessKeyId: adapter?.accessKeyId || process.env.DO_ACCESS_KEY_ID,
secretAccessKey: process.env.DO_SECRET_ACCESS_KEY, secretAccessKey: adapter?.secretAccessKey || process.env.DO_SECRET_ACCESS_KEY,
}; };
AWS.config.update({ credentials: credentials, region: process.env.DO_REGION }); AWS.config.update({ credentials: credentials, region: adapter?.region || process.env.DO_REGION });
const spacesEndpoint = new AWS.Endpoint(process.env.DO_ENDPOINT); const spacesEndpoint = adapter?.endpoint || new AWS.Endpoint(process.env.DO_ENDPOINT);
const s3 = new AWS.S3({ endpoint: spacesEndpoint }); const s3 = new AWS.S3({ endpoint: spacesEndpoint });
@@ -19,7 +19,7 @@ export default function getPresignedUrl(url) {
// presignedGETURL return presignedUrl with expires time // presignedGETURL return presignedUrl with expires time
const presignedGETURL = s3.getSignedUrl('getObject', { const presignedGETURL = s3.getSignedUrl('getObject', {
Bucket: process.env.DO_SPACE, Bucket: adapter?.bucketName || process.env.DO_SPACE,
Key: filename, //filename Key: filename, //filename
Expires: 160, //time to expire in seconds Expires: 160, //time to expire in seconds
}); });
@@ -29,26 +29,51 @@ export default function getPresignedUrl(url) {
export async function getSignedUrl(request) { export async function getSignedUrl(request) {
try { try {
const docId = request.params.docId || ''; const docId = request.params.docId || '';
const templateId = request.params.templateId || '';
const url = request.params.url; const url = request.params.url;
if (docId) { const fileAdapterId = request.params.fileAdapterId || '';
if (docId || templateId) {
try { try {
const query = new Parse.Query('contracts_Document'); if (fileAdapterId || useLocal !== 'true') {
query.equalTo('objectId', docId); const query = new Parse.Query(docId ? 'contracts_Document' : 'contracts_Template');
query.notEqualTo('IsEnableOTP', true); query.equalTo('objectId', docId ? docId : templateId);
query.include('CreatedBy'); query.include('ExtUserPtr.TenantId');
query.include('Signers'); query.notEqualTo('IsArchive', true);
query.include('AuditTrail.UserPtr'); const res = await query.first({ useMasterKey: true });
query.include('Placeholders'); if (res) {
query.include('DeclineBy'); const _resDoc = JSON.parse(JSON.stringify(res));
query.notEqualTo('IsArchive', true); if (_resDoc?.IsEnableOTP) {
const res = await query.first({ useMasterKey: true }); if (!request?.user) {
if (res) { throw new Parse.Error(
if (useLocal !== 'true') { Parse.Error.INVALID_SESSION_TOKEN,
const presignedUrl = getPresignedUrl(url); 'User is not authenticated.'
return presignedUrl; );
} else { } else {
return url; let adapterConfig = {};
if (fileAdapterId) {
// `adapterConfig` is used to get file in user's fileAdapter
adapterConfig =
_resDoc?.ExtUserPtr?.TenantId?.FileAdapters?.find(
x => x.id === fileAdapterId
) || {};
}
const presignedUrl = getPresignedUrl(url, adapterConfig);
return presignedUrl;
}
} else {
let adapterConfig = {};
if (fileAdapterId) {
// `adapterConfig` is used to get file in user's fileAdapter
adapterConfig =
_resDoc?.ExtUserPtr?.TenantId?.FileAdapters?.find(x => x.id === fileAdapterId) ||
{};
}
const presignedUrl = getPresignedUrl(url, adapterConfig);
return presignedUrl;
}
} }
} else {
return url;
} }
} catch (err) { } catch (err) {
console.log('Err in presigned url', err); console.log('Err in presigned url', err);
@@ -14,7 +14,9 @@ async function getUserDetails(request) {
userQuery.include('UserId'); userQuery.include('UserId');
userQuery.include('CreatedBy'); userQuery.include('CreatedBy');
userQuery.exclude('CreatedBy.authData'); userQuery.exclude('CreatedBy.authData');
userQuery.exclude('TenantId.FileAdapters');
userQuery.exclude('google_refresh_token'); userQuery.exclude('google_refresh_token');
userQuery.exclude('TenantId.PfxFile');
if (userId) { if (userId) {
userQuery.equalTo('CreatedBy', { __type: 'Pointer', className: '_User', objectId: userId }); userQuery.equalTo('CreatedBy', { __type: 'Pointer', className: '_User', objectId: userId });
} }
@@ -29,7 +29,13 @@ const saveRoleContact = async contact => {
contactQuery.set('CreatedBy', contact.CreatedBy); contactQuery.set('CreatedBy', contact.CreatedBy);
contactQuery.set('UserId', contact.UserId); contactQuery.set('UserId', contact.UserId);
contactQuery.set('UserRole', 'contracts_Guest'); contactQuery.set('UserRole', 'contracts_Guest');
contactQuery.set('TenantId', contact.TenantId); if (contact?.TenantId) {
contactQuery.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: contact.TenantId,
});
}
contactQuery.set('IsDeleted', false); contactQuery.set('IsDeleted', false);
const acl = new Parse.ACL(); const acl = new Parse.ACL();
acl.setReadAccess(contact.CreatedBy.objectId, true); acl.setReadAccess(contact.CreatedBy.objectId, true);
@@ -54,7 +60,7 @@ export default async function linkContactToDoc(req) {
if (docId) { if (docId) {
// Execute the query to get the document with the specified 'docId' // Execute the query to get the document with the specified 'docId'
const docQuery = new Parse.Query('contracts_Document'); const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr'); docQuery.include('ExtUserPtr,ExtUserPtr.TenantId');
const docRes = await docQuery.get(docId, { useMasterKey: true }); const docRes = await docQuery.get(docId, { useMasterKey: true });
// Check if the document was found; if not, throw an error indicating the document was not found // Check if the document was found; if not, throw an error indicating the document was not found
if (!docRes) { if (!docRes) {
@@ -125,7 +131,7 @@ export default async function linkContactToDoc(req) {
Email: email, Email: email,
Phone: _extUser?.Phone ? _extUser.Phone : '', Phone: _extUser?.Phone ? _extUser.Phone : '',
CreatedBy: _docRes.CreatedBy, CreatedBy: _docRes.CreatedBy,
TenantId: _docRes.ExtUserPtr.TenantId, TenantId: _docRes.ExtUserPtr?.TenantId?.objectId,
}; };
// if user present on platform create contact on the basis of extended user details // if user present on platform create contact on the basis of extended user details
const contactRes = await saveRoleContact(contact); const contactRes = await saveRoleContact(contact);
@@ -177,7 +183,7 @@ export default async function linkContactToDoc(req) {
Email: email, Email: email,
Phone: phone, Phone: phone,
CreatedBy: _docRes.CreatedBy, CreatedBy: _docRes.CreatedBy,
TenantId: _docRes.ExtUserPtr.TenantId, TenantId: _docRes.ExtUserPtr?.TenantId?.objectId,
}; };
// Create new contract on the basis provided contact details by user and userId from _User class // Create new contract on the basis provided contact details by user and userId from _User class
const contactRes = await saveRoleContact(contact); const contactRes = await saveRoleContact(contact);
@@ -234,7 +240,7 @@ export default async function linkContactToDoc(req) {
Email: email, Email: email,
Phone: phone, Phone: phone,
CreatedBy: _docRes.CreatedBy, CreatedBy: _docRes.CreatedBy,
TenantId: _docRes.ExtUserPtr.TenantId, TenantId: _docRes.ExtUserPtr?.TenantId?.objectId,
}; };
// Create new contract on the basis provided contact details by user and userId from _User class // Create new contract on the basis provided contact details by user and userId from _User class
const contactRes = await saveRoleContact(contact); const contactRes = await saveRoleContact(contact);
@@ -274,7 +280,7 @@ export default async function linkContactToDoc(req) {
} }
} }
} catch (err) { } catch (err) {
console.log('Err', err); console.log('Err while creating contact link to doc', err);
} }
} else { } else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
@@ -6,18 +6,36 @@ import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
import { PDFDocument } from 'pdf-lib'; import { PDFDocument } from 'pdf-lib';
import { cloudServerUrl, replaceMailVaribles, saveFileUsage } from '../../../Utils.js'; import { cloudServerUrl, replaceMailVaribles, saveFileUsage } from '../../../Utils.js';
import GenerateCertificate from './GenerateCertificate.js'; import GenerateCertificate from './GenerateCertificate.js';
import uploadFileToS3 from '../uploadFiletoS3.js';
const serverUrl = cloudServerUrl; // process.env.SERVER_URL; const serverUrl = cloudServerUrl; // process.env.SERVER_URL;
const APPID = process.env.APP_ID; const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY; const masterKEY = process.env.MASTER_KEY;
const eSignName = 'opensign'; const eSignName = 'opensign';
const eSigncontact = 'hello@opensignlabs.com'; const eSigncontact = 'hello@opensignlabs.com';
// `updateDoc` is used to create url in from pdfFile // `updateDoc` is used to create url in from pdfFile
async function uploadFile(pdfName, filepath) { async function uploadFile(pdfName, filepath, adapter) {
try { try {
const filedata = fs.readFileSync(filepath); const filedata = fs.readFileSync(filepath);
const file = new Parse.File(pdfName, [...filedata], 'application/pdf'); let fileUrl;
await file.save({ useMasterKey: true }); if (adapter?.bucketName) {
const fileUrl = file.url(); const adapterConfig = {
id: adapter?.id,
fileAdapter: adapter?.fileAdapter,
bucketName: adapter?.bucketName,
region: adapter?.region,
endpoint: adapter?.endpoint,
accessKeyId: adapter?.accessKeyId,
secretAccessKey: adapter?.secretAccessKey,
baseUrl: adapter?.baseUrl,
};
// `uploadFileToS3` is used to save document in user's file storage
fileUrl = await uploadFileToS3(filedata, pdfName, 'application/pdf', adapterConfig);
} else {
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
await file.save({ useMasterKey: true });
fileUrl = file.url();
}
return { imageUrl: fileUrl }; return { imageUrl: fileUrl };
} catch (err) { } catch (err) {
console.log('Err ', err); console.log('Err ', err);
@@ -79,12 +97,13 @@ async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
// `sendCompletedMail` is used to send copy of completed document mail // `sendCompletedMail` is used to send copy of completed document mail
async function sendCompletedMail(obj) { async function sendCompletedMail(obj) {
const url = obj.url; const url = obj.doc?.SignedUrl;
const doc = obj.doc; const doc = obj.doc;
const sender = obj.doc.ExtUserPtr; const sender = obj.doc.ExtUserPtr;
const pdfName = doc.Name; const pdfName = doc.Name;
const mailLogo = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; const mailLogo = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
const recipient = sender.Email; const recipient =
doc?.Signers?.length > 0 ? doc?.Signers?.map(x => x?.Email)?.join(',') : sender.Email;
let subject = `Document "${pdfName}" has been signed by all parties`; let subject = `Document "${pdfName}" has been signed by all parties`;
let body = let body =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=" + "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=" +
@@ -223,10 +242,14 @@ async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) {
} }
// `sendMailsaveCertifcate` is used send completion mail and update complete status of document // `sendMailsaveCertifcate` is used send completion mail and update complete status of document
const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailProvider, userId) => { async function sendMailsaveCertifcate(doc, P12Buffer, isCustomMail, mailProvider, adapterConfig) {
const certificate = await GenerateCertificate(doc); const certificate = await GenerateCertificate(doc);
const certificatePdf = await PDFDocument.load(certificate); const certificatePdf = await PDFDocument.load(certificate);
const p12 = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null }); let passphrase = process.env.PASS_PHRASE;
if (doc?.ExtUserPtr?.TenantId?.PfxFile?.password) {
passphrase = doc?.ExtUserPtr?.TenantId?.PfxFile?.password;
}
const p12 = new P12Signer(P12Buffer, { passphrase: passphrase || null });
// `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate // `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate
pdflibAddPlaceholder({ pdflibAddPlaceholder({
pdfDoc: certificatePdf, pdfDoc: certificatePdf,
@@ -245,7 +268,7 @@ const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailPro
//below is used to save signed certificate in exports folder //below is used to save signed certificate in exports folder
fs.writeFileSync('./exports/certificate.pdf', signedCertificate); fs.writeFileSync('./exports/certificate.pdf', signedCertificate);
const file = await uploadFile('certificate.pdf', './exports/certificate.pdf'); const file = await uploadFile('certificate.pdf', './exports/certificate.pdf', adapterConfig);
const body = { CertificateUrl: file.imageUrl }; const body = { CertificateUrl: file.imageUrl };
await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, { await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, {
headers: { headers: {
@@ -258,12 +281,11 @@ const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailPro
if (doc.IsSendMail === false) { if (doc.IsSendMail === false) {
console.log("don't send mail"); console.log("don't send mail");
} else { } else {
const mailObj = { url: url, isCustomMail: isCustomMail, doc: doc, mailProvider: mailProvider }; sendCompletedMail({ isCustomMail, doc, mailProvider });
sendCompletedMail(mailObj);
} }
saveFileUsage(CertificateBuffer.length, file.imageUrl, userId); saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId);
sendDoctoWebhook(doc, url, 'completed', '', file.imageUrl); sendDoctoWebhook(doc, doc?.SignedUrl, 'completed', '', file.imageUrl);
}; }
/** /**
* *
* @param docId Id of Document in which user is signing * @param docId Id of Document in which user is signing
@@ -280,7 +302,7 @@ async function PDF(req) {
const sign = req.params.signature || ''; const sign = req.params.signature || '';
// below bode is used to get info of docId // below bode is used to get info of docId
const docQuery = new Parse.Query('contracts_Document'); const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr,Signers'); docQuery.include('ExtUserPtr,Signers,ExtUserPtr.TenantId');
docQuery.equalTo('objectId', docId); docQuery.equalTo('objectId', docId);
const resDoc = await docQuery.first({ useMasterKey: true }); const resDoc = await docQuery.first({ useMasterKey: true });
if (!resDoc) { if (!resDoc) {
@@ -294,6 +316,14 @@ async function PDF(req) {
} }
} }
const _resDoc = resDoc?.toJSON(); const _resDoc = resDoc?.toJSON();
// `FileAdapter` && `ActiveFileAdapter` is used to save file in user's fileAdapter
const ActiveFileAdapter = _resDoc?.ExtUserPtr?.TenantId?.ActiveFileAdapter;
const FileAdapter =
_resDoc?.ExtUserPtr?.TenantId?.FileAdapters?.find(x => x.id === ActiveFileAdapter) || {};
let adapterConfig = {};
if (FileAdapter && ActiveFileAdapter) {
adapterConfig = { ActiveFileAdapter: ActiveFileAdapter, ...FileAdapter };
}
let signUser; let signUser;
let className; let className;
// `reqUserId` is send throught pdfrequest signing flow // `reqUserId` is send throught pdfrequest signing flow
@@ -315,10 +345,15 @@ async function PDF(req) {
// `PdfBuffer` used to create buffer from pdf file // `PdfBuffer` used to create buffer from pdf file
let PdfBuffer = Buffer.from(req.params.pdfFile, 'base64'); let PdfBuffer = Buffer.from(req.params.pdfFile, 'base64');
// `P12Buffer` used to create buffer from p12 certificate // `P12Buffer` used to create buffer from p12 certificate
const pfxFile = process.env.PFX_BASE64; let pfxFile = process.env.PFX_BASE64;
let passphrase = process.env.PASS_PHRASE;
if (_resDoc?.ExtUserPtr?.TenantId?.PfxFile?.base64) {
pfxFile = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.base64;
passphrase = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.password;
}
// const P12Buffer = fs.readFileSync(); // const P12Buffer = fs.readFileSync();
const P12Buffer = Buffer.from(pfxFile, 'base64'); const P12Buffer = Buffer.from(pfxFile, 'base64');
const p12Cert = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null }); const p12Cert = new P12Signer(P12Buffer, { passphrase: passphrase || null });
const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId }; const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId };
const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP }; const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP };
let updateAuditTrail; let updateAuditTrail;
@@ -338,8 +373,9 @@ async function PDF(req) {
isCompleted = true; isCompleted = true;
} }
const randomNumber = Math.floor(Math.random() * 5000); const randomNumber = Math.floor(Math.random() * 5000);
const name = `exported_file_${randomNumber}.pdf`; const docName = _resDoc?.Name?.replace(/\s+/g, '_')?.toLowerCase();
const pdfName = `./exports/${name}`; const name = `signed_${docName}_${randomNumber}.pdf`;
const filePath = `./exports/${name}`;
let pdfSize = PdfBuffer.length; let pdfSize = PdfBuffer.length;
if (isCompleted) { if (isCompleted) {
const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>'); const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>');
@@ -376,22 +412,22 @@ async function PDF(req) {
const signedDocs = await OBJ.sign(PdfBuffer, p12Cert); const signedDocs = await OBJ.sign(PdfBuffer, p12Cert);
//`saveUrl` is used to save signed pdf in exports folder //`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, signedDocs); fs.writeFileSync(filePath, signedDocs);
pdfSize = signedDocs.length; pdfSize = signedDocs.length;
} else { } else {
//`saveUrl` is used to save signed pdf in exports folder //`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, PdfBuffer); fs.writeFileSync(filePath, PdfBuffer);
pdfSize = PdfBuffer.length; pdfSize = PdfBuffer.length;
} }
// `uploadFile` is used to upload pdf to aws s3 and get it's url // `uploadFile` is used to upload pdf to aws s3 and get it's url
const data = await uploadFile(name, pdfName); const data = await uploadFile(name, filePath, adapterConfig);
if (data && data.imageUrl) { if (data && data.imageUrl) {
// `axios` is used to update signed pdf url in contracts_Document classes for given DocId // `axios` is used to update signed pdf url in contracts_Document classes for given DocId
const updatedDoc = await updateDoc( const updatedDoc = await updateDoc(
req.params.docId, //docId req.params.docId, //docId
data.imageUrl, // url data.imageUrl, // SignedUrl
signUser.objectId, // userID signUser.objectId, // userID
userIP, // client ipAddress, userIP, // client ipAddress,
_resDoc, // auditTrail, signers, etc data _resDoc, // auditTrail, signers, etc data
@@ -401,19 +437,12 @@ async function PDF(req) {
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser); sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId); saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
if (updatedDoc && updatedDoc.isCompleted) { if (updatedDoc && updatedDoc.isCompleted) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail }; const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
sendMailsaveCertifcate( sendMailsaveCertifcate(doc, P12Buffer, isCustomMail, mailProvider, adapterConfig);
doc,
P12Buffer,
data.imageUrl,
isCustomMail,
mailProvider,
_resDoc?.CreatedBy?.objectId
);
} }
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder // `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
fs.unlinkSync(pdfName); fs.unlinkSync(filePath);
console.log(`New Signed PDF created called: ${pdfName}`); console.log(`New Signed PDF created called: ${filePath}`);
if (updatedDoc.message === 'success') { if (updatedDoc.message === 'success') {
return { status: 'success', data: data.imageUrl }; return { status: 'success', data: data.imageUrl };
} else { } else {
@@ -29,6 +29,7 @@ export default function reportJson(id, userId) {
'Signers.Phone', 'Signers.Phone',
'Placeholders', 'Placeholders',
'IsSignyourself', 'IsSignyourself',
'FileAdapterId',
], ],
}; };
@@ -65,6 +66,8 @@ export default function reportJson(id, userId) {
'Folder.Name', 'Folder.Name',
'URL', 'URL',
'ExtUserPtr.Name', 'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name', 'Signers.Name',
'Signers.Email', 'Signers.Email',
'Signers.Phone', 'Signers.Phone',
@@ -72,6 +75,7 @@ export default function reportJson(id, userId) {
'AuditTrail', 'AuditTrail',
'Placeholders', 'Placeholders',
'SignedUrl', 'SignedUrl',
'FileAdapterId',
], ],
}; };
// In progess report // In progess report
@@ -100,6 +104,8 @@ export default function reportJson(id, userId) {
'Folder.Name', 'Folder.Name',
'URL', 'URL',
'ExtUserPtr.Name', 'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name', 'Signers.Name',
'Signers.Email', 'Signers.Email',
'Signers.Phone', 'Signers.Phone',
@@ -109,6 +115,7 @@ export default function reportJson(id, userId) {
'SendMail', 'SendMail',
'Placeholders', 'Placeholders',
'SignedUrl', 'SignedUrl',
'FileAdapterId',
], ],
}; };
// completed documents report // completed documents report
@@ -139,6 +146,8 @@ export default function reportJson(id, userId) {
'TimeToCompleteDays', 'TimeToCompleteDays',
'Placeholders', 'Placeholders',
'IsSignyourself', 'IsSignyourself',
'IsCompleted',
'FileAdapterId',
], ],
}; };
// declined documents report // declined documents report
@@ -168,6 +177,7 @@ export default function reportJson(id, userId) {
'Placeholders', 'Placeholders',
'DeclineReason', 'DeclineReason',
'SignedUrl', 'SignedUrl',
'FileAdapterId',
], ],
}; };
// Expired Documents report // Expired Documents report
@@ -200,6 +210,7 @@ export default function reportJson(id, userId) {
'Signers.Phone', 'Signers.Phone',
'Placeholders', 'Placeholders',
'SignedUrl', 'SignedUrl',
'FileAdapterId',
], ],
}; };
// Recently sent for signatures report show on dashboard // Recently sent for signatures report show on dashboard
@@ -227,6 +238,8 @@ export default function reportJson(id, userId) {
'Folder.Name', 'Folder.Name',
'URL', 'URL',
'ExtUserPtr.Name', 'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name', 'Signers.Name',
'Signers.Email', 'Signers.Email',
'Signers.Phone', 'Signers.Phone',
@@ -235,6 +248,7 @@ export default function reportJson(id, userId) {
'ExpiryDate', 'ExpiryDate',
'Placeholders', 'Placeholders',
'SignedUrl', 'SignedUrl',
'FileAdapterId',
], ],
}; };
// Recent signature requests report show on dashboard // Recent signature requests report show on dashboard
@@ -268,6 +282,8 @@ export default function reportJson(id, userId) {
'Name', 'Name',
'URL', 'URL',
'ExtUserPtr.Name', 'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name', 'Signers.Name',
'Signers.UserId', 'Signers.UserId',
'AuditTrail', 'AuditTrail',
@@ -275,6 +291,7 @@ export default function reportJson(id, userId) {
'Signers.Phone', 'Signers.Phone',
'Placeholders', 'Placeholders',
'SignedUrl', 'SignedUrl',
'FileAdapterId',
], ],
}; };
// Drafts report show on dashboard // Drafts report show on dashboard
@@ -303,6 +320,7 @@ export default function reportJson(id, userId) {
'Signers.Email', 'Signers.Email',
'Signers.Phone', 'Signers.Phone',
'Placeholders', 'Placeholders',
'FileAdapterId',
], ],
}; };
// contact book report // contact book report
@@ -335,6 +353,7 @@ export default function reportJson(id, userId) {
'Folder.Name', 'Folder.Name',
'URL', 'URL',
'ExtUserPtr.Name', 'ExtUserPtr.Name',
'ExtUserPtr.active_mail_adapter',
'Signers.Name', 'Signers.Name',
'Signers.Email', 'Signers.Email',
'Signers.Phone', 'Signers.Phone',
@@ -342,6 +361,7 @@ export default function reportJson(id, userId) {
'IsPublic', 'IsPublic',
'SharedWith.Name', 'SharedWith.Name',
'SendinOrder', 'SendinOrder',
'FileAdapterId',
], ],
}; };
default: default:
@@ -0,0 +1,60 @@
import uploadFileToS3 from './uploadFiletoS3.js';
export default async function saveToFileAdapter(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
if (!request.params.fileBase64) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide file.');
}
const fileBase64 = request.params.fileBase64;
const id = request.params.id;
try {
const extCls = new Parse.Query('contracts_Users');
extCls.equalTo('UserId', request.user);
extCls.include('TenantId');
const resExt = await extCls.first({ useMasterKey: true });
if (resExt) {
const _resExt = JSON.parse(JSON.stringify(resExt));
const fileAdapters = _resExt?.TenantId.FileAdapters || [];
const fileAdapter = fileAdapters?.find(x => x.id === id) || {};
if (fileAdapter?.accessKeyId) {
const adapterConfig = {
id: id,
fileAdapter: fileAdapter?.fileAdapter,
bucketName: fileAdapter?.bucketName,
region: fileAdapter?.region,
endpoint: fileAdapter?.endpoint,
accessKeyId: fileAdapter?.accessKeyId,
secretAccessKey: fileAdapter?.secretAccessKey,
baseUrl: fileAdapter?.baseUrl,
};
const buffer = Buffer.from(fileBase64, 'base64');
const fileName = request.params.fileName;
const ext = request.params.fileName?.split('.')?.pop();
let mimeType;
if (ext === 'pdf') {
mimeType = 'application/pdf';
} else if (ext === 'png' || ext === 'jpeg' || ext === 'jpg') {
mimeType = `image/${ext}`;
}
try {
const presignedUrl = await uploadFileToS3(buffer, fileName, mimeType, adapterConfig);
return { url: presignedUrl };
} catch (err) {
console.error('Error generate presigned url:', err);
const msg = 'Fileadapter credentials are invalid.';
throw new Parse.Error(400, msg);
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'S3 credentials not found.');
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
console.log('err in savetoS3', err);
throw err;
}
}
@@ -37,7 +37,8 @@ const makeEmail = async (to, from, subject, html, url, pdfName) => {
let Pdf = fs.createWriteStream('test.pdf'); let Pdf = fs.createWriteStream('test.pdf');
const writeToLocalDisk = () => { const writeToLocalDisk = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (useLocal !== 'true') { const isSecure = new URL(url)?.protocol === 'https:';
if (useLocal !== 'true' || isSecure) {
https.get(url, async function (response) { https.get(url, async function (response) {
response.pipe(Pdf); response.pipe(Pdf);
response.on('end', () => resolve('success')); response.on('end', () => resolve('success'));
@@ -68,7 +69,11 @@ const makeEmail = async (to, from, subject, html, url, pdfName) => {
type: 'application/pdf', type: 'application/pdf',
path: './exports/certificate.pdf', path: './exports/certificate.pdf',
}; };
attachments = [file, certificate]; if (fs.existsSync(certificate.path)) {
attachments = [file, certificate];
} else {
attachments = [file];
}
} catch (err) { } catch (err) {
attachments = [file]; attachments = [file];
console.log('Err in read certificate sendmailv3', err); console.log('Err in read certificate sendmailv3', err);
@@ -143,6 +148,14 @@ export default async function sendMailGmailProvider(_extRes, template) {
}, },
}); });
console.log('gmail provider res: ', response?.status); console.log('gmail provider res: ', response?.status);
const certificatePath = './exports/certificate.pdf';
if (fs.existsSync(certificatePath)) {
try {
fs.unlinkSync(certificatePath);
} catch (err) {
console.log('Err in unlink certificate sendmailgmail provider');
}
}
return { code: 200, message: 'Email sent successfully' }; return { code: 200, message: 'Email sent successfully' };
} catch (error) { } catch (error) {
console.error('Error sending email:', error); console.error('Error sending email:', error);

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