Compare commits

...
139 Commits
Author SHA1 Message Date
AndrewandGitHub 025c30b685 Merge pull request #836 from OpenSignLabs/fix_smtp
Fix: smtp is not working
2024-06-12 16:58:42 +05:30
prafull-opensignlabs e742e85f0e Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into fix_smtp 2024-06-12 16:58:21 +05:30
prafull-opensignlabs 476a99feae fix: smtpsecure is not defined 2024-06-12 16:56:28 +05:30
prafull-opensignlabs 29464585b8 fix: SMTP setup is not working 2024-06-12 16:54:09 +05:30
AmolandGitHub 9ef1e22fda Merge pull request #835 from OpenSignLabs/fix_linkcontact 2024-06-12 11:11:40 +05:30
prafull-opensignlabsandGitHub 655401e04b "Fix: Third contact is not receiving mail when second contact is non-existent." 2024-06-12 11:10:25 +05:30
AmolandGitHub 4d7348abc2 Merge pull request #832 from OpenSignLabs/fix_actions 2024-06-11 11:34:58 +05:30
prafull-opensignlabsandGitHub ec68479e98 fix: Ensure real-time updates to records in reports after actions are performed 2024-06-11 11:32:37 +05:30
prafull-opensignlabsandGitHub b387b6b402 Merge pull request #829 from Zathiel/staging
build(deps): minor update
2024-06-11 10:01:54 +05:30
ZathielandGitHub e13baeb2fc Merge pull request #20 from Zathiel/dependabot/npm_and_yarn/apps/OpenSign/babel/runtime-corejs2-7.24.7
build(deps-dev): bump @babel/runtime-corejs2 from 7.24.6 to 7.24.7 in /apps/OpenSign
2024-06-11 00:32:07 +02:00
ZathielandGitHub 52eb8e4a68 Merge pull request #15 from Zathiel/dependabot/npm_and_yarn/apps/OpenSignServer/babel/eslint-parser-7.24.7
build(deps-dev): bump @babel/eslint-parser from 7.24.6 to 7.24.7 in /apps/OpenSignServer
2024-06-11 00:31:19 +02:00
ZathielandGitHub 71189783a8 Merge pull request #18 from Zathiel/dependabot/npm_and_yarn/apps/OpenSignServer/nodemon-3.1.3
build(deps-dev): bump nodemon from 3.1.1 to 3.1.3 in /apps/OpenSignServer
2024-06-11 00:30:31 +02:00
ZathielandGitHub aec9396524 Merge pull request #21 from Zathiel/dependabot/npm_and_yarn/apps/OpenSign/tailwindcss-3.4.4
build(deps-dev): bump tailwindcss from 3.4.3 to 3.4.4 in /apps/OpenSign
2024-06-11 00:29:36 +02:00
ZathielandGitHub d74390b974 Merge pull request #23 from Zathiel/dependabot/npm_and_yarn/apps/OpenSignServer/braces-3.0.3
build(deps): bump braces from 3.0.2 to 3.0.3 in /apps/OpenSignServer
2024-06-11 00:29:03 +02:00
ZathielandGitHub 998fe013c8 Merge pull request #24 from Zathiel/dependabot/npm_and_yarn/braces-3.0.3
build(deps-dev): bump braces from 3.0.2 to 3.0.3
2024-06-11 00:28:48 +02:00
dependabot[bot]andGitHub 06d4c9aeaf build(deps-dev): bump nodemon in /apps/OpenSignServer
Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.1 to 3.1.3.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v3.1.1...v3.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 22:27:44 +00:00
dependabot[bot]andGitHub bc77a9b0a1 build(deps-dev): bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 22:27:19 +00:00
dependabot[bot]andGitHub 07c0390401 build(deps): bump braces from 3.0.2 to 3.0.3 in /apps/OpenSignServer
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 22:27:00 +00:00
ZathielandGitHub c50d753d74 Merge pull request #22 from Zathiel/dependabot/npm_and_yarn/apps/OpenSignServer/grpc/grpc-js-1.10.9
build(deps): bump @grpc/grpc-js from 1.10.8 to 1.10.9 in /apps/OpenSignServer
2024-06-11 00:26:56 +02:00
dependabot[bot]andGitHub 65eef5739d build(deps): bump @grpc/grpc-js in /apps/OpenSignServer
Bumps [@grpc/grpc-js](https://github.com/grpc/grpc-node) from 1.10.8 to 1.10.9.
- [Release notes](https://github.com/grpc/grpc-node/releases)
- [Commits](https://github.com/grpc/grpc-node/compare/@grpc/grpc-js@1.10.8...@grpc/grpc-js@1.10.9)

---
updated-dependencies:
- dependency-name: "@grpc/grpc-js"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 22:22:20 +00:00
dependabot[bot]andGitHub 01eed3664b build(deps-dev): bump tailwindcss from 3.4.3 to 3.4.4 in /apps/OpenSign
Bumps [tailwindcss](https://github.com/tailwindlabs/tailwindcss) from 3.4.3 to 3.4.4.
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/v3.4.4/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/compare/v3.4.3...v3.4.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 16:00:42 +00:00
dependabot[bot]andGitHub 685e09d406 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.24.6 to 7.24.7.
- [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.24.7/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-06-10 16:00:25 +00:00
dependabot[bot]andGitHub 7bacca1f78 build(deps-dev): bump @babel/eslint-parser in /apps/OpenSignServer
Bumps [@babel/eslint-parser](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-parser) from 7.24.6 to 7.24.7.
- [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.24.7/eslint/babel-eslint-parser)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 15:51:30 +00:00
prafull-opensignlabsandGitHub 3764ab5c30 Merge pull request #827 from OpenSignLabs/resolve_main
fix: conflict resolve
2024-06-10 20:40:07 +05:30
RaktimaNXG 2f7a1d67b6 fix: conflict resolve 2024-06-10 20:38:42 +05:30
prafull-opensignlabsandGitHub e073e83ad6 Merge pull request #824 from OpenSignLabs/raktima-opensignlabs-patch-9
fix: a mismatch permits a signer placeholder to sign any placeholder not allocated to that signer issue in the quick send flow.
2024-06-10 18:42:08 +05:30
RaktimaNXG 69140d2139 fix: a mismatch permits a signer placeholder to sign any placeholder not allocated to that signer issue in the quick send flow. 2024-06-10 18:14:28 +05:30
prafull-opensignlabsandGitHub 5518d6932e Merge pull request #823 from OpenSignLabs/raktima-opensignlabs-patch-9
fix: second non existing signer is not receiving the signature request email in the quick-send process.
2024-06-10 12:19:49 +05:30
RaktimaNXG f14e301b6c fix: change condition of email send 2024-06-10 12:15:48 +05:30
RaktimaNXG 901ddf35bb remove console 2024-06-10 11:43:56 +05:30
RaktimaNXG 9aa3166528 fix: second signer is not receiving the signature request email in the quick-send process. 2024-06-10 11:24:35 +05:30
prafull-opensignlabsandGitHub a90e679228 Merge pull request #813 from OpenSignLabs/raktima-opensignlabs-patch-9
fix: display the list of unsigned signers in the 'Yet to Sign' panel using the QuickSend flow.
2024-06-07 20:16:34 +05:30
RaktimaNXG 90b5e3c860 fix: exist contact not recieving maiin in quick send flow 2024-06-07 19:55:40 +05:30
RaktimaNXG 74f6612271 refactor code 2024-06-07 19:33:28 +05:30
RaktimaNXG 5c3f967128 fix: add validation of signature widgetat in quick send flow, update SentToOthers field when document is in inProgress , resolve resend nonexist email issue 2024-06-07 19:07:56 +05:30
RaktimaNXG 2900464272 fix: display the list of unsigned signers in the 'Yet to Sign' panel using the QuickSend flow. 2024-06-07 14:20:36 +05:30
AmolandGitHub 302f38d472 Merge pull request #808 from OpenSignLabs/autoreminder 2024-06-06 19:36:19 +05:30
prafull-opensignlabsandGitHub 0be1816046 Merge pull request #801 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/nodemon-3.1.2
build(deps-dev): bump nodemon from 3.1.1 to 3.1.2 in /apps/OpenSignServer
2024-06-06 11:09:30 +05:30
prafull-opensignlabsandGitHub feb162e03e Merge pull request #809 from Zathiel/staging
build(deps) : react-rnd && mongoose
2024-06-06 11:06:42 +05:30
ZathielandGitHub a793fc3722 Merge pull request #1 from Zathiel/dependabot/npm_and_yarn/apps/OpenSign/react-rnd-10.4.11
build(deps): bump react-rnd from 10.4.10 to 10.4.11 in /apps/OpenSign
2024-06-05 14:26:30 +02:00
ZathielandGitHub ffdabe4a00 Merge pull request #13 from Zathiel/dependabot/npm_and_yarn/apps/OpenSignServer/mongoose-8.4.1
build(deps): bump mongoose from 8.4.0 to 8.4.1 in /apps/OpenSignServer
2024-06-05 14:25:43 +02:00
prafull-opensignlabsandGitHub ba1c3145f3 fix: mail and document count in autoreminder API is not correct 2024-06-05 13:49:20 +05:30
AmolandGitHub 4898c4a851 Merge pull request #807 from OpenSignLabs/feat_sso 2024-06-04 10:06:34 +05:30
prafull-opensignlabs 85aa6b2947 fix: update sso url 2024-06-04 10:03:30 +05:30
AmolandGitHub 3b02d2bcdb fix: update SSO url 2024-06-04 01:01:52 +05:30
dependabot[bot]andGitHub ed01f4a7a0 build(deps): bump mongoose from 8.4.0 to 8.4.1 in /apps/OpenSignServer
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.4.0 to 8.4.1.
- [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.4.0...8.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 15:38:51 +00:00
AmolandGitHub 1f395cf25f Merge pull request #806 from OpenSignLabs/feat_sso 2024-06-03 18:52:09 +05:30
prafull-opensignlabs cf98e73d13 fix: freeze sign in with sso button after click 2024-06-03 18:50:38 +05:30
AmolandGitHub 10112ac20d Merge pull request #805 from OpenSignLabs/feat_sso 2024-06-03 18:28:12 +05:30
prafull-opensignlabs 9a0183274f fix: Set send_email to false to stop receiving mails through API 2024-06-03 18:27:17 +05:30
AmolandGitHub 6b2e16c21d Merge pull request #804 from OpenSignLabs/feat_sso 2024-06-03 15:49:45 +05:30
prafull-opensignlabs 3c51baf1c9 build(deps): updated nth-check dependency for security 2024-06-03 15:46:24 +05:30
dependabot[bot]andGitHub 3f4be1c4f3 build(deps): bump react-rnd from 10.4.10 to 10.4.11 in /apps/OpenSign
Bumps [react-rnd](https://github.com/bokuweb/react-rnd) from 10.4.10 to 10.4.11.
- [Release notes](https://github.com/bokuweb/react-rnd/releases)
- [Commits](https://github.com/bokuweb/react-rnd/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 09:15:34 +00:00
AmolandGitHub 62b36f58f7 Merge pull request #803 from OpenSignLabs/feat_sso 2024-06-03 14:00:27 +05:30
prafull-opensignlabs ba3591c7bc fix: enable mail send feature for non-quick send documents when sendinorder is true 2024-06-03 13:51:15 +05:30
dependabot[bot]andGitHub 1ec11b8733 build(deps-dev): bump nodemon in /apps/OpenSignServer
Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.1 to 3.1.2.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v3.1.1...v3.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 07:16:39 +00:00
prafull-opensignlabsandGitHub ab89d3b71f Merge pull request #799 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/prettier-3.3.0
build(deps-dev): bump prettier from 3.2.5 to 3.3.0 in /apps/OpenSignServer
2024-06-03 12:45:41 +05:30
prafull-opensignlabsandGitHub 1811173648 Merge pull request #796 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/reactour-1.19.4
build(deps): bump reactour from 1.19.3 to 1.19.4 in /apps/OpenSign
2024-06-03 12:44:34 +05:30
AmolandGitHub b218e96107 Merge pull request #802 from OpenSignLabs/fix_email 2024-06-03 12:39:24 +05:30
prafull-opensignlabs 64a0be8776 fix: cannot read property of undefined 'email' 2024-06-03 12:38:17 +05:30
dependabot[bot]andGitHub 414e2d819c build(deps-dev): bump prettier in /apps/OpenSignServer
Bumps [prettier](https://github.com/prettier/prettier) from 3.2.5 to 3.3.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.2.5...3.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 01:11:29 +00:00
dependabot[bot]andGitHub 0769101f28 build(deps): bump reactour from 1.19.3 to 1.19.4 in /apps/OpenSign
Bumps [reactour](https://github.com/elrumordelaluz/reactour) from 1.19.3 to 1.19.4.
- [Release notes](https://github.com/elrumordelaluz/reactour/releases)
- [Commits](https://github.com/elrumordelaluz/reactour/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-03 00:25:47 +00:00
AmolandGitHub 5dc86119d3 Merge pull request #794 from OpenSignLabs/feat_sso 2024-05-31 19:28:52 +05:30
prafull-opensignlabs 4999dc2b18 Fix: password field is not visible for non-sso user 2024-05-31 19:27:18 +05:30
AmolandGitHub 962d36c088 Merge pull request #793 from OpenSignLabs/feat_sso 2024-05-31 19:02:40 +05:30
prafull-opensignlabs d071986aac feat: hide password input for sso and restict sso 2024-05-31 19:00:29 +05:30
prafull-opensignlabs afb4de9d0c Feat: create SSO user account on platform 2024-05-31 18:22:16 +05:30
prafull-opensignlabs bc5c8cac33 feat: existing user can sign in with sso 2024-05-31 11:41:53 +05:30
prafull-opensignlabsandGitHub be93e6a214 Merge pull request #777 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/babel/runtime-corejs2-7.24.6
build(deps-dev): bump @babel/runtime-corejs2 from 7.24.5 to 7.24.6 in /apps/OpenSign
2024-05-31 10:40:37 +05:30
prafull-opensignlabsandGitHub 37748057e3 Merge pull request #791 from OpenSignLabs/public_url
fix: incorrect condition implementation for setting template as public based on isEnableSubscription
2024-05-30 18:10:34 +05:30
RaktimaNXG eb3bb77692 fix: incorrect condition implementation for setting template as public based on isEnableSubscription 2024-05-30 18:08:54 +05:30
prafull-opensignlabsandGitHub df0ce3f086 Merge pull request #788 from OpenSignLabs/public_url
feat: create public profile URLs to add username and develop functionality to make public template from report
2024-05-30 17:53:07 +05:30
AmolandGitHub 0a99b5181e Merge pull request #790 from OpenSignLabs/feat_bulksend 2024-05-30 17:37:45 +05:30
prafull-opensignlabs 8a5d3a0368 Fix: not able to create contract from add user icon in PDF UI 2024-05-30 17:35:40 +05:30
RaktimaNXG c00406e401 fix: template list status issue on pagination 2024-05-30 17:24:33 +05:30
RaktimaNXG aeead23c86 fix: change public profile url 2024-05-30 15:57:16 +05:30
RaktimaNXG 83fe88911f fix: public toggle issue,cloud function error handling 2024-05-30 15:55:16 +05:30
prafull-opensignlabsandGitHub 527bca32d2 Merge pull request #789 from Zathiel/staging
FIX : update USE_LOCAL variable
2024-05-30 15:14:09 +05:30
ZathielandGitHub 9bf4940c2e Merge branch 'OpenSignLabs:staging' into staging 2024-05-30 10:46:55 +02:00
ZathielandGitHub a653df6935 FIX : USE_LOCAL variable 2024-05-30 01:45:05 +02:00
RaktimaNXG dfc6da388c fix: add comment 2024-05-29 19:44:17 +05:30
RaktimaNXG 3e2c822bbc fix: handle condition to save username 2024-05-29 19:39:27 +05:30
RaktimaandGitHub 1a20841a94 Merge branch 'staging' into public_url 2024-05-29 19:31:17 +05:30
RaktimaNXG c6be81923b fix: conflict resolved in userProfile 2024-05-29 19:24:04 +05:30
RaktimaNXG 6b47f6a27b fix: add comment 2024-05-29 18:56:17 +05:30
RaktimaNXG 33b0111ed0 fix: remove console 2024-05-29 18:43:24 +05:30
RaktimaNXG 48fd9e52b3 fix: update isEnableSuscription variable nname 2024-05-29 18:20:16 +05:30
RaktimaNXG d85074b127 fix: username issue to get details from GetPublicUserNmae cloud function 2024-05-29 18:17:07 +05:30
AmolandGitHub 5ada9a411b Merge pull request #787 from OpenSignLabs/feat_bulksend 2024-05-29 17:56:43 +05:30
prafull-opensignlabs 6f50de1ef1 fix: make phone field optional for contact 2024-05-29 17:55:22 +05:30
AmolandGitHub 612150cf4b Merge pull request #786 from OpenSignLabs/feat_bulksend 2024-05-29 16:59:49 +05:30
prafull-opensignlabs 634db83e8e fix: duplicate contact are creatable, Update the the header 'Document signed' in the success message popup 2024-05-29 16:58:35 +05:30
RaktimaNXG b32c8068ef fix: merge changes from staging 2024-05-29 16:07:05 +05:30
RaktimaNXG ae464274b0 fix: update ReportJson file 2024-05-29 15:55:07 +05:30
AmolandGitHub c5ceba8d57 Merge pull request #785 from OpenSignLabs/feat_bulksend
fix: issue of something went wrong due to quick send
2024-05-29 13:56:05 +05:30
prafull-opensignlabs 903d626c27 fix: issue of something went wrong due to quick send 2024-05-29 13:05:57 +05:30
prafull-opensignlabsandGitHub e5b4fb123f Merge pull request #782 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/nodemon-3.1.1
build(deps-dev): bump nodemon from 3.1.0 to 3.1.1 in /apps/OpenSignServer
2024-05-28 20:21:00 +05:30
prafull-opensignlabsandGitHub 116893c1b4 Merge pull request #781 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/babel/eslint-parser-7.24.6
build(deps-dev): bump @babel/eslint-parser from 7.24.5 to 7.24.6 in /apps/OpenSignServer
2024-05-28 20:20:36 +05:30
prafull-opensignlabsandGitHub 592254a9a3 Merge pull request #778 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/lint-staged-15.2.5
build(deps-dev): bump lint-staged from 15.2.4 to 15.2.5 in /apps/OpenSign
2024-05-28 20:20:19 +05:30
AmolandGitHub f52c6e24fe Merge pull request #784 from OpenSignLabs/feat_bulksend 2024-05-28 19:23:50 +05:30
prafull-opensignlabs 70ae1e24fc fix: merge conflict 2024-05-28 19:18:41 +05:30
prafull-opensignlabs c491595fc8 Fix: issues in quick send 2024-05-28 18:56:56 +05:30
AmolandGitHub 6875135ede Merge pull request #783 from OpenSignLabs/feat_bulksend
Fix: issues in quick send
2024-05-28 10:42:33 +05:30
RaktimaNXG 662049d878 fix: remove console 2024-05-28 09:50:38 +05:30
RaktimaNXG 1764d7f9ba feat: create public profile URLs for users and develop functionality to make the report template public 2024-05-27 21:08:05 +05:30
prafull-opensignlabs 07933b56b1 Fix: issue of 'something went wrong' err in guest login flow when using quick send 2024-05-27 20:53:38 +05:30
prafull-opensignlabs f6ba4db62d Fix: Role, type are not visible on placeholder for non-exist contact 2024-05-27 20:06:06 +05:30
prafull-opensignlabs 5bd36f91d9 Fix: issue with multiple linking user in quick send 2024-05-27 18:46:37 +05:30
prafull-opensignlabs c199c3019e feat: add message for all signers linked in template for quick send 2024-05-27 14:50:17 +05:30
prafull-opensignlabs facbc8e76e Fix: issue of showing 'Please add Signers or Roles in template' even when roles are already exist in quick send 2024-05-27 12:59:43 +05:30
prafull-opensignlabs c3c6f6bab3 fix: send mail only first signer if sendinorder, takeout quick send btn from 3 dots 2024-05-27 11:59:25 +05:30
prafull-opensignlabsandGitHub f5e216fedc Merge pull request #768 from OpenSignLabs/raktima-opensignlabs-patch-8
feat: add download, certificate & print buttons on the document completion popup
2024-05-27 10:40:31 +05:30
RaktimaandGitHub c40a923388 Merge branch 'staging' into raktima-opensignlabs-patch-8 2024-05-27 10:30:38 +05:30
dependabot[bot]andGitHub a86f4fd9b7 build(deps-dev): bump nodemon in /apps/OpenSignServer
Bumps [nodemon](https://github.com/remy/nodemon) from 3.1.0 to 3.1.1.
- [Release notes](https://github.com/remy/nodemon/releases)
- [Commits](https://github.com/remy/nodemon/compare/v3.1.0...v3.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 00:27:27 +00:00
dependabot[bot]andGitHub 59f6d022cf build(deps-dev): bump @babel/eslint-parser in /apps/OpenSignServer
Bumps [@babel/eslint-parser](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-parser) from 7.24.5 to 7.24.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.24.6/eslint/babel-eslint-parser)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 00:27:04 +00:00
dependabot[bot]andGitHub b7beeee260 build(deps-dev): bump lint-staged in /apps/OpenSign
Bumps [lint-staged](https://github.com/okonet/lint-staged) from 15.2.4 to 15.2.5.
- [Release notes](https://github.com/okonet/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/master/CHANGELOG.md)
- [Commits](https://github.com/okonet/lint-staged/compare/v15.2.4...v15.2.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-27 00:14:13 +00:00
dependabot[bot]andGitHub 31c861cbe4 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.24.5 to 7.24.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.24.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-05-27 00:13:57 +00:00
AmolandGitHub 6a9408b7b4 docs: add Zathiel as a contributor for security 2024-05-24 19:24:52 +05:30
AmolandGitHub a9fd264a01 Merge pull request #772 from OpenSignLabs/feat_bulksend
Feat: quick send feature to create & send multiple documents at a time
2024-05-24 16:42:08 +05:30
prafull-opensignlabs 6c77bbfe75 feat: quick send feature to create & send multiple documents at a time 2024-05-24 16:29:03 +05:30
prafull-opensignlabs 890c3d2b1f Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into feat_bulksend 2024-05-24 11:34:58 +05:30
AmolandGitHub 3ede441f54 Merge pull request #771 from OpenSignLabs/staging
v1.7.4
2024-05-24 10:29:10 +05:30
AmolandGitHub 2433ad5957 Merge pull request #770 from OpenSignLabs/build_dep 2024-05-24 10:23:10 +05:30
prafull-opensignlabs fffd907d63 Fix: handle owner subscription has expired error in guest login 2024-05-24 10:13:34 +05:30
AmolandGitHub 51edc339cd Merge pull request #769 from OpenSignLabs/staging
v1.7.3
2024-05-23 18:22:49 +05:30
RaktimaNXG 08a97707b7 fix: completion popup design issue in tablet view 2024-05-23 13:46:17 +05:30
RaktimaNXG d99fecd46e feat: add download, certificate & print buttons on the document completion popup 2024-05-23 12:52:10 +05:30
prafull-opensignlabs 7e4f12e41a feat: bulk send is working for exist contact 2024-05-22 19:31:32 +05:30
prafull-opensignlabs ffc8e4b23e Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into feat_bulksend 2024-05-22 13:44:14 +05:30
prafull-opensignlabs cd1a6fdaf6 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into feat_bulksend 2024-05-22 12:11:46 +05:30
prafull-opensignlabs ca6cb60d78 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into feat_bulksend 2024-05-22 09:49:51 +05:30
prafull-opensignlabs aca2848406 add Ui for bulk send, changes in reportjson 2024-05-21 20:26:46 +05:30
AmolandGitHub 741efff1fa Merge pull request #739 from OpenSignLabs/staging
v1.7.2
2024-05-14 18:56:09 +05:30
AmolandGitHub 38b7311e66 Merge pull request #736 from OpenSignLabs/staging
v1.7.1
2024-05-14 10:45:00 +05:30
AmolandGitHub e0ccf049a6 Merge pull request #734 from OpenSignLabs/staging
v1.7.0
2024-05-14 01:49:55 +05:30
AmolandGitHub 498bd18630 Merge pull request #717 from OpenSignLabs/staging
v1.6.2
2024-05-10 16:02:39 +05:30
AmolandGitHub b530ef882a Merge pull request #715 from OpenSignLabs/staging
v1.6.1
2024-05-10 11:18:58 +05:30
AmolandGitHub 850d14064a Merge pull request #710 from OpenSignLabs/staging
v1.6.0
2024-05-09 16:38:17 +05:30
prafull-opensignlabsandGitHub 273ada837b Merge pull request #703 from OpenSignLabs/staging
v1.5.9
2024-05-08 16:12:46 +05:30
69 changed files with 8033 additions and 5101 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ DO_SECRET_ACCESS_KEY=
# Digital ocean spaces region or AWS s3 region
DO_REGION=us-west
# local storage
USE_LOCAL=TRUE
USE_LOCAL=true
# Email mailgun or set SMTP_ENABLE true to use SMTP config (The app will not initialize if any of these 3 variables are not set) *********************************************************************************************************************
MAILGUN_API_KEY=
+1 -1
View File
@@ -136,7 +136,7 @@ We would like to thank all our contributors and users for their support and feed
<td align="center" valign="top" width="14.28%"><a href="https://github.com/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/daniel-mutwiri"><img src="https://avatars.githubusercontent.com/u/8936960?v=4?s=100" width="100px;" alt="Daniel Mutwiri"/><br /><sub><b>Daniel Mutwiri</b></sub></a><br /><a href="#code-daniel-mutwiri" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/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>
</tr>
</tbody>
</table>
+3899 -3379
View File
File diff suppressed because it is too large Load Diff
+11 -7
View File
@@ -4,13 +4,14 @@
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@radix-ui/themes": "^2.0.3",
"@radix-ui/themes": "^3.0.5",
"@react-pdf/renderer": "^3.4.4",
"@reduxjs/toolkit": "^2.2.5",
"axios": "^1.7.2",
"file-saver": "^2.0.5",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"nth-check": "^2.1.1",
"parse": "^5.1.0",
"pdf-lib": "^1.17.1",
"print-js": "^1.6.0",
@@ -29,10 +30,10 @@
"react-helmet": "^6.1.0",
"react-konva": "^18.2.10",
"react-modal": "^3.16.1",
"react-pdf": "^8.0.2",
"react-pdf": "^9.0.0",
"react-quill": "^2.0.0",
"react-redux": "^9.1.2",
"react-rnd": "^10.4.10",
"react-rnd": "^10.4.11",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1",
@@ -40,7 +41,7 @@
"react-signature-canvas": "^1.0.6",
"react-tooltip": "^5.26.4",
"react-web-share": "^2.0.2",
"reactour": "^1.19.3",
"reactour": "^1.19.4",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"regex-parser": "^2.3.0",
@@ -84,16 +85,19 @@
}
},
"devDependencies": {
"@babel/runtime-corejs2": "^7.24.5",
"@babel/runtime-corejs2": "^7.24.7",
"autoprefixer": "^10.4.19",
"commitizen": "^4.3.0",
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.34.1",
"lint-staged": "^15.2.4",
"lint-staged": "^15.2.5",
"postcss": "^8.4.38",
"prettier": "^2.8.8",
"pretty-quick": "^3.3.1",
"tailwindcss": "^3.4.3"
"tailwindcss": "^3.4.4"
},
"overrides": {
"nth-check": "$nth-check"
}
}
+3 -2
View File
@@ -16,6 +16,7 @@ import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage";
import { isEnableSubscription } from "./constant/const";
import SSOVerify from "./pages/SSOVerify";
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
@@ -28,8 +29,7 @@ const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
const ManageSign = lazy(() => import("./pages/Managesign"));
const GenerateToken = lazy(() => import("./pages/GenerateToken"));
const Webhook = lazy(() => import("./pages/Webhook"));
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.mjs`;
const Loader = () => {
return (
<div
@@ -181,6 +181,7 @@ function App() {
element={<PdfRequestFiles />}
/>
</Route>
<Route path="/sso" element={<SSOVerify />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
+150 -136
View File
@@ -23,7 +23,7 @@ const AddSigner = (props) => {
);
if (savedUserDetails && addYourself) {
setName(savedUserDetails.name);
setPhone(savedUserDetails.phone);
setPhone(savedUserDetails?.phone || "");
setEmail(savedUserDetails.email);
}
}, [addYourself]);
@@ -50,145 +50,161 @@ const AddSigner = (props) => {
e.stopPropagation();
setIsLoader(true);
if (localStorage.getItem("TenantId")) {
try {
const contactQuery = new Parse.Object("contracts_Contactbook");
contactQuery.set("Name", name);
contactQuery.set("Phone", phone);
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest");
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", name);
_user.set("username", email);
_user.set("email", email);
_user.set("phone", phone);
_user.set("password", phone);
const user = await _user.save();
if (user) {
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessionToken: localStorage.getItem("accesstoken")
};
const body = {
appName: localStorage.getItem("_appName"),
roleName: "contracts_Guest",
userId: user.id
};
await axios.post(roleurl, body, { headers: headers });
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey]
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
const user = Parse.User.current();
const query = new Parse.Query("contracts_Contactbook");
query.equalTo("CreatedBy", user);
query.notEqualTo("IsDeleted", true);
query.equalTo("Email", email);
const res = await query.first();
// console.log(res);
if (!res) {
const contactQuery = new Parse.Object("contracts_Contactbook");
contactQuery.set("Name", name);
if (phone) {
contactQuery.set("Phone", phone);
}
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest");
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", name);
_user.set("username", email);
_user.set("email", email);
_user.set("password", email);
if (phone) {
_user.set("phone", phone);
}
const user = await _user.save();
if (user) {
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessionToken: localStorage.getItem("accesstoken")
};
const body = {
appName: localStorage.getItem("_appName"),
roleName: "contracts_Guest",
userId: user.id
};
await axios.post(roleurl, body, { headers: headers });
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey]
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: email };
const userRes = await Parse.Cloud.run("getUserId", params);
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessionToken: localStorage.getItem("accesstoken")
};
const body = {
appName: localStorage.getItem("_appName"),
roleName: "contracts_Guest",
userId: userRes.id
};
await axios.post(roleurl, body, { headers: headers });
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey]
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
}
} else {
alert("Contact already exist!");
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: email };
const userRes = await Parse.Cloud.run("getUserId", params);
const roleurl = `${parseBaseUrl}functions/AddUserToRole`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessionToken: localStorage.getItem("accesstoken")
};
const body = {
appName: localStorage.getItem("_appName"),
roleName: "contracts_Guest",
userId: userRes.id
};
await axios.post(roleurl, body, { headers: headers });
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey]
});
}
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
props.handleUserData(parseData);
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
console.log("err in fetch contact", err);
setIsLoader(false);
alert("something went wrong, please try again later");
}
} catch (err) {
// console.log("err", err);
} else {
setIsLoader(false);
alert("something went wrong!");
}
}else{
alert('something went wrong, please try again later')
alert("something went wrong, please try again later");
}
};
@@ -266,10 +282,10 @@ const AddSigner = (props) => {
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={(e) => setEmail(e.target.value?.toLowerCase())}
required
disabled={addYourself}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs lowercase"
/>
</div>
<div className="mb-3">
@@ -278,14 +294,12 @@ const AddSigner = (props) => {
className="block text-xs text-gray-700 font-semibold"
>
Phone
<span style={{ color: "red", fontSize: 13 }}> *</span>
</label>
<input
type="text"
id="phone"
value={phone}
onChange={(e) => setPhone(e.target.value)}
required
disabled={addYourself}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
/>
+4 -2
View File
@@ -62,8 +62,10 @@ const AddUser = (props) => {
_user.set("name", name);
_user.set("username", email);
_user.set("email", email);
_user.set("phone", phone);
_user.set("password", phone);
_user.set("password", email);
if (phone) {
_user.set("phone", phone);
}
const user = await _user.save();
if (user) {
+261
View File
@@ -0,0 +1,261 @@
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import SuggestionInput from "./shared/fields/SuggestionInput";
const BulkSendUi = (props) => {
const [forms, setForms] = useState([]);
const [formId, setFormId] = useState(2);
const formRef = useRef(null);
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [allowedForm, setAllowedForm] = useState(0);
const [isSignatureExist, setIsSignatureExist] = useState();
const allowedSigners = 50;
useEffect(() => {
signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function to check atleast one signature field exist
const signatureExist = () => {
const getPlaceholder = props.item?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
};
useEffect(() => {
if (scrollOnNextUpdate && formRef.current) {
formRef.current.scrollIntoView({
behavior: "smooth",
block: "end",
inline: "nearest"
});
setScrollOnNextUpdate(false);
}
}, [forms, scrollOnNextUpdate]);
useEffect(() => {
(() => {
if (props?.Placeholders?.length > 0) {
let users = [];
props?.Placeholders?.forEach((element) => {
if (!element.signerObjId) {
users = [
...users,
{
fieldId: element.Id,
email: "",
label: element.Role,
signer: {}
}
];
}
});
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
const totalForms = Math.floor(allowedSigners / users?.length);
setAllowedForm(totalForms);
}
})();
// eslint-disable-next-line
}, []);
const handleInputChange = (index, signer, fieldIndex) => {
const newForms = [...forms];
newForms[index].fields[fieldIndex].email = signer?.Email
? signer?.Email
: signer || "";
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
setForms(newForms);
};
const handleAddForm = (e) => {
e.preventDefault();
// Check if the quick send limit has been reached
if (forms?.length < allowedForm) {
if (props?.Placeholders.length > 0) {
let newForm = [];
props?.Placeholders?.forEach((element) => {
if (!element.signerObjId) {
newForm = [
...newForm,
{
fieldId: element.Id,
email: "",
label: element.Role,
signer: {}
}
];
}
});
setForms([...forms, { Id: formId, fields: newForm }]);
}
setFormId(formId + 1);
setScrollOnNextUpdate(true);
} else {
// If the limit has been reached, throw an error with the appropriate message
alert("Quick send reached limit.");
}
};
const handleRemoveForm = (index) => {
const updatedForms = forms.filter((_, i) => i !== index);
setForms(updatedForms);
};
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmit(true);
// Create a copy of Placeholders array from props.item
let Placeholders = [...props.item.Placeholders];
// Initialize an empty array to store updated documents
let Documents = [];
// Loop through each form
forms.forEach((form) => {
// Map through the copied Placeholders array to update email values
const updatedPlaceholders = Placeholders.map((placeholder) => {
// Find the field in the current form that matches the placeholder Id
const field = form.fields.find(
(element) => parseInt(element.fieldId) === placeholder.Id
);
// If a matching field is found, update the email value in the placeholder
const signer = field?.signer?.objectId ? field.signer : {};
if (field) {
return {
...placeholder,
email: field.email,
signerObjId: field?.signer?.objectId || "",
signerPtr: signer
};
}
// If no matching field is found, keep the placeholder as is
return placeholder;
});
// Push a new document object with updated Placeholders into the Documents array
Documents.push({ ...props.item, Placeholders: updatedPlaceholders });
});
// console.log("Documents ", Documents);
await batchQuery(Documents);
};
const batchQuery = async (Documents) => {
const serverUrl = localStorage.getItem("baseUrl");
const functionsUrl = `${serverUrl}functions/batchdocuments`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const params = {
Documents: JSON.stringify(Documents)
};
try {
const res = await axios.post(functionsUrl, params, { headers: headers });
// console.log("res ", res);
if (res.data && res.data.result) {
props.handleClose("success", Documents?.length);
}
} catch (err) {
console.log("Err ", err);
props.handleClose("error", 0);
} finally {
setIsSubmit(false);
}
};
return (
<>
{isSubmit && (
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-40">
<div
style={{
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37 "
></div>
</div>
)}
{props.Placeholders?.length > 0 ? (
isSignatureExist ? (
<>
{props.Placeholders?.some((x) => !x.signerObjId) ? (
<form onSubmit={handleSubmit}>
<div className=" min-h-max max-h-[250px] overflow-y-auto">
{forms?.map((form, index) => (
<div
key={form.Id}
className="p-3 rounded-xl border-[1px] border-gray-400 m-4 bg-white text-black grid grid-cols-1 md:grid-cols-2 gap-2 relative"
>
{form?.fields?.map((field, fieldIndex) => (
<div className="flex flex-col " key={field.fieldId}>
<label>{field.label}</label>
<SuggestionInput
required
type="email"
value={field.value}
index={fieldIndex}
onChange={(signer) =>
handleInputChange(index, signer, fieldIndex)
}
/>
</div>
))}
{forms?.length > 1 && (
<button
onClick={() => handleRemoveForm(index)}
className="absolute right-3 top-1 border border-gray-300 rounded-lg px-2 py-1"
>
<i className="fa-solid fa-trash"></i>
</button>
)}
<div ref={formRef}></div>
</div>
))}
</div>
<div className="flex flex-col mx-4 mb-4 gap-3">
<button
onClick={handleAddForm}
className="bg-[#32a3ac] p-2 text-white w-full rounded-full focus:outline-none"
>
<i className="fa-solid fa-plus"></i> <span>Add new</span>
</button>
<button
type="submit"
className="bg-[#32a3ac] p-2 text-white w-full rounded-full focus:outline-none"
>
<i className="fa-solid fa-paper-plane"></i>{" "}
<span>Send</span>
</button>
</div>
</form>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
All roles in this document are currently linked to contacts. To
&apos;quick send&apos; copies of this template to multiple
signers, please ensure that at least one role is not linked to
any contact.
</div>
)}
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
Please ensure there&#39;s at least one signature widget added for
all recipients.
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
Please add at least one role to this template in order to &apos;quick
send&apos; copies of it to multiple signers.
</div>
)}
</>
);
};
export default BulkSendUi;
+3 -1
View File
@@ -44,7 +44,9 @@ const Header = ({ showSidebar }) => {
const closeDropdown = () => {
setIsOpen(false);
Parse.User.logOut();
if (Parse?.User?.current()) {
Parse.User.logOut();
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let appName = localStorage.getItem("appName");
@@ -49,7 +49,7 @@ const LoginFacebook = ({
const extRes = await Parse.Cloud.run("getUserDetails", params);
// console.log("extRes ", extRes);
if (extRes) {
const params = { ...details, Phone: extRes.get("Phone") };
const params = { ...details, Phone: extRes?.get("Phone") || "" };
const payload = await Parse.Cloud.run("facebooksign", params);
// console.log("payload ", payload);
if (payload && payload.sessiontoken) {
@@ -59,7 +59,7 @@ const LoginFacebook = ({
const LocalUserDetails = {
name: details.Name,
email: details.Email,
phone: extRes.get("Phone"),
phone: extRes?.get("Phone") || "",
company: extRes.get("Company")
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
@@ -75,7 +75,7 @@ const LoginFacebook = ({
const handleSubmitbtn = async () => {
if (userDetails.Phone && userDetails.Company) {
setThirdpartyLoader(true);
const params = { ...fBDetails, Phone: userDetails.Phone };
const params = { ...fBDetails, Phone: userDetails?.Phone || "" };
const payload = await Parse.Cloud.run("facebooksign", params);
// console.log("payload ", payload);
@@ -85,7 +85,7 @@ const LoginFacebook = ({
name: userDetails.Name,
email: userDetails.Email,
// "passsword":userDetails.Phone,
phone: userDetails.Phone,
phone: userDetails?.Phone || "",
role: "contracts_User",
company: userDetails.Company
}
@@ -96,7 +96,7 @@ const LoginFacebook = ({
const LocalUserDetails = {
name: userDetails.Name,
email: userDetails.Email,
phone: userDetails.Phone,
phone: userDetails?.Phone || "",
company: userDetails.Company
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
+5 -5
View File
@@ -110,7 +110,7 @@ const GoogleSignInBtn = ({
const extRes = await Parse.Cloud.run("getUserDetails", params);
// console.log("extRes ", extRes);
if (extRes) {
const params = { ...details, Phone: extRes.get("Phone") };
const params = { ...details, Phone: extRes?.get("Phone") || "" };
const payload = await Parse.Cloud.run("googlesign", params);
// console.log("payload ", payload);
if (payload && payload.sessiontoken) {
@@ -121,7 +121,7 @@ const GoogleSignInBtn = ({
const LocalUserDetails = {
name: details.Name,
email: details.Gmail,
phone: extRes.get("Phone"),
phone: extRes?.get("Phone") || "",
company: extRes.get("Company")
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
@@ -139,7 +139,7 @@ const GoogleSignInBtn = ({
setThirdpartyLoader(true);
// e.preventDefault()
// console.log("handelSubmit", userDetails);
const params = { ...googleDetails, Phone: userDetails.Phone };
const params = { ...googleDetails, Phone: userDetails?.Phone };
const payload = await Parse.Cloud.run("googlesign", params);
// console.log("payload ", payload);
@@ -149,7 +149,7 @@ const GoogleSignInBtn = ({
name: userDetails.Name,
email: userDetails.Email,
// "passsword":userDetails.Phone,
phone: userDetails.Phone,
phone: userDetails?.Phone || "",
role: "contracts_User",
company: userDetails.Company,
jobTitle: userDetails.Destination
@@ -161,7 +161,7 @@ const GoogleSignInBtn = ({
const LocalUserDetails = {
name: userDetails.Name,
email: userDetails.Email,
phone: userDetails.Phone,
phone: userDetails?.Phone || "",
company: userDetails.Company
// jobTitle: userDetails.JobTitle
};
+62 -137
View File
@@ -1,15 +1,15 @@
import React, { useState } from "react";
import PrevNext from "./PrevNext";
import printModule from "print-js";
import { getBase64FromUrl } from "../../constant/Utils";
import { saveAs } from "file-saver";
import {
handleDownloadCertificate,
handleDownloadPdf,
handleToPrint
} from "../../constant/Utils";
import "../../styles/signature.css";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { useNavigate } from "react-router-dom";
import { themeColor } from "../../constant/const";
import axios from "axios";
import ModalUi from "../../primitives/ModalUi";
import { appInfo } from "../../constant/appinfo";
function Header({
isPdfRequestFiles,
@@ -40,7 +40,7 @@ function Header({
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
const isMobile = window.innerWidth < 767;
const navigate = useNavigate();
const [isCertificate, setIsCertificate] = useState(false);
const [isDownloading, setIsDownloading] = useState("");
const isGuestSigner = localStorage.getItem("isGuestSigner");
//for go to previous page
function previousPage() {
@@ -59,123 +59,6 @@ function Header({
setIsDecline(currentDecline);
};
//function for print digital sign pdf
const handleToPrint = async (event) => {
event.preventDefault();
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
`${appInfo.baseUrl}/functions/getsignedurl`,
{ url: pdfUrl },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": appInfo.appId,
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
const url = axiosRes.data.result;
const pdf = await getBase64FromUrl(url);
const isAndroidDevice = navigator.userAgent.match(/Android/i);
const isAppleDevice =
(/iPad|iPhone|iPod/.test(navigator.platform) ||
(navigator.platform === "MacIntel" &&
navigator.maxTouchPoints > 1)) &&
!window.MSStream;
if (isAndroidDevice || isAppleDevice) {
const byteArray = Uint8Array.from(
atob(pdf)
.split("")
.map((char) => char.charCodeAt(0))
);
const blob = new Blob([byteArray], { type: "application/pdf" });
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl, "_blank");
} else {
printModule({ printable: pdf, type: "pdf", base64: true });
}
} catch (err) {
console.log("err in getsignedurl", err);
alert("something went wrong, please try again later.");
}
};
//handle download signed pdf
const handleDownloadPdf = async () => {
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
`${appInfo.baseUrl}/functions/getsignedurl`,
{ url: pdfUrl },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": appInfo.appId,
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
const url = axiosRes.data.result;
saveAs(url, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
} catch (err) {
console.log("err in getsignedurl", err);
alert("something went wrong, please try again later.");
}
};
const sanitizeFileName = (pdfName) => {
// Replace spaces with underscore
return pdfName.replace(/ /g, "_");
};
//handle download signed pdf
const handleDownloadCertificate = async () => {
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try {
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
} catch (err) {
console.log("err in download in certificate", err);
}
} else {
setIsCertificate(true);
try {
const data = {
docId: pdfDetails[0]?.objectId
};
const docDetails = await axios.post(
`${localStorage.getItem("baseUrl")}functions/getDocument`,
data,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
}
}
);
if (docDetails.data && docDetails.data.result) {
const doc = docDetails.data.result;
if (doc?.CertificateUrl) {
await fetch(doc?.CertificateUrl);
const certificateUrl = doc?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
setIsCertificate(false);
} else {
setIsCertificate(true);
}
}
} catch (err) {
console.log("err in download in certificate", err);
alert("something went wrong, please try again later.");
}
}
};
return (
<div style={{ padding: "5px 0px 5px 0px" }} className="mobileHead">
{isMobile && isShowHeader ? (
@@ -248,7 +131,9 @@ function Header({
>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleDownloadPdf()}
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<div
style={{
@@ -267,7 +152,12 @@ function Header({
{isCompleted && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleDownloadCertificate()}
onClick={() =>
handleDownloadCertificate(
pdfDetails,
setIsDownloading
)
}
>
<div
style={{
@@ -306,7 +196,9 @@ function Header({
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={handleToPrint}
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading)
}
>
<div
style={{
@@ -388,7 +280,13 @@ function Header({
>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() => handleDownloadPdf()}
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i
className="fa fa-arrow-down mr-[5px]"
@@ -475,7 +373,9 @@ function Header({
{isCompleted && (
<button
type="button"
onClick={() => handleDownloadCertificate()}
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsDownloading)
}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
>
<i
@@ -487,7 +387,7 @@ function Header({
)}
<button
onClick={handleToPrint}
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
>
@@ -498,7 +398,9 @@ function Header({
<button
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() => handleDownloadPdf()}
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">Download</span>
@@ -547,7 +449,13 @@ function Header({
>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() => handleDownloadPdf()}
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i
className="fa fa-arrow-down mr-[5px]"
@@ -567,7 +475,9 @@ function Header({
{isCompleted && (
<button
type="button"
onClick={() => handleDownloadCertificate()}
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsDownloading)
}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
>
<i
@@ -578,7 +488,7 @@ function Header({
</button>
)}
<button
onClick={handleToPrint}
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
>
@@ -588,7 +498,9 @@ function Header({
<button
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() => handleDownloadPdf()}
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">Download</span>
@@ -628,12 +540,25 @@ function Header({
)}
</div>
)}
{isDownloading === "pdf" && (
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<div
style={{ fontSize: "45px", color: "#3dd3e0" }}
className="loader-37"
></div>
</div>
)}
<ModalUi
isOpen={isCertificate}
title={"Generating certificate"}
handleClose={() => setIsCertificate(false)}
isOpen={isDownloading === "certificate"}
title={
isDownloading === "certificate"
? "Generating certificate"
: "PDF Download"
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center">
{isDownloading === "certificate"}{" "}
<p>
Your completion certificate is being generated. Please wait
momentarily.
+23 -23
View File
@@ -204,34 +204,34 @@ function RenderPdf({
};
//function for render placeholder block over pdf document
const checkSignedSignes = (data) => {
const checkSign = signedSigners.filter(
(sign) => sign.objectId === data.signerObjId
let checkSign = [];
//condition to handle quick send flow and using normal request sign flow
checkSign = signedSigners.filter(
(sign) => sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
);
if (data.signerObjId === signerObjectId) {
setCurrentSigner(true);
}
const handleAllUserName = (Id, Role, type) => {
return pdfDetails[0].Signers.map((signerData, key) => {
return (
signerData.objectId === data.signerObjId && (
<React.Fragment key={key}>
{signerData?.Name && (
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{signerData.Name}
</div>
)}
{type && (
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
)}
{Role && (
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{`(${Role})`}
</div>
)}
</React.Fragment>
)
);
});
return (
<React.Fragment>
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{
pdfDetails[0].Signers?.find(
(signer) => signer.objectId === data.signerObjId
)?.Name
}
</div>
{type && (
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
)}
{Role && (
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{`(${Role})`}
</div>
)}
</React.Fragment>
);
};
return (
@@ -0,0 +1,92 @@
import React, { useState, useEffect, useRef } from "react";
import { findContact } from "../../../constant/Utils";
const SuggestionInput = (props) => {
const [inputValue, setInputValue] = useState(props?.value || "");
const [suggestions, setSuggestions] = useState([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const ref = useRef(null);
useEffect(() => {
document.addEventListener("mousedown", Clickout);
return () => {
document.removeEventListener("mousedown", Clickout);
};
}, []);
const Clickout = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setShowSuggestions(false);
}
};
// create debounce to avoid unnecessay api calls
useEffect(() => {
let timer;
if (inputValue) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
(async () => {
const res = await findContact(inputValue);
if (res?.length > 0) {
setSuggestions(res);
setShowSuggestions(true);
} else {
setSuggestions(res);
setShowSuggestions(false);
}
})();
}, 1000);
}
return () => clearTimeout(timer);
}, [inputValue]);
const handleInputChange = async (e) => {
const value = e.target.value;
setInputValue(value?.toLowerCase());
if (props.onChange) {
props.onChange(value);
}
if (value.trim() === "") {
setSuggestions([]);
setShowSuggestions(false);
return;
}
};
const handleSuggestionClick = (suggestion) => {
setInputValue(suggestion.Email);
setSuggestions([]);
setShowSuggestions(false);
if (props.onChange) {
props.onChange(suggestion);
}
};
return (
<div className="flex flex-col items-center relative">
<input
type={props?.type || "text"}
value={inputValue}
onChange={handleInputChange}
placeholder="Enter Email..."
className="w-full border-[1px] border-gray-400 p-2 text-black rounded lowercase"
required={props.required}
/>
{showSuggestions && (
<ul
ref={ref}
className="absolute z-50 left-0 top-[2.55rem] w-full max-h-[100px] overflow-auto bg-white border border-gray-300 rounded shadow-md"
>
{suggestions.map((suggestion, index) => (
<li
key={index}
className="py-2 px-2 w-full text-sm cursor-pointer hover:bg-gray-100"
onClick={() => handleSuggestionClick(suggestion)}
>
{suggestion.Email}
</li>
))}
</ul>
)}
</div>
);
};
export default SuggestionInput;
+5 -1
View File
@@ -10,7 +10,11 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
? `/${item.pageType}/${item.objectId}`
: `/${item.objectId}`
}
className="mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4 cursor-pointer focus:text-[#0056b3] focus:bg-[#eef1f5]"
className={({ isActive }) =>
`${
isActive ? "text-[#0056b3] bg-[#eef1f5]" : ""
} mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4 cursor-pointer`
}
onClick={closeSidebar}
tabIndex={isOpen ? 0 : -1}
role="menuitem"
+163 -7
View File
@@ -4,13 +4,20 @@ import { isEnableSubscription, themeColor } from "./const";
import React from "react";
import { rgb } from "pdf-lib";
import Parse from "parse";
import { appInfo } from "./appinfo";
import { saveAs } from "file-saver";
import printModule from "print-js";
export const isMobile = window.innerWidth < 767;
export const textInputWidget = "text input";
export const textWidget = "text";
export const radioButtonWidget = "radio button";
export const openInNewTab = (url) => {
window.open(url, "_blank", "noopener,noreferrer");
export const openInNewTab = (url, target) => {
if (target) {
window.open(url, target, "noopener,noreferrer");
} else {
window.open(url, "_blank", "noopener,noreferrer");
}
};
export async function fetchSubscription(
@@ -19,11 +26,12 @@ export async function fetchSubscription(
isGuestSign = false
) {
try {
const extClass = localStorage.getItem("Extand_Class");
const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class);
// console.log("extClass ", extClass);
let extUser;
if (extClass && extClass.length > 0) {
const jsonSender = JSON.parse(extClass);
extUser = jsonSender[0].objectId;
extUser = extClass[0].objectId;
} else {
extUser = extUserId;
}
@@ -661,6 +669,7 @@ export const createDocument = async (template, placeholders, signerData) => {
Name: Doc.Name,
URL: Doc.URL,
SignedUrl: Doc.SignedUrl,
SentToOthers: Doc.SentToOthers,
Description: Doc.Description,
Note: Doc.Note,
Placeholders: placeholdersArr,
@@ -1350,8 +1359,8 @@ export const multiSignEmbed = async (
position.type === radioButtonWidget
? 10
: position.type === "checkbox"
? 10
: newUpdateHeight;
? 10
: newUpdateHeight;
const newHeight = ind ? (ind > 0 ? widgetHeight : 0) : widgetHeight;
if (signyourself) {
@@ -2029,3 +2038,150 @@ export const handleSendOTP = async (email) => {
alert(error.message);
}
};
//handle download signed pdf
export const handleDownloadPdf = async (
pdfDetails,
pdfUrl,
setIsDownloading
) => {
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
setIsDownloading("pdf");
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
`${appInfo.baseUrl}/functions/getsignedurl`,
{ url: pdfUrl },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": appInfo.appId,
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
const url = axiosRes.data.result;
saveAs(url, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
setIsDownloading("");
} catch (err) {
console.log("err in getsignedurl", err);
setIsDownloading("");
alert("something went wrong, please try again later.");
}
};
export const sanitizeFileName = (pdfName) => {
// Replace spaces with underscore
return pdfName.replace(/ /g, "_");
};
//function for print digital sign pdf
export const handleToPrint = async (event, pdfUrl, setIsDownloading) => {
event.preventDefault();
setIsDownloading("pdf");
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
`${appInfo.baseUrl}/functions/getsignedurl`,
{ url: pdfUrl },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": appInfo.appId,
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
const url = axiosRes.data.result;
const pdf = await getBase64FromUrl(url);
const isAndroidDevice = navigator.userAgent.match(/Android/i);
const isAppleDevice =
(/iPad|iPhone|iPod/.test(navigator.platform) ||
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)) &&
!window.MSStream;
if (isAndroidDevice || isAppleDevice) {
const byteArray = Uint8Array.from(
atob(pdf)
.split("")
.map((char) => char.charCodeAt(0))
);
const blob = new Blob([byteArray], { type: "application/pdf" });
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl, "_blank");
setIsDownloading("");
} else {
printModule({ printable: pdf, type: "pdf", base64: true });
setIsDownloading("");
}
} catch (err) {
setIsDownloading("");
console.log("err in getsignedurl", err);
alert("something went wrong, please try again later.");
}
};
//handle download signed pdf
export const handleDownloadCertificate = async (
pdfDetails,
setIsDownloading
) => {
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try {
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
} catch (err) {
console.log("err in download in certificate", err);
}
} else {
setIsDownloading("certificate");
try {
const data = {
docId: pdfDetails[0]?.objectId
};
const docDetails = await axios.post(
`${localStorage.getItem("baseUrl")}functions/getDocument`,
data,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
}
}
);
if (docDetails.data && docDetails.data.result) {
const doc = docDetails.data.result;
if (doc?.CertificateUrl) {
await fetch(doc?.CertificateUrl);
const certificateUrl = doc?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
setIsDownloading("");
} else {
setIsDownloading("certificate");
}
}
} catch (err) {
console.log("err in download in certificate", err);
alert("something went wrong, please try again later.");
}
}
};
export async function findContact(value) {
try {
const currentUser = Parse.User.current();
const contactbook = new Parse.Query("contracts_Contactbook");
contactbook.equalTo(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactbook.notEqualTo("IsDeleted", true);
contactbook.matches("Email", new RegExp(value, "i"));
const contactRes = await contactbook.find();
if (contactRes) {
const res = JSON.parse(JSON.stringify(contactRes));
return res;
}
} catch (error) {
console.error("Error fetching suggestions:", error);
}
}
+13 -2
View File
@@ -3,7 +3,7 @@ export default function reportJson(id) {
const head = ["Sr.No", "Title", "Note", "Folder", "File", "Owner", "Signers"];
const contactbook = ["Sr.No", "Title", "Email", "Phone"];
const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"];
switch (id) {
// draft documents report
case "ByHuevtCFY":
@@ -326,7 +326,7 @@ export default function reportJson(id) {
case "6TeaPr321t":
return {
reportName: "Templates",
heading: head,
heading: templateReport,
actions: [
{
btnId: "2234",
@@ -341,6 +341,17 @@ export default function reportJson(id) {
message:
"Click the 'Use' button to create a new document from an existing template. "
},
{
btnId: "1631",
btnLabel: "Quick send",
hoverLabel: "Quick send",
btnIcon: "fa-solid fa-envelope",
redirectUrl: "",
action: "bulksend",
selector: "tourbulksend",
message:
"To quickly create and send multiple documents using an existing template, click the 'Quick Send' button."
},
{
btnId: "2234",
hoverLabel: "option",
+180 -163
View File
@@ -1,16 +1,20 @@
import React, { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import "../styles/loginPage.css";
import loader from "../assets/images/loader2.gif";
import axios from "axios";
import { isEnableSubscription, themeColor } from "../constant/const";
import {
isEnableSubscription,
modalSubmitBtnColor,
themeColor
} from "../constant/const";
import { contractUsers, getAppLogo } from "../constant/Utils";
import logo from "../assets/images/logo.png";
import { appInfo } from "../constant/appinfo";
import Parse from "parse";
function GuestLogin() {
const { id, userMail, contactBookId, base64url } = useParams();
let navigate = useNavigate();
const navigate = useNavigate();
const [email, setEmail] = useState(userMail);
const [OTP, setOTP] = useState("");
const [EnterOTP, setEnterOtp] = useState(false);
@@ -20,9 +24,9 @@ function GuestLogin() {
const [documentId, setDocumentId] = useState(id);
const [contactId, setContactId] = useState(contactBookId);
const [sendmail, setSendmail] = useState();
const [contact, setContact] = useState({ name: "", phone: "", email: "" });
useEffect(() => {
handleServerUrl();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -53,54 +57,51 @@ function GuestLogin() {
//split url in array from '/'
const checkSplit = decodebase64.split("/");
setDocumentId(checkSplit[0]);
setContact((prev) => ({ ...prev, email: checkSplit[1] }));
setEmail(checkSplit[1]);
setContactId(checkSplit[2]);
const contactId = checkSplit?.[2];
setSendmail(checkSplit[3]);
if (!contactId) {
const params = { email: checkSplit[1], docId: checkSplit[0] };
try {
const linkContactRes = await Parse.Cloud.run(
"linkcontacttodoc",
params
);
// console.log("linkContactRes ", linkContactRes);
setContactId(linkContactRes?.contactId);
} catch (err) {
console.log("Err in link ext contact", err);
}
} else {
setContactId(checkSplit[2]);
}
}
setIsLoading(false);
};
const handleChange = (event) => {
const { value } = event.target;
setOTP(value);
};
//send email OTP function
const SendOtp = async (e) => {
const serverUrl =
localStorage.getItem("baseUrl") && localStorage.getItem("baseUrl");
const parseId =
localStorage.getItem("parseAppId") && localStorage.getItem("parseAppId");
if (serverUrl && localStorage) {
setLoading(true);
e.preventDefault();
setEmail(email);
try {
let url = `${serverUrl}functions/SendOTPMailV1/`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseId
};
let body = {
email: email.toString(),
docId: documentId
};
let Otp = await axios.post(url, body, { headers: headers });
if (Otp) {
setLoading(false);
setEnterOtp(true);
}
} catch (error) {
alert("something went wrong!");
const SendOtp = async () => {
setLoading(true);
setEmail(email);
try {
const params = { email: email.toString(), docId: documentId };
const Otp = await Parse.Cloud.run("SendOTPMailV1", params);
if (Otp) {
setLoading(false);
setEnterOtp(true);
}
} else {
} catch (error) {
alert("something went wrong!");
}
};
const handleSendOTPBtn = async (e) => {
e.preventDefault();
await SendOtp();
};
//verify OTP send on via email
const VerifyOTP = async (e) => {
e.preventDefault();
@@ -136,7 +137,8 @@ function GuestLogin() {
`Parse/${parseId}/currentUser`,
JSON.stringify(_user)
);
if (contractUserDetails) {
// console.log("contractUserDetails ", contractUserDetails);
if (contractUserDetails && contractUserDetails.length > 0) {
localStorage.setItem(
"Extand_Class",
JSON.stringify(contractUserDetails)
@@ -163,156 +165,171 @@ function GuestLogin() {
alert("Please Enter OTP!");
}
};
const handleUserData = async (e) => {
e.preventDefault();
const params = { ...contact, docId: documentId };
try {
setLoading(true);
const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params);
// console.log("linkContactRes ", linkContactRes);
setContactId(linkContactRes.contactId);
setEnterOtp(true);
await SendOtp();
} catch (err) {
setLoading(false);
alert("something went wron, please try agian later.");
console.log("Err in link ext contact", err);
}
};
const handleInputChange = (e) => {
setContact((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
return (
<div style={{ padding: "2rem", background: "white" }}>
{isLoading ? (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh",
flexDirection: "column"
}}
>
<img
alt="no img"
src={loader}
style={{ width: "80px", height: "80px" }}
/>
<div className="flex flex-col justify-center items-center h-[100vh]">
<img className="w-[80px] h-[80px]" alt="loader" src={loader} />
<span style={{ fontSize: "13px", color: "gray" }}>
{isLoading.message}
</span>
</div>
) : (
<div
style={{
margin: "10px",
border: "0.5px solid #c5c7c9",
padding: "30px",
boxShadow: "rgba(99, 99, 99, 0.2) 0px 2px 8px 0px"
}}
>
<div className="main_head">
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
{appLogo && (
<img
src={appLogo}
className="object-contain h-full"
alt="logo"
/>
)}
</div>
<div className="m-1 md:m-2 border-[0.5px] border-[#c5c7c9] p-[30px] shadow-md">
<div className="md:w-[250px] md:h-[66px] inline-block overflow-hidden mt-2 mb-11">
{appLogo && (
<img src={appLogo} className="object-contain h-full" alt="logo" />
)}
</div>
{!EnterOTP ? (
<div className="row">
<div className="col-sm-6 KLO">
<span className="welcomeText">Welcome Back !</span>
<br />
<span className="KNLO">
Verification code is sent to your email
</span>
<div className="card card-box" style={{ borderRadius: "0px" }}>
<div className="card-body">
{contactId ? (
<>
{!EnterOTP ? (
<div className="w-full md:w-[50%]">
<h1 className="text-2xl md:text-[30px]">Welcome Back!</h1>
<legend className="text-[12px] text-[#878787] mt-2">
Verification code is sent to your email
</legend>
<div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
<input
type="email"
name="mobile"
value={email}
className="outline-none px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs disabled:bg-[#e0e5f5]"
disabled
className="loginInput"
/>
<br />
</div>
<div className="mt-4">
<button
className="w-[100px] text-white text-sm font-medium py-1 rounded-sm shadow-md hover:shadow-lg focus:outline-none"
style={{ background: themeColor }}
onClick={(e) => handleSendOTPBtn(e)}
disabled={loading}
>
{loading ? "Loading..." : "Send OTP"}
</button>
</div>
</div>
<div className="btnContainer">
{loading ? (
<button
type="button"
style={{
background: themeColor,
color: "white"
}}
className="verifyBtn"
disabled
>
<span
className="spinner-border spinner-border-sm "
role="status"
aria-hidden="true"
></span>
Loading...
</button>
) : (
<button
className="verifyBtn"
style={{
background: themeColor,
color: "white",
marginLeft: "0px !important"
}}
onClick={(e) => SendOtp(e)}
>
Send OTP
</button>
)}
</div>
</div>
</div>
) : (
<div className="row">
<div className="col-sm-6 KLO">
<span className="welcomeText">Welcome Back !</span>
<br />
<span className="KNLO">You will get a OTP via Email</span>
<div className="card card-box">
<div className="card-body">
<label>Enter Verification Code</label>
) : (
<form className="w-full md:w-[50%]" onSubmit={VerifyOTP}>
<h1 className="text-2xl md:text-[30px]">Welcome Back!</h1>
<legend className="text-[12px] text-[#878787] mt-2">
You will get a OTP via Email
</legend>
<div className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
<p>Enter Verification Code</p>
<input
type="number"
className="loginInput"
className="mt-1 outline-none px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs bg-[#e0e5f5]"
name="OTP"
value={OTP}
onChange={handleChange}
onChange={(e) => setOTP(e.target.value)}
/>
<br />
</div>
</div>
<div>
{loading ? (
<div className="mt-4">
<button
style={{
background: themeColor,
color: "white"
}}
className="verifyBtn"
type="button"
disabled
style={{ background: themeColor }}
className="w-[100px] text-white text-sm font-medium py-1 rounded-sm shadow-md hover:shadow-lg focus:outline-none"
type="submit"
disabled={loading}
>
<span
className="spinner-border spinner-border-sm "
role="status"
aria-hidden="true"
></span>
Loading...
{loading ? "Loading..." : "Verify"}
</button>
) : (
<button
type="button"
onClick={(e) => VerifyOTP(e)}
style={{
background: themeColor,
color: "white"
}}
className="verifyBtn"
>
Verify
</button>
)}
</div>
</form>
)}
</>
) : (
<div className="w-full md:w-[50%]">
<h1 className="text-2xl md:text-[30px]">Welcome</h1>
<legend className="text-[12px] text-[#878787] mt-2">
Provide your details
</legend>
<form
className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 my-2 rounded shadow-md"
onSubmit={handleUserData}
>
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
Name
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={contact.name}
onChange={handleInputChange}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded disabled:bg-[#e0e5f5] focus:outline-none text-xs"
disabled={loading}
required
/>
</div>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
Email
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={contact.email}
onChange={handleInputChange}
className="px-3 py-2 w-full border-[1px] border-gray-300 disabled:bg-[#e0e5f5] rounded focus:outline-none text-xs"
required
disabled
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
Phone
</label>
<input
type="text"
name="phone"
value={contact.phone}
onChange={handleInputChange}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
disabled={loading}
/>
</div>
<div className="mt-4 flex justify-start">
<button
type="submit"
style={{ backgroundColor: modalSubmitBtnColor }}
className="mr-2 px-[20px] py-1.5 text-white rounded shadow-md text-center focus:outline-none "
disabled={loading}
>
{loading ? "Loading..." : "Next"}
</button>
</div>
</form>
</div>
)}
</div>
+145 -128
View File
@@ -19,7 +19,7 @@ import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import { fetchSubscription, getAppLogo } from "../constant/Utils";
import { fetchSubscription, getAppLogo, openInNewTab } from "../constant/Utils";
function Login() {
const navigate = useNavigate();
const location = useLocation();
@@ -45,6 +45,7 @@ function Login() {
});
const [isModal, setIsModal] = useState(false);
const [image, setImage] = useState();
const [isLoginSSO, setIsLoginSSO] = useState(false);
useEffect(() => {
if (localStorage.getItem("accesstoken")) {
@@ -263,7 +264,7 @@ function Login() {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
phone: results[0].get("Phone"),
phone: results[0]?.get("Phone") || "",
company: results[0].get("Company")
};
localStorage.setItem(
@@ -317,7 +318,7 @@ function Login() {
const LocalUserDetails = {
name: _user.name,
email: email,
phone: _user.phone
phone: _user?.phone || ""
// company: results.get("Company"),
};
localStorage.setItem(
@@ -868,7 +869,7 @@ function Login() {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
phone: results[0].get("Phone"),
phone: results[0]?.get("Phone") || "",
company: results[0].get("Company")
};
localStorage.setItem(
@@ -905,7 +906,7 @@ function Login() {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
phone: results[0].get("Phone")
phone: results[0]?.get("Phone") || ""
// company: results.get("Company"),
};
localStorage.setItem(
@@ -982,7 +983,7 @@ function Login() {
userDetails: {
name: userInformation.name,
email: userInformation.email,
phone: userInformation.phone,
phone: userInformation?.phone || "",
role: "contracts_User",
company: userDetails.Company,
jobTitle: userDetails.Destination
@@ -994,7 +995,7 @@ function Login() {
const LocalUserDetails = {
name: userInformation.name,
email: userInformation.email,
phone: userInformation.phone,
phone: userInformation?.phone || "",
company: userDetails.Company,
jobTitle: userDetails.JobTitle
};
@@ -1016,8 +1017,9 @@ function Login() {
const handleCloseModal = () => {
setIsModal(false);
Parse.User.logOut();
if (Parse?.User?.current()) {
Parse.User.logOut();
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let appName = localStorage.getItem("appName");
@@ -1040,6 +1042,26 @@ function Login() {
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
};
// `handleSignInWithSSO` is trigger when user click sign in with sso and open sso authorize endpoint
const handleSignInWithSSO = () => {
if (isLoginSSO === false) {
if (state?.email) {
setIsLoginSSO(true);
const encodedEmail = encodeURIComponent(state.email);
const clientUrl = window.location.origin;
const domain = state.email.split("@")?.pop();
const ssoApiUrl =
process.env.SSO_API_URL || "https://sso.opensignlabs.com/api";
openInNewTab(
`${ssoApiUrl}/oauth/authorize?response_type=code&provider=saml&tenant=${domain}&product=OpenSign&redirect_uri=${clientUrl}/sso&state=${encodedEmail}`,
"_self"
);
} else {
alert("Please provide email.");
}
}
};
return (
<div className="bg-white">
<Title title={"Login Page"} />
@@ -1087,136 +1109,131 @@ function Login() {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
<div>
<div>
<form onSubmit={handleSubmit} aria-label="Login Form">
<h1 className="text-[30px] mt-6">Welcome Back!</h1>
<fieldset>
<legend className="text-[12px] text-[#878787]">
Login to your account
</legend>
<div className="px-6 py-4 outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
<label className="block text-xs" htmlFor="email">
Username
</label>
<input
id="email"
type="text"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
name="email"
value={state.email}
onChange={handleChange}
required
/>
<hr className="my-2 border-none" />
<label className="block text-xs" htmlFor="password">
Password
</label>
<div className="relative">
<input
id="password"
type={state.passwordVisible ? "text" : "password"}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
name="password"
value={state.password}
onChange={handleChange}
required
/>
<span
className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer ${
state.passwordVisible
? "text-[#007bff]"
: "text-black"
}`}
onClick={togglePasswordVisibility}
>
{state.passwordVisible ? (
<i className="fa fa-eye-slash text-xs pb-1" /> // Close eye icon
) : (
<i className="fa fa-eye text-xs pb-1 " /> // Open eye icon
)}
</span>
</div>
<div className="relative mt-1">
<NavLink
to="/forgetpassword"
className="text-[13px] text-[#002864] hover:underline underline-offset-1 focus:outline-none cursor-pointer ml-1"
>
Forgot Password?
</NavLink>
</div>
<form onSubmit={handleSubmit} aria-label="Login Form">
<h1 className="text-[30px] mt-6">Welcome Back!</h1>
<fieldset>
<legend className="text-[12px] text-[#878787]">
Login to your account
</legend>
<div className="px-6 py-4 outline outline-1 outline-slate-300/50 my-2 rounded shadow-md">
<label className="block text-xs" htmlFor="email">
Email
</label>
<input
id="email"
type="text"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
name="email"
value={state.email}
onChange={handleChange}
required
/>
<hr className="my-2 border-none" />
{!isLoginSSO && (
<>
<label className="block text-xs" htmlFor="password">
Password
</label>
<div className="relative">
<input
id="password"
type={
state.passwordVisible ? "text" : "password"
}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded text-xs"
name="password"
value={state.password}
onChange={handleChange}
required
/>
<span
className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer ${
state.passwordVisible
? "text-[#007bff]"
: "text-black"
}`}
onClick={togglePasswordVisibility}
>
{state.passwordVisible ? (
<i className="fa fa-eye-slash text-xs pb-1" /> // Close eye icon
) : (
<i className="fa fa-eye text-xs pb-1 " /> // Open eye icon
)}
</span>
</div>
</>
)}
<div className="relative mt-1">
<NavLink
to="/forgetpassword"
className="text-[13px] text-[#002864] hover:underline underline-offset-1 focus:outline-none cursor-pointer ml-1"
>
Forgot Password?
</NavLink>
</div>
</fieldset>
<div className="flex flex-col md:flex-row justify-between items-stretch gap-8 text-center text-xs font-bold mt-2">
<button
type="submit"
className="rounded-sm bg-[#3ac9d6] text-white w-full py-3 shadow outline-none uppercase focus:ring-2 focus:ring-blue-600"
disabled={state.loading}
>
{state.loading ? "Loading..." : "Login"}
</button>
<NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
to={
location.search
? "/signup" + location.search
: "/signup"
}
style={width < 768 ? { textAlign: "center" } : {}}
>
Create Account
</NavLink>
</div>
</form>
<br />
{(appInfo.fbAppId || appInfo.googleClietId) && (
<div className="text-sm flex justify-center items-center">
<hr className="border-[1px] border-gray-300 w-full" />
<span className="px-2 text-gray-500 cursor-default">
OR
</span>
<hr className="border-[1px] border-gray-300 w-full" />
</div>
)}
<br />
<div
style={{
textAlign: "center",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
{/* {appInfo.fbAppId && appInfo.fbAppId !== "" ? (
</fieldset>
<div className="flex flex-col md:flex-row justify-between items-stretch gap-8 text-center text-xs font-bold mt-2">
<button
type="submit"
className="rounded-sm bg-[#3ac9d6] text-white w-full py-3 shadow outline-none uppercase focus:ring-2 focus:ring-blue-600"
disabled={state.loading}
>
{state.loading ? "Loading..." : "Login"}
</button>
<NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
to={
location.search
? "/signup" + location.search
: "/signup"
}
style={width < 768 ? { textAlign: "center" } : {}}
>
Create Account
</NavLink>
</div>
</form>
<br />
{appInfo.googleClietId && (
<div className="text-sm flex justify-center items-center">
<hr className="border-[1px] border-gray-300 w-full" />
<span className="px-2 text-gray-500 cursor-default">
OR
</span>
<hr className="border-[1px] border-gray-300 w-full" />
</div>
)}
<br />
<div className="flex flex-col justify-center items-center gap-y-3">
{/* {appInfo?.fbAppId && (
<LoginFacebook
FBCred={appInfo.fbAppId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
/>
) : null} */}
</div>
<div style={{ margin: "10px 0" }}></div>
<div
style={{
textAlign: "center",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
{appInfo.googleClietId && appInfo.googleClietId !== "" ? (
<GoogleSignInBtn
GoogleCred={appInfo.googleClietId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
/>
) : null}
</div>
)} */}
{appInfo?.googleClietId && (
<GoogleSignInBtn
GoogleCred={appInfo.googleClietId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
/>
)}
{isEnableSubscription && (
<div
className="cursor-pointer border-[1px] border-gray-300 rounded px-[40px] py-2 font-semibold text-sm hover:border-[#d2e3fc] hover:bg-[#ecf3feb7]"
onClick={() => handleSignInWithSSO()}
>
Sign in with SSO
</div>
)}
</div>
</div>
{width >= 768 && (
<div className="self-center">
<div className="place-self-center">
<div className="mx-auto md:w-[300px] lg:w-[400px] xl:w-[500px]">
<img
src={login_img}
+342 -230
View File
@@ -29,7 +29,11 @@ import {
convertPdfArrayBuffer,
contractUsers,
handleSendOTP,
contactBook
contactBook,
handleDownloadPdf,
handleToPrint,
handleDownloadCertificate,
darkenColor
} from "../constant/Utils";
import Loader from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
@@ -41,6 +45,7 @@ import DefaultSignature from "../components/pdf/DefaultSignature";
import ModalUi from "../primitives/ModalUi";
import VerifyEmail from "../components/pdf/VerifyEmail";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import { appInfo } from "../constant/appinfo";
function useQuery() {
return new URLSearchParams(useLocation().search);
}
@@ -70,7 +75,7 @@ function PdfRequestFiles() {
const [selectWidgetId, setSelectWidgetId] = useState("");
const [otpLoader, setOtpLoader] = useState(false);
const [isCelebration, setIsCelebration] = useState(false);
const [requestSignTour, setRequestSignTour] = useState(true);
const [requestSignTour, setRequestSignTour] = useState(false);
const [tourStatus, setTourStatus] = useState([]);
const [isLoading, setIsLoading] = useState({
isLoad: true,
@@ -91,6 +96,7 @@ function PdfRequestFiles() {
const [expiredDate, setExpiredDate] = useState("");
const [signerUserId, setSignerUserId] = useState();
const [isDontShow, setIsDontShow] = useState(false);
const [isDownloading, setIsDownloading] = useState("");
const [defaultSignAlert, setDefaultSignAlert] = useState({
isShow: false,
alertMessage: ""
@@ -164,7 +170,11 @@ function PdfRequestFiles() {
const handleResend = async (e) => {
e.preventDefault();
setOtpLoader(true);
await handleSendOTP(Parse.User.current().getEmail());
const localuser = localStorage.getItem(
`Parse/${appInfo.appId}/currentUser`
);
const currentUser = JSON.parse(JSON.stringify(localuser));
await handleSendOTP(currentUser?.email);
setOtpLoader(false);
alert("OTP sent on you email");
};
@@ -172,10 +182,14 @@ function PdfRequestFiles() {
const handleVerifyEmail = async (e) => {
e.preventDefault();
setOtpLoader(true);
const localuser = localStorage.getItem(
`Parse/${appInfo.appId}/currentUser`
);
const currentUser = JSON.parse(JSON.stringify(localuser));
try {
const resEmail = await Parse.Cloud.run("verifyemail", {
otp: otp,
email: Parse.User.current().getEmail()
email: currentUser?.email
});
if (resEmail?.message === "Email is verified.") {
setIsEmailVerified(true);
@@ -196,7 +210,11 @@ function PdfRequestFiles() {
//`handleVerifyBtn` function is used to send otp on user mail
const handleVerifyBtn = async () => {
setIsVerifyModal(true);
await handleSendOTP(Parse.User.current().getEmail());
const localuser = localStorage.getItem(
`Parse/${appInfo.appId}/currentUser`
);
const currentUser = JSON.parse(JSON.stringify(localuser));
await handleSendOTP(currentUser?.email);
};
async function checkIsSubscribed(extUserId, contactId) {
const isGuestSign = location.pathname.includes("/load/") || false;
@@ -259,7 +277,9 @@ function PdfRequestFiles() {
currUserId
);
}
setSignerObjectId(currUserId);
if (currUserId) {
setSignerObjectId(currUserId);
}
if (documentData[0].SignedUrl) {
setPdfUrl(documentData[0].SignedUrl);
} else {
@@ -273,6 +293,10 @@ function PdfRequestFiles() {
};
setAlreadySign(true);
setIsCompleted(data);
setIsCelebration(true);
setTimeout(() => {
setIsCelebration(false);
}, 5000);
} else if (declined) {
const currentDecline = {
currnt: "another",
@@ -285,6 +309,17 @@ function PdfRequestFiles() {
);
setIsExpired(true);
setExpiredDate(expireDateFormat);
} // Check if the current signer is not a last signer and handle the complete message.
else if (isNextUser) {
setIsCelebration(true);
setTimeout(() => {
setIsCelebration(false);
}, 5000);
setIsCompleted({
isModal: true,
message:
"You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document will be sent to the owner over email once it is signed by all signers."
});
}
const isGuestSign = location.pathname.includes("/load/");
@@ -314,198 +349,176 @@ function PdfRequestFiles() {
}
}
}
if (documentData.length > 0) {
const checkDocIdExist =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
(data) => data.Activity === "Signed"
);
const audittrailData =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter((data) => data.Activity === "Signed");
const checkAlreadySign =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
(data) =>
data.UserPtr.objectId === currUserId && data.Activity === "Signed"
);
const checkAlreadySign =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
(data) =>
data.UserPtr.objectId === currUserId && data.Activity === "Signed"
);
if (
checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0
) {
setAlreadySign(true);
} else {
const obj = documentData?.[0];
setSendInOrder(obj?.SendinOrder || false);
if (
checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0
obj &&
obj.Signers &&
obj.Signers.length > 0 &&
obj.Placeholders &&
obj.Placeholders.length > 0
) {
setAlreadySign(true);
} else {
const obj = documentData?.[0];
setSendInOrder(obj?.SendinOrder || false);
if (
obj &&
obj.Signers &&
obj.Signers.length > 0 &&
obj.Placeholders &&
obj.Placeholders.length > 0
) {
const params = {
event: "viewed",
contactId: currUserId,
body: {
objectId: documentData?.[0].objectId,
file: documentData?.[0]?.SignedUrl || documentData?.[0]?.URL,
name: documentData?.[0].Name,
note: documentData?.[0].Note || "",
description: documentData?.[0].Description || "",
signers: documentData?.[0].Signers?.map((x) => ({
name: x?.Name,
email: x?.Email,
phone: x?.Phone
})),
viewedBy: jsonSender.email,
viewedAt: new Date(),
createdAt: documentData?.[0].createdAt
}
};
const params = {
event: "viewed",
contactId: currUserId,
body: {
objectId: documentData?.[0].objectId,
file: documentData?.[0]?.SignedUrl || documentData?.[0]?.URL,
name: documentData?.[0].Name,
note: documentData?.[0].Note || "",
description: documentData?.[0].Description || "",
signers: documentData?.[0].Signers?.map((x) => ({
name: x?.Name,
email: x?.Email,
phone: x?.Phone
})),
viewedBy: jsonSender.email,
viewedAt: new Date(),
createdAt: documentData?.[0].createdAt
}
};
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/callwebhook`,
params,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id":
localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
}
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/callwebhook`,
params,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
}
);
} catch (err) {
console.log("Err ", err);
}
}
}
let signers = [];
let unSignedSigner = [];
//check document is signed or not
if (checkDocIdExist && checkDocIdExist.length > 0) {
setIsDocId(true);
const signerRes = documentData[0].Signers;
//comparison auditTrail user details with signers user details
for (let i = 0; i < signerRes.length; i++) {
const signerId = signerRes[i].objectId;
let isSignedSignature = false;
for (let j = 0; j < checkDocIdExist.length; j++) {
const signedExist =
checkDocIdExist[j] && checkDocIdExist[j].UserPtr.objectId;
//checking signerObjId and auditTrail User objId
// if match then add signed data in signer array and break loop
if (signerId === signedExist) {
signers.push({ ...signerRes[i], ...signerRes[i] });
isSignedSignature = true;
break;
}
// if does not match then add unsigned data in unSignedSigner array
}
if (!isSignedSignature) {
unSignedSigner.push({ ...signerRes[i], ...signerRes[i] });
}
);
} catch (err) {
console.log("Err ", err);
}
setSignedSigners(signers);
setUnSignedSigners(unSignedSigner);
}
}
setSignerPos(documentData[0].Placeholders);
let signers = [];
let unSignedSigner = [];
const placeholdersOrSigners = [];
for (const placeholder of documentData[0].Placeholders) {
//`emailExist` variable to handle condition for quick send flow and show unsigned signers list
const emailExist = placeholder?.email;
if (emailExist) {
placeholdersOrSigners.push(placeholder);
} else {
let unsigned = [];
for (let i = 0; i < documentData.length; i++) {
unsigned.push(documentData[i].Signers);
}
setUnSignedSigners(unsigned[0]);
setSignerPos(documentData[0].Placeholders);
}
setPdfDetails(documentData);
// Check if the current signer is not a last signer and handle the complete message.
if (isNextUser) {
const getSignedAuditTrail = documentData[0].AuditTrail.filter(
(data) => data.Activity === "Signed"
const getSignerData = documentData[0].Signers.find(
(data) => data.objectId === placeholder?.signerObjId
);
const isLastSigner =
getSignedAuditTrail?.length === documentData?.[0]?.Signers?.length;
if (!isLastSigner) {
setIsCompleted({
isModal: true,
message:
"You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document will be sent to the owner over email once it is signed by all signers."
});
} else {
setIsCelebration(true);
setTimeout(() => {
setIsCelebration(false);
}, 5000);
placeholdersOrSigners.push(getSignerData);
}
}
//condition to check already signed document by someone
if (audittrailData && audittrailData.length > 0) {
setIsDocId(true);
for (const item of placeholdersOrSigners) {
const checkEmail = item?.email;
//if email exist then compare user signed by using email else signers objectId
const emailOrId = checkEmail ? item.email : item.objectId;
//`isSignedSignature` variable to handle break loop whenever it get true
let isSignedSignature = false;
//checking the signer who signed the document by using audit trail details.
//and save signedSigners and unsignedSigners details
for (const doc of audittrailData) {
const signedExist = checkEmail
? doc?.UserPtr.Email
: doc?.UserPtr.objectId;
if (emailOrId === signedExist) {
signers.push({ ...item });
isSignedSignature = true;
break;
}
}
if (!isSignedSignature) {
unSignedSigner.push({ ...item });
}
}
//checking if condition current user already sign or owner does not exist as a signer or document has been declined by someone or document has been expired
//then stop to display tour message
if (
(checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0) ||
!currUserId ||
declined ||
currDate > expireUpdateDate
) {
setRequestSignTour(false);
} else {
//else condition to check current user exist in contracts_Users class and check tour message status
//if not then check user exist in contracts_Contactbook class and check tour message status
const currentUser = JSON.parse(JSON.stringify(Parse.User.current()));
const currentUserEmail = currentUser.email;
const res = await contractUsers(currentUserEmail);
setSignedSigners(signers);
setUnSignedSigners(unSignedSigner);
setSignerPos(documentData[0].Placeholders);
} else {
//else condition is show there are no details in audit trail then direct push all signers details
//in unsignedsigners array
setUnSignedSigners(placeholdersOrSigners);
setSignerPos(documentData[0].Placeholders);
}
setPdfDetails(documentData);
//checking if condition current user already sign or owner does not exist as a signer or document has been declined by someone or document has been expired
//then stop to display tour message
if (
(checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0) ||
!currUserId ||
declined ||
currDate > expireUpdateDate
) {
setRequestSignTour(true);
} else {
//else condition to check current user exist in contracts_Users class and check tour message status
//if not then check user exist in contracts_Contactbook class and check tour message status
const localuser = localStorage.getItem(
`Parse/${appInfo.appId}/currentUser`
);
const currentUser = JSON.parse(JSON.stringify(localuser));
const currentUserEmail = currentUser.email;
const res = await contractUsers(currentUserEmail);
if (res === "Error: Something went wrong!") {
setHandleError("Error: Something went wrong!");
} else if (res[0] && res?.length) {
setContractName("_Users");
currUserId = res[0].objectId;
setSignerUserId(currUserId);
const tourData = res[0].TourStatus && res[0].TourStatus;
if (tourData && tourData.length > 0) {
setTourStatus(tourData);
setRequestSignTour(tourData[0]?.requestSign || false);
}
} else if (res?.length === 0) {
const res = await contactBook(currUserId);
if (res === "Error: Something went wrong!") {
setHandleError("Error: Something went wrong!");
} else if (res[0] && res?.length) {
setContractName("_Users");
currUserId = res[0].objectId;
setSignerUserId(currUserId);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRequestSign = tourstatus.filter(
(data) => data.requestSign
);
if (checkTourRequestSign && checkTourRequestSign.length > 0) {
setRequestSignTour(checkTourRequestSign[0].requestSign);
}
}
} else if (res?.length === 0) {
const res = await contactBook(currUserId);
if (res === "Error: Something went wrong!") {
setHandleError("Error: Something went wrong!");
} else if (res[0] && res.length) {
setContractName("_Contactbook");
const objectId = res[0].objectId;
setSignerUserId(objectId);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRequestSign = tourstatus.filter(
(data) => data.requestSign
);
if (checkTourRequestSign && checkTourRequestSign.length > 0) {
setRequestSignTour(checkTourRequestSign[0].requestSign);
}
}
} else if (res.length === 0) {
setHandleError("Error: User does not exist!");
} else if (res[0] && res.length) {
setContractName("_Contactbook");
const objectId = res[0].objectId;
setSignerUserId(objectId);
const tourData = res[0].TourStatus && res[0].TourStatus;
if (tourData && tourData.length > 0) {
setTourStatus(tourData);
setRequestSignTour(tourData[0]?.requestSign || false);
}
} else if (res.length === 0) {
setHandleError("Error: User does not exist!");
}
}
setIsUiLoading(false);
} else {
alert("No data found!");
}
setIsUiLoading(false);
} else if (
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
@@ -561,7 +574,6 @@ function PdfRequestFiles() {
setIsLoading(loadObj);
});
};
//function for embed signature or image url in pdf
async function embedWidgetsData() {
try {
@@ -796,7 +808,12 @@ function PdfRequestFiles() {
(x) => x.Email === jsonSender.email
);
const newIndex = index + 1;
const user = pdfDetails?.[0].Signers[newIndex];
const usermail = {
Email: pdfDetails?.[0]?.Placeholders[newIndex]?.email || ""
};
const user = usermail?.Email
? usermail
: pdfDetails?.[0]?.Signers[newIndex];
if (sendmail !== "false" && sendInOrder) {
const requestBody = pdfDetails?.[0]?.RequestBody;
const requestSubject = pdfDetails?.[0]?.RequestSubject;
@@ -820,19 +837,26 @@ function PdfRequestFiles() {
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem(
"baseUrl"
)}functions/sendmailv3/`;
)}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id":
localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const objectId = user.objectId;
const objectId = user?.objectId;
const hostUrl = window.location.origin;
//encode this url value `${pdfDetails?.[0].objectId}/${user.Email}/${objectId}` to base64 using `btoa` function
const encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}/${objectId}`
);
let encodeBase64;
if (objectId) {
encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}/${objectId}`
);
} else {
encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${user.Email}`
);
}
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl =
"https://www.opensignlabs.com/contact-us";
@@ -994,8 +1018,10 @@ function PdfRequestFiles() {
}
const getFirstLetter = (name) => {
const firstLetter = name.charAt(0);
return firstLetter;
if (name) {
const firstLetter = name.charAt(0);
return firstLetter;
}
};
//function for image upload or update
const onImageChange = (event) => {
@@ -1107,9 +1133,23 @@ function PdfRequestFiles() {
};
const checkSignerBackColor = (obj) => {
const data = signerPos.filter((data) => data.signerObjId === obj.objectId);
let data = "";
if (obj?.Id) {
data = signerPos.filter((data) => data.Id === obj.Id);
} else {
data = signerPos.filter((data) => data.signerObjId === obj.objectId);
}
return data && data.length > 0 && data[0].blockColor;
};
const checkUserNameColor = (obj) => {
const getBackColor = checkSignerBackColor(obj);
if (getBackColor) {
const color = darkenColor(getBackColor, 0.4);
return color;
} else {
return "#abd1d0";
}
};
//function for set decline true on press decline button
const declineDoc = async () => {
@@ -1208,7 +1248,7 @@ function PdfRequestFiles() {
};
//function to close tour and save tour status
const closeRequestSignTour = async () => {
setRequestSignTour(false);
setRequestSignTour(true);
if (isDontShow) {
let updatedTourStatus = [];
if (tourStatus.length > 0) {
@@ -1330,7 +1370,6 @@ function PdfRequestFiles() {
/>
);
};
return (
<DndProvider backend={HTML5Backend}>
<Title title={"Request Sign"} />
@@ -1399,7 +1438,7 @@ function PdfRequestFiles() {
}}
ref={divRef}
>
{requestSignTour && requestSignTourFunction()}
{!requestSignTour && requestSignTourFunction()}
<ModalUi
headerColor={"#dc3545"}
isOpen={isAlert.isShow}
@@ -1573,42 +1612,111 @@ function PdfRequestFiles() {
{/* this modal is used show this document is already sign */}
<ModalUi
isOpen={isCompleted.isModal}
title={"Sign Documents"}
title={"Document signed"}
handleClose={() => {
setIsCompleted((prev) => ({ ...prev, isModal: false }));
}}
reduceWidth={
!isCompleted?.message &&
"md:min-w-[440px] md:max-w-[400px]"
}
>
<div style={{ height: "100%", padding: 20 }}>
<p>
{" "}
{isCompleted?.message ||
"This document has been signed by all Signers."}
</p>
<div
style={{
height: "1px",
backgroundColor: "#9f9f9f",
width: "100%",
marginTop: "15px",
marginBottom: "15px"
}}
></div>
<button
type="button"
className="finishBtn cancelBtn"
onClick={() => {
setIsCompleted((prev) => ({
...prev,
isModal: false
}));
}}
>
Close
</button>
{isCompleted?.message ? (
<p>{isCompleted?.message}</p>
) : (
<div className="px-[15px]">
<span>
Congratulations! 🎉 This document has been
successfully signed by all participants!
</span>
</div>
)}
{!isCompleted?.message && (
<div className="flex mt-4 gap-1 px-[15px]">
<button
type="button"
onClick={() =>
handleDownloadCertificate(
pdfDetails,
setIsDownloading
)
}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
>
<i
className="fa-solid fa-award py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block ml-1">
Certificate
</span>
</button>
<button
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading)
}
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
>
<i
className="fa fa-print py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block ml-1">Print</span>
</button>
<button
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i
className="fa fa-download py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block ml-1">
Download
</span>
</button>
</div>
)}
</div>
</ModalUi>
{isDownloading === "pdf" && (
<div className="fixed z-[1000] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<div
style={{ fontSize: "45px", color: "#3dd3e0" }}
className="loader-37"
></div>
</div>
)}
<ModalUi
isOpen={isDownloading === "certificate"}
title={
isDownloading === "certificate"
? "Generating certificate"
: "PDF Download"
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center">
{isDownloading === "certificate"}{" "}
<p>
Your completion certificate is being generated. Please
wait momentarily.
</p>
<p>
If the download doesn&apos;t start shortly, click the
button again.
</p>
</div>
</ModalUi>
{/* this component is used for signature pad modal */}
<SignPad
isSignPad={isSignPad}
@@ -1710,7 +1818,7 @@ function PdfRequestFiles() {
<div
className="signerStyle"
style={{
background: "#abd1d0",
background: checkUserNameColor(obj),
width: 30,
height: 30,
display: "flex",
@@ -1729,7 +1837,7 @@ function PdfRequestFiles() {
textTransform: "uppercase"
}}
>
{getFirstLetter(obj.Name)}
{getFirstLetter(obj?.Name || obj?.Role)}
</span>
</div>
<div
@@ -1738,9 +1846,11 @@ function PdfRequestFiles() {
flexDirection: "column"
}}
>
<span className="userName">{obj.Name}</span>
<span className="userName">
{obj?.Name || obj?.Role}
</span>
<span className="useEmail">
{obj.Email}
{obj?.Email || obj?.email}
</span>
</div>
</div>
@@ -1779,7 +1889,7 @@ function PdfRequestFiles() {
<div
className="signerStyle"
style={{
background: "#abd1d0",
background: checkUserNameColor(obj),
width: 30,
height: 30,
display: "flex",
@@ -1798,7 +1908,7 @@ function PdfRequestFiles() {
textTransform: "uppercase"
}}
>
{getFirstLetter(obj.Name)}
{getFirstLetter(obj?.Name || obj?.email)}
</span>
</div>
<div
@@ -1807,9 +1917,11 @@ function PdfRequestFiles() {
flexDirection: "column"
}}
>
<span className="userName">{obj.Name}</span>
<span className="userName">
{obj?.Name || obj?.Role}
</span>
<span className="useEmail">
{obj.Email}
{obj?.Email || obj?.email}
</span>
</div>
<hr />
+4 -2
View File
@@ -127,7 +127,9 @@ const PgSignUp = () => {
user.set("username", zohoRes.data.result.email);
user.set("password", obj.password);
user.set("email", zohoRes.data.result.email);
user.set("phone", zohoRes.data.result.phone);
if (zohoRes?.data?.result?.phone) {
user.set("phone", zohoRes.data.result.phone);
}
user.set("name", zohoRes.data.result.name);
const res = await user.signUp();
@@ -140,7 +142,7 @@ const PgSignUp = () => {
company: zohoRes.data.result.company,
name: zohoRes.data.result.name,
email: zohoRes.data.result.email,
phone: zohoRes.data.result.phone,
phone: zohoRes?.data?.result?.phone || "",
role: obj.role
},
subscription: zohoRes.data.result.subscription
+5 -3
View File
@@ -936,6 +936,7 @@ function PlaceHolderSign() {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
SentToOthers: true,
ExpiryDate: {
iso: updateExpiryDate,
__type: "Date"
@@ -945,7 +946,8 @@ function PlaceHolderSign() {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers
Signers: signers,
SentToOthers: true
};
}
await axios
@@ -1108,10 +1110,10 @@ function PlaceHolderSign() {
document_title: documentName,
sender_name: senderName,
sender_mail: senderEmail,
sender_phone: senderPhone,
sender_phone: senderPhone || "",
receiver_name: signerMail[i].Name,
receiver_email: signerMail[i].Email,
receiver_phone: signerMail[i].Phone,
receiver_phone: signerMail[i]?.Phone || "",
expiry_date: localExpireDate,
company_name: orgName,
signing_url: `<a href=${signPdf}>Sign here</a>`
+11 -2
View File
@@ -16,8 +16,17 @@ const PlanSubscriptions = () => {
const navigate = useNavigate();
const [yearlyVisible, setYearlyVisible] = useState(false);
const [isLoader, setIsLoader] = useState(true);
const userDetails = JSON.parse(localStorage.getItem("userDetails"));
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const user = {
name: extUser?.Name,
email: extUser?.Email,
company: extUser?.Company,
phone: extUser?.Phone
};
const userDetails = JSON.parse(localStorage.getItem("userDetails")) || user;
// console.log("userDetails ", userDetails);
const name =
userDetails && userDetails.name
? "first_name=" + encodeURIComponent(userDetails.name)
+413
View File
@@ -0,0 +1,413 @@
import React, { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import Parse from "parse";
import axios from "axios";
import { appInfo } from "../constant/appinfo";
import {
isEnableSubscription,
modalCancelBtnColor,
modalSubmitBtnColor
} from "../constant/const";
import { fetchSubscription } from "../constant/Utils";
import { useDispatch } from "react-redux";
import { showTenant } from "../redux/reducers/ShowTenant";
import ModalUi from "../primitives/ModalUi";
import loader from "../assets/images/loader2.gif";
const SSOVerify = () => {
const location = useLocation();
const navigate = useNavigate();
const dispatch = useDispatch();
const [isModal, setIsModal] = useState(false);
const [message, setMessage] = useState("Verifying SSO...");
const [isLoader, setIsLoader] = useState(false);
const [userDetails, setUserDetails] = useState({
name: "",
email: "",
phone: "",
Destination: "",
Company: ""
});
useEffect(() => {
linkUserWithSSO();
// eslint-disable-next-line
}, []);
// `linkUserWithSSO` is used to sign in or sign up a user using an SSO code and check if the user is present in the extended class
const linkUserWithSSO = async () => {
const param = new URLSearchParams(location.search);
const code = param?.get("code");
const state = param?.get("state");
try {
// The `ssosign` cloud function is used to sign in or sign up a user
const ssosign = await Parse.Cloud.run("ssosign", {
code: code,
email: state
});
localStorage.setItem("accesstoken", ssosign.sessiontoken);
// `checkExtUser` checks if the user is present in the extended class `contracts_Users` and if not, initiates the new user flow
await checkExtUser(ssosign);
} catch (err) {
setMessage("Error: " + err.message);
console.log("err", err.message);
}
};
const checkExtUser = async (ssosign) => {
const params = { email: ssosign?.email };
try {
// `isextenduser` checks if the current user is present in the extended class
const extRes = await Parse.Cloud.run("isextenduser", params);
if (extRes?.isUserExist) {
// `getUserDetails` retrieves the current user's details from the extended class
const extRes = await Parse.Cloud.run("getUserDetails", params);
if (ssosign && ssosign.sessiontoken) {
const LocalUserDetails = {
name: extRes.Name,
email: extRes.email,
phone: extRes?.get("Phone") || "",
company: extRes.get("Company")
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(ssosign.sessiontoken);
}
} else {
setIsModal(true);
setUserDetails((prev) => ({
...prev,
name: ssosign.name,
email: ssosign.email,
phone: ssosign?.phone || ""
}));
}
} catch (err) {
console.log("Err in isextenduser or getuserdetails cloud function", err);
setMessage("Error: " + err.message);
}
};
// `handleSubmitbtn` is used to create a user in the extended class
const handleSubmitbtn = async (e) => {
e.preventDefault();
setIsLoader(true);
let phone;
if (userDetails?.phone) {
phone = validateInput(userDetails?.phone);
} else {
phone = true;
}
if (userDetails.Destination && userDetails.Company && phone) {
const payload = { sessionToken: localStorage.getItem("accesstoken") };
if (payload && payload.sessionToken) {
const params = {
userDetails: {
name: userDetails.name,
email: userDetails.email,
phone: userDetails?.phone || "",
role: "contracts_User",
company: userDetails.Company,
jobTitle: userDetails.Destination
}
};
try {
const userSignUp = await Parse.Cloud.run("usersignup", params);
if (userSignUp && userSignUp.sessionToken) {
const LocalUserDetails = params.userDetails;
localStorage.setItem(
"userDetails",
JSON.stringify(LocalUserDetails)
);
await thirdpartyLoginfn(userSignUp.sessionToken);
setIsLoader(false);
} else {
alert(userSignUp.message);
setIsLoader(false);
}
} catch (err) {
console.log("error in usersignup", err);
localStorage.removeItem("accesstoken");
alert("Something went wrong.");
setIsLoader(false);
}
} else {
localStorage.removeItem("accesstoken");
alert("Internal server error.");
setIsLoader(false);
}
} else {
alert("Please fill required details correctly.");
setIsLoader(false);
}
};
// `thirdpartyLoginfn` is used to save necessary parameters locally for the logged-in user
const thirdpartyLoginfn = async (sessionToken) => {
const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
try {
const validUser = await Parse.User.become(sessionToken);
if (validUser) {
localStorage.setItem("accesstoken", sessionToken);
const _user = JSON.parse(JSON.stringify(validUser));
localStorage.setItem("UserInformation", JSON.stringify(_user));
if (_user.ProfilePic) {
localStorage.setItem("profileImg", _user.ProfilePic);
} else {
localStorage.setItem("profileImg", "");
}
// Check extended class user role and tenentId
try {
let userRoles = [];
const userSettings = appInfo.settings;
//Get Current user roles
const url = `${baseUrl}functions/UserGroups`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessionToken: _user.sessionToken
};
const body = { appname: appInfo.appname };
const UserGroupsRes = await axios.post(url, JSON.stringify(body), {
headers: headers
});
userRoles = (UserGroupsRes.data && UserGroupsRes.data.result) || [];
if (userRoles) {
let _currentRole = "";
const excludeRoles = ["contracts_Guest", `contracts_appeditor`];
const filteredRole = userRoles.filter(
(x) => !excludeRoles.includes(x)
);
if (filteredRole?.length > 1) {
console.log("user has two roles");
// _currentRole = filteredRole.filter((x) => x === "contracts_User");
} else {
_currentRole = filteredRole?.[0] || "";
}
if (_currentRole) {
const roleSetting = userSettings?.find(
(setting) => setting.role === _currentRole
);
const redirectUrl =
location?.state?.from ||
`/${roleSetting.pageType}/${roleSetting.pageId}`;
const _role = _currentRole.replace(`${appInfo.appname}_`, "");
localStorage.setItem("_user_role", _role);
// Get TenentID from Extendend Class
localStorage.setItem(
"extended_class",
roleSetting.extended_class
);
try {
const extUser = await Parse.Cloud.run("getUserDetails", {
email: _user.email
});
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("userEmail", _extUser.Email);
localStorage.setItem("username", _extUser.Name);
localStorage.setItem("scriptId", true);
let tenentInfo = [];
const results = [extUser];
if (results) {
let extendedInfo_stringify = JSON.stringify(results);
localStorage.setItem(
"Extand_Class",
extendedInfo_stringify
);
if (_extUser.TenantId) {
const obj = {
tenentId: _extUser.TenantId.objectId,
tenentName: _extUser.TenantId.TenantName || ""
};
localStorage.setItem("TenantId", obj.tenentId);
tenentInfo.push(obj);
dispatch(showTenant(obj.tenentName || ""));
localStorage.setItem("TenantName", obj.tenentName || "");
}
localStorage.setItem("PageLanding", roleSetting.pageId);
localStorage.setItem("defaultmenuid", roleSetting.menuId);
localStorage.setItem("pageType", roleSetting.pageType);
if (isEnableSubscription) {
const res = await fetchSubscription();
const plan = res.plan;
const billingDate = res.billingDate;
if (plan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
} else {
navigate(`/subscription`, {
replace: true
});
}
} else {
navigate(redirectUrl);
}
}
}
} catch (err) {
alert("user not exist.");
setMessage("Error: User not exist.");
console.log("err in get extUser", err);
}
} else {
alert("Role does not exists.");
setMessage("Error: Role does not exists.");
}
} else {
alert("Role does not exists.");
setMessage("Error: Role does not exists.");
}
} catch (err) {
console.log("err in usergroups", err);
}
}
} catch (err) {
console.log("Err in become method", err);
}
};
// `handleCloseModal` is triggered when the user wants to close the new user flow modal
const handleCloseModal = () => {
setIsModal(false);
if (Parse?.User?.current()) {
Parse.User.logOut();
}
};
// `validateInput` is used to verify the phone pattern
function validateInput(input) {
if (input) {
const pattern = /^(?!.*\+.*\+)[\d+-]*$/;
return pattern.test(input);
} else {
return true;
}
}
return (
<div>
<div className="w-full h-screen flex flex-col justify-center items-center text-sm md:text-xl ">
{message === "Verifying SSO..." && (
<img alt="loader" src={loader} className="w-[80px] h-[80px]" />
)}
<div className="text-[gray]">{message}</div>
</div>
<ModalUi isOpen={isModal} title="Additional Info" showClose={false}>
<div className="relative">
{isLoader && (
<div className="absolute w-full h-full bg-black bg-opacity-25 flex justify-center items-center">
<div
style={{
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37"
></div>
</div>
)}
<form className="px-4 py-3" onSubmit={handleSubmitbtn}>
<div className="mb-3">
<label
htmlFor="Company"
style={{ display: "flex" }}
className="block text-xs text-gray-700 font-semibold"
>
Phone <span style={{ fontSize: 13, color: "red" }}>*</span>
</label>
<input
type="tel"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
id="Phone"
value={userDetails.phone}
onChange={(e) =>
setUserDetails({
...userDetails,
phone: e.target.value
})
}
disabled={isLoader}
required
/>
<p className="text-[10px] text-[red] ml-2">
{!validateInput(userDetails.phone) && "Invalid phone value"}
</p>
</div>
<div className="mb-3">
<label
htmlFor="Company"
style={{ display: "flex" }}
className="block text-xs text-gray-700 font-semibold"
>
Company <span style={{ fontSize: 13, color: "red" }}>*</span>
</label>
<input
type="text"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
id="Company"
value={userDetails.Company}
onChange={(e) =>
setUserDetails({
...userDetails,
Company: e.target.value
})
}
disabled={isLoader}
required
/>
</div>
<div className="mb-3">
<label
htmlFor="JobTitle"
style={{ display: "flex" }}
className="block text-xs text-gray-700 font-semibold"
>
Job Title
<span style={{ fontSize: 13, color: "red" }}>*</span>
</label>
<input
type="text"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
id="JobTitle"
value={userDetails.Destination}
onChange={(e) =>
setUserDetails({
...userDetails,
Destination: e.target.value
})
}
disabled={isLoader}
required
/>
</div>
<div className="mt-4">
<button
type="submit"
className="px-3 py-1.5 text-white rounded shadow-md text-center focus:outline-none "
style={{
marginRight: 10,
backgroundColor: modalSubmitBtnColor
}}
>
Login
</button>
<button
type="button"
className="py-1.5 text-black border-[1px] border-[#ccc] shadow-md rounded focus:outline-none"
onClick={handleCloseModal}
style={{ width: 75, backgroundColor: modalCancelBtnColor }}
>
Cancel
</button>
</div>
</form>
</div>
</ModalUi>
</div>
);
};
export default SSOVerify;
+4 -2
View File
@@ -562,9 +562,11 @@ const Signup = () => {
<input
id="email"
type="text"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs lowercase"
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={(e) =>
setEmail(e.target.value?.toLowerCase())
}
required
/>
<hr className="my-2 border-none" />
+1 -1
View File
@@ -1192,7 +1192,7 @@ function SignYourSelf() {
{/* this modal is used show this document is already sign */}
<ModalUi
isOpen={showAlreadySignDoc.status}
title={"Sign Documents"}
title={"Document signed"}
handleClose={() => {
setShowAlreadySignDoc({ status: false });
}}
+137 -43
View File
@@ -1,4 +1,8 @@
import React, { useState, useEffect } from "react";
import React, {
useState,
useEffect
// useRef
} from "react";
import { Navigate, useNavigate } from "react-router-dom";
import Parse from "parse";
import { SaveFileSize } from "../constant/saveFileSize";
@@ -27,6 +31,10 @@ function UserProfile() {
const [percentage, setpercentage] = useState(0);
const [isDisableDocId, setIsDisableDocId] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
// const [publicUserName, setPublicUserName] = useState(
// extendUser && extendUser?.[0]?.UserName
// );
// const previousPublicUserName = useRef(publicUserName);
const [company, setCompany] = useState(
extendUser && extendUser?.[0]?.Company
);
@@ -37,6 +45,7 @@ function UserProfile() {
const [otp, setOtp] = useState("");
const [otpLoader, setOtpLoader] = useState(false);
const [isEmailVerified, setIsEmailVerified] = useState(false);
// const [userNameError, setUserNameError] = useState("");
useEffect(() => {
getUserDetail();
}, []);
@@ -74,46 +83,72 @@ function UserProfile() {
}
}
};
//function to check public username already exist
// const handleCheckPublicUserName = async () => {
// try {
// const res = await Parse.Cloud.run("getpublicusername", {
// username: publicUserName
// });
// if (res) {
// setIsLoader(false);
// setUserNameError("user name already exist");
// setTimeout(() => {
// setUserNameError("");
// }, 3000);
// return res;
// }
// } catch (e) {
// console.log("error in getpublicusername cloud function");
// }
// };
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoader(true);
let phn = Phone;
try {
const tour = Parse.Object.extend("_User");
const query = new Parse.Query(tour);
await query.get(UserProfile.objectId).then((object) => {
object.set("name", name);
object.set("ProfilePic", Image);
object.set("phone", phn);
object.save().then(
async (response) => {
if (response) {
let res = response.toJSON();
let rr = JSON.stringify(res);
localStorage.setItem("UserInformation", rr);
SetName(res.name);
SetPhone(res.phone);
setImage(res.ProfilePic);
localStorage.setItem("username", res.name);
localStorage.setItem("profileImg", res.ProfilePic);
await updateExtUser({ Name: res.name, Phone: res.phone });
alert("Profile updated successfully.");
setEditMode(false);
navigate("/dashboard/35KBoSgoAK");
let phn = Phone,
res = "";
//condition to call cloud function when user change publicUserName
// if (previousPublicUserName.current !== publicUserName) {
// res = await handleCheckPublicUserName();
// }
if (!res) {
setIsLoader(true);
try {
const userQuery = Parse.Object.extend("_User");
const query = new Parse.Query(userQuery);
await query.get(UserProfile.objectId).then((object) => {
object.set("name", name);
object.set("ProfilePic", Image);
object.set("phone", phn || "");
object.save().then(
async (response) => {
if (response) {
let res = response.toJSON();
let rr = JSON.stringify(res);
localStorage.setItem("UserInformation", rr);
SetName(res.name);
SetPhone(res?.phone || "");
setImage(res.ProfilePic);
localStorage.setItem("username", res.name);
localStorage.setItem("profileImg", res.ProfilePic);
await updateExtUser({
Name: res.name,
Phone: res?.phone || ""
});
alert("Profile updated successfully.");
setEditMode(false);
setIsLoader(false);
//navigate("/dashboard/35KBoSgoAK");
}
},
(error) => {
alert("Something went wrong.");
console.error("Error while updating tour", error);
setIsLoader(false);
}
},
(error) => {
alert("Something went wrong.");
console.error("Error while updating tour", error);
setIsLoader(false);
}
);
});
} catch (error) {
console.log("err", error);
);
});
} catch (error) {
console.log("err", error);
}
}
};
@@ -122,14 +157,15 @@ function UserProfile() {
const extClass = localStorage.getItem("extended_class");
const extData = JSON.parse(localStorage.getItem("Extand_Class"));
const ExtUserId = extData[0].objectId;
const body = {
Phone: obj.Phone,
Phone: obj?.Phone || "",
Name: obj.Name,
HeaderDocId: isDisableDocId,
JobTitle: jobTitle,
Company: company
// UserName: publicUserName || ""
};
await axios.put(
parseBaseUrl + "classes/" + extClass + "/" + ExtUserId,
body,
@@ -147,7 +183,6 @@ function UserProfile() {
const json = JSON.parse(JSON.stringify([res]));
const extRes = JSON.stringify(json);
localStorage.setItem("Extand_Class", extRes);
// console.log("updateRes ", updateRes);
};
@@ -241,6 +276,27 @@ function UserProfile() {
setOtpLoader(false);
alert("OTP sent on you email");
};
//function to handle onchange username and restrict 6-characters username for free users
// const handleOnchangeUserName = (e) => {
// const value = e.target.value;
// if (value.length > 6 && !isSubscribe) {
// setUserNameError("Please upgrade to allow more than 6 characters.");
// setTimeout(() => {
// setUserNameError("");
// }, 2000);
// } else {
// setPublicUserName(e.target.value);
// }
// };
const handleCancel = () => {
setEditMode(false);
SetName(localStorage.getItem("username"));
SetPhone(UserProfile && UserProfile.phone);
setImage(localStorage.getItem("profileImg"));
// setPublicUserName(extendUser && extendUser?.[0]?.UserName);
setCompany(extendUser && extendUser?.[0]?.Company);
setJobTitle(extendUser?.[0]?.JobTitle);
};
return (
<React.Fragment>
<Title title={"Profile"} />
@@ -262,7 +318,14 @@ function UserProfile() {
></div>
</div>
) : (
<div className="flex justify-center items-center w-full">
<div className="flex justify-center items-center w-full relative">
{/* {userNameError && (
<div
className={`z-[1000] fixed top-[50%] transform border-[1px] text-sm border-[#f0a8a8] bg-[#f4bebe] text-[#c42121] rounded py-[.75rem] px-[1.25rem]`}
>
{userNameError}
</div>
)} */}
<div className="bg-white flex flex-col justify-center shadow rounded w-[450px]">
<div className="flex flex-col justify-center items-center my-4">
<div className="w-[200px] h-[200px] overflow-hidden rounded-full">
@@ -390,6 +453,37 @@ function UserProfile() {
)}
</span>
</li>
{/* {isEnableSubscription && (
<li className="flex justify-between items-center border-t-[1px] border-gray-300 py-2 break-all">
<span className="font-semibold">
Public profile :{" "}
<Tooltip
message={`this is your public URL. Copy or share it
with the signer, and you will be able to see
all your publicly set templates.`}
/>
</span>
<div className="flex items-center">
<span>opensign-me.vercel.app/</span>
{editmode ? (
<input
onChange={handleOnchangeUserName}
value={publicUserName}
disabled={!editmode}
placeholder="enter user name"
className="border-[1px] border-gray-200 rounded-[3px]"
/>
) : (
<input
value={extendUser?.[0]?.UserName}
disabled
placeholder="enter user name"
className="border-[1px] border-gray-200 rounded-[3px]"
/>
)}
</div>
</li>
)} */}
<li className="border-y-[1px] border-gray-300 break-all">
<div className="flex justify-between items-center py-2">
<span
@@ -460,7 +554,7 @@ function UserProfile() {
type="button"
onClick={() => {
if (editmode) {
setEditMode(false);
handleCancel();
} else {
navigate("/changepassword");
}
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import Parse from "parse";
import axios from "axios";
import "../styles/AddUser.css";
const AddUser = (props) => {
const AddContact = (props) => {
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [email, setEmail] = useState("");
@@ -21,7 +21,7 @@ const AddUser = (props) => {
);
if (savedUserDetails && addYourself) {
setName(savedUserDetails.name);
setPhone(savedUserDetails.phone);
setPhone(savedUserDetails?.phone || "");
setEmail(savedUserDetails.email);
}
}, [addYourself]);
@@ -50,7 +50,9 @@ const AddUser = (props) => {
try {
const contactQuery = new Parse.Object("contracts_Contactbook");
contactQuery.set("Name", name);
contactQuery.set("Phone", phone);
if (phone) {
contactQuery.set("Phone", phone);
}
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest");
@@ -68,8 +70,10 @@ const AddUser = (props) => {
_user.set("name", name);
_user.set("username", email);
_user.set("email", email);
_user.set("phone", phone);
_user.set("password", phone);
_user.set("password", email);
if (phone) {
_user.set("phone", phone);
}
const user = await _user.save();
if (user) {
@@ -245,23 +249,21 @@ const AddUser = (props) => {
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
onChange={(e) => setEmail(e.target.value?.toLowerCase())}
required
disabled={addYourself}
className="addUserInput"
className="addUserInput lowercase"
/>
</div>
<div className="form-section">
<label htmlFor="phone" style={{ fontSize: 13 }}>
Phone
<span style={{ color: "red", fontSize: 13 }}> *</span>
</label>
<input
type="text"
id="phone"
value={phone}
onChange={(e) => setPhone(e.target.value)}
required
disabled={addYourself}
className="addUserInput"
/>
@@ -285,4 +287,4 @@ const AddUser = (props) => {
);
};
export default AddUser;
export default AddContact;
+560 -67
View File
@@ -4,27 +4,35 @@ import { useNavigate } from "react-router-dom";
import axios from "axios";
import ModalUi from "./ModalUi";
import AddSigner from "../components/AddSigner";
import { modalSubmitBtnColor, modalCancelBtnColor } from "../constant/const";
import {
modalSubmitBtnColor,
modalCancelBtnColor
// isEnableSubscription
} from "../constant/const";
import Alert from "./Alert";
import Tooltip from "./Tooltip";
import { RWebShare } from "react-web-share";
import Tour from "reactour";
import Parse from "parse";
import { saveAs } from "file-saver";
import { replaceMailVaribles } from "../constant/Utils";
import {
// copytoData,
replaceMailVaribles
} from "../constant/Utils";
// import Confetti from "react-confetti";
import EditorToolbar, {
module1,
formats
} from "../components/pdf/EditorToolbar";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import BulkSendUi from "../components/BulkSendUi";
const ReportTable = (props) => {
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(1);
const [actLoader, setActLoader] = useState({});
const [isAlert, setIsAlert] = useState(false);
const [isDocErr, setIsDocErr] = useState(false);
const [isContactform, setIsContactform] = useState(false);
const [isDeleteModal, setIsDeleteModal] = useState({});
const [isRevoke, setIsRevoke] = useState({});
@@ -36,9 +44,21 @@ const ReportTable = (props) => {
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
const [isResendMail, setIsResendMail] = useState({});
// const [isMakePublic, setIsMakePublic] = useState({});
const [mail, setMail] = useState({ subject: "", body: "" });
const [userDetails, setUserDetails] = useState({});
const [isNextStep, setIsNextStep] = useState({});
const [isBulkSend, setIsBulkSend] = useState({});
const [templateDeatils, setTemplateDetails] = useState({});
const [placeholders, setPlaceholders] = useState([]);
const [isLoader, setIsLoader] = useState({});
// const [selectedPublicRole, setSelectedPublicRole] = useState("");
// const [isCelebration, setIsCelebration] = useState(false);
// const [currentLists, setCurrentLists] = useState([]);
// const [isPublic, setIsPublic] = useState({});
// const [isPublicProfile, setIsPublicProfile] = useState({});
// const [publicUserName, setIsPublicUserName] = useState("");
const [isViewShare, setIsViewShare] = useState({});
const startIndex = (currentPage - 1) * props.docPerPage;
const { isMoreDocs, setIsNextRecord } = props;
// For loop is used to calculate page numbers visible below table
@@ -126,6 +146,7 @@ const ReportTable = (props) => {
Name: Doc.Name,
URL: Doc.URL,
SignedUrl: Doc.SignedUrl,
SentToOthers: Doc?.SentToOthers || false,
Description: Doc.Description,
Note: Doc.Note,
Placeholders: placeholdersArr,
@@ -180,7 +201,6 @@ const ReportTable = (props) => {
setActLoader({});
}
} else {
setIsDocErr(true);
setActLoader({});
}
} else {
@@ -223,13 +243,27 @@ const ReportTable = (props) => {
setIsOption({ [item.objectId]: !isOption[item.objectId] });
} else if (act.action === "resend") {
setIsResendMail({ [item.objectId]: true });
} else if (act.action === "bulksend") {
handleBulkSend(item);
}
};
// Get current list
const indexOfLastDoc = currentPage * props.docPerPage;
const indexOfFirstDoc = indexOfLastDoc - props.docPerPage;
// `currentLists` is total record render on current page
const currentLists = props.List?.slice(indexOfFirstDoc, indexOfLastDoc);
const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc);
// useEffect(() => {
// // `currentLists` is total record render on current page
// const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc);
// //check public template and save in a object to show public and private template
// // setIsPublic(
// // currentList.reduce((acc, item) => {
// // acc[item.objectId] = item?.IsPublic || false;
// // return acc;
// // }, {})
// // );
// setCurrentLists(currentList);
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [indexOfLastDoc, indexOfFirstDoc]);
// Change page
const paginateFront = () => setCurrentPage(currentPage + 1);
@@ -294,6 +328,13 @@ const ReportTable = (props) => {
const handleClose = () => {
setIsRevoke({});
setIsDeleteModal({});
// setIsMakePublic({});
// setSelectedPublicRole("");
// setIsPublic((prevStates) => ({
// ...prevStates,
// [item.objectId]: !prevStates[item.objectId]
// }));
// setIsPublicProfile({});
};
const handleShare = (item) => {
@@ -302,14 +343,18 @@ const ReportTable = (props) => {
const sendMail = item?.SendMail || false;
const getUrl = (x) => {
//encode this url value `${item.objectId}/${x.Email}/${x.objectId}` to base64 using `btoa` function
const encodeBase64 = btoa(
`${item.objectId}/${x.Email}/${x.objectId}/${sendMail}`
);
return `${host}/login/${encodeBase64}`;
if (x?.signerObjId) {
const encodeBase64 = btoa(
`${item.objectId}/${x.signerPtr.Email}/${x.signerPtr.objectId}/${sendMail}`
);
return `${host}/login/${encodeBase64}`;
} else {
const encodeBase64 = btoa(`${item.objectId}/${x.email}`);
return `${host}/login/${encodeBase64}`;
}
};
const urls = item.Signers.map((x) => ({
email: x.Email,
const urls = item?.Placeholders?.map((x) => ({
email: x.email ? x.email : x.signerPtr.Email,
url: getUrl(x)
}));
setShareUrls(urls);
@@ -330,9 +375,9 @@ const ReportTable = (props) => {
await axios
.put(
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
"_appName"
)}_Document/${item.objectId}`,
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${
item.objectId
}`,
data,
{
headers: {
@@ -455,9 +500,9 @@ const ReportTable = (props) => {
// `handleSubjectChange` is used to add or change subject of resend mail
const handleSubjectChange = (subject, doc) => {
const encodeBase64 = btoa(
`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`
);
const encodeBase64 = userDetails?.objectId
? btoa(`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`)
: btoa(`${doc.objectId}/${userDetails.Email}`);
const expireDate = doc.ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString("en-US", {
@@ -470,10 +515,10 @@ const ReportTable = (props) => {
document_title: doc.Name,
sender_name: doc.ExtUserPtr.Name,
sender_mail: doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr.Phone,
receiver_name: userDetails.Name,
receiver_email: userDetails.Email,
receiver_phone: userDetails.Phone,
sender_phone: doc.ExtUserPtr?.Phone || "",
receiver_name: userDetails?.Name,
receiver_email: userDetails?.Email,
receiver_phone: userDetails?.Phone || "",
expiry_date: localExpireDate,
company_name: doc.ExtUserPtr.Company,
signing_url: `<a href=${signPdf}>Sign here</a>`
@@ -484,9 +529,9 @@ const ReportTable = (props) => {
};
// `handlebodyChange` is used to add or change body of resend mail
const handlebodyChange = (body, doc) => {
const encodeBase64 = btoa(
`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`
);
const encodeBase64 = userDetails?.objectId
? btoa(`${doc.objectId}/${userDetails.Email}/${userDetails.objectId}`)
: btoa(`${doc.objectId}/${userDetails.Email}`);
const expireDate = doc.ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString("en-US", {
@@ -499,10 +544,10 @@ const ReportTable = (props) => {
document_title: doc.Name,
sender_name: doc.ExtUserPtr.Name,
sender_mail: doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr.Phone,
receiver_name: userDetails.Name,
receiver_email: userDetails.Email,
receiver_phone: userDetails.Phone,
sender_phone: doc.ExtUserPtr?.Phone || "",
receiver_name: userDetails?.Name || "",
receiver_email: userDetails?.Email || "",
receiver_phone: userDetails?.Phone || "",
expiry_date: localExpireDate,
company_name: doc.ExtUserPtr.Company,
signing_url: `<a href=${signPdf}>Sign here</a>`
@@ -516,8 +561,18 @@ const ReportTable = (props) => {
// `handleNextBtn` is used to open edit mail template screen in resend mail modal
// as well as replace variable with original one
const handleNextBtn = (user, doc) => {
setUserDetails(user);
const encodeBase64 = btoa(`${doc.objectId}/${user.Email}/${user.objectId}`);
const userdata = {
Name: user?.signerPtr?.Name,
Email: user.email ? user?.email : user.signerPtr?.Email,
Phone: user?.signerPtr?.Phone,
objectId: user?.signerPtr?.objectId
};
setUserDetails(userdata);
const encodeBase64 = user.email
? btoa(`${doc.objectId}/${user.email}`)
: btoa(
`${doc.objectId}/${user.signerPtr.Email}/${user.signerPtr.objectId}`
);
const expireDate = doc.ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString("en-US", {
@@ -530,10 +585,10 @@ const ReportTable = (props) => {
document_title: doc.Name,
sender_name: doc.ExtUserPtr.Name,
sender_mail: doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr.Phone,
receiver_name: user.Name,
receiver_email: user.Email,
receiver_phone: user.Phone,
sender_phone: doc.ExtUserPtr?.Phone || "",
receiver_name: user?.signerPtr?.Name || "",
receiver_email: user?.email ? user?.email : user?.signerPtr?.Email,
receiver_phone: user?.signerPtr?.Phone || "",
expiry_date: localExpireDate,
company_name: doc?.ExtUserPtr?.Company || "",
signing_url: `<a href=${signPdf}>Sign here</a>`
@@ -544,15 +599,14 @@ const ReportTable = (props) => {
`{{sender_name}} has requested you to sign "{{document_title}}"`;
const body =
doc?.RequestBody ||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;<b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br></body> </html>`;
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign <b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br></body> </html>`;
const res = replaceMailVaribles(subject, body, variables);
setMail((prev) => ({ ...prev, subject: res.subject, body: res.body }));
setIsNextStep({ [user.objectId]: true });
setIsNextStep({ [user.Id]: true });
};
const handleResendMail = async (e, doc, user) => {
e.preventDefault();
console.log("first");
setActLoader({ [user.objectId]: true });
setActLoader({ [user?.Id]: true });
const url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
@@ -562,16 +616,22 @@ const ReportTable = (props) => {
let params = {
mailProvider: doc?.ExtUserPtr?.active_mail_adapter,
extUserId: doc?.ExtUserPtr?.objectId,
recipient: userDetails.Email,
recipient: userDetails?.Email,
subject: mail.subject,
from: doc?.ExtUserPtr?.Email,
html: mail.body
};
try {
const res = await axios.post(url, params, { headers: headers });
if (res) {
if (res?.data?.result?.status === "success") {
setIsAlert(true);
setAlertMsg({ type: "success", message: "Mail sent successfully." });
} else {
setIsAlert(true);
setAlertMsg({
type: "danger",
message: "Something went wrong, please try again later!"
});
}
} catch (err) {
console.log("err in sendmail", err);
@@ -588,9 +648,9 @@ const ReportTable = (props) => {
}
};
const fetchUserStatus = (user, doc) => {
const audit = doc?.AuditTrail?.find(
(x) => x.UserPtr.objectId === user.objectId
);
const email = user.email ? user.email : user.signerPtr.Email;
const audit = doc?.AuditTrail?.find((x) => x.UserPtr.Email === email);
return (
<div className="flex flex-row gap-2 justify-center items-center">
<div className="flex justify-center items-center bg-gray-200 text-xs text-black shadow rounded-full w-[65px] h-[23px] cursor-default">
@@ -609,6 +669,196 @@ const ReportTable = (props) => {
</div>
);
};
// `handleQuickSendClose` is trigger when bulk send component trigger close event
const handleQuickSendClose = (status, count) => {
setIsBulkSend({});
setIsAlert(true);
if (status === "success") {
if (count > 1) {
setAlertMsg({
type: "success",
message: count + " Document sent successfully!"
});
setTimeout(() => setIsAlert(false), 1500);
} else {
setAlertMsg({
type: "success",
message: count + " Document sent successfully!"
});
setTimeout(() => setIsAlert(false), 1500);
}
} else {
setAlertMsg({
type: "danger",
message: "Something went wrong, Please try again later!"
});
setTimeout(() => setIsAlert(false), 1500);
}
};
// `handleBulkSend` is used to open modal as well as fetch template
// and show Ui on the basis template response
const handleBulkSend = async (template) => {
setIsBulkSend({ [template.objectId]: true });
setIsLoader({ [template.objectId]: true });
try {
const params = {
templateId: template.objectId,
include: ["Placeholders.signerPtr"]
};
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}functions/getTemplate`,
params,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
}
}
);
const templateRes = axiosRes.data && axiosRes.data.result;
setPlaceholders(templateRes?.Placeholders);
setTemplateDetails(templateRes);
setIsLoader({});
} catch (err) {
console.log("err in fetch template in bulk modal", err);
setIsBulkSend({});
setIsAlert(true);
setAlertMsg({
type: "danger",
message: "Something went wrong, Please try again later!"
});
setTimeout(() => setIsAlert(false), 1500);
}
};
//function to make template public and set public role
// const handlePublicTemplate = async (item) => {
// if (selectedPublicRole || !isPublic[item.objectId]) {
// setActLoader({ [item.objectId]: true });
// setIsMakePublic(false);
// try {
// const res = await Parse.Cloud.run("createpublictemplate", {
// templateid: item.objectId,
// ispublic: isPublic[item.objectId],
// publicrole: [selectedPublicRole]
// });
// if (res.status === "success") {
// setIsAlert(true);
// setTimeout(() => setIsAlert(false), 1500);
// if (isPublic[item.objectId]) {
// setAlertMsg({
// type: "success",
// message: "You have successfully made the template public."
// });
// setIsCelebration(true);
// setTimeout(() => {
// setIsCelebration(false);
// }, 5000);
// setIsPublicProfile({ [item.objectId]: isPublic[item.objectId] });
// } else {
// setAlertMsg({
// type: "success",
// message: "You have successfully made the template private."
// });
// setSelectedPublicRole("");
// }
// const updateList = props.List.map((x) =>
// x.objectId === item.objectId
// ? { ...x, IsPublic: isPublic[item.objectId] }
// : x
// );
// props.setList(updateList);
// setActLoader({});
// }
// } catch (e) {
// console.log("error in createpublictemplate", e);
// setIsAlert(true);
// setAlertMsg({
// type: "danger",
// message: "Something went wrong, Please try again later!"
// });
// setTimeout(() => setIsAlert(false), 1500);
// setIsPublic((prevStates) => ({
// ...prevStates,
// [item.objectId]: !prevStates[item.objectId]
// }));
// }
// } else {
// setIsAlert(true);
// setAlertMsg({
// type: "danger",
// message: "You need to select a role for the public signers."
// });
// setTimeout(() => setIsAlert(false), 1500);
// }
// };
const handleViewSigners = (item) => {
setIsViewShare({ [item.objectId]: true });
};
//function to handle change template status is public or private
// const handlePublicChange = async (e, item) => {
// const getPlaceholder = item?.Placeholders;
// //condiiton to check role is exist or not
// if (getPlaceholder && getPlaceholder.length > 0) {
// const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
// placeholderObj?.placeHolder?.some((holder) =>
// holder?.pos?.some((posItem) => posItem?.type === "signature")
// )
// );
// if (checkIsSignatureExistt) {
// let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
// const userName = extendUser[0]?.UserName;
// setIsPublicUserName(extendUser[0]?.UserName);
// //condition to check user have public url or not
// if (userName) {
// setIsPublic((prevStates) => ({
// ...prevStates,
// [item.objectId]: e.target.checked
// }));
// const getPlaceholder = item?.Placeholders;
// if (getPlaceholder.length === 1) {
// setSelectedPublicRole(getPlaceholder[0]?.Role);
// }
// setIsMakePublic({ [item.objectId]: true });
// } else {
// setIsPublicProfile({ [item.objectId]: true });
// }
// } else {
// setIsAlert(true);
// setAlertMsg({
// type: "danger",
// message:
// " Please ensure there's at least one signature widget added for all recipients."
// });
// setTimeout(() => setIsAlert(false), 5000);
// }
// } else {
// setIsAlert(true);
// setAlertMsg({
// type: "danger",
// message: "Please assign at least one role to make this template public."
// });
// setTimeout(() => setIsAlert(false), 5000);
// }
// };
//function to copy public profile links
// const copytoProfileLink = () => {
// const url = `https://opensign-me.vercel.app/${publicUserName}`;
// copytoData(url);
// setIsAlert(true);
// setAlertMsg({
// type: "success",
// message: "Copied."
// });
// setTimeout(() => setIsAlert(false), 1500);
// };
return (
<div className="relative">
{Object.keys(actLoader)?.length > 0 && (
@@ -620,6 +870,11 @@ const ReportTable = (props) => {
</div>
)}
<div className="p-2 overflow-x-scroll w-full bg-white rounded-md">
{/* {isCelebration && (
<div style={{ position: "relative", zIndex: "1000" }}>
<Confetti width={window.innerWidth} height={window.innerHeight} />
</div>
)} */}
{isAlert && <Alert type={alertMsg.type}>{alertMsg.message}</Alert>}
{props.tourData && props.ReportName === "Templates" && (
<Tour
@@ -666,12 +921,15 @@ const ReportTable = (props) => {
{props.actions?.length > 0 && (
<th className="px-4 py-2 font-thin">Action</th>
)}
{/* {props.ReportName === "Templates" && isEnableSubscription && (
<th className="px-4 py-2 font-thin">Public</th>
)} */}
</tr>
</thead>
<tbody className="text-[12px]">
{props.List?.length > 0 && (
<>
{currentLists.map((item, index) =>
{currentList.map((item, index) =>
props.ReportName === "Contactbook" ? (
<tr className="border-y-[1px]" key={index}>
{props.heading.includes("Sr.No") && (
@@ -763,7 +1021,17 @@ const ReportTable = (props) => {
{formatRow(item?.ExtUserPtr)}
</td>
<td className="px-4 py-2">
{item?.Signers ? formatRow(item?.Signers) : "-"}
{/* {item?.Signers ? formatRow(item?.Signers) : "-"} */}
{item?.Placeholders ? (
<button
onClick={() => handleViewSigners(item)}
className="text-[blue] hover:underline focus:outline-none"
>
View
</button>
) : (
"-"
)}
</td>
<td className="px-2 py-2 text-white flex flex-row gap-x-2 gap-y-1 justify-center items-center">
{props.actions?.length > 0 &&
@@ -793,19 +1061,21 @@ const ReportTable = (props) => {
)}
{isOption[item.objectId] &&
act.action === "option" && (
<div className="absolute -right-2 top-5 bg-white rounded shadow z-[20] overflow-hidden">
<div className="absolute -right-2 top-5 p-1.5 bg-white text-nowrap rounded shadow-md z-[20] overflow-hidden">
{act.subaction?.map((subact) => (
<div
key={subact.btnId}
className="hover:bg-gray-300 cursor-pointer px-2 py-1.5 flex justify-start items-center text-black"
className="hover:bg-gray-300 rounded cursor-pointer px-2 py-1.5 flex justify-start items-center text-black"
onClick={() =>
handleActionBtn(subact, item)
}
title={subact.hoverLabel}
>
<i className={subact.btnIcon}></i>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="ml-[4px] text-xs capitalize">
<span className="ml-[4px] text-[13px] capitalize font-medium">
{subact.btnLabel}
</span>
)}
@@ -815,6 +1085,48 @@ const ReportTable = (props) => {
)}
</button>
))}
{isViewShare[item.objectId] && (
<div className="fixed z-[999] inset-0 w-full h-full bg-black bg-opacity-[75%]">
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm bg-white rounded shadow-md max-h-90 min-w-[90%] md:min-w-[400px] overflow-y-auto max-h-[340px] md:max-h-[400px] hide-scrollbar">
<div
className="cursor-pointer absolute text-white text-[22px] font-medium rounded-full z-50 top-1 right-3"
onClick={() => setIsViewShare({})}
>
&times;
</div>
<table className="table-auto w-full">
<thead className="text-white h-[38px] sticky top-0 bg-[#32a3ac]">
<tr>
{props.ReportName === "Templates" && (
<th className="p-2 pl-3">Roles</th>
)}
<th className="p-2">Signers</th>
</tr>
</thead>
<tbody>
{item.Placeholders.map((x, i) => (
<tr
key={i}
className="text-sm font-normal text-black odd:bg-white even:bg-gray-200"
>
{props.ReportName === "Templates" && (
<td className="text-[12px] p-2 pl-3">
{x.Role && x.Role}
</td>
)}
<td className="text-[12px] p-2 break-all">
{x.email
? x.email
: x?.signerPtr?.Email || "-"}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
{isDeleteModal[item.objectId] && (
<ModalUi
isOpen
@@ -849,6 +1161,28 @@ const ReportTable = (props) => {
</div>
</ModalUi>
)}
{isBulkSend[item.objectId] && (
<ModalUi
isOpen
title={"Quick send"}
handleClose={() => setIsBulkSend({})}
>
{isLoader[item.objectId] ? (
<div className="w-full h-[100px] md:h-[100px] rounded-b-md flex justify-center items-center bg-black bg-opacity-30 z-30">
<div
style={{ fontSize: "45px", color: "#3dd3e0" }}
className="loader-37"
></div>
</div>
) : (
<BulkSendUi
Placeholders={placeholders}
item={templateDeatils}
handleClose={handleQuickSendClose}
/>
)}
</ModalUi>
)}
{isShare[item.objectId] && (
<ModalUi
isOpen
@@ -936,11 +1270,11 @@ const ReportTable = (props) => {
}}
>
<div className=" overflow-y-auto max-h-[340px] md:max-h-[400px]">
{item?.Signers.map((user) => (
<React.Fragment key={user.objectId}>
{isNextStep[user.objectId] && (
{item?.Placeholders?.map((user) => (
<React.Fragment key={user.Id}>
{isNextStep[user.Id] && (
<div className="relative ">
{actLoader[user.objectId] && (
{actLoader[user.Id] && (
<div className="absolute w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-30">
<div
style={{
@@ -960,7 +1294,7 @@ const ReportTable = (props) => {
>
<div className="absolute right-5 text-xs z-40">
<Tooltip
id={`${user.objectId}_help`}
id={`${user.Id}_help`}
message={
"You can use following variables which will get replaced with their actual values:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}."
}
@@ -1018,7 +1352,12 @@ const ReportTable = (props) => {
{Object?.keys(isNextStep) <= 0 && (
<div className="flex justify-between items-center gap-2 my-2 px-3">
<div className="text-black">
{user.Name} {`<${user.Email}>`}
{user?.signerPtr?.Name || "-"}{" "}
{`<${
user?.email
? user.email
: user.signerPtr.Email
}>`}
</div>
<>{fetchUserStatus(user, item)}</>
</div>
@@ -1029,6 +1368,170 @@ const ReportTable = (props) => {
</ModalUi>
)}
</td>
{/* {isEnableSubscription && (
<td className=" pl-[20px] py-2 ">
{props.ReportName === "Templates" && (
<div className=" flex flex-row-">
<label
className={
"cursor-pointer relative inline-flex items-center mb-0"
}
>
<input
checked={isPublic?.[item.objectId]}
onChange={(e) => handlePublicChange(e, item)}
type="checkbox"
value=""
className="sr-only peer"
/>
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-1 peer-focus:ring-black rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-black peer-checked:bg-blue-600"></div>
</label>
</div>
)}
{isMakePublic[item.objectId] && (
<ModalUi
isOpen
title={
isPublic[item.objectId]
? "Make template public"
: "Make template private"
}
handleClose={() => {
setIsMakePublic({});
setSelectedPublicRole("");
setIsPublic((prevStates) => ({
...prevStates,
[item.objectId]: !prevStates[item.objectId]
}));
}}
>
<div className="m-[20px]">
<div className="font-normal text-black">
<p className="text-lg">
{" "}
{isPublic[item.objectId]
? `Are you sure you want tof make this templat public ?`
: `Are you sure you want to make this template private? This will remove it from your public profile ?`}
</p>
{isPublic[item.objectId] && (
<div className="flex mt-2 gap-2 md:items-center">
<p className="text-[15px]">
Public role :{" "}
</p>
{item?.Placeholders?.length > 1 ? (
<select
className="w-[60%] md:w-[70%] border-[1px] border-gray-200 rounded-sm p-[2px]"
name="textvalidate"
value={selectedPublicRole}
onChange={(e) =>
setSelectedPublicRole(
e.target.value
)
}
>
<option
disabled
style={{ fontSize: "13px" }}
value=""
>
Select...
</option>
{item?.Placeholders.map(
(data, ind) => {
return (
<option
style={{ fontSize: "13px" }}
key={ind}
value={data?.Role}
>
{data?.Role}
</option>
);
}
)}
</select>
) : (
<div className="w-[60%] md:w-[70%] border-[1px] border-gray-200 rounded-sm p-[2px]">
{item?.Placeholders[0]?.Role}
</div>
)}
</div>
)}
</div>
<hr className="bg-[#ccc] mt-2 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() => handlePublicTemplate(item)}
className="px-4 py-1.5 text-white rounded shadow-md text-center focus:outline-none "
style={{
backgroundColor: modalSubmitBtnColor
}}
>
Submit
</button>
<button
onClick={() => handleClose(item)}
className="px-4 py-1.5 text-black border-[1px] border-[#ccc] shadow-md rounded focus:outline-none"
style={{
backgroundColor: modalCancelBtnColor
}}
>
No
</button>
</div>
</div>
</ModalUi>
)}
{isPublicProfile[item.objectId] && (
<ModalUi
isOpen
title={"Public URL"}
handleClose={() => {
setIsPublicProfile({});
}}
reduceWidth={"md:min-w-[440px] md:max-w-[400px]"}
>
<div className="m-[20px]">
{publicUserName ? (
<div className="font-normal text-black">
<p>
Heres your public URL. Copy or share it
with the signer, and you will be able to
see all your publicly set templates.
</p>
<div className=" flex items-center gap-5 mt-2 p-[2px] w-[75%]">
<span className="font-bold">
Public URL :{" "}
</span>
<span
onClick={() => copytoProfileLink()}
className="underline underline-offset-2 cursor-pointer"
>{`https://opensign.me/${publicUserName}`}</span>
</div>
</div>
) : (
<div className="font-normal text-black">
<p>
Please add your public URL, and you will
be able to make a public template.
</p>
<button
className="px-4 py-1.5 mt-3 text-white rounded shadow-md text-center focus:outline-none "
style={{
backgroundColor: modalSubmitBtnColor
}}
onClick={() => navigate("/profile")}
>
Add
</button>
</div>
)}
</div>
</ModalUi>
)}
</td>
)} */}
</tr>
)
)}
@@ -1096,16 +1599,6 @@ const ReportTable = (props) => {
closePopup={handleContactFormModal}
/>
</ModalUi>
<ModalUi
headColor={"#dc3545"}
isOpen={isDocErr}
title={"Receipent required"}
handleClose={() => setIsDocErr(false)}
>
<div style={{ height: "100%", padding: 20 }}>
<p>Please add receipent in template!</p>
</div>
</ModalUi>
</div>
</div>
);
@@ -1,6 +1,6 @@
import React from "react";
import SelectSigners from "../components/shared/fields/SelectSigners";
import AddUser from "./AddUser";
import AddContact from "./AddContact";
import ModalUi from "./ModalUi";
const LinkUserModal = (props) => {
@@ -43,7 +43,7 @@ const LinkUserModal = (props) => {
}}
></span>
</div>
<AddUser details={props.handleAddUser} closePopup={props.closePopup} />
<AddContact details={props.handleAddUser} closePopup={props.closePopup} />
</ModalUi>
);
};
+4 -6
View File
@@ -10,7 +10,7 @@ const ModalUi = ({
handleClose,
showHeader = true,
showClose = true,
styleClass,
reduceWidth,
showHeaderMessage
}) => {
return (
@@ -18,11 +18,9 @@ const ModalUi = ({
{isOpen && (
<div className="fixed z-[999] top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]">
<div
className={
styleClass
? styleClass
: "fixed z-[1000] top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm bg-white rounded shadow-md max-h-90 min-w-[90%] md:min-w-[500px] overflow-y-auto hide-scrollbar"
}
className={`${
reduceWidth || "md:min-w-[500px]"
} fixed z-[1000] top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm bg-white rounded shadow-md max-h-90 min-w-[90%] overflow-y-auto hide-scrollbar `}
>
{showHeader && (
<div
-197
View File
@@ -1,197 +0,0 @@
.loginInput {
padding: 10px;
border-radius: 4px;
width: 100%;
font-size: 0.75rem;
border: 1px solid #e2dddd;
background-color: #e0e5f5;
color: black;
padding-left: 1rem !important;
padding-right: 1rem !important;
padding-top: 0.5rem !important;
font-family: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
font-weight: inherit;
overflow: visible;
}
.loginInput:focus {
outline: none;
border: none;
}
.verifyBtn {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
padding: 3px 30px;
color: white;
font-weight: 500 !important;
font-size: 14px !important;
border: none;
}
.verifyBtn:focus {
border: none;
outline: none;
}
.verifyBtn:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
color: white;
}
.fakeimg {
height: 200px;
background: #aaa;
}
.GTRY {
border: 1px solid #dee2e6;
margin-top: 75px;
padding-bottom: 20px;
background-color: #fff;
}
.main_head {
width: 100%;
height: 66px;
background-color: #fff;
margin-bottom: 52px;
padding-top: 10px;
}
.main-logo {
width: 250px;
height: 66px;
display: inline-block;
}
.card-box {
background: #fff;
min-height: 50px;
box-shadow: 0 20px 20px rgba(0, 0, 0, 0.1);
position: relative;
margin-bottom: 20px;
transition: 0.5s;
border: 1px solid #f2f2f2;
border-radius: 7px;
margin-top: 10px;
}
.LKJH {
display: flex;
}
.GTR {
text-align: left;
}
.JUI {
text-align: right;
}
.btn-info {
color: #fff;
background-color: #15b4e9;
border-color: #15b4e9;
}
.btn-info:hover {
color: #fff;
background-color: #15b4e9;
border-color: #15b4e9;
}
.btn-reg {
color: #15b4e9;
background-color: #ffffff;
border-color: #15b4e9;
}
.btn-reg:hover {
color: #15b4e9;
background-color: #ffffff;
border-color: #15b4e9;
}
.SYTU {
display: flex;
}
.MLKI {
float: right;
margin-bottom: 12px;
}
.login-btn {
width: 100% !important;
display: block !important;
}
@media only screen and (max-width: 667px) {
.MLKI {
float: inherit;
}
.login-btn {
width: 210px !important;
}
}
.MLKI a {
color: #212529;
}
.KNLO {
color: #878787;
font-size: 13px;
}
.form-check-label {
margin-left: 17px;
margin-bottom: 12px;
}
.welcomeText {
font-size: 30px;
}
@media only screen and (max-width: 600px) {
.KLO1 {
display: none;
}
.MLKI a {
font-size: 12px;
}
.form-check-label {
font-size: 12px;
margin-bottom: 14px;
}
.MKUY {
text-align: center;
padding-top: 10px;
}
.main_head {
margin-bottom: 12px;
padding-top: 10px;
border-bottom: 1px dashed #eeeeee;
}
}
@media screen and (max-width: 366px) {
.main-logo {
width: 150px;
height: 46px;
display: inline-block;
}
.welcomeText {
font-size: 20px;
}
.KNLO {
color: #878787;
font-size: 10px;
}
.btnContainer {
display: flex;
justify-content: center;
align-items: center;
}
}
+3
View File
@@ -247,3 +247,6 @@ export function sanitizeFileName(fileName) {
}
export const useLocal = process.env.USE_LOCAL ? process.env.USE_LOCAL.toLowerCase() : 'false';
export const smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '465' ? false : true;
export const smtpenable =
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false;
+28
View File
@@ -0,0 +1,28 @@
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const ssoApiUrl = process.env.SSO_API_URL || 'https://sso.opensignlabs.com/api'; //'https://osl-jacksonv2.vercel.app/api';
export const SSOAuth = {
// Returns a promise that fulfills if this user mail is valid.
validateAuthData: async authData => {
try {
const response = await axios.get(ssoApiUrl + '/oauth/userinfo', {
headers: {
Authorization: `Bearer ${authData.access_token}`,
},
});
if (response.data && response.data.id && response.data.email === authData.id) {
return;
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'SSO auth is invalid for this user.');
} catch (error) {
console.log('error in sso adapter', error?.response);
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'SSO auth is invalid for this user.');
}
},
// Returns a promise that fulfills if this app id is valid.
validateAppId: () => {
return Promise.resolve();
},
};
@@ -23,7 +23,7 @@ function replaceMailVaribles(subject, body, variables) {
}
// `sendmail` is used to signing reminder mail to signer
async function sendMail(doc, signer, mailcount) {
async function sendMail(doc, signer) {
const subject = `{{sender_name}} has requested you to sign "{{document_title}}"`;
const body = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;<b>"{{document_title}}"</b>.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br></body> </html>`;
const url = `${process.env.SERVER_URL}/functions/sendmailv3`;
@@ -46,10 +46,10 @@ async function sendMail(doc, signer, mailcount) {
document_title: doc.Name,
sender_name: doc.ExtUserPtr.Name,
sender_mail: doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr.Phone,
sender_phone: doc.ExtUserPtr?.Phone || '',
receiver_name: signer.Name,
receiver_email: signer.Email,
receiver_phone: signer.Phone,
receiver_phone: signer?.Phone || '',
expiry_date: localExpireDate,
company_name: doc?.ExtUserPtr?.Company || '',
signing_url: `<a href=${signPdf}>Sign here</a>`,
@@ -69,8 +69,7 @@ async function sendMail(doc, signer, mailcount) {
const res = await axios.post(url, params, { headers: headers });
// console.log('res ', res.data.result);
if (res.data.result.status === 'success') {
mailcount += 1;
return { status: 'success', count: mailcount };
return { status: 'success' };
}
} catch (err) {
console.log('err in mail of sendreminder api', err);
@@ -99,10 +98,11 @@ export default async function autoReminder(request, response) {
if (docsArr && docsArr.length > 0) {
const _docsArr = JSON.parse(JSON.stringify(docsArr));
let mailCount = 0;
let docCount = 0;
for (const doc of _docsArr) {
// The reminderDate variable is used to calculate the next reminder date.
const RemindOnceInEvery = doc?.RemindOnceInEvery || 5;
const ReminderDate = new Date(doc?.NextReminderDate?.iso);
const ReminderDate = new Date();
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
// The sendInOrder variable is used to determine whether to send emails in order or not.
const SendInOrder = doc?.SendinOrder || false;
@@ -112,9 +112,10 @@ export default async function autoReminder(request, response) {
const count = auditTrail?.length || 0;
const signer = doc?.Signers?.[count];
if (signer) {
const mailRes = await sendMail(doc, signer, mailCount);
docCount += 1;
const mailRes = await sendMail(doc, signer);
if (mailRes && mailRes.status === 'success') {
mailCount += mailRes.count;
mailCount += 1;
}
try {
// The code below is used to update the next reminder date of the document based on the "remind once every X days" setting.
@@ -139,11 +140,12 @@ export default async function autoReminder(request, response) {
}
});
if (signers?.length > 0) {
docCount += 1;
// The for...of loop below is used to send a signing reminder to every signer who hasn't signed the document yet.
for (const signer of signers) {
const mailRes = await sendMail(doc, signer, mailCount);
const mailRes = await sendMail(doc, signer);
if (mailRes && mailRes.status === 'success') {
mailCount += mailRes.count;
mailCount += 1;
}
}
}
@@ -151,10 +153,11 @@ export default async function autoReminder(request, response) {
// The for...of loop below is used to send a signing reminder to every signer who hasn't signed the document yet.
const signers = doc?.Signers;
if (signers?.length > 0) {
docCount += 1;
for (const signer of signers) {
const mailRes = await sendMail(doc, signer, mailCount);
const mailRes = await sendMail(doc, signer);
if (mailRes && mailRes.status === 'success') {
mailCount += mailRes.count;
mailCount += 1;
}
}
}
@@ -171,7 +174,11 @@ export default async function autoReminder(request, response) {
}
}
}
response.json({ status: 'success', document_count: docsArr.length, mail_count: mailCount });
if (docCount > 0) {
response.json({ status: 'success', document_count: docCount, mail_count: mailCount });
} else {
response.json({ status: 'no record found' });
}
} else {
response.json({ status: 'no record found' });
}
@@ -179,6 +179,7 @@ export default async function createDocumentWithTemplate(request, response) {
}
object.set('URL', template.URL);
object.set('SignedUrl', template.URL);
object.set('SentToOthers', true);
if (TimeToCompleteDays) {
object.set('TimeToCompleteDays', TimeToCompleteDays);
}
@@ -208,9 +209,6 @@ export default async function createDocumentWithTemplate(request, response) {
});
let sender = template.ExtUserPtr.Email;
let sendMail;
const serverUrl = process.env.SERVER_URL;
const newServer = serverUrl.replaceAll('/', '%2F');
const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`;
if (send_email === false) {
console.log("don't send mail");
} else {
@@ -265,10 +263,10 @@ export default async function createDocumentWithTemplate(request, response) {
document_title: template.Name,
sender_name: template.ExtUserPtr.Name,
sender_mail: template.ExtUserPtr.Email,
sender_phone: template.ExtUserPtr.Phone,
sender_phone: template.ExtUserPtr?.Phone || '',
receiver_name: contactMail[i].name,
receiver_email: contactMail[i].email,
receiver_phone: contactMail[i].phone,
receiver_phone: contactMail[i]?.phone || '',
expiry_date: localExpireDate,
company_name: orgName,
signing_url: signPdf,
@@ -314,7 +312,11 @@ export default async function createDocumentWithTemplate(request, response) {
name: template?.Name,
note: template?.Note || '',
description: template?.Description || '',
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x.phone })),
signers: contact?.map(x => ({
name: x.name,
email: x.email,
phone: x?.phone || '',
})),
createdAt: res.createdAt,
};
if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) {
@@ -3,7 +3,7 @@ export default async function createContact(request, response) {
const serverUrl = process.env.SERVER_URL;
const appId = process.env.APP_ID;
const name = request.body.name;
const phone = request.body.phone;
const phone = request.body?.phone;
const email = request.body.email;
const reqToken = request.headers['x-api-token'];
if (!reqToken) {
@@ -49,7 +49,9 @@ export default async function createContact(request, response) {
const contactQuery = new Parse.Object('contracts_Contactbook');
contactQuery.set('Name', name);
contactQuery.set('Phone', phone);
if (phone) {
contactQuery.set('Phone', phone);
}
contactQuery.set('Email', email);
contactQuery.set('UserRole', 'contracts_Guest');
if (tenantRes && tenantRes.id) {
@@ -65,8 +67,10 @@ export default async function createContact(request, response) {
_user.set('name', name);
_user.set('username', email);
_user.set('email', email);
_user.set('phone', phone);
_user.set('password', phone);
_user.set('password', email);
if (phone) {
_user.set('phone', phone);
}
const user = await _user.save();
if (user) {
@@ -106,7 +110,7 @@ export default async function createContact(request, response) {
objectId: parseRes.objectId,
name: parseRes.Name,
email: parseRes.Email,
phone: parseRes.Phone,
phone: parseRes?.Phone || '',
createdAt: parseRes.createdAt,
updatedAt: parseRes.updatedAt,
});
@@ -155,7 +159,7 @@ export default async function createContact(request, response) {
objectId: parseRes.objectId,
name: parseRes.Name,
email: parseRes.Email,
phone: parseRes.Phone,
phone: parseRes?.Phone || '',
createdAt: parseRes.createdAt,
updatedAt: parseRes.updatedAt,
});
@@ -36,7 +36,7 @@ async function sendDoctoWebhook(doc, WebhookUrl, userId) {
}
})
.catch(err => {
console.log('Err send data to webhook', err);
console.log('Err send data to webhook', err?.message);
try {
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', err?.status);
@@ -47,7 +47,7 @@ async function sendDoctoWebhook(doc, WebhookUrl, userId) {
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err);
console.log('err save in contracts_Webhook', err?.message);
}
});
// console.log('res ', res.data);
@@ -141,6 +141,7 @@ export default async function createDocumentwithCoordinate(request, response) {
}
object.set('URL', fileUrl);
object.set('SignedUrl', fileUrl);
object.set('SentToOthers', true);
object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr);
if (TimeToCompleteDays) {
@@ -255,7 +256,7 @@ export default async function createDocumentwithCoordinate(request, response) {
name: name,
note: note || '',
description: description || '',
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x.phone })),
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })),
createdAt: res.createdAt,
};
if (parseExtUser && parseExtUser.Webhook) {
@@ -269,9 +270,6 @@ export default async function createDocumentwithCoordinate(request, response) {
year: 'numeric',
});
let sender = parseExtUser.Email;
const serverUrl = process.env.SERVER_URL;
const newServer = serverUrl.replaceAll('/', '%2F');
const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`;
if (send_email === false) {
console.log("don't send mail");
} else {
@@ -325,10 +323,10 @@ export default async function createDocumentwithCoordinate(request, response) {
document_title: name,
sender_name: parseExtUser.Name,
sender_mail: parseExtUser.Email,
sender_phone: parseExtUser.Phone,
sender_phone: parseExtUser?.Phone || '',
receiver_name: contactMail[i].name,
receiver_email: contactMail[i].email,
receiver_phone: contactMail[i].phone,
receiver_phone: contactMail[i]?.phone || '',
expiry_date: localExpireDate,
company_name: orgName,
signing_url: signPdf,
@@ -91,7 +91,7 @@ export default async function createTemplatewithCoordinate(request, response) {
let createContactUrl = protocol + '/v1/createcontact';
for (const [index, element] of parseSigners.entries()) {
if (element?.name && element.phone && element?.email) {
if (element?.name && element?.email) {
const body = {
name: element?.name || '',
email: element?.email || '',
@@ -37,7 +37,7 @@ export default async function getContact(request, response) {
objectId: parseRes.objectId,
name: parseRes.Name,
email: parseRes.Email,
phone: parseRes.Phone,
phone: parseRes?.Phone || '',
createdAt: parseRes.createdAt,
updatedAt: parseRes.updatedAt,
});
@@ -31,7 +31,7 @@ export default async function getContactList(request, response) {
objectId: x.objectId,
name: x.Name,
email: x.Email,
phone: x.Phone,
phone: x?.Phone || '',
createdAt: x.createdAt,
updatedAt: x.updatedAt,
}));
@@ -37,7 +37,7 @@ export default async function getUser(request, response) {
objectId: parseRes.objectId,
name: parseRes.Name,
email: parseRes.Email,
phone: parseRes.Phone,
phone: parseRes?.Phone || '',
jobTitle: parseRes.JobTitle,
company: parseRes.Company,
createdAt: parseRes.createdAt,
@@ -97,10 +97,10 @@ export default async function resendMail(request, response) {
document_title: title,
sender_name: _resDoc.ExtUserPtr.Name,
sender_mail: _resDoc.ExtUserPtr.Email,
sender_phone: _resDoc.ExtUserPtr.Phone,
sender_phone: _resDoc.ExtUserPtr?.Phone || '',
receiver_name: contact.Name,
receiver_email: contact.Email,
receiver_phone: contact.Phone,
receiver_phone: contact?.Phone || '',
expiry_date: localExpireDate,
company_name: orgName,
signing_url: signPdf,
+33 -12
View File
@@ -1,4 +1,4 @@
import PDF from './parsefunction/pdf/PDF.min.js';
import PDF from './parsefunction/pdf/PDF.js';
import sendmailv3 from './parsefunction/sendMailv3.js';
import GoogleSign from './parsefunction/GoogleSign.js';
import ZohoDetails from './parsefunction/ZohoDetails.js';
@@ -37,7 +37,32 @@ import saveSubscription from './parsefunction/saveSubscription.js';
import VerifyEmail from './parsefunction/VerifyEmail.js';
import encryptedpdf from './parsefunction/encryptedPdf.js';
import { getSignedUrl } from './parsefunction/getSignedUrl.js';
import createBatchDocs from './parsefunction/createBatchDocs.js';
import linkContactToDoc from './parsefunction/linkContactToDoc.js';
import CreatePublicTemplate from './parsefunction/CreatePublicTemplate.js';
import GetPublicUserName from './parsefunction/GetPublicUserName.js';
import GetPublicTemplate from './parsefunction/GetPublicTemplate.js';
import ssoSignin from './parsefunction/ssoSignin.js';
import isextenduser from './parsefunction/isextenduser.js';
// 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_Contactbook', ContactbookAftersave);
Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave);
Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave);
// This beforeSave function triggers before an object is added or updated in the specified class, allowing for validation or modification.
Parse.Cloud.beforeSave('contracts_Document', DocumentBeforesave);
Parse.Cloud.beforeSave('contracts_Template', TemplateBeforeSave);
// This afterFind function triggers after a query retrieves objects from the specified class, allowing for post-processing of the results.
Parse.Cloud.afterFind(Parse.User, UserAfterFind);
Parse.Cloud.afterFind('contracts_Document', DocumentBeforeFind);
Parse.Cloud.afterFind('contracts_Template', TemplateAfterFind);
Parse.Cloud.afterFind('contracts_Signature', SignatureAfterFind);
Parse.Cloud.afterFind('partners_Tenant', TenantAterFind);
// This define function creates a custom Cloud Function that can be called from the client-side, enabling custom business logic on the server.
Parse.Cloud.define('AddUserToRole', addUserToGroups);
Parse.Cloud.define('UserGroups', getUserGroups);
Parse.Cloud.define('signPdf', PDF);
@@ -62,18 +87,14 @@ Parse.Cloud.define('freesubscription', SubscribeFree);
Parse.Cloud.define('getinvoices', getInvoices);
Parse.Cloud.define('getpayments', getPayments);
Parse.Cloud.define('getsubscriptions', getSubscriptions);
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave);
Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave);
Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave);
Parse.Cloud.beforeSave('contracts_Document', DocumentBeforesave);
Parse.Cloud.beforeSave('contracts_Template', TemplateBeforeSave);
Parse.Cloud.afterFind(Parse.User, UserAfterFind);
Parse.Cloud.afterFind('contracts_Document', DocumentBeforeFind);
Parse.Cloud.afterFind('contracts_Template', TemplateAfterFind);
Parse.Cloud.afterFind('contracts_Signature', SignatureAfterFind);
Parse.Cloud.afterFind('partners_Tenant', TenantAterFind);
Parse.Cloud.define('savesubscription', saveSubscription);
Parse.Cloud.define('verifyemail', VerifyEmail);
Parse.Cloud.define('encryptedpdf', encryptedpdf);
Parse.Cloud.define('getsignedurl', getSignedUrl);
Parse.Cloud.define('batchdocuments', createBatchDocs);
Parse.Cloud.define('linkcontacttodoc', linkContactToDoc);
Parse.Cloud.define('createpublictemplate', CreatePublicTemplate);
Parse.Cloud.define('getpublicusername', GetPublicUserName);
Parse.Cloud.define('getpublictemplate', GetPublicTemplate);
Parse.Cloud.define('ssosign', ssoSignin);
Parse.Cloud.define('isextenduser', isextenduser);
@@ -8,7 +8,9 @@ async function ContractUsersAftersave(request) {
contactbook.set('Name', request.object.get('Name'));
contactbook.set('Email', request.object.get('Email'));
contactbook.set('ExtUserPtr', request.object.get('objectId'));
contactbook.set('Phone', request.object.get('Phone'));
if (request.object?.get('Phone')) {
contactbook.set('Phone', request.object.get('Phone'));
}
contactbook.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
@@ -0,0 +1,41 @@
export default async function CreatePublicTemplate(request) {
const templateid = request.params.templateid;
const ispublic = request.params.ispublic;
const publicrole = request.params.publicrole;
try {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
} else {
const userId = request?.user && request?.user?.id;
if (templateid) {
const updateTemplate = new Parse.Object('contracts_Template');
updateTemplate.id = templateid;
if (ispublic) {
updateTemplate.set('PublicRole', publicrole);
}
updateTemplate.set('IsPublic', ispublic);
const Acl = new Parse.ACL();
if (ispublic) {
Acl.setPublicReadAccess(true);
}
Acl.setReadAccess(userId, true);
Acl.setWriteAccess(userId, true);
updateTemplate.setACL(Acl);
const savedObject = await updateTemplate.save(null, { useMasterKey: true });
const res = savedObject.toJSON();
if (res) {
return {
status: 'success',
};
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Please provide required parameters!');
}
}
} catch (err) {
const code = err.code || 400;
const msg = err.message;
const error = new Parse.Error(code, msg);
throw error;
}
}
@@ -1,4 +1,4 @@
import axios from "axios";
import axios from 'axios';
const serverUrl = process.env.SERVER_URL;
const APPID = process.env.APP_ID;
@@ -18,23 +18,23 @@ export default async function FacebookSign(request) {
const userGoogleId = request.params.Id;
const userAccessToken = request.params.AccessToken;
const userEmail = request.params.Email;
const phone = request.params.Phone;
const phone = request.params?.Phone || '';
const name = request.params.Name;
const authData = {
facebook: { id: userGoogleId, access_token: userAccessToken },
};
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo("email", userEmail);
userQuery.equalTo('email', userEmail);
const res = await userQuery.first({ useMasterKey: true });
if (res) {
try {
const SignIn = await axios.put(
serverUrl + "/users/" + res.id,
serverUrl + '/users/' + res.id,
{ authData: authData },
{
headers: {
"X-Parse-Application-Id": APPID,
"X-Parse-Master-key": masterKEY,
'X-Parse-Application-Id': APPID,
'X-Parse-Master-key': masterKEY,
},
}
);
@@ -42,22 +42,22 @@ export default async function FacebookSign(request) {
if (SignIn.data) {
// console.log("google Sign in", SignIn);
const sessiontoken = SignIn.data.sessionToken;
console.log("Google sessiontoken", sessiontoken);
console.log('Google sessiontoken', sessiontoken);
return {
email: userEmail,
message: "User Sign In",
message: 'User Sign In',
sessiontoken: sessiontoken,
};
}
} catch (err) {
console.log("err in user google sign in", err);
return { message: "Internal server error" };
console.log('err in user google sign in', err);
return { message: 'Internal server error' };
}
} else {
// console.log("in sign up condition");
try {
const SignUp = await axios.post(
serverUrl + "/users",
serverUrl + '/users',
{
authData: authData,
username: userEmail,
@@ -67,8 +67,8 @@ export default async function FacebookSign(request) {
},
{
headers: {
"X-Parse-Application-Id": APPID,
"X-Parse-Revocable-Session": "1",
'X-Parse-Application-Id': APPID,
'X-Parse-Revocable-Session': '1',
},
}
);
@@ -79,14 +79,14 @@ export default async function FacebookSign(request) {
const sessiontoken = SignUp.data.sessionToken;
const payload = {
email: userEmail,
message: "User Sign Up",
message: 'User Sign Up',
sessiontoken: sessiontoken,
};
return payload;
}
} catch (err) {
console.log("err in user google sign up", err);
return { message: "Internal server err" };
console.log('err in user google sign up', err);
return { message: 'Internal server err' };
}
}
}
@@ -0,0 +1,32 @@
async function GetPublicTemplate(request) {
try {
const username = request.params.username;
if (username) {
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('UserName', username);
const res = await extUserQuery.first({ useMasterKey: true });
const userId = res.get('UserId').id;
if (userId) {
const templatQuery = new Parse.Query('contracts_Template');
templatQuery.equalTo('CreatedBy', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
templatQuery.equalTo('IsPublic', true);
const getTemplate = await templatQuery.find({ useMasterKey: true });
return getTemplate;
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found');
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Please provide required parameters!');
}
} catch (err) {
const code = err.code || 400;
const msg = err.message;
const error = new Parse.Error(code, msg);
throw error;
}
}
export default GetPublicTemplate;
@@ -0,0 +1,23 @@
async function GetPublicUserName(request) {
try {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
} else {
const username = request.params.username;
if (username) {
const userQuery = new Parse.Query('contracts_Users');
userQuery.equalTo('UserName', username);
const res = await userQuery.first({ useMasterKey: true });
return res;
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Please provide required parameters!');
}
}
} catch (err) {
const code = err.code || 400;
const msg = err.message;
const error = new Parse.Error(code, msg);
throw error;
}
}
export default GetPublicUserName;
@@ -1,4 +1,4 @@
import axios from "axios";
import axios from 'axios';
const serverUrl = process.env.SERVER_URL;
const APPID = process.env.APP_ID;
@@ -9,8 +9,8 @@ const masterKEY = process.env.MASTER_KEY;
* @param Id It is google Id
* @param TokenId It is google token Id
* @param Gmail It is user's gmail with user sign in/sign up
* @param Phone It is user's Phone number
* @param Name It is user's Name
* @param Phone It is user's Phone number
* @param Name It is user's Name
* @returns if success {email, message, sessiontoken} else on reject {message}
*/
@@ -18,21 +18,21 @@ export default async function GoogleSign(request) {
const userGoogleId = request.params.Id;
const userTokenId = request.params.TokenId;
const userEmail = request.params.Gmail;
const phone = request.params.Phone;
const phone = request.params?.Phone || '';
const name = request.params.Name;
const authData = { google: { id: userGoogleId, id_token: userTokenId } };
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo("email", userEmail);
userQuery.equalTo('email', userEmail);
const res = await userQuery.first({ useMasterKey: true });
if (res) {
try {
const SignIn = await axios.put(
serverUrl + "/users/" + res.id,
serverUrl + '/users/' + res.id,
{ authData: authData },
{
headers: {
"X-Parse-Application-Id": APPID,
"X-Parse-Master-key": masterKEY,
'X-Parse-Application-Id': APPID,
'X-Parse-Master-key': masterKEY,
},
}
);
@@ -40,22 +40,22 @@ export default async function GoogleSign(request) {
if (SignIn.data) {
// console.log("google Sign in", SignIn);
const sessiontoken = SignIn.data.sessionToken;
console.log("Google sessiontoken", sessiontoken);
console.log('Google sessiontoken', sessiontoken);
return {
email: userEmail,
message: "User Sign In",
message: 'User Sign In',
sessiontoken: sessiontoken,
};
}
} catch (err) {
console.log("err in user google sign in", err);
return { message: "Internal server error" };
console.log('err in user google sign in', err);
return { message: 'Internal server error' };
}
} else {
// console.log("in sign up condition");
try {
const SignUp = await axios.post(
serverUrl + "/users",
serverUrl + '/users',
{
authData: authData,
username: userEmail,
@@ -65,8 +65,8 @@ export default async function GoogleSign(request) {
},
{
headers: {
"X-Parse-Application-Id": APPID,
"X-Parse-Revocable-Session": "1",
'X-Parse-Application-Id': APPID,
'X-Parse-Revocable-Session': '1',
},
}
);
@@ -77,14 +77,14 @@ export default async function GoogleSign(request) {
const sessiontoken = SignUp.data.sessionToken;
const payload = {
email: userEmail,
message: "User Sign Up",
message: 'User Sign Up',
sessiontoken: sessiontoken,
};
return payload;
}
} catch (err) {
console.log("err in user google sign up", err);
return { message: "Internal server err" };
console.log('err in user google sign up', err);
return { message: 'Internal server err' };
}
}
}
@@ -1,4 +1,4 @@
import { updateMailCount } from '../../Utils.js';
import { smtpenable, updateMailCount } from '../../Utils.js';
async function getDocument(docId) {
try {
const query = new Parse.Query('contracts_Document');
@@ -25,9 +25,7 @@ async function sendMailOTPv1(request) {
if (email) {
const recipient = request.params.email;
const mailsender = process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER;
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
try {
await Parse.Cloud.sendEmail({
from: 'Opensign™' + ' <' + mailsender + '>',
@@ -39,7 +37,7 @@ async function sendMailOTPv1(request) {
code +
'</p></div> </div> </div></body></html>',
});
console.log('OTP sent');
console.log('OTP sent', code);
if (request.params?.docId) {
const extUserId = await getDocument(request.params?.docId);
if (extUserId) {
@@ -2,41 +2,41 @@
//-- Export Modules
import dotenv from 'dotenv';
dotenv.config()
import axios from "axios";
dotenv.config();
import axios from 'axios';
export async function getUserGroups(request) {
try {
var appname = request.params.appname;
if (appname == "") {
return Promise.reject("Error:please provide appname");
if (appname == '') {
return Promise.reject('Error:please provide appname');
}
var response = {};
var rolelist = {};
appname = appname + "_";
appname = appname + '_';
//--function to get the userid from session token
function getuserid(request) {
return new Promise(function (resolve, reject) {
const options = {
url: process.env.SERVER_URL + "/users/me",
method: "get",
url: process.env.SERVER_URL + '/users/me',
method: 'get',
headers: {
"X-Parse-Application-Id": process.env.APP_ID,
"X-Parse-Session-Token": request.headers["sessiontoken"],
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
};
axios(options)
.then((x) => {
.then(x => {
const body = x.data;
var error = body == "" ? true : false;
var error = body == '' ? true : false;
if (error) {
reject("result not found!");
reject('result not found!');
} else {
resolve(body);
}
})
.catch((err) => {
.catch(err => {
if (err) {
console.error(err);
return;
@@ -46,7 +46,7 @@ export async function getUserGroups(request) {
}
var userData = await getuserid(request);
var userid = userData.objectId;
console.log("userid " + userid);
// console.log("userid " + userid);
var url =
process.env.SERVER_URL +
'/roles?where={"users":{"__type":"Pointer","className":"_User","objectId":"' +
@@ -60,29 +60,29 @@ export async function getUserGroups(request) {
return new Promise(function (resolve, reject) {
const options = {
url: url,
method: "get",
method: 'get',
headers: {
"X-Parse-Application-Id": process.env.APP_ID,
'X-Parse-Application-Id': process.env.APP_ID,
},
};
axios(options)
.then((x) => {
.then(x => {
const body = x.data;
var roleres = [];
for (var i = 0; i < body["results"].length; i++) {
var rolename = body["results"][i]["name"];
for (var i = 0; i < body['results'].length; i++) {
var rolename = body['results'][i]['name'];
//var roleprefix = rolename.split("_")[0];
roleres.push(rolename);
}
var error = roleres == "" ? true : false;
var error = roleres == '' ? true : false;
if (error) {
reject("result not found!");
reject('result not found!');
} else {
resolve(roleres);
}
})
.catch((err) => {
.catch(err => {
if (err) {
console.error(err);
return;
@@ -92,7 +92,7 @@ export async function getUserGroups(request) {
}
rolelist = await getRoleList(request);
console.log(rolelist);
// console.log(rolelist);
//--check user roles according to appId
var rolesInapp = [];
for (let i = 0; i < rolelist.length; i++) {
@@ -102,11 +102,11 @@ export async function getUserGroups(request) {
rolesInapp.push(rolelist[i]);
}
}
console.log(rolesInapp);
// console.log(rolesInapp);
return rolesInapp;
} catch (err) {
console.log("err in usergroup");
console.log('err in usergroup');
console.log(err);
return Promise.reject("Error:Result not found");
return Promise.reject('Error:Result not found');
}
}
@@ -26,12 +26,15 @@ export default async function ZohoDetails(request) {
// console.log("Access Token:", res.data);
if (res.data.access_token) {
const hostedpages = request.params.hostedpagesId;
const userData = await axios.get('https://billing.zoho.in/api/v1/hostedpages/' + hostedpages, {
headers: {
Authorization: 'Zoho-oauthtoken ' + res.data.access_token,
'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID,
},
});
const userData = await axios.get(
'https://www.zohoapis.in/billing/v1/hostedpages/' + hostedpages,
{
headers: {
Authorization: 'Zoho-oauthtoken ' + res.data.access_token,
'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID,
},
}
);
const first_name = userData.data.data.subscription.contactpersons[0].first_name || '';
const last_name = userData.data.data.subscription.contactpersons[0].last_name || '';
@@ -44,7 +47,7 @@ export default async function ZohoDetails(request) {
userData.data.data.subscription.customer.cd_job_title) ||
'';
const resData = {
phone: userData.data.data.subscription.contactpersons[0].mobile,
phone: userData.data.data.subscription.contactpersons[0]?.mobile || '',
name: first_name + ' ' + last_name,
email: userData.data.data.subscription.contactpersons[0].email,
nextBillingDate: userData.data.data.subscription.next_billing_at,
@@ -0,0 +1,182 @@
import axios from 'axios';
const serverUrl = process.env.SERVER_URL;
const appId = process.env.APP_ID;
async function sendMail(document, sessionToken) {
const baseUrl = new URL(process.env.PUBLIC_URL);
// console.log("pdfDetails", pdfDetails);
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
const ExpireDate = new Date(document.createdAt);
ExpireDate.setDate(ExpireDate.getDate() + timeToCompleteDays);
const newDate = ExpireDate;
const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
const sender = document.ExtUserPtr.Email;
let signerMail = document.Placeholders;
if (document.SendinOrder) {
signerMail = signerMail.slice();
signerMail.splice(1);
}
for (let i = 0; i < signerMail.length; i++) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
let url = `${serverUrl}/functions/sendmailv3`;
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
sessionToken: sessionToken,
};
const objectId = signerMail[i]?.signerObjId;
const hostUrl = baseUrl.origin;
let encodeBase64;
let existSigner = {};
if (objectId) {
existSigner = document?.Signers?.find(user => user.objectId === objectId);
encodeBase64 = btoa(`${document.objectId}/${existSigner?.Email}/${objectId}`);
} else {
encodeBase64 = btoa(`${document.objectId}/${signerMail[i].email}`);
}
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl = 'https://www.opensignlabs.com/';
const orgName = document.ExtUserPtr.Company ? document.ExtUserPtr.Company : '';
const themeBGcolor = '#47a3ad';
let params = {
extUserId: document.ExtUserPtr.objectId,
recipient: objectId ? existSigner?.Email : signerMail[i].email,
subject: `${document.ExtUserPtr.Name} has requested you to sign "${document.Name}"`,
mailProvider: document?.ExtUserPtr?.active_mail_adapter || '',
from: sender,
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: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src='" +
imgPng +
"' height='50' style='padding:20px; width:170px; height:40px;' /></div><div style='padding: 2px;font-family: system-ui;background-color:" +
themeBGcolor +
";'><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;'> " +
document.ExtUserPtr.Name +
' has requested you to review and sign <strong> ' +
document.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'>" +
sender +
"</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 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 " +
sender +
' 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>',
};
const sendMail = await axios.post(url, params, { headers: headers });
// if (sendMail.data.result.status === 'success') {
// console.log('batch login mail sent');
// }
} catch (error) {
console.log('error', error);
}
}
}
export default async function createBatchDocs(request) {
const strDocuments = request.params.Documents;
const sessionToken = request.headers['sessiontoken'];
const Documents = JSON.parse(strDocuments);
const Ip = request?.headers?.['x-real-ip'] || '';
// console.log('Documents ', Documents);
const parseConfig = {
baseURL: serverUrl, //localStorage.getItem('baseUrl'),
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': sessionToken,
'Content-Type': 'application/json',
},
};
try {
const requests = Documents.map(x => {
const Signers = x.Signers;
const allSigner = x?.Placeholders?.map(
item => Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr
).filter(signer => Object.keys(signer).length > 0);
const date = new Date();
const isoDate = date.toISOString();
let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } };
if (allSigner && allSigner.length > 0) {
allSigner.forEach(x => {
const obj = { [x.CreatedBy.objectId]: { read: true, write: true } };
Acl = { ...Acl, ...obj };
});
}
return {
method: 'POST',
path: '/app/classes/contracts_Document',
body: {
Name: x.Name,
URL: x.URL,
Note: x.Note,
Description: x.Description,
CreatedBy: x.CreatedBy,
SendinOrder: x.SendinOrder || true,
ExtUserPtr: {
__type: 'Pointer',
className: x.ExtUserPtr.className,
objectId: x.ExtUserPtr.objectId,
},
Placeholders: x.Placeholders.map(y =>
y?.signerPtr?.objectId
? {
...y,
signerPtr: {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.signerPtr.objectId,
},
signerObjId: y.signerObjId,
}
: { ...y, signerPtr: {}, signerObjId: '' }
),
SignedUrl: x.URL || x.SignedUrl,
SentToOthers: true,
Signers: allSigner?.map(y => ({
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.objectId,
})),
ACL: Acl,
SentToOthers: true,
RemindOnceInEvery: x.RemindOnceInEvery || 5,
AutomaticReminders: x.AutomaticReminders || false,
TimeToCompleteDays: x.TimeToCompleteDays || 15,
OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate },
},
};
});
// console.log('requests ', requests);
const response = await axios.post('batch', { requests: requests }, parseConfig);
// // Handle the batch query response
// console.log('Batch query response:', response.data);
if (response.data && response.data.length > 0) {
const updateDocuments = Documents.map((x, i) => ({
...x,
objectId: response.data[i]?.success?.objectId,
createdAt: response.data[i]?.success?.createdAt,
}));
for (let i = 0; i < updateDocuments.length; i++) {
sendMail(updateDocuments[i], sessionToken);
}
return 'success';
}
// Handle individual responses within response.data.results
} catch (error) {
console.error('Error performing batch query:', error);
}
}
@@ -30,7 +30,7 @@ export default async function getReport(request) {
'X-Parse-Application-Id': appId,
'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`;
const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys}&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr,Placeholders.signerPtr`;
const res = await axios.get(url, { headers: headers });
if (res.data && res.data.results) {
return res.data.results;
@@ -0,0 +1,30 @@
/**
* Checks if a user exists in the extended class 'contracts_Users' based on the provided email.
* @param email - The request contains parameters, such as the user's email.
* @returns {Object} - Returns an object indicating whether the user exists.
*/
export default async function isextenduser(request) {
try {
// Query the 'contracts_Users' class in the database based on the provided email
const userQuery = new Parse.Query('contracts_Users');
userQuery.equalTo('Email', request.params.email);
// Execute the query
const res = await userQuery.first({ useMasterKey: true });
// Check if a user was found
if (res) {
// If user exists, return object with 'isUserExist' set to true
return { isUserExist: true };
} else {
// If user does not exist, return object with 'isUserExist' set to false
return { isUserExist: false };
}
} catch (err) {
// Handle errors
console.log('Error in userexist', err);
const code = err?.code || 400;
const message = err?.message || 'Something went wrong.';
throw new Parse.Error(code, message);
}
}
@@ -0,0 +1,293 @@
// `saveRoleContact` is used to save user in contracts_Guest role and create contact
const saveRoleContact = async contact => {
try {
const Role = new Parse.Query(Parse.Role);
const guestRole = await Role.equalTo('name', 'contracts_Guest').first();
if (guestRole) {
// Check if the user is already in the role
const relation = guestRole.relation('users');
const usersInRoleQuery = relation.query();
usersInRoleQuery.equalTo('objectId', contact.UserId.objectId);
const usersInRole = await usersInRoleQuery.find();
if (usersInRole.length > 0) {
console.log('User already added to Guest role.');
} else {
relation.add({ __type: 'Pointer', className: '_User', id: contact.UserId.objectId });
await guestRole.save(null, { useMasterKey: true });
// console.log('User added to Guest role successfully.');
}
}
} catch (err) {
console.log('err in role save', err);
}
const contactQuery = new Parse.Object('contracts_Contactbook');
contactQuery.set('Name', contact.Name);
contactQuery.set('Email', contact.Email);
if (contact?.Phone) {
contactQuery.set('Phone', contact.Phone);
}
contactQuery.set('CreatedBy', contact.CreatedBy);
contactQuery.set('UserId', contact.UserId);
contactQuery.set('UserRole', 'contracts_Guest');
contactQuery.set('TenantId', contact.TenantId);
contactQuery.set('IsDeleted', false);
const acl = new Parse.ACL();
acl.setReadAccess(contact.CreatedBy.objectId, true);
acl.setWriteAccess(contact.CreatedBy.objectId, true);
acl.setReadAccess(contact.UserId.objectId, true);
acl.setWriteAccess(contact.UserId.objectId, true);
contactQuery.setACL(acl);
const contactRes = await contactQuery.save();
if (contactRes) {
return contactRes;
}
};
// `linkContactToDoc` cloud function is used to create contact, add this contact in contracts_Guest role and
// save contact pointer in placeholder, signers and ACL of Document
export default async function linkContactToDoc(req) {
const email = req.params.email;
const docId = req.params.docId;
const name = req.params.name;
const phone = req.params.phone;
try {
if (docId) {
// Execute the query to get the document with the specified 'docId'
const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr');
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
if (!docRes) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
const _docRes = JSON.parse(JSON.stringify(docRes));
const Placeholders = _docRes?.Placeholders || [];
const index = Placeholders?.findIndex(x => x.email && x.email === email);
if (index !== -1) {
// `signerObjectId` holds the value of `signerObjId` from the `Placeholders` array at the current index.
// This value is used to check if `signerObjId` is present or not.
const signerObjectId = Placeholders[index]?.signerObjId;
if (signerObjectId) {
return { contactId: signerObjectId };
}
// Execute the query to check if a contact already exists in the 'contracts_Contactbook' class
const contactCls = new Parse.Query('contracts_Contactbook');
contactCls.equalTo('Email', email);
contactCls.equalTo('CreatedBy', _docRes.CreatedBy);
contactCls.notEqualTo('IsDeleted', true);
const existContact = await contactCls.first({ useMasterKey: true });
if (existContact) {
//update contact in placeholder, signers and update ACl in provide document
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = docId;
const signers = _docRes?.Signers || [];
const signerobj = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: existContact.id,
};
// The splice method is used to add a signer at the desired index
// index is the variable where the signer needs to be added
// 0 indicates that no elements should be deleted
// signerobj is the reference to the signer object
signers.splice(index, 0, signerobj);
updateDoc.set('Signers', signers);
Placeholders[index] = {
...Placeholders[index],
signerObjId: existContact.id,
signerPtr: {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: existContact.id,
},
};
updateDoc.set('Placeholders', Placeholders);
const Acl = docRes.getACL();
Acl.setReadAccess(existContact.get('UserId').id, true);
Acl.setWriteAccess(existContact.get('UserId').id, true);
updateDoc.setACL(Acl);
// const parseData = JSON.parse(JSON.stringify(res));
const resDoc = await updateDoc.save(null, { useMasterKey: true });
if (resDoc) {
return { contactId: existContact.id };
}
} else {
// Execute the query to check if a user already exists in the 'contracts_Users' class
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('Email', email);
const extUser = await extUserQuery.first({ useMasterKey: true });
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
const contact = {
UserId: _extUser.UserId,
Name: _extUser.Name,
Email: email,
Phone: _extUser?.Phone ? _extUser.Phone : '',
CreatedBy: _docRes.CreatedBy,
TenantId: _docRes.ExtUserPtr.TenantId,
};
// if user present on platform create contact on the basis of extended user details
const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = docId;
const signers = _docRes?.Signers || [];
const signerobj = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactRes.id,
};
// The splice method is used to add a signer at the desired index
// index is the variable where the signer needs to be added
// 0 indicates that no elements should be deleted
// signerobj is the reference to the signer object
signers.splice(index, 0, signerobj);
updateDoc.set('Signers', signers);
Placeholders[index] = {
...Placeholders[index],
signerObjId: contactRes.id,
signerPtr: {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactRes.id,
},
};
updateDoc.set('Placeholders', Placeholders);
const Acl = docRes.getACL();
Acl.setReadAccess(_extUser.UserId.objectId, true);
Acl.setWriteAccess(_extUser.UserId.objectId, true);
updateDoc.setACL(Acl);
// const parseData = JSON.parse(JSON.stringify(res));
const resDoc = await updateDoc.save(null, { useMasterKey: true });
if (resDoc) {
return { contactId: contactRes.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: _docRes.CreatedBy,
TenantId: _docRes.ExtUserPtr.TenantId,
};
// 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 updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = docId;
const signers = _docRes?.Signers || [];
const signerobj = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactRes.id,
};
// The splice method is used to add a signer at the desired index
// index is the variable where the signer needs to be added
// 0 indicates that no elements should be deleted
// signerobj is the reference to the signer object
signers.splice(index, 0, signerobj);
updateDoc.set('Signers', signers);
Placeholders[index] = {
...Placeholders[index],
signerObjId: contactRes.id,
signerPtr: {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactRes.id,
},
};
updateDoc.set('Placeholders', Placeholders);
const Acl = docRes.getACL();
Acl.setReadAccess(userRes.id, true);
Acl.setWriteAccess(userRes.id, true);
updateDoc.setACL(Acl);
// const parseData = JSON.parse(JSON.stringify(res));
const resDoc = await updateDoc.save(null, { useMasterKey: true });
if (resDoc) {
return { contactId: contactRes.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: _docRes.CreatedBy,
TenantId: _docRes.ExtUserPtr.TenantId,
};
// 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 updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = docId;
const signers = _docRes?.Signers || [];
const signerobj = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactRes.id,
};
// The splice method is used to add a signer at the desired index
// index is the variable where the signer needs to be added
// 0 indicates that no elements should be deleted
// signerobj is the reference to the signer object
signers.splice(index, 0, signerobj);
updateDoc.set('Signers', signers);
Placeholders[index] = {
...Placeholders[index],
signerObjId: contactRes.id,
signerPtr: {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactRes.id,
},
};
updateDoc.set('Placeholders', Placeholders);
const Acl = docRes.getACL();
Acl.setReadAccess(newUserRes.id, true);
Acl.setWriteAccess(newUserRes.id, true);
updateDoc.setACL(Acl);
// const parseData = JSON.parse(JSON.stringify(res));
const resDoc = await updateDoc.save(null, { useMasterKey: true });
if (resDoc) {
return { contactId: contactRes.id };
}
}
} catch (err) {
console.log('Err', err);
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
}
} else {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'unauthorized');
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
} catch (err) {
console.log('err in linkcontacttodoc', err);
throw err;
}
}
@@ -0,0 +1,473 @@
import fs from 'node:fs';
import axios from 'axios';
import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
import { PDFDocument } from 'pdf-lib';
import { replaceMailVaribles, saveFileUsage } from '../../../Utils.js';
import GenerateCertificate from './GenerateCertificate.js';
const serverUrl = process.env.SERVER_URL;
const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY;
const eSignName = 'opensign';
const eSigncontact = 'hello@opensignlabs.com';
// `updateDoc` is used to create url in from pdfFile
async function uploadFile(pdfName, filepath) {
try {
const filedata = fs.readFileSync(filepath);
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
await file.save({ useMasterKey: true });
const fileUrl = file.url();
return { imageUrl: fileUrl };
} catch (err) {
console.log('Err ', err);
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
fs.unlinkSync(filepath);
}
}
// `updateDoc` is used to update signedUrl, AuditTrail, Iscompleted in document
async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
try {
const UserPtr = {
__type: 'Pointer',
className: className,
objectId: userId,
};
const obj = {
UserPtr: UserPtr,
SignedUrl: url,
Activity: 'Signed',
ipAddress: ipAddress,
SignedOn: new Date(),
Signature: sign,
};
let updateAuditTrail;
if (data.AuditTrail && data.AuditTrail.length > 0) {
const AuditTrail = JSON.parse(JSON.stringify(data.AuditTrail));
const existingIndex = AuditTrail.findIndex(
entry => entry.UserPtr.objectId === userId && entry.Activity !== 'Created'
);
existingIndex !== -1
? (AuditTrail[existingIndex] = { ...AuditTrail[existingIndex], ...obj })
: AuditTrail.push(obj);
updateAuditTrail = AuditTrail;
} else {
updateAuditTrail = [obj];
}
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
let isCompleted = false;
if (data.Signers && data.Signers.length > 0) {
if (auditTrail.length === data.Placeholders.length) {
isCompleted = true;
}
} else {
isCompleted = true;
}
const body = {
SignedUrl: url,
AuditTrail: updateAuditTrail,
IsCompleted: isCompleted,
};
const signedRes = await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
return { isCompleted: isCompleted, message: 'success', AuditTrail: updateAuditTrail };
} catch (err) {
console.log('update doc err ', err);
return 'err';
}
}
// `sendCompletedMail` is used to send copy of completed document mail
async function sendCompletedMail(obj) {
const url = obj.url;
const doc = obj.doc;
const sender = obj.doc.ExtUserPtr;
const pdfName = doc.Name;
const mailLogo = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
const recipient = sender.Email;
let subject = `Document "${pdfName}" has been signed by all parties`;
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=" +
mailLogo +
" height='50' style='padding:20px'/> </div><div style='padding:2px;font-family:system-ui; background-color: #47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document signed successfully</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document " +
`<b>"${pdfName}"</b>` +
'. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' +
sender.Email +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>';
if (obj?.isCustomMail) {
try {
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
tenantCreditsQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: sender.UserId.objectId,
});
const res = await tenantCreditsQuery.first();
if (res) {
const _res = JSON.parse(JSON.stringify(res));
if (_res?.CompletionSubject) {
subject = _res?.CompletionSubject;
}
if (_res?.CompletionBody) {
body = _res?.CompletionBody;
}
const expireDate = doc.ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
const variables = {
document_title: pdfName,
sender_name: sender.Name,
sender_mail: sender.Email,
sender_phone: sender?.Phone || '',
receiver_name: sender.Name,
receiver_email: sender.Email,
receiver_phone: sender?.Phone || '',
expiry_date: localExpireDate,
company_name: sender.Company,
};
const replaceVar = replaceMailVaribles(subject, body, variables);
subject = replaceVar.subject;
body = replaceVar.body;
}
} catch (err) {
console.log('error in fetch tenant in signpdf', err.message);
}
}
const params = {
extUserId: sender.objectId,
url: url,
from: 'OpenSign™',
recipient: recipient,
subject: subject,
pdfName: pdfName,
html: body,
mailProvider: obj.mailProvider,
};
const res = await axios.post(serverUrl + '/functions/sendmailv3', params, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
// console.log('Res ', res);
}
// `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) {
let signers = [];
if (signUser) {
signers = {
name: signUser?.Name,
email: signUser?.Email,
phone: signUser?.Phone,
};
} else {
signers = doc?.Signers?.map(x => ({
name: x.Name,
email: x.Email,
phone: x.Phone,
})) || [
{
name: doc?.ExtUserPtr?.Name,
email: doc?.ExtUserPtr?.Email,
phone: doc?.ExtUserPtr?.Phone,
},
];
}
if (doc.ExtUserPtr?.Webhook) {
const time =
event === 'signed'
? { signer: signers, signedAt: new Date() }
: { signers: signers, completedAt: new Date() };
const certificate = certificateUrl ? { certificate: certificateUrl } : {};
const params = {
event: event,
objectId: doc?.objectId,
file: Url || '',
...certificate,
name: doc?.Name,
note: doc?.Note || '',
description: doc?.Description || '',
...time,
createdAt: doc?.createdAt,
};
axios
.post(doc?.ExtUserPtr?.Webhook, params, {
headers: { 'Content-Type': 'application/json' },
})
.then(res => {
try {
// console.log('res ', res);
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', res?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: doc.ExtUserPtr.UserId.objectId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
})
.catch(err => {
console.log('Err send data to webhook', err.message);
try {
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', err?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: doc.ExtUserPtr.UserId.objectId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
});
// console.log('res ', res.data);
}
}
// `sendMailsaveCertifcate` is used send completion mail and update complete status of document
const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailProvider, userId) => {
const certificate = await GenerateCertificate(doc);
const certificatePdf = await PDFDocument.load(certificate);
const p12 = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
// `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate
pdflibAddPlaceholder({
pdfDoc: certificatePdf,
reason: 'Digitally signed by OpenSign.',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await certificatePdf.save();
const CertificateBuffer = Buffer.from(pdfWithPlaceholderBytes);
//`new signPDF` create new instance of CertificateBuffer and p12Buffer
const certificateOBJ = new SignPdf();
// `signedCertificate` is used to sign certificate digitally
const signedCertificate = await certificateOBJ.sign(CertificateBuffer, p12);
//below is used to save signed certificate in exports folder
fs.writeFileSync('./exports/certificate.pdf', signedCertificate);
const file = await uploadFile('certificate.pdf', './exports/certificate.pdf');
const body = { CertificateUrl: file.imageUrl };
await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
// used in API only
if (doc.IsSendMail === false) {
console.log("don't send mail");
} else {
const mailObj = {
url: url,
isCustomMail: isCustomMail,
doc: doc,
mailProvider: mailProvider,
};
sendCompletedMail(mailObj);
}
saveFileUsage(CertificateBuffer.length, file.imageUrl, userId);
sendDoctoWebhook(doc, url, 'completed', '', file.imageUrl);
};
/**
*
* @param docId Id of Document in which user is signing
* @param pdfFile base64 of pdfFile which you want sign
* @returns if success {status, data} else {status, message}
*/
async function PDF(req) {
try {
if (!req?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
} else {
const password = '';
const authUser = req?.user?.toJSON();
const docId = req.params.docId;
const reqUserId = req.params.userId;
const isCustomMail = req.params.isCustomCompletionMail || false;
const mailProvider = req.params.mailProvider || '';
const sign = req.params.signature || '';
// below bode is used to get info of docId
const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr,Signers');
docQuery.equalTo('objectId', docId);
const resDoc = await docQuery.first({ useMasterKey: true });
if (!resDoc) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
const _resDoc = resDoc?.toJSON();
let signUser;
let className;
// `reqUserId` is send throught pdfrequest signing flow
if (reqUserId) {
// to get contracts_Contactbook details for currentuser from reqUserId
const _contractUser = _resDoc.Signers.find(x => x.objectId === reqUserId);
if (_contractUser) {
signUser = _contractUser;
className = 'contracts_Contactbook';
}
} else {
className = 'contracts_Users';
signUser = _resDoc.ExtUserPtr;
}
const username = signUser.Name;
const userEmail = signUser.Email;
if (req.params.pdfFile) {
// `PdfBuffer` used to create buffer from pdf file
let PdfBuffer = Buffer.from(req.params.pdfFile, 'base64');
// `P12Buffer` used to create buffer from p12 certificate
const pfxFile = process.env.PFX_BASE64;
// const P12Buffer = fs.readFileSync();
const P12Buffer = Buffer.from(pfxFile, 'base64');
const p12Cert = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
const UserPtr = {
__type: 'Pointer',
className: className,
objectId: signUser.objectId,
};
const obj = {
UserPtr: UserPtr,
SignedUrl: '',
Activity: 'Signed',
ipAddress: req.headers['x-real-ip'],
};
let updateAuditTrail;
if (_resDoc.AuditTrail && _resDoc.AuditTrail.length > 0) {
updateAuditTrail = [..._resDoc.AuditTrail, obj];
} else {
updateAuditTrail = [obj];
}
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
let isCompleted = false;
if (_resDoc.Signers && _resDoc.Signers.length > 0) {
if (auditTrail.length === _resDoc.Signers.length) {
isCompleted = true;
}
} else {
isCompleted = true;
}
const randomNumber = Math.floor(Math.random() * 5000);
const name = `exported_file_${randomNumber}.pdf`;
const pdfName = `./exports/${name}`;
let pdfSize = PdfBuffer.length;
if (isCompleted) {
const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>');
if (signersName && signersName.length > 0) {
// `pdflibAddPlaceholder` is used to add code of only digitial sign without widget
const pdfDoc = await PDFDocument.load(PdfBuffer);
pdflibAddPlaceholder({
pdfDoc: pdfDoc,
reason: 'Digitally signed by OpenSign for ' + signersName?.join(', '),
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
} else {
// `pdflibAddPlaceholder` is used to add code of only digitial sign without widget (signyourself)
const pdfDoc = await PDFDocument.load(PdfBuffer);
pdflibAddPlaceholder({
pdfDoc: pdfDoc,
reason: 'Digitally signed by OpenSign for ' + username + ' <' + userEmail + '>',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
}
//`new signPDF` create new instance of pdfBuffer and p12Buffer
const OBJ = new SignPdf();
// `signedDocs` is used to signpdf digitally
const signedDocs = await OBJ.sign(PdfBuffer, p12Cert);
//`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, signedDocs);
pdfSize = signedDocs.length;
} else {
//`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, PdfBuffer);
pdfSize = PdfBuffer.length;
}
// `uploadFile` is used to upload pdf to aws s3 and get it's url
const data = await uploadFile(name, pdfName);
if (data && data.imageUrl) {
// `axios` is used to update signed pdf url in contracts_Document classes for given DocId
const updatedDoc = await updateDoc(
req.params.docId, //docId
data.imageUrl, // url
signUser.objectId, // userID
req.headers['x-real-ip'], // client ipAddress,
_resDoc, // auditTrail, signers, etc data
className,
sign
);
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
saveFileUsage(pdfSize, data.imageUrl, authUser.objectId);
if (updatedDoc && updatedDoc.isCompleted) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail };
sendMailsaveCertifcate(
doc,
P12Buffer,
data.imageUrl,
isCustomMail,
mailProvider,
authUser.objectId
);
}
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
fs.unlinkSync(pdfName);
console.log(`New Signed PDF created called: ${pdfName}`);
if (updatedDoc.message === 'success') {
return { status: 'success', data: data.imageUrl };
} else {
const error = new Error('Please provide required parameters!');
error.code = 400; // Set the error code (e.g., 400 for bad request)
throw error;
}
}
} else {
const error = new Error('Pdf file not present!');
error.code = 400; // Set the error code (e.g., 400 for bad request)
throw error;
}
}
} catch (err) {
console.log('Err in signpdf', err);
throw err;
}
}
export default PDF;
-337
View File
@@ -1,337 +0,0 @@
import fs from 'node:fs';
import axios from 'axios';
import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
import { PDFDocument } from 'pdf-lib';
import { replaceMailVaribles, saveFileUsage } from '../../../Utils.js';
import GenerateCertificate from './GenerateCertificate.js';
const serverUrl = process.env.SERVER_URL,
APPID = process.env.APP_ID,
masterKEY = process.env.MASTER_KEY,
eSignName = 'opensign',
eSigncontact = 'hello@opensignlabs.com';
async function uploadFile(e, t) {
try {
var a = fs.readFileSync(t),
r = new Parse.File(e, [...a], 'application/pdf'),
i = (await r.save({ useMasterKey: !0 }), r.url());
return { imageUrl: i };
} catch (e) {
console.log('Err ', e), fs.unlinkSync(t);
}
}
async function updateDoc(a, r, i, n, s, o, l) {
try {
var c,
d,
p = {
UserPtr: { __type: 'Pointer', className: o, objectId: i },
SignedUrl: r,
Activity: 'Signed',
ipAddress: n,
SignedOn: new Date(),
Signature: l,
};
let e;
var m = (e =
s.AuditTrail && 0 < s.AuditTrail.length
? (-1 !==
(d = (c = JSON.parse(JSON.stringify(s.AuditTrail))).findIndex(
e => e.UserPtr.objectId === i && 'Created' !== e.Activity
))
? (c[d] = { ...c[d], ...p })
: c.push(p),
c)
: [p]).filter(e => 'Signed' === e.Activity);
let t = !1;
!((s.Signers && 0 < s.Signers.length && m.length !== s.Signers.length) || !(t = !0));
var g = { SignedUrl: r, AuditTrail: e, IsCompleted: t };
await axios.put(serverUrl + '/classes/contracts_Document/' + a, g, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
return { isCompleted: t, message: 'success', AuditTrail: e };
} catch (e) {
return console.log('update doc err ', e), 'err';
}
}
async function sendCompletedMail(e) {
var t = e.url,
a = e.doc,
r = e.doc.ExtUserPtr,
i = a.Name,
n = r.Email;
let s = `Document "${i}" has been signed by all parties`,
o =
"<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=https://qikinnovation.ams3.digitaloceanspaces.com/logo.png height='50' style='padding:20px'/> </div><div style='padding:2px;font-family:system-ui; background-color: #47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document signed successfully</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document " +
`<b>"${i}"</b>` +
'. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' +
r.Email +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>';
if (e?.isCustomMail)
try {
var l,
c,
d,
p,
m,
g = new Parse.Query('partners_Tenant');
g.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: r.UserId.objectId });
const f = await g.first();
f &&
((l = JSON.parse(JSON.stringify(f)))?.CompletionSubject && (s = l?.CompletionSubject),
l?.CompletionBody && (o = l?.CompletionBody),
(c = a.ExpiryDate.iso),
(d = new Date(c).toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
})),
(p = {
document_title: i,
sender_name: r.Name,
sender_mail: r.Email,
sender_phone: r.Phone,
receiver_name: r.Name,
receiver_email: r.Email,
receiver_phone: r.Phone,
expiry_date: d,
company_name: r.Company,
}),
(m = replaceMailVaribles(s, o, p)),
(s = m.subject),
(o = m.body));
} catch (e) {
console.log('error in fetch tenant in signpdf', e.message);
}
g = {
extUserId: r.objectId,
url: t,
from: 'OpenSign™',
recipient: n,
subject: s,
pdfName: i,
html: o,
mailProvider: e.mailProvider,
};
await axios.post(serverUrl + '/functions/sendmailv3', g, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
}
async function sendDoctoWebhook(a, e, t, r, i) {
let n = [];
(n = r
? { name: r?.Name, email: r?.Email, phone: r?.Phone }
: a?.Signers?.map(e => ({ name: e.Name, email: e.Email, phone: e.Phone })) || [
{ name: a?.ExtUserPtr?.Name, email: a?.ExtUserPtr?.Email, phone: a?.ExtUserPtr?.Phone },
]),
a.ExtUserPtr?.Webhook &&
((r =
'signed' === t
? { signer: n, signedAt: new Date() }
: { signers: n, completedAt: new Date() }),
(t = {
event: t,
objectId: a?.objectId,
file: e || '',
...(i ? { certificate: i } : {}),
name: a?.Name,
note: a?.Note || '',
description: a?.Description || '',
...r,
createdAt: a?.createdAt,
}),
axios
.post(a?.ExtUserPtr?.Webhook, t, { headers: { 'Content-Type': 'application/json' } })
.then(e => {
try {
var t = new Parse.Object('contracts_Webhook');
t.set('Log', e?.status),
t.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: a.ExtUserPtr.UserId.objectId,
}),
t.save(null, { useMasterKey: !0 });
} catch (e) {
console.log('err save in contracts_Webhook', e.message);
}
})
.catch(e => {
console.log('Err send data to webhook', e.message);
try {
var t = new Parse.Object('contracts_Webhook');
t.set('Log', e?.status),
t.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: a.ExtUserPtr.UserId.objectId,
}),
t.save(null, { useMasterKey: !0 });
} catch (e) {
console.log('err save in contracts_Webhook', e.message);
}
}));
}
const sendMailsaveCertifcate = async (e, t, a, r, i, n) => {
var s = await GenerateCertificate(e),
s = await PDFDocument.load(s),
t = new P12Signer(t, { passphrase: process.env.PASS_PHRASE || null }),
s =
(pdflibAddPlaceholder({
pdfDoc: s,
reason: 'Digitally signed by OpenSign.',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15e3,
}),
await s.save()),
s = Buffer.from(s),
t = await new SignPdf().sign(s, t),
t =
(fs.writeFileSync('./exports/certificate.pdf', t),
await uploadFile('certificate.pdf', './exports/certificate.pdf')),
o = { CertificateUrl: t.imageUrl };
await axios.put(serverUrl + '/classes/contracts_Document/' + e.objectId, o, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
}),
e.IsSendMail && !1 === e.IsSendMail
? console.log("don't send mail")
: sendCompletedMail({ url: a, isCustomMail: r, doc: e, mailProvider: i }),
saveFileUsage(s.length, t.imageUrl, n),
sendDoctoWebhook(e, a, 'completed', '', t.imageUrl);
};
async function PDF(s) {
try {
if (!s?.user)
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
{
var o = s?.user?.toJSON(),
e = s.params.docId;
const C = s.params.userId;
var l = s.params.isCustomCompletionMail || !1,
c = s.params.mailProvider || '',
d = s.params.signature || '',
t = new Parse.Query('contracts_Document'),
a =
(t.include('ExtUserPtr,Signers'),
t.equalTo('objectId', e),
await t.first({ useMasterKey: !0 }));
if (!a) throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
var r,
p = a?.toJSON();
let i, n;
C
? ((r = p.Signers.find(e => e.objectId === C)),
console.log('_contractUser ', r),
r && ((i = r), (n = 'contracts_Contactbook')))
: ((n = 'contracts_Users'), (i = p.ExtUserPtr));
var m,
g = i.Name,
f = i.Email;
if (!s.params.pdfFile) throw (((m = new Error('Pdf file not present!')).code = 400), m);
{
let e = Buffer.from(s.params.pdfFile, 'base64');
var u = process.env.PFX_BASE64,
h = Buffer.from(u, 'base64'),
y = new P12Signer(h, { passphrase: process.env.PASS_PHRASE || null }),
P = {
UserPtr: { __type: 'Pointer', className: n, objectId: i.objectId },
SignedUrl: '',
Activity: 'Signed',
ipAddress: s.headers['x-real-ip'],
};
let t;
var v = (t = p.AuditTrail && 0 < p.AuditTrail.length ? [...p.AuditTrail, P] : [P]).filter(
e => 'Signed' === e.Activity
);
let a = !1;
!((p.Signers && 0 < p.Signers.length && v.length !== p.Signers.length) || !(a = !0));
var S,
b,
w,
U,
I,
_,
E = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
D = './exports/' + E;
let r = e.length;
r = (
a
? ((S = p.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
(e =
S && 0 < S.length
? ((b = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: b,
reason: 'Digitally signed by OpenSign for ' + S?.join(', '),
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15e3,
}),
(w = await b.save()),
Buffer.from(w))
: ((U = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: U,
reason: 'Digitally signed by OpenSign for ' + g + ' <' + f + '>',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15e3,
}),
(I = await U.save()),
Buffer.from(I))),
(_ = await new SignPdf().sign(e, y)),
fs.writeFileSync(D, _),
_)
: (fs.writeFileSync(D, e), e)
).length;
var A = await uploadFile(E, D);
if (A && A.imageUrl) {
var x,
j,
N = await updateDoc(
s.params.docId,
A.imageUrl,
i.objectId,
s.headers['x-real-ip'],
p,
n,
d
);
if (
(sendDoctoWebhook(p, A.imageUrl, 'signed', i),
saveFileUsage(r, A.imageUrl, o.objectId),
N &&
N.isCompleted &&
((x = { ...p, AuditTrail: N.AuditTrail }),
sendMailsaveCertifcate(x, h, A.imageUrl, l, c, o.objectId)),
fs.unlinkSync(D),
console.log('New Signed PDF created called: ' + D),
'success' === N.message)
)
return { status: 'success', data: A.imageUrl };
throw (((j = new Error('Please provide required parameters!')).code = 400), j);
}
}
}
} catch (e) {
throw (console.log('Err in signpdf', e), e);
}
}
export default PDF;
@@ -27,6 +27,7 @@ export default function reportJson(id, userId) {
'Signers.Name',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
@@ -67,6 +68,7 @@ export default function reportJson(id, userId) {
'Signers.Phone',
'Signers.UserId',
'AuditTrail',
'Placeholders',
],
};
// In progess report
@@ -75,7 +77,7 @@ export default function reportJson(id, userId) {
reportName: 'In-progress documents',
params: {
Type: { $ne: 'Folder' },
Signers: { $exists: true, $ne: [] },
SignedUrl: { $ne: null },
Placeholders: { $ne: null },
IsCompleted: { $ne: true },
IsDeclined: { $ne: true },
@@ -102,6 +104,7 @@ export default function reportJson(id, userId) {
'AuditTrail.UserPtr',
'ExpiryDate',
'SendMail',
'Placeholders',
],
};
// completed documents report
@@ -130,6 +133,7 @@ export default function reportJson(id, userId) {
'Signers.Email',
'Signers.Phone',
'TimeToCompleteDays',
'Placeholders',
],
};
@@ -157,6 +161,7 @@ export default function reportJson(id, userId) {
'Signers.Name',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
// Expired Documents report
@@ -187,6 +192,7 @@ export default function reportJson(id, userId) {
'Signers.Name',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
// Recently sent for signatures report show on dashboard
@@ -195,7 +201,7 @@ export default function reportJson(id, userId) {
reportName: 'Recently sent for signatures',
params: {
Type: { $ne: 'Folder' },
Signers: { $exists: true, $ne: [] },
SignedUrl: { $ne: null },
Placeholders: { $ne: null },
IsCompleted: { $ne: true },
IsDeclined: { $ne: true },
@@ -220,6 +226,7 @@ export default function reportJson(id, userId) {
'AuditTrail',
'AuditTrail.UserPtr',
'ExpiryDate',
'Placeholders',
],
};
// Recent signature requests report show on dashboard
@@ -257,6 +264,7 @@ export default function reportJson(id, userId) {
'AuditTrail',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
// Drafts report show on dashboard
@@ -284,6 +292,7 @@ export default function reportJson(id, userId) {
'Signers.Name',
'Signers.Email',
'Signers.Phone',
'Placeholders',
],
};
// contact book report
@@ -324,6 +333,8 @@ export default function reportJson(id, userId) {
'Signers.Name',
'Signers.Email',
'Signers.Phone',
'Placeholders',
'IsPublic',
],
};
default:
@@ -2,7 +2,7 @@ import fs from 'node:fs';
import https from 'https';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import { updateMailCount } from '../../Utils.js';
import { smtpenable, smtpsecure, updateMailCount } from '../../Utils.js';
import sendMailGmailProvider from './sendMailGmailProvider.js';
import { createTransport } from 'nodemailer';
async function sendMailProvider(req) {
@@ -10,11 +10,11 @@ async function sendMailProvider(req) {
let transporterSMTP;
let mailgunClient;
let mailgunDomain;
if (process.env.SMTP_ENABLE) {
if (smtpenable) {
transporterSMTP = createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 465,
secure: process.env.SMTP_SECURE || true,
secure: smtpsecure,
auth: {
user: process.env.SMTP_USER_EMAIL,
pass: process.env.SMTP_PASS,
@@ -54,8 +54,8 @@ async function sendMailProvider(req) {
const pdfName = req.params.pdfName && `${req.params.pdfName}.pdf`;
const file = {
filename: pdfName || 'exported.pdf',
content: process.env.SMTP_ENABLE ? PdfBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
data: process.env.SMTP_ENABLE ? undefined : PdfBuffer,
content: smtpenable ? PdfBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
data: smtpenable ? undefined : PdfBuffer,
};
let attachment;
@@ -64,8 +64,8 @@ async function sendMailProvider(req) {
const certificateBuffer = fs.readFileSync('./exports/certificate.pdf');
const certificate = {
filename: 'certificate.pdf',
content: process.env.SMTP_ENABLE ? certificateBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
data: process.env.SMTP_ENABLE ? undefined : certificateBuffer,
content: smtpenable ? certificateBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
data: smtpenable ? undefined : certificateBuffer,
};
attachment = [file, certificate];
} catch (err) {
@@ -73,9 +73,7 @@ async function sendMailProvider(req) {
console.log('Err in read certificate sendmailv3', err);
}
const from = req.params.from || '';
const mailsender = process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER;
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
const messageParams = {
from: from + ' <' + mailsender + '>',
@@ -83,8 +81,8 @@ async function sendMailProvider(req) {
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
attachments: process.env.SMTP_ENABLE ? attachment : undefined,
attachment: process.env.SMTP_ENABLE ? undefined : attachment,
attachments: smtpenable ? attachment : undefined,
attachment: smtpenable ? undefined : attachment,
};
if (transporterSMTP) {
const res = await transporterSMTP.sendMail(messageParams);
@@ -120,9 +118,7 @@ async function sendMailProvider(req) {
}
} else {
const from = req.params.from || '';
const mailsender = process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER;
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
const messageParams = {
from: from + ' <' + mailsender + '>',
@@ -0,0 +1,132 @@
import axios from 'axios';
const serverUrl = process.env.SERVER_URL;
const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY;
const clientUrl = process.env.PUBLIC_URL;
const ssoApiUrl = process.env.SSO_API_URL || 'https://sso.opensignlabs.com/api'; //'https://osl-jacksonv2.vercel.app/api';
/**
* ssoSign is function which is used to sign up/sign in with SSO
* @param code It is code return by jackson using authorize endpoint
* @param email It is user's email with user sign in/sign up
* @returns if success {email, name, phone message, sessiontoken} else on reject error {code, message}
*/
export default async function ssoSignin(request) {
const code = request.params.code;
const userEmail = request.params.email;
try {
const headers = { 'content-type': 'application/x-www-form-urlencoded' };
const axiosRes = await axios.post(
ssoApiUrl + '/oauth/token',
{
grant_type: 'authorization_code',
client_id: 'dummy',
tenant: 'Okta-dev-nxglabs-in',
product: 'OpenSign',
client_secret: 'dummy',
redirect_uri: clientUrl + '/sso',
code: code,
},
{ headers: headers }
);
const ssoAccessToken = axiosRes.data && axiosRes.data.access_token;
const authData = { sso: { id: userEmail, access_token: ssoAccessToken } };
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('username', userEmail);
const res = await userQuery.first({ useMasterKey: true });
if (res) {
try {
const SignIn = await axios.put(
serverUrl + '/users/' + res.id,
{ authData: authData },
{
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Master-key': masterKEY,
},
}
);
if (SignIn.data) {
const response = await axios.get(ssoApiUrl + '/oauth/userinfo', {
headers: {
Authorization: `Bearer ${ssoAccessToken}`,
},
});
const sessiontoken = SignIn.data.sessionToken;
// console.log('sso sessiontoken', sessiontoken);
const payload = {
email: userEmail,
name: response.data?.firstName + ' ' + response.data?.lastName,
phone: response?.data?.phone || '',
message: 'User Sign In',
sessiontoken: sessiontoken,
};
return payload;
}
} catch (err) {
const errCode = err?.response?.data?.code || err?.response?.status || err?.code || 400;
const message =
err?.response?.data?.error ||
err?.response?.data ||
err?.message ||
'Internal server error.';
console.log('err in user sso sign in', errCode, message);
throw new Parse.Error(errCode, message);
}
} else {
// console.log("in sign up condition");
const response = await axios.get(ssoApiUrl + '/oauth/userinfo', {
headers: {
Authorization: `Bearer ${ssoAccessToken}`,
},
});
if (response.data && response.data.id) {
try {
const SignUp = await axios.post(
serverUrl + '/users',
{
authData: authData,
username: response.data.email,
email: response.data.email,
phone: response.data?.phone,
name: response.data?.firstName + ' ' + response.data?.lastName,
},
{
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Revocable-Session': '1',
},
}
);
if (SignUp.data) {
const sessiontoken = SignUp.data.sessionToken;
const payload = {
email: userEmail,
name: SignUp?.data?.name,
phone: SignUp?.data?.phone || '',
message: 'User Sign Up',
sessiontoken: sessiontoken,
};
return payload;
}
} catch (err) {
const errCode = err?.response?.data?.code || err?.response?.status || err?.code || 400;
const message =
err?.response?.data?.error ||
err?.response?.data ||
err?.message ||
'Internal server error.';
console.log('err in user sso sign up', errCode, message);
throw new Parse.Error(errCode, message);
}
}
}
} catch (err) {
const errCode = err?.response?.status || err?.code || 400;
const message = err?.response?.data || err?.message || 'Internal server error.';
console.log('err in ssoSign', errCode, message);
throw new Parse.Error(errCode, message);
}
}
@@ -30,7 +30,9 @@ async function saveUser(userDetails) {
user.set('username', userDetails.email);
user.set('password', userDetails.password);
user.set('email', userDetails.email);
user.set('phone', userDetails.phone);
if (userDetails?.phone) {
user.set('phone', userDetails.phone);
}
user.set('name', userDetails.name);
const res = await user.signUp();
@@ -77,7 +79,9 @@ export default async function usersignup(request) {
objectId: user.id,
});
partnerQuery.set('ContactNumber', userDetails.phone);
if (userDetails?.phone) {
partnerQuery.set('ContactNumber', userDetails.phone);
}
partnerQuery.set('TenantName', userDetails.name);
partnerQuery.set('EmailAddress', userDetails.email);
partnerQuery.set('IsActive', true);
@@ -113,7 +117,9 @@ export default async function usersignup(request) {
newObj.set('UserRole', userDetails.role);
newObj.set('Email', userDetails.email);
newObj.set('Name', userDetails.name);
newObj.set('Phone', userDetails.phone);
if (userDetails?.phone) {
newObj.set('Phone', userDetails?.phone);
}
newObj.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
@@ -126,7 +132,9 @@ export default async function usersignup(request) {
newObj.set('JobTitle', userDetails.jobTitle);
}
const extRes = await newObj.save(null, { useMasterKey: true });
await saveSubscription(extRes.id, user.id, tenantRes.id, subscription);
if (subscription) {
await saveSubscription(extRes.id, user.id, tenantRes.id, subscription);
}
return { message: 'User sign up', sessionToken: user.sessionToken };
}
} catch (err) {
@@ -0,0 +1,35 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
// TODO: set className here
const className = 'contracts_Template';
const schema = new Parse.Schema(className);
schema.addArray('PublicRole');
schema.addBoolean('IsPublic');
// TODO: Set the schema here
// Example:
// schema.addString('name').addNumber('cash');
return schema.update();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
// TODO: set className here
const className = 'contracts_Template';
const schema = new Parse.Schema(className);
schema.deleteField('PublicRole');
schema.deleteField('IsPublic');
// TODO: Set the schema here
// Example:
// schema.deleteField('name').deleteField('cash');
return schema.update();
};
+7 -16
View File
@@ -17,8 +17,8 @@ import { exec } from 'child_process';
import { createTransport } from 'nodemailer';
import { app as v1 } from './cloud/customRoute/v1/apiV1.js';
import { PostHog } from 'posthog-node';
import { useLocal } from './Utils.js';
import { smtpenable, smtpsecure, useLocal } from './Utils.js';
import { SSOAuth } from './auth/authadapter.js';
let fsAdapter;
if (useLocal !== 'true') {
try {
@@ -54,12 +54,12 @@ let transporterMail;
let mailgunClient;
let mailgunDomain;
let isMailAdapter = false;
if (process.env.SMTP_ENABLE) {
if (smtpenable) {
try {
transporterMail = createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 465,
secure: process.env.SMTP_SECURE || true,
secure: smtpsecure,
auth: {
user: process.env.SMTP_USER_EMAIL,
pass: process.env.SMTP_PASS,
@@ -85,9 +85,7 @@ if (process.env.SMTP_ENABLE) {
console.log('Please provide valid Mailgun credentials');
}
}
const mailsender = process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER;
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
export const config = {
databaseURI:
process.env.DATABASE_URI || process.env.MONGODB_URI || 'mongodb://localhost:27017/dev',
@@ -101,7 +99,7 @@ export const config = {
masterKey: process.env.MASTER_KEY, //Add your master key here. Keep it secret!
masterKeyIps: ['0.0.0.0/0', '::/0'], // '::1'
serverURL: 'http://localhost:8080/app', // Don't forget to change to https if needed
verifyUserEmails: isMailAdapter === true ? true : false,
verifyUserEmails: false,
publicServerURL: process.env.SERVER_URL || 'http://localhost:8080/app',
// Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'Opensign',
@@ -147,14 +145,7 @@ export const config = {
google: {
enabled: true,
},
ldap: {
enabled: true,
url: 'ldap://ldap.forumsys.com:389',
suffix: 'dc=example,dc=com',
// dn: 'ou=mathematicians, dc=example, dc=com',
groupCn: 'mathematicians',
groupFilter: '(&(uniqueMember=uid=,dc=example,dc=com)(objectClass=groupOfUniqueNames))',
},
sso: SSOAuth,
},
};
// Client-keys like the javascript key or the .NET key are not necessary with parse-server
+26 -26
View File
@@ -25,7 +25,7 @@
"googleapis": "^134.0.0",
"jsonschema": "^1.4.1",
"mailgun.js": "^10.2.1",
"mongoose": "^8.4.0",
"mongoose": "^8.4.1",
"multer": "^1.4.5-lts.1",
"multer-s3": "^3.0.1",
"node-forge": "^1.3.1",
@@ -38,13 +38,13 @@
"posthog-node": "^4.0.1"
},
"devDependencies": {
"@babel/eslint-parser": "^7.24.5",
"@babel/eslint-parser": "^7.24.7",
"eslint": "^9.3.0",
"jasmine": "^5.1.0",
"mongodb-runner": "^5.6.2",
"nodemon": "^3.1.0",
"nodemon": "^3.1.3",
"nyc": "15.1.0",
"prettier": "^3.2.5",
"prettier": "^3.3.0",
"uglify-js": "^3.17.4"
},
"engines": {
@@ -1303,9 +1303,9 @@
}
},
"node_modules/@babel/eslint-parser": {
"version": "7.24.5",
"resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.5.tgz",
"integrity": "sha512-gsUcqS/fPlgAw1kOtpss7uhY6E9SFFANQ6EFX5GTvzUwaV0+sGaZWk6xq22MOdeT9wfxyokW3ceCUvOiRtZciQ==",
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.7.tgz",
"integrity": "sha512-SO5E3bVxDuxyNxM5agFv480YA2HO6ohZbGxbazZdIk3KQOPOGVNw6q78I9/lbviIf95eq6tPozeYnJLbjnC8IA==",
"dev": true,
"dependencies": {
"@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1",
@@ -1906,9 +1906,9 @@
}
},
"node_modules/@grpc/grpc-js": {
"version": "1.10.8",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.8.tgz",
"integrity": "sha512-vYVqYzHicDqyKB+NQhAc54I1QWCBLCrYG6unqOIcBTHx+7x8C9lcoLj3KVJXs2VB4lUbpWY+Kk9NipcbXYWmvg==",
"version": "1.10.9",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.9.tgz",
"integrity": "sha512-5tcgUctCG0qoNyfChZifz2tJqbRbXVO9J7X6duFcOjY3HUNCxg5D0ZCK7EP9vIcZ0zRpLU9bWkyCqVCLZ46IbQ==",
"optional": true,
"dependencies": {
"@grpc/proto-loader": "^0.7.13",
@@ -4300,11 +4300,11 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -5761,9 +5761,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
@@ -8190,9 +8190,9 @@
}
},
"node_modules/mongoose": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.4.0.tgz",
"integrity": "sha512-fgqRMwVEP1qgRYfh+tUe2YBBFnPO35FIg2lfFH+w9IhRGg1/ataWGIqvf/MjwM29cZ60D5vSnqtN2b8Qp0sOZA==",
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.4.1.tgz",
"integrity": "sha512-odQ2WEWGL3hb0Qex+QMN4eH6D34WdMEw7F1If2MGABApSDmG9cMmqv/G1H6WsXmuaH9mkuuadW/WbLE5+tHJwA==",
"dependencies": {
"bson": "^6.7.0",
"kareem": "2.6.3",
@@ -8365,9 +8365,9 @@
}
},
"node_modules/nodemon": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
"integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz",
"integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==",
"dev": true,
"dependencies": {
"chokidar": "^3.5.2",
@@ -10298,9 +10298,9 @@
}
},
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.0.tgz",
"integrity": "sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
+4 -4
View File
@@ -34,7 +34,7 @@
"googleapis": "^134.0.0",
"jsonschema": "^1.4.1",
"mailgun.js": "^10.2.1",
"mongoose": "^8.4.0",
"mongoose": "^8.4.1",
"multer": "^1.4.5-lts.1",
"multer-s3": "^3.0.1",
"node-forge": "^1.3.1",
@@ -48,13 +48,13 @@
},
"type": "module",
"devDependencies": {
"@babel/eslint-parser": "^7.24.5",
"@babel/eslint-parser": "^7.24.7",
"eslint": "^9.3.0",
"jasmine": "^5.1.0",
"mongodb-runner": "^5.6.2",
"nodemon": "^3.1.0",
"nodemon": "^3.1.3",
"nyc": "15.1.0",
"prettier": "^3.2.5",
"prettier": "^3.3.0",
"uglify-js": "^3.17.4"
},
"engines": {
Binary file not shown.
+7 -7
View File
@@ -54,12 +54,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@@ -196,9 +196,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"