Compare commits

...
392 Commits
Author SHA1 Message Date
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
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 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 bf307abe9a Update plansArr.js 2024-05-23 18:17:00 +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-opensignlabsandGitHub 6c59c0ec9a Merge pull request #767 from OpenSignLabs/raktima-opensignlabs-patch-8
feat: add help button for text validation
2024-05-23 10:35:45 +05:30
RaktimaNXG 32c75d1fc3 feat: add help button for text validation 2024-05-23 10:30:41 +05:30
AmolandGitHub 7f0f81c334 Merge pull request #766 from OpenSignLabs/build_dep
fix: issue in guestlogin of owner subscription has expired
2024-05-22 19:59:23 +05:30
prafull-opensignlabsandGitHub a882f07f57 Merge pull request #765 from OpenSignLabs/tour_message_requestsign
fix: download menu is not wokring inProgress report in mobile view
2024-05-22 19:58:21 +05:30
prafull-opensignlabs 74b81a5363 fix: issue in guestlogin of owner subscription has expired 2024-05-22 19:56:44 +05:30
AmolandGitHub 9b75764717 Merge pull request #764 from OpenSignLabs/build_dep
fix: resend mail not working, create contact API not working
2024-05-22 19:47:11 +05:30
RaktimaNXG c1468112b0 fix: download menu is not wokring inProgress report in mobile view 2024-05-22 19:46:22 +05:30
prafull-opensignlabs 950b74789c fix: create contact API not working 2024-05-22 19:46:09 +05:30
prafull-opensignlabs 7e4f12e41a feat: bulk send is working for exist contact 2024-05-22 19:31:32 +05:30
prafull-opensignlabs d75c1d7c0c fix: resend mail not working 2024-05-22 19:19:33 +05:30
prafull-opensignlabsandGitHub 7eacbedf59 Merge pull request #763 from OpenSignLabs/tour_message_requestsign
feat: add tour messages to request signatures flow
2024-05-22 19:02:12 +05:30
RaktimaNXG 2e5947279f fix:onclick upgrade button navigate user to new window 2024-05-22 18:51:57 +05:30
RaktimaNXG ef5ac7d626 fix: err is not defined 2024-05-22 17:49:24 +05:30
RaktimaNXG 658c37cc4d feat: add tour messages to request signatures flow 2024-05-22 17:48:15 +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-opensignlabsandGitHub 3fb96fc792 Merge pull request #762 from OpenSignLabs/build_dep
build(deps): update dependencies
2024-05-22 12:55:31 +05:30
prafull-opensignlabs 71016ba5e0 build(deps): update dependecies 2024-05-22 12:46:34 +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-opensignlabsandGitHub c2afeed448 Merge pull request #760 from OpenSignLabs/raktima-opensignlabs-patch-7
fix: unnecessary emails are sent when a signers sign a document via a shared link.
2024-05-22 11:24:15 +05:30
RaktimaNXG 37fd7bbeda fix: update migration script add SendCompletionMail filed in contracts_Document class 2024-05-22 11:22:35 +05:30
RaktimaNXG 71fbde0099 fix: add migration script to add new filed SendMail and handle email sent using share link from inProgress report 2024-05-22 09:50:12 +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-opensignlabsandGitHub df1c0ea66c Merge pull request #761 from Zathiel/staging
build(deps): react-redux, autoprefixer , react-datepicker axios, @reduxjs/toolkit, react-tooltip, tailwindcss react-pdf
2024-05-22 09:45:54 +05:30
Zathiel 46279efc2c build(deps): react-redux, autoprefixer , react-datepicker axios, @reduxjs/toolkit, react-tooltip, tailwindcss react-pdf 2024-05-21 15:29:38 +00:00
prafull-opensignlabs aca2848406 add Ui for bulk send, changes in reportjson 2024-05-21 20:26:46 +05:30
prafull-opensignlabsandGitHub eaec3cf607 Merge pull request #750 from Zathiel/staging
build(deps): update @reduxjs/toolkit package
2024-05-21 20:00:00 +05:30
ZathielandGitHub ad25d73f26 Merge branch 'OpenSignLabs:staging' into staging 2024-05-21 16:19:31 +02:00
RaktimaNXG 481b9ac1e0 fix: add send mail false using share link from inprogress report 2024-05-21 17:36:41 +05:30
AmolandGitHub 4cd3791feb Merge pull request #759 from OpenSignLabs/feat_resendmail 2024-05-21 15:18:10 +05:30
RaktimaNXG 7de8699437 fix: unnecessary emails are sent when a user signs a document via a shared link. 2024-05-21 15:08:40 +05:30
prafull-opensignlabsandGitHub 4c162676b1 Merge pull request #753 from OpenSignLabs/raktima-opensignlabs-patch-7
feat:  celebration confetti on document completion
2024-05-21 14:02:28 +05:30
prafull-opensignlabs ca390bdfa6 refactor: remove Signpdf file 2024-05-21 13:56:37 +05:30
prafull-opensignlabs 22d064fd38 build(dep): update node-signpdf pacakge and it's dependencies, remove customsignpdf folder 2024-05-21 13:49:10 +05:30
AmolandGitHub 444c9bbfdf Merge pull request #756 from OpenSignLabs/feat_resendmail 2024-05-21 13:44:07 +05:30
prafull-opensignlabs b289686d42 build(dep): update parse-server package and it's dependencies 2024-05-21 13:42:50 +05:30
RaktimaNXG eeb878b340 refactor:refactor code 2024-05-21 13:40:54 +05:30
RaktimaNXG 7894d69cc9 fix: increase time to show confetti celebration 2024-05-21 13:35:22 +05:30
RaktimaNXG b992d7db24 fix: change celebration effect and add for whole screen 2024-05-21 13:03:51 +05:30
AmolandGitHub 50b72f4d85 Merge pull request #755 from OpenSignLabs/feat_resendmail 2024-05-20 18:43:49 +05:30
prafull-opensignlabs 44639f66b0 fix: autoreminder API not working 2024-05-20 18:42:32 +05:30
RaktimaNXG ae740efced refactor: refactor code 2024-05-20 17:46:07 +05:30
RaktimaNXG 66c020d903 fix: fix celebration ui for mobile view 2024-05-20 17:26:18 +05:30
AmolandGitHub 562a7d6fc8 Merge pull request #754 from OpenSignLabs/feat_resendmail
feat: now user can send automatic reminders
2024-05-20 15:44:54 +05:30
RaktimaNXG 090c17c2c0 refactor: add comment 2024-05-20 15:41:04 +05:30
RaktimaNXG 70637d6143 fix: handle condition to show complete message 2024-05-20 15:39:14 +05:30
prafull-opensignlabs f38e8705de feat: now user can sent automatic reminders 2024-05-20 15:31:20 +05:30
RaktimaNXG ba06ff72a0 fix: mismatch complete message issue in multiple signers flow 2024-05-20 14:01:06 +05:30
RaktimaNXG 82312a1e0d feat: celebration confetti on document completion 2024-05-20 11:42:37 +05:30
ZathielandGitHub 1a8cf11a3e Merge pull request #22 from Zathiel/dependabot/npm_and_yarn/apps/OpenSign/reduxjs/toolkit-2.2.4
build(deps): bump @reduxjs/toolkit from 2.2.1 to 2.2.4 in /apps/OpenSign
2024-05-19 02:18:41 +02:00
ZathielandGitHub 3deacbabc3 Merge pull request #8 from Zathiel/dependabot/npm_and_yarn/apps/OpenSignServer/babel/eslint-parser-7.24.5
build(deps-dev): bump @babel/eslint-parser from 7.24.1 to 7.24.5 in /apps/OpenSignServer
2024-05-19 02:18:23 +02:00
AmolandGitHub c28805add9 Merge pull request #749 from OpenSignLabs/feat_resendmail 2024-05-17 19:22:04 +05:30
prafull-opensignlabs 80fb320ae4 refactor: add mail in resend mail UI 2024-05-17 19:20:17 +05:30
AmolandGitHub 76c9c1fa53 Merge pull request #748 from OpenSignLabs/feat_resendmail 2024-05-17 14:53:57 +05:30
prafull-opensignlabs f38d83dbe0 style: change z-index of alert 2024-05-17 12:05:18 +05:30
AmolandGitHub ee9411729d Merge pull request #747 from OpenSignLabs/feat_resendmail
refactor: change in subject & body of resend mail
2024-05-17 10:57:29 +05:30
prafull-opensignlabs f6f7c6cecd refactor: change in subject & body of resend mail 2024-05-17 10:56:14 +05:30
AmolandGitHub a45fc0dd60 Merge pull request #746 from OpenSignLabs/feat_resendmail
fix: Cannot read properties of undefined (reading 'find') in resend mail & resend mail not available dashboard report
2024-05-17 10:49:06 +05:30
prafull-opensignlabs 348f7810b9 fix: Cannot read properties of undefined (reading 'find') in resend mail & resend mail not available dashboard report 2024-05-17 10:09:15 +05:30
AmolandGitHub 45bec128a9 Merge pull request #744 from OpenSignLabs/feat_resendmail 2024-05-16 19:37:39 +05:30
prafull-opensignlabs 4da307315c feat: add resend mail functionality for inprogress document 2024-05-16 19:34:28 +05:30
AmolandGitHub c87aa415a9 Merge pull request #743 from OpenSignLabs/signPdf
feat: move forget password button in login box
2024-05-15 20:13:38 +05:30
prafull-opensignlabs b1786ec16a feat: move forget password button in login box 2024-05-15 20:03:30 +05:30
AmolandGitHub 6ad35e979e Merge pull request #742 from OpenSignLabs/signPdf 2024-05-15 16:12:25 +05:30
prafull-opensignlabs 9d996d5cb7 feat: add copy icon beside API token 2024-05-15 16:09:11 +05:30
AmolandGitHub 008e3e8783 Merge pull request #741 from OpenSignLabs/signPdf 2024-05-15 14:52:04 +05:30
prafull-opensignlabs 8458906b0b fix: delete foler are still visible 2024-05-15 14:49:36 +05:30
AmolandGitHub 6933aa309b Merge pull request #740 from OpenSignLabs/signPdf 2024-05-15 13:55:49 +05:30
prafull-opensignlabs 067ea68d3b feat: add download button in 3 dots beside finish button 2024-05-15 13:25:13 +05:30
prafull-opensignlabs e4e8a0b721 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into signPdf 2024-05-15 11:10:29 +05:30
prafull-opensignlabs dc392adc1d feat: add delete folder API 2024-05-14 19:19:17 +05:30
AmolandGitHub 93b0d00ab0 Merge pull request #738 from OpenSignLabs/signPdf
fix: some webhook event are not working for second signer & add certificate in completed webhook
2024-05-14 18:33:31 +05:30
prafull-opensignlabs d731951159 fix: some webhook event are not working for second signer & add certificate in completed webhook 2024-05-14 18:30:26 +05:30
AmolandGitHub 24bf08c14f Merge pull request #737 from OpenSignLabs/signPdf
fix: resend mail API is not working
2024-05-14 16:44:42 +05:30
prafull-opensignlabs c03819f5a9 fix: resend mail API is not working 2024-05-14 16:36:58 +05:30
prafull-opensignlabsandGitHub 4521d16891 Merge pull request #719 from ugoconsonni/patch-4
Update createDocumentwithCoordinate.js
2024-05-14 13:57:08 +05:30
prafull-opensignlabs f77525cd8b fix: change baseUrl in APIs 2024-05-14 13:55:32 +05:30
AmolandGitHub c934f8ccd6 Merge pull request #735 from OpenSignLabs/signPdf
fix: change  parameter name in updatefolder API
2024-05-14 10:40:30 +05:30
prafull-opensignlabs 4e128e5a5a fix: change parameter name in updatefolder API 2024-05-14 10:40:04 +05:30
AndrewandGitHub b202d78059 Merge pull request #730 from OpenSignLabs/signPdf 2024-05-13 19:35:04 +05:30
prafull-opensignlabsandGitHub e15136ce51 Merge pull request #705 from Zathiel/staging
[SECURITY] Package update
2024-05-13 19:34:38 +05:30
prafull-opensignlabs 39c9211167 fix: pass parentFolderId as query parameter in folderlist API 2024-05-13 19:31:01 +05:30
AndrewandGitHub 64864e3f54 Merge pull request #729 from OpenSignLabs/signPdf 2024-05-13 18:25:15 +05:30
prafull-opensignlabs af0bef6d29 refactor: update response for folder APIs 2024-05-13 18:24:12 +05:30
AndrewandGitHub 130704b32f Merge pull request #728 from OpenSignLabs/signPdf 2024-05-13 17:09:23 +05:30
prafull-opensignlabsandGitHub 5b139b66a9 Merge pull request #727 from OpenSignLabs/raktima-opensignlabs-patch-6
feat: add help message button on send-in-order in request signature form
2024-05-13 17:07:06 +05:30
prafull-opensignlabs f9c5041af1 feat: add folder api 2024-05-13 17:02:37 +05:30
dependabot[bot]andGitHub 1e58b35db5 build(deps): bump @reduxjs/toolkit from 2.2.1 to 2.2.4 in /apps/OpenSign
Bumps [@reduxjs/toolkit](https://github.com/reduxjs/redux-toolkit) from 2.2.1 to 2.2.4.
- [Release notes](https://github.com/reduxjs/redux-toolkit/releases)
- [Commits](https://github.com/reduxjs/redux-toolkit/compare/v2.2.1...v2.2.4)

---
updated-dependencies:
- dependency-name: "@reduxjs/toolkit"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-13 10:51:45 +00:00
RaktimaNXG 245bf8046b feat: add help message on send-in-order in request signature form 2024-05-13 15:49:26 +05:30
AndrewandGitHub 94eb5f9efe Merge pull request #726 from OpenSignLabs/signPdf 2024-05-13 13:22:16 +05:30
prafull-opensignlabs 044128f36a feat: make entire folder selection box clickable 2024-05-13 13:21:32 +05:30
AndrewandGitHub 594383fe14 Merge pull request #725 from OpenSignLabs/signPdf 2024-05-13 11:50:03 +05:30
prafull-opensignlabs 90a585367e fix: change body parameter of create document api 2024-05-13 11:48:03 +05:30
prafull-opensignlabsandGitHub 0b8382df00 Merge pull request #723 from OpenSignLabs/raktima-opensignlabs-patch-5
fix: help button fridge in user's profile of disable documentId
2024-05-13 11:15:19 +05:30
RaktimaNXG a54c7e7fd7 fix: help button fridge in user profile ofdisable documentId 2024-05-13 10:57:53 +05:30
prafull-opensignlabsandGitHub 45a2c88e90 Merge pull request #718 from OpenSignLabs/raktima-opensignlabs-patch-4
fix: tour & model for email verification is shown simaltaneously in signyourself flow, navigate tour message on another page
2024-05-13 10:25:55 +05:30
AndrewandGitHub e4526f026b Merge pull request #720 from OpenSignLabs/signPdf 2024-05-10 21:48:25 +05:30
prafull-opensignlabs 11a2f6b8ec feat: add resend mail api, add expiry date in create document api & return certificate url in get document api 2024-05-10 21:07:06 +05:30
RaktimaNXG 3bf35166fc fix: bookmark color according to user's widget color 2024-05-10 20:27:04 +05:30
RaktimaNXG cd50c8cf20 Merge branch 'raktima-opensignlabs-patch-4' of https://github.com/OpenSignLabs/OpenSign into raktima-opensignlabs-patch-4 2024-05-10 19:31:42 +05:30
RaktimaNXG 055ef3f209 fix: min required checkbox validation issue 2024-05-10 19:28:53 +05:30
ugoconsonniandGitHub 964ec0d422 Update createDocumentwithCoordinate.js
fix: email and api return url server and not front
2024-05-10 15:22:00 +02:00
RaktimaandGitHub ec0e291604 Update RenderAllPdfPage.js 2024-05-10 18:16:11 +05:30
RaktimaNXG 836e952ba0 fix: add comment 2024-05-10 18:14:19 +05:30
RaktimaNXG c5f39e2a24 fix: tour & model for email verification is shown simaltaneously, issue of navigate tour message on another page to show warning complete signature 2024-05-10 16:59:58 +05:30
AndrewandGitHub 56dd060531 Merge pull request #716 from OpenSignLabs/fix_guestlogin 2024-05-10 15:14:19 +05:30
prafull-opensignlabs 960fbdb0eb fix: issue of owner subscription has expired in guest login 2024-05-10 15:10:57 +05:30
AndrewandGitHub f8145feebc Merge pull request #714 from OpenSignLabs/prafull-opensignlabs-patch-17 2024-05-10 11:14:26 +05:30
prafull-opensignlabsandGitHub df38d15611 fix: error something went wrong while signing document in request sign 2024-05-10 11:12:29 +05:30
ZathielandGitHub bb69518674 Merge branch 'OpenSignLabs:staging' into staging 2024-05-09 18:08:05 +02:00
prafull-opensignlabsandGitHub fee53fb020 Merge pull request #713 from OpenSignLabs/revert-712-raktima-opensignlabs-path-3
Revert "fix: owner's subsciption expired issue in guest login"
2024-05-09 20:56:08 +05:30
prafull-opensignlabsandGitHub d086e60ae5 Revert "fix: owner's subsciption expired issue in guest login" 2024-05-09 20:55:30 +05:30
prafull-opensignlabsandGitHub 51150d4e47 Merge pull request #712 from OpenSignLabs/raktima-opensignlabs-path-3
fix: owner's subsciption expired issue in guest login
2024-05-09 20:34:44 +05:30
RaktimaNXG 1d3cd02f9d fix: owner's subsciption expired issue in guest login 2024-05-09 20:32:33 +05:30
prafull-opensignlabsandGitHub d03515efc4 Merge pull request #708 from OpenSignLabs/raktima-opensignlabs-patch-2
feat: implemented a solution allowing for the simultaneous signing of a document by multiple signers
2024-05-09 16:36:37 +05:30
AmolandGitHub 30e6e4b3fd Merge pull request #709 from OpenSignLabs/prafull-opensignlabs-patch-16 2024-05-09 15:08:26 +05:30
prafull-opensignlabsandGitHub cbdd729e95 fix: pdf download is not working 2024-05-09 14:53:11 +05:30
RaktimaNXG ac82209d52 feat: implemented a solution allowing for the simultaneous signing of a document by multiple signers 2024-05-09 13:36:02 +05:30
RaktimaandGitHub d5cc6f6758 Merge pull request #707 from OpenSignLabs/prafull-opensignlabs-patch-15
refactor: change default cursor pointer to crosshair cursor pointer in debugUI
2024-05-09 12:33:50 +05:30
prafull-opensignlabsandGitHub 3aa211dc1a refactor: change default cursor pointer to crosshair cursor pointer in debugUI 2024-05-09 12:31:42 +05:30
prafull-opensignlabsandGitHub 33d5eecbbf Merge pull request #706 from OpenSignLabs/raktima-opensignlabs-patch-1
fix: can not access template before initialization
2024-05-09 12:00:25 +05:30
RaktimaNXG 1d6c108ba4 fix: can not access template before initialization 2024-05-09 11:58:57 +05:30
prafull-opensignlabsandGitHub 79da0f285d Merge pull request #704 from OpenSignLabs/raktima-opensignlabs-patch-1
fix:  integrate validation for the signature widget as mandatory using API
2024-05-09 11:36:36 +05:30
RaktimaNXG e8eef32958 fix: update condition for checking signature widget validation 2024-05-09 11:33:20 +05:30
ZathielandGitHub dc1fab5250 Merge pull request #14 from Zathiel/dependabot/npm_and_yarn/apps/OpenSign/ejs-3.1.10
build(deps): bump ejs from 3.1.9 to 3.1.10 in /apps/OpenSign
2024-05-08 23:21:58 +02:00
ZathielandGitHub 3e185ef796 Merge pull request #15 from Zathiel/dependabot/npm_and_yarn/apps/OpenSign/tar-6.2.1
build(deps): bump tar from 6.2.0 to 6.2.1 in /apps/OpenSign
2024-05-08 23:19:47 +02:00
ZathielandGitHub 8cf5adb273 Merge pull request #16 from Zathiel/dependabot/npm_and_yarn/apps/OpenSign/webpack-dev-middleware-5.3.4
build(deps): bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /apps/OpenSign
2024-05-08 23:15:43 +02:00
ZathielandGitHub 803faed97b Merge pull request #17 from Zathiel/dependabot/npm_and_yarn/apps/OpenSign/react-pdf-7.7.3
build(deps): bump react-pdf from 7.7.1 to 7.7.3 in /apps/OpenSign
2024-05-08 23:13:12 +02:00
RaktimaNXG 660eaeb714 fix: integrate validation for the signature widget as mandatory using API 2024-05-08 19:25:21 +05:30
RaktimaandGitHub 79126700c4 Merge pull request #702 from OpenSignLabs/prafull-opensignlabs-patch-14
fix: issue of user name is visible in subject of mail instead of document title
2024-05-08 16:06:43 +05:30
prafull-opensignlabsandGitHub 6355c02485 Update createDocumentwithCoordinate.js 2024-05-08 16:00:02 +05:30
prafull-opensignlabsandGitHub bce028877b Fix: issue of user name is visible in subject of mail instead of document title 2024-05-08 15:58:51 +05:30
prafull-opensignlabsandGitHub e47211a8c0 Merge pull request #701 from OpenSignLabs/raktima-opensignlabs-patch-1
fix: modal design of signature validation in both flow signyour-self and request-sign
2024-05-08 15:57:01 +05:30
RaktimaNXG 4a4c9eaa2d refactor: remove line 2024-05-08 15:54:48 +05:30
RaktimaNXG 0a5653f53d fix: modal design of signature validation in both flow signyour-self and request-sign 2024-05-08 15:48:53 +05:30
AmolandGitHub 5a90a0f484 Merge pull request #700 from OpenSignLabs/prafull-opensignlabs-patch-13
refactor: update certificate generating message
2024-05-08 15:43:24 +05:30
prafull-opensignlabsandGitHub a3a398bd92 refactor: update certificate generating message 2024-05-08 15:38:27 +05:30
AmolandGitHub 7d70162137 Merge pull request #699 from OpenSignLabs/raktima-opensignlabs-patch-1 2024-05-08 14:13:48 +05:30
RaktimaNXG e4c62c4eeb feat : integrate validation for the signature widget as mandatory. 2024-05-08 14:12:18 +05:30
AmolandGitHub 3eadca01fa Merge pull request #698 from OpenSignLabs/prafull-opensignlabs-patch-12
fix: issue of showing originator in completion certificate's signer list when add only one signer
2024-05-08 12:55:15 +05:30
prafull-opensignlabsandGitHub 659b7fe0f4 fix: issue of showing originator in completion certificate's signer list when add only one signer 2024-05-08 12:54:01 +05:30
AmolandGitHub ade6694427 Merge pull request #695 from OpenSignLabs/signPdf
fix: issue of showing document in drafts report after creating from document API
2024-05-08 12:13:20 +05:30
RaktimaandGitHub f8160665a4 Merge pull request #697 from OpenSignLabs/prafull-opensignlabs-patch-10
fix: something went wrong error in guest login when document created through Document API
2024-05-08 12:11:12 +05:30
prafull-opensignlabsandGitHub 5778bdecae fix: something went wrong error in guest login when document created through Document API 2024-05-08 12:10:11 +05:30
prafull-opensignlabs 9122c71c90 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into signPdf 2024-05-08 12:00:53 +05:30
prafull-opensignlabs 2c2065aca8 fix: issue of showing document in drafts report after creating from document API 2024-05-08 11:03:38 +05:30
dependabot[bot]andGitHub b1dd4df3c0 build(deps): bump react-pdf from 7.7.1 to 7.7.3 in /apps/OpenSign
Bumps [react-pdf](https://github.com/wojtekmaj/react-pdf/tree/HEAD/packages/react-pdf) from 7.7.1 to 7.7.3.
- [Release notes](https://github.com/wojtekmaj/react-pdf/releases)
- [Commits](https://github.com/wojtekmaj/react-pdf/commits/v7.7.3/packages/react-pdf)

---
updated-dependencies:
- dependency-name: react-pdf
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 17:03:30 +00:00
AmolandGitHub 64fd3580ac Merge pull request #691 from OpenSignLabs/all-contributors/add-daniel-mutwiri
docs: add daniel-mutwiri as a contributor for code
2024-05-07 20:42:06 +05:30
AmolandGitHub 01273777b7 Merge branch 'staging' into all-contributors/add-daniel-mutwiri 2024-05-07 20:41:56 +05:30
AmolandGitHub 23eaa1ae81 Merge pull request #690 from OpenSignLabs/all-contributors/add-Zathiel
docs: add Zathiel as a contributor for code
2024-05-07 20:37:35 +05:30
allcontributors[bot]andGitHub 3fd0613407 docs: update .all-contributorsrc 2024-05-07 15:07:21 +00:00
allcontributors[bot]andGitHub d45566257f docs: update README.md 2024-05-07 15:07:20 +00:00
allcontributors[bot]andGitHub e9eb59aa7a docs: update .all-contributorsrc 2024-05-07 15:06:43 +00:00
allcontributors[bot]andGitHub 9e3ba2cf5c docs: update README.md 2024-05-07 15:06:42 +00:00
AmolandGitHub 81afc92c14 Merge pull request #689 from OpenSignLabs/all-contributors/add-ugoconsonni
docs: add ugoconsonni as a contributor for code
2024-05-07 20:36:07 +05:30
allcontributors[bot]andGitHub d3bf9e9e9b docs: update .all-contributorsrc 2024-05-07 15:05:51 +00:00
allcontributors[bot]andGitHub 18e25f6cd9 docs: update README.md 2024-05-07 15:05:50 +00:00
AmolandGitHub a6d97d587f Merge pull request #688 from OpenSignLabs/main
Main
2024-05-07 20:04:14 +05:30
AmolandGitHub ff9a495775 Merge pull request #656 from OpenSignLabs/staging
v1.5.8
2024-05-07 20:02:52 +05:30
AmolandGitHub b29c7d5c7f Merge pull request #687 from OpenSignLabs/revert-680-documentLoad_issue
Revert "fix: document loading error No PDF file specified"
2024-05-07 20:00:41 +05:30
AmolandGitHub fddeba8d44 Revert "fix: document loading error No PDF file specified" 2024-05-07 20:00:26 +05:30
prafull-opensignlabsandGitHub c564b5741c Merge pull request #686 from OpenSignLabs/guestsign_url
fix: document loading error No PDF file specified
2024-05-07 19:59:20 +05:30
RaktimaNXG 179c3c5dfe fix: document loading error No PDF file specified 2024-05-07 19:57:39 +05:30
prafull-opensignlabsandGitHub 221f71c061 Merge pull request #685 from OpenSignLabs/guestsign_url
fix: x is not defined error in createDocumentWithTemplate and createDocumentwithCoordinate function
2024-05-07 19:32:19 +05:30
RaktimaNXG a26e65f76f fix: x is not defined error in createDocumentWithTemplate and createDocumentwithCoordinate function 2024-05-07 19:29:09 +05:30
prafull-opensignlabsandGitHub 9ba05d23fd Merge pull request #676 from OpenSignLabs/guestsign_url
fix: issue of url encoded parameter in guest login url
2024-05-07 18:53:27 +05:30
RaktimaNXG 0f67013005 refactor: remove commented code and remove extra condition 2024-05-07 18:30:45 +05:30
RaktimaNXG 65d5c7f337 refactor: remove commented code and remove extra condition 2024-05-07 18:30:06 +05:30
AndrewandGitHub a7dd47f1b1 Merge pull request #683 from OpenSignLabs/signPdf 2024-05-07 16:08:50 +05:30
prafull-opensignlabs 350212c02f refactor: changes in subject of request signature mail 2024-05-07 16:06:31 +05:30
RaktimaNXG d9126d6701 refactor: add comment 2024-05-07 16:04:47 +05:30
RaktimaNXG 51c3a0f71a fix: in opensign-drive unnecessary render tour message and onclick same folder route then it show no data found issue 2024-05-07 15:55:46 +05:30
RaktimaNXG a4bf9ff915 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into guestsign_url 2024-05-07 14:39:44 +05:30
AmolandGitHub 28a2a113e2 Merge pull request #682 from OpenSignLabs/signPdf
fix: issue of showing currently encrypted file not supported when try to sign document
2024-05-07 14:22:35 +05:30
prafull-opensignlabs 07382822bd fix: issue of showing currently incrypted file not supported when try to sign document 2024-05-07 13:39:39 +05:30
prafull-opensignlabsandGitHub 06677d923c Merge pull request #680 from OpenSignLabs/documentLoad_issue
fix: document loading error No PDF file specified
2024-05-07 12:24:38 +05:30
AmolandGitHub 534dc3d3f5 Merge pull request #681 from OpenSignLabs/revert-679-document_load
Revert "fix: Document loading error `No PDF file specified`"
2024-05-07 12:21:01 +05:30
AmolandGitHub a69682cebf Revert "fix: Document loading error No PDF file specified" 2024-05-07 12:20:43 +05:30
RaktimaNXG 2ae7e8ced2 fix: document loading error No PDF file specified 2024-05-07 11:53:17 +05:30
prafull-opensignlabsandGitHub e20b75cfc1 Merge pull request #679 from OpenSignLabs/document_load
fix: Document loading error `No PDF file specified`
2024-05-07 11:52:43 +05:30
RaktimaNXG 37b73e36ca fix: document error No PDF file specified 2024-05-07 11:42:48 +05:30
prafull-opensignlabsandGitHub cc6b64d140 Merge pull request #678 from daniel-mutwiri/staging
Fix: draftDocument.js saveFileUsage is not defined.
2024-05-07 11:35:59 +05:30
Daniel MutwiriandGitHub 4b765a8e1d Merge pull request #1 from gitonga123/fix/draftdocument
Bug Fix:  draftDocument.js saveFileUsage is not defined.
2024-05-07 08:59:45 +03:00
Daniel Mutwiri 4a7108e4f6 Bug Fix: saveFileUsage is not defined.
saveFileUsage imported from Utils.js
2024-05-07 08:48:55 +03:00
RaktimaNXG 66bf577676 refactor: add comment 2024-05-07 09:54:10 +05:30
dependabot[bot]andGitHub 13e2d92ce6 build(deps): bump webpack-dev-middleware in /apps/OpenSign
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 02:26:18 +00:00
dependabot[bot]andGitHub 4b9f22a5bc build(deps): bump tar from 6.2.0 to 6.2.1 in /apps/OpenSign
Bumps [tar](https://github.com/isaacs/node-tar) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.2.0...v6.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 02:25:30 +00:00
dependabot[bot]andGitHub 77c61c89a2 build(deps): bump ejs from 3.1.9 to 3.1.10 in /apps/OpenSign
Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-07 02:24:33 +00:00
prafull-opensignlabsandGitHub d04b79001a Merge pull request #675 from Zathiel/staging
Package Update
2024-05-06 19:01:09 +05:30
AmolandGitHub e14b2a2a17 Merge pull request #674 from OpenSignLabs/signPdf 2024-05-06 18:54:23 +05:30
RaktimaNXG 6afa97a76b fix: encode in base64 of guest signing url and decode it in guest login page 2024-05-06 18:54:07 +05:30
RaktimaNXG 3a28b1cdd7 fix: encode in base64 of guest signing url and decode it in guest login page 2024-05-06 18:51:47 +05:30
Zathiel | Sys 23b48838ae deps : upgrade prettier, pretty-quick, postcss, autoprefixer, @babel/runtime-corejs2, reactour, react-rnd, @react-pdf/renderer, @formkit/auto-animate 2024-05-06 13:18:57 +00:00
Zathiel | Sys bddebf2758 deps : upgrade pdfkit, nodemailer, mongoose, express, eslint package version 2024-05-06 13:18:04 +00:00
prafull-opensignlabs 5a68e18c48 fix: pdf url are not downloadable after some time 2024-05-06 18:46:12 +05:30
dependabot[bot]andGitHub 280b812e5b 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.1 to 7.24.5.
- [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.5/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-06 12:51:25 +00:00
AndrewandGitHub 082f0569e2 Merge pull request #673 from OpenSignLabs/signPdf 2024-05-06 17:18:32 +05:30
prafull-opensignlabs b01618c8ca fix: Ip address overlap with security level in certificate 2024-05-06 17:16:28 +05:30
AndrewandGitHub 7b0f0d5b9f Merge pull request #672 from OpenSignLabs/signPdf 2024-05-06 15:19:52 +05:30
prafull-opensignlabs 1381a65c0b Merge branch 'signPdf' of https://github.com/OpenSignLabs/OpenSign into signPdf 2024-05-06 15:18:02 +05:30
prafull-opensignlabs 961ec2b2ba fix: pdf password modal is not closeable in form 2024-05-06 15:17:53 +05:30
prafull-opensignlabsandGitHub 022cb20b64 Merge pull request #662 from OpenSignLabs/signPdf
fix: verify email otp varification for inProgress reports
2024-05-06 14:36:56 +05:30
RaktimaNXG 7b4fa4fe23 fix: design of verify email 2024-05-06 13:55:15 +05:30
prafull-opensignlabs c85c21ef8a Merge branch 'signPdf' of https://github.com/OpenSignLabs/OpenSign into signPdf 2024-05-06 13:31:43 +05:30
prafull-opensignlabs 09fb01862d feat: handle encrypted pdf 2024-05-06 13:29:31 +05:30
RaktimaNXG f6ce1b45fb refactor: add comment for email verify functions 2024-05-06 12:11:48 +05:30
RaktimaNXG 8fd2d4210e refactor : add comment, handle unnecessary query call 2024-05-06 11:32:30 +05:30
prafull-opensignlabsandGitHub 0f7f1f1ba2 Merge pull request #667 from ugoconsonni/patch-3
Update sendMailv3.js
2024-05-06 10:08:02 +05:30
ugoconsonniandGitHub 8693ef29f6 Update sendMailv3.js
Fix: [Bug]: createTransport was not imported #666
2024-05-05 21:51:36 +02:00
AmolandGitHub dc67d2c23d Merge pull request #664 from OpenSignLabs/fix_login
fix: login with google issue
2024-05-05 18:29:40 +05:30
prafull-opensignlabs 761925fced fix: login issue 2024-05-05 18:25:50 +05:30
RaktimaNXG c240eb133d fix: add verify email otp verification in signyour-self flow 2024-05-05 15:53:51 +05:30
RaktimaNXG 9daed4a5ed refactor : remove commented code 2024-05-03 20:19:38 +05:30
RaktimaNXG ee18f82fa9 fix: verify email otp varification for inProgress reports 2024-05-03 20:11:57 +05:30
AndrewandGitHub 4085f8259d Merge pull request #661 from OpenSignLabs/signPdf 2024-05-03 18:31:39 +05:30
prafull-opensignlabs 98f84f6cc8 fix: update description of plan 2024-05-03 18:15:28 +05:30
prafull-opensignlabs da9363f588 fix: update description of plan 2024-05-03 18:11:50 +05:30
AndrewandGitHub 5afb60a687 Merge pull request #660 from OpenSignLabs/signPdf 2024-05-03 18:09:45 +05:30
prafull-opensignlabs a18733ad79 fix: show correct document creation date on certificate 2024-05-03 18:08:53 +05:30
AndrewandGitHub 400e132f11 Merge pull request #659 from OpenSignLabs/signPdf 2024-05-03 17:23:40 +05:30
prafull-opensignlabs c56830b7be fix: show correct ipv4 address on completion certificate 2024-05-03 17:14:26 +05:30
AndrewandGitHub f988c25491 Merge pull request #658 from OpenSignLabs/signPdf 2024-05-03 15:51:57 +05:30
RaktimaNXG 53bd13e03b fix: signature is not visiable in completion certificate 2024-05-03 15:50:52 +05:30
prafull-opensignlabs 68edecae18 fix: issue of encryptedpdf error api not working 2024-05-03 14:26:54 +05:30
prafull-opensignlabs b421c0f1a9 fix: remove uncessary import statement 2024-05-03 14:05:00 +05:30
prafull-opensignlabs 3858746d64 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into signPdf 2024-05-03 13:24:02 +05:30
prafull-opensignlabs 41e8aa18a7 feat: add pdftopdf and wordtopdf functionality 2024-05-03 13:20:31 +05:30
AmolandGitHub fb8915e9db Merge pull request #654 from OpenSignLabs/encrypted_pdf
refactor: add encrypted pdf error event
2024-05-02 00:04:24 +05:30
AmolandGitHub ba2bb07aa9 Merge pull request #655 from OpenSignLabs/staging
v1.5.7
2024-05-02 00:02:02 +05:30
prafull-opensignlabs 74083d0606 refactor: add encrypted pdf error event 2024-05-01 11:36:03 +05:30
prafull-opensignlabs 0f2862c52e Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into signPdf 2024-05-01 10:30:46 +05:30
AndrewandGitHub 61e11b7105 Merge pull request #652 from OpenSignLabs/encrypted_pdf 2024-04-30 21:30:07 +05:30
prafull-opensignlabs d7de8648e9 fix: stuck on loader screen while sending pdf for signing 2024-04-30 19:43:11 +05:30
prafull-opensignlabs c279fd6525 feat: add email verification in user profile 2024-04-29 21:12:54 +05:30
prafull-opensignlabs 3599fbee21 fix: changes in certificate 2024-04-29 17:38:25 +05:30
prafull-opensignlabsandGitHub 9ad51a1d6a Merge pull request #648 from OpenSignLabs/fix_issue
fix: send wrong user activeMailAdapter data in sendmailv3 and signPdf function
2024-04-26 18:05:13 +05:30
RaktimaNXG 03e22dbf33 refactor: remove comment 2024-04-26 17:49:03 +05:30
RaktimaNXG 5693fa230b fix: send wrong user activeMailAdapter data in sendmailv3 and signPdf function 2024-04-26 17:45:22 +05:30
RaktimaNXG 8cd90911a8 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into fix_issue 2024-04-26 17:22:50 +05:30
prafull-opensignlabsandGitHub 7cf820d205 Merge pull request #645 from OpenSignLabs/signPdf
fix: document not found
2024-04-25 12:42:51 +05:30
prafull-opensignlabs 4d01547fd5 fix: document not found 2024-04-25 12:39:49 +05:30
AndrewandGitHub 6597f67f0a Merge pull request #644 from OpenSignLabs/signPdf 2024-04-25 12:25:48 +05:30
prafull-opensignlabs 14ce91350e fix: missing credintials in config aws_config_file 2024-04-25 12:16:45 +05:30
RaktimaNXG 7a5f75ea79 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into fix_issue 2024-04-24 18:05:40 +05:30
AmolandGitHub a44cb7168f Merge pull request #643 from OpenSignLabs/staging
v1.5.6
2024-04-24 17:53:59 +05:30
prafull-opensignlabsandGitHub 1c6b056d6c Merge pull request #642 from OpenSignLabs/signPdf
fix: freeplan users are forcefully navigate to subscription
2024-04-24 17:05:58 +05:30
prafull-opensignlabs cf6759ce79 fix: handle freeplan users are forcefully navigate to subscription 2024-04-24 17:03:45 +05:30
AmolandGitHub d422f46f47 Merge pull request #641 from OpenSignLabs/staging
v1.5.5
2024-04-24 12:10:12 +05:30
AndrewandGitHub 85f94c8ffa Merge pull request #640 from OpenSignLabs/prafull-opensignlabs-patch-8 2024-04-24 10:57:57 +05:30
prafull-opensignlabsandGitHub 91da331ce6 fix: after connected to gmail mail is not sending from personal mail 2024-04-24 10:33:17 +05:30
AmolandGitHub 3db6946198 Merge pull request #639 from OpenSignLabs/staging
v1.5.4
2024-04-23 16:13:35 +05:30
AmolandGitHub 94c8caa3e6 Merge pull request #638 from OpenSignLabs/prafull-opensignlabs-patch-7
fix: redirect_uri mismatch in googleauth
2024-04-23 16:13:01 +05:30
prafull-opensignlabsandGitHub ed1120927c fix: redirect_uri mismatch in googleauth 2024-04-23 12:31:47 +05:30
AmolandGitHub 79b4c38587 Merge pull request #637 from OpenSignLabs/staging
v1.5.3
2024-04-23 12:04:51 +05:30
AmolandGitHub 2cc5517bc1 Merge pull request #636 from OpenSignLabs/prafull-opensignlabs-patch-7
fix : Update googleauth.js
2024-04-23 11:57:09 +05:30
prafull-opensignlabsandGitHub eddf70f102 Update googleauth.js 2024-04-23 11:56:20 +05:30
AmolandGitHub b6774519f9 Merge pull request #635 from OpenSignLabs/staging
v1.5.2
2024-04-23 10:53:39 +05:30
RaktimaNXG d6bb83f997 fix: remove conosle log 2024-04-22 18:56:28 +05:30
AndrewandGitHub 55e8042b0a Merge pull request #634 from OpenSignLabs/fix_issue 2024-04-22 18:56:20 +05:30
RaktimaNXG d5bc2df20f feat: add mailProvider parameter in sendmailv3 function and signPdf function 2024-04-22 18:53:41 +05:30
prafull-opensignlabsandGitHub c623e868f7 Merge pull request #633 from OpenSignLabs/fix_issue
fix:  subscription issue in guest login flow
2024-04-22 17:52:46 +05:30
RaktimaNXG 174b9b9941 fix: fix subscription issue 2024-04-22 17:45:48 +05:30
prafull-opensignlabsandGitHub 0eb247ae0c Merge pull request #632 from OpenSignLabs/fix_issue
fix: update getSubscription cloud function and handle subscription details for guest login flow
2024-04-22 17:30:22 +05:30
RaktimaNXG f96df699e4 fix: update getSubscription cloud function for guest login flow and handle subscription details 2024-04-22 17:26:14 +05:30
AmolandGitHub dcaca98aec Merge pull request #631 from OpenSignLabs/staging
v1.5.2
2024-04-22 16:57:12 +05:30
AmolandGitHub d70f4dddb6 Merge pull request #629 from OpenSignLabs/fix_issue
fix: text could not be selectable from validation dropdown issue
2024-04-22 16:21:23 +05:30
AmolandGitHub db34c4248c Merge pull request #630 from OpenSignLabs/gmail_provider
feat: add google mail provider
2024-04-22 15:42:11 +05:30
prafull-opensignlabs 1d39db6d7d refactor: change parameter for custom mail adapter 2024-04-22 14:22:55 +05:30
RaktimaNXG 654dadce93 fix: text could not be selectable from validation dropdown issue 2024-04-22 12:14:54 +05:30
prafull-opensignlabsandGitHub e578981791 Merge pull request #628 from OpenSignLabs/fix_issue
fix: remove validation for text input widgets
2024-04-22 11:32:58 +05:30
RaktimaNXG c4297a260e fix: remove validation for text input widgets 2024-04-22 11:17:55 +05:30
AmolandGitHub ecc453957c Merge pull request #625 from OpenSignLabs/staging
v1.5.1
2024-04-21 16:02:53 +05:30
AndrewandGitHub 498e0c6be6 Merge pull request #624 from OpenSignLabs/fix_issue 2024-04-21 11:08:48 +05:30
RaktimaNXG b4d73b2b44 fix: text input validation issue in template flow 2024-04-21 10:30:33 +05:30
prafull-opensignlabs f14c98505a feat: provide send mail through user gmail 2024-04-19 20:56:43 +05:30
AmolandGitHub 3f23bdd372 Merge pull request #622 from OpenSignLabs/staging
v1.5.1
2024-04-18 22:07:23 +05:30
prafull-opensignlabsandGitHub 440d253124 Merge pull request #621 from OpenSignLabs/fix_issue
fix: text input validation issue if user not select any validation
2024-04-18 19:40:39 +05:30
RaktimaNXG 2b6bf7bcaa fix: server url and parse appid issue to save file in parters classess 2024-04-18 19:25:21 +05:30
RaktimaNXG cb6d9e9b9b fix: text input validation issue if user not select any validation or free user also 2024-04-18 17:20:44 +05:30
AndrewandGitHub 493731a6af Merge pull request #615 from OpenSignLabs/staging 2024-04-17 21:15:27 +05:30
AmolandGitHub 28fd98c0db Merge pull request #551 from OpenSignLabs/staging
v1.4.2
2024-04-01 20:37:24 +05:30
AmolandGitHub b04d2abbab Merge pull request #548 from OpenSignLabs/staging
v1.4.1
2024-04-01 11:48:17 +05:30
AmolandGitHub 387154c07b Merge pull request #536 from OpenSignLabs/staging
v1.4.0-beta
2024-03-26 21:49:47 +05:30
AmolandGitHub f7606bce35 Merge pull request #507 from OpenSignLabs/staging
v1.3.2-beta
2024-03-19 16:03:24 +05:30
AmolandGitHub f08758fd71 Merge pull request #491 from OpenSignLabs/staging 2024-03-14 21:45:14 +05:30
AmolandGitHub d3b1a37a7c Merge pull request #473 from OpenSignLabs/staging
v1.3.0-beta
2024-03-08 20:34:25 +05:30
AmolandGitHub 4718c653f7 Merge pull request #454 from OpenSignLabs/staging
minor package updates
2024-03-02 13:26:17 +05:30
AndrewandGitHub f9b8437981 Merge pull request #421 from OpenSignLabs/prafull-opensignlabs-patch-4 2024-02-22 18:28:02 +05:30
prafull-opensignlabsandGitHub b0c73bc63e Update Docker.yml 2024-02-22 18:27:35 +05:30
AmolandGitHub 269246659b Merge pull request #411 from OpenSignLabs/staging
v1.2.1-beta
2024-02-22 12:28:38 +05:30
AmolandGitHub 51fe5d133e Merge pull request #404 from OpenSignLabs/staging
refactor analytics code
2024-02-16 20:15:05 +05:30
AmolandGitHub 36a1123535 Merge pull request #402 from OpenSignLabs/staging
v1.2.0-beta
2024-02-16 19:57:43 +05:30
AmolandGitHub 3c1ceb4995 Merge pull request #331 from OpenSignLabs/staging
v1.1.1-beta
2024-01-15 19:21:24 +05:30
AmolandGitHub 53d154fa59 Merge pull request #315 from OpenSignLabs/staging
v1.1.0-beta
2024-01-10 21:07:41 +05:30
146 changed files with 14405 additions and 8008 deletions
+27
View File
@@ -205,6 +205,33 @@
"contributions": [
"code"
]
},
{
"login": "ugoconsonni",
"name": "ugoconsonni",
"avatar_url": "https://avatars.githubusercontent.com/u/13661702?v=4",
"profile": "https://github.com/ugoconsonni",
"contributions": [
"code"
]
},
{
"login": "daniel-mutwiri",
"name": "Daniel Mutwiri",
"avatar_url": "https://avatars.githubusercontent.com/u/8936960?v=4",
"profile": "https://github.com/daniel-mutwiri",
"contributions": [
"code"
]
},
{
"login": "Zathiel",
"name": "Zathiel",
"avatar_url": "https://avatars.githubusercontent.com/u/26553418?v=4",
"profile": "https://github.com/Zathiel",
"contributions": [
"code"
]
}
]
}
+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=
+3
View File
@@ -134,6 +134,9 @@ We would like to thank all our contributors and users for their support and feed
</tr>
<tr>
<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><a href="#security-Zathiel" title="Security">🛡️</a></td>
</tr>
</tbody>
</table>
+4074 -3585
View File
File diff suppressed because it is too large Load Diff
+24 -19
View File
@@ -3,22 +3,24 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.1",
"@radix-ui/themes": "^2.0.3",
"@react-pdf/renderer": "^3.4.0",
"@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.8",
"@formkit/auto-animate": "^0.8.2",
"@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",
"parse": "^4.3.1",
"nth-check": "^2.1.1",
"parse": "^5.1.0",
"pdf-lib": "^1.17.1",
"print-js": "^1.6.0",
"radix-ui": "^1.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.2",
"react-confetti": "^6.1.0",
"react-cookie": "^7.1.4",
"react-datepicker": "^6.4.0",
"react-datepicker": "^6.9.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^8.0.3",
@@ -28,24 +30,24 @@
"react-helmet": "^6.1.0",
"react-konva": "^18.2.10",
"react-modal": "^3.16.1",
"react-pdf": "^7.7.1",
"react-pdf": "^9.0.0",
"react-quill": "^2.0.0",
"react-redux": "^9.1.0",
"react-rnd": "^10.4.1",
"react-redux": "^9.1.2",
"react-rnd": "^10.4.11",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.8.0",
"react-signature-canvas": "^1.0.6",
"react-tooltip": "^5.26.3",
"react-tooltip": "^5.26.4",
"react-web-share": "^2.0.2",
"reactour": "^1.19.2",
"reactour": "^1.19.4",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"regex-parser": "^2.3.0",
"serve": "^14.2.1",
"serve": "^14.2.3",
"styled-components": "^4.4.1",
"web-vitals": "^2.1.4"
"web-vitals": "^4.0.1"
},
"scripts": {
"start": "serve -s build",
@@ -83,16 +85,19 @@
}
},
"devDependencies": {
"@babel/runtime-corejs2": "^7.24.1",
"autoprefixer": "^10.4.18",
"@babel/runtime-corejs2": "^7.24.6",
"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.2",
"postcss": "^8.4.37",
"lint-staged": "^15.2.5",
"postcss": "^8.4.38",
"prettier": "^2.8.8",
"pretty-quick": "^3.3.1",
"tailwindcss": "^3.4.1"
"tailwindcss": "^3.4.3"
},
"overrides": {
"nth-check": "$nth-check"
}
}
+7 -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
@@ -109,6 +109,10 @@ function App() {
path="/login/:id/:userMail/:contactBookId/:serverUrl"
element={<LazyPage Page={GuestLogin} />}
/>
<Route
path="/login/:base64url"
element={<LazyPage Page={GuestLogin} />}
/>
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
<Route
path="/forgetpassword"
@@ -177,6 +181,7 @@ function App() {
element={<PdfRequestFiles />}
/>
</Route>
<Route path="/sso" element={<SSOVerify />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
+151 -135
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]);
@@ -49,144 +49,162 @@ const AddSigner = (props) => {
e.preventDefault();
e.stopPropagation();
setIsLoader(true);
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");
if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
}
if (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!");
alert("something went wrong, please try again later");
}
};
@@ -264,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">
@@ -276,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));
+8 -8
View File
@@ -110,22 +110,22 @@ 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) {
// setThirdpartyLoader(true);
const billingDate =
extRes.get("Next_billing_date") && extRes.get("Next_billing_date");
// const billingDate =
// extRes.get("Next_billing_date") && extRes.get("Next_billing_date");
// console.log("billingDate expired", billingDate > new Date());
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));
thirdpartyLoginfn(payload.sessiontoken, billingDate);
thirdpartyLoginfn(payload.sessiontoken);
}
return { msg: "exist" };
} else {
@@ -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
};
@@ -22,7 +22,8 @@ const RenderDebugPdf = (props) => {
flex: 1,
position: "relative",
border: "1px solid grey",
overflow: "auto"
overflow: "auto",
cursor: "crosshair"
}}
onMouseMove={props.handleMouseMoveDiv}
>
@@ -17,7 +17,7 @@ function DefaultSignature({ defaultSignImg, xyPostion, setDefaultSignAlert }) {
};
return (
<div>
<div data-tut="reactourThird">
<div
style={{
background: themeColor,
@@ -1,6 +1,9 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import "../../styles/AddUser.css";
import { getFileName } from "../../constant/Utils";
import { checkIsSubscribed, getFileName } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
// import SelectFolder from "../../premitives/SelectFolder";
const EditTemplate = ({ template, onSuccess }) => {
@@ -9,9 +12,21 @@ const EditTemplate = ({ template, onSuccess }) => {
Name: template?.Name || "",
Note: template?.Note || "",
Description: template?.Description || "",
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false"
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
AutomaticReminders: template?.AutomaticReminders || false,
RemindOnceInEvery: template?.RemindOnceInEvery || 5
});
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => {
fetchSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const fetchSubscription = async () => {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
};
const handleStrInput = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
@@ -25,12 +40,26 @@ const EditTemplate = ({ template, onSuccess }) => {
e.preventDefault();
e.stopPropagation();
const isChecked = formData.SendinOrder === "true" ? true : false;
const data = { ...formData, SendinOrder: isChecked };
const AutoReminder = formData?.AutomaticReminders || false;
let reminderDate = {};
if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
const ReminderDate = new Date(template?.createdAt);
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
reminderDate = { NextReminderDate: ReminderDate };
}
const data = { ...formData, SendinOrder: isChecked, ...reminderDate };
onSuccess(data);
};
const handleAutoReminder = () => {
setFormData((prev) => ({
...prev,
AutomaticReminders: !formData.AutomaticReminders
}));
};
return (
<div className="addusercontainer">
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
<div className="form-wrapper">
<form onSubmit={handleSubmit}>
<div>
@@ -128,6 +157,57 @@ const EditTemplate = ({ template, onSuccess }) => {
<div style={{ fontSize: 12 }}>No</div>
</div>
</div>
<div className="text-xs mt-2">
{!isEnableSubscription && (
<PremiumAlertHeader
message={
"Disable Auto reminder is free in beta, this feature will incur a fee later."
}
/>
)}
<span
className={
isSubscribe || !isEnableSubscription
? "font-semibold"
: "font-semibold text-gray-300"
}
>
Auto reminder{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<label
className={`${
isSubscribe || !isEnableSubscription
? "cursor-pointer "
: "pointer-events-none opacity-50"
} relative block items-center mb-0`}
>
<input
checked={formData.AutomaticReminders}
onChange={handleAutoReminder}
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 mt-2"></div>
</label>
</div>
{isSubscribe && formData?.AutomaticReminders === true && (
<div className="text-xs mt-2">
<label className="block">
Remind once in every (Days)
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
value={formData.RemindOnceInEvery}
name="RemindOnceInEvery"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
onChange={handleStrInput}
required
/>
</div>
)}
{/* <SelectFolder onSuccess={handleFolder} folderCls={"contracts_Template"} /> */}
<div className="buttoncontainer">
<button type="submit" className="submitbutton">
@@ -20,7 +20,7 @@ export function EmailBody(props) {
required
value={props.requestSubject}
onChange={(e) => props.setRequestSubject(e.target.value)}
placeholder="${senderName} has requested you to sign ${documentName}"
placeholder='${senderName} has requested you to sign "${documentName}"'
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
/>
<label className="text-sm ml-2 mt-3">
@@ -10,20 +10,22 @@ import loader from "../../assets/images/loader2.gif";
function EmailComponent({
isEmail,
pdfUrl,
isCeleb,
setIsEmail,
setSuccessEmail,
pdfName,
sender,
setIsAlert,
extUserId
extUserId,
activeMailAdapter
}) {
const [emailList, setEmailList] = useState([]);
const [emailValue, setEmailValue] = useState();
const [isLoading, setIsLoading] = useState(false);
const [isEmailCelebration, setIsEmailCelebration] = useState(false);
//function for send email
const sendEmail = async () => {
setIsLoading(true);
let sendMail;
for (let i = 0; i < emailList.length; i++) {
try {
@@ -39,6 +41,7 @@ function EmailComponent({
const openSignUrl = "https://www.opensignlabs.com/contact-us";
const themeBGcolor = themeColor;
let params = {
mailProvider: activeMailAdapter,
extUserId: extUserId,
pdfName: pdfName,
url: pdfUrl,
@@ -59,6 +62,10 @@ function EmailComponent({
" target=_blank>here</a> </p></div></div></body></html>"
};
sendMail = await axios.post(url, params, { headers: headers });
setIsEmailCelebration(true);
setTimeout(() => {
setIsEmailCelebration(false);
}, 3000);
} catch (error) {
console.log("error", error);
setIsLoading(false);
@@ -242,22 +249,17 @@ function EmailComponent({
</div>
</div>
<div style={{ height: "100%", padding: 20 }}>
{isCeleb && (
<div
style={{
position: "absolute",
marginLeft: "50px"
}}
>
{isEmailCelebration && (
<div className="absolute w-[100%] flex justify-center items-center">
<img
alt="print img"
width={300}
height={250}
// style={styles.gifCeleb}
src={celebration}
/>
</div>
)}
<p
style={{
fontFamily: "system-ui",
+188 -212
View File
@@ -1,14 +1,15 @@
import React from "react";
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 Certificate from "./Certificate";
import { PDFDownloadLink } from "@react-pdf/renderer";
import ModalUi from "../../primitives/ModalUi";
function Header({
isPdfRequestFiles,
@@ -39,6 +40,7 @@ function Header({
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
const isMobile = window.innerWidth < 767;
const navigate = useNavigate();
const [isDownloading, setIsDownloading] = useState("");
const isGuestSigner = localStorage.getItem("isGuestSigner");
//for go to previous page
function previousPage() {
@@ -57,100 +59,6 @@ function Header({
setIsDecline(currentDecline);
};
//function for print digital sign pdf
const handleToPrint = async (event) => {
event.preventDefault();
const pdf = await getBase64FromUrl(pdfUrl);
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 });
}
};
//handle download signed pdf
const handleDownloadPdf = () => {
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
};
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);
}
}
};
const GenerateCertificate = () => {
//after generate download certifcate pdf
const handleDownload = (pdfBlob, fileName) => {
if (pdfBlob) {
const url = window.URL.createObjectURL(pdfBlob);
// Create a temporary anchor element
const link = document.createElement("a");
link.href = url;
link.download = fileName;
// Append the anchor to the body
document.body.appendChild(link);
// Programmatically click the anchor to trigger the download
link.click();
// Remove the anchor from the body
document.body.removeChild(link);
// Release the object URL to free up resources
window.URL.revokeObjectURL(url);
}
};
return (
<PDFDownloadLink
onClick={(e) => e.preventDefault()}
style={{ textDecoration: "none", zIndex: "35" }}
document={<Certificate pdfData={pdfDetails} />}
>
{({ blob, loading }) => (
<button
onClick={() =>
handleDownload(
blob,
`completion certificate-${
pdfDetails[0] && pdfDetails[0].Name
}.pdf`
)
}
disabled={loading}
className=" md:bg-[#08bc66] border-none focus:outline-none flex flex-row items-center md:shadow md:rounded-[3px] py-[3px] md:px-[11px] md:text-white text-black md:font-[500] text-[13px] mr-[5px]"
>
<i className="fa-solid fa-award py-[3px]" aria-hidden="true"></i>
<span className="md:hidden lg:block ml-1">Certificate</span>
</button>
)}
</PDFDownloadLink>
);
};
return (
<div style={{ padding: "5px 0px 5px 0px" }} className="mobileHead">
{isMobile && isShowHeader ? (
@@ -223,7 +131,9 @@ function Header({
>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleDownloadPdf()}
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<div
style={{
@@ -240,32 +150,29 @@ function Header({
</div>
</DropdownMenu.Item>
{isCompleted && (
<>
{pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleDownloadCertificate()}
>
<div
style={{
border: "none",
backgroundColor: "#fff"
}}
>
<i
className="fa-solid fa-award"
style={{ marginRight: "2px" }}
aria-hidden="true"
></i>
Certificate
</div>
</DropdownMenu.Item>
) : (
<DropdownMenu.Item className="DropdownMenuItem">
<GenerateCertificate />
</DropdownMenu.Item>
)}
</>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
handleDownloadCertificate(
pdfDetails,
setIsDownloading
)
}
>
<div
style={{
border: "none",
backgroundColor: "#fff"
}}
>
<i
className="fa-solid fa-award"
style={{ marginRight: "2px" }}
aria-hidden="true"
></i>
Certificate
</div>
</DropdownMenu.Item>
)}
{isSignYourself && (
<DropdownMenu.Item
@@ -289,7 +196,9 @@ function Header({
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={handleToPrint}
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading)
}
>
<div
style={{
@@ -309,24 +218,16 @@ function Header({
</DropdownMenu.Portal>
</DropdownMenu.Root>
) : (
<div style={{ display: "flex", justifyContent: "space-around" }}>
<div className="flex justify-around items-center">
{/* current signer is checking user send request and check status of pdf sign than if current
user exist than show finish button else no
*/}
{currentSigner && (
<>
<div className="flex items-center" data-tut="reactourFifth">
{decline && (
<div
onClick={() => {
handleDeclinePdfAlert();
}}
style={{
color: "red",
border: "none",
fontWeight: "650",
fontSize: "16px",
marginRight: "10px"
}}
onClick={() => handleDeclinePdfAlert()}
className="text-[red] border-none font-[650] text-[14px] mr-2"
>
Decline
</div>
@@ -338,12 +239,8 @@ function Header({
alertSendEmail();
}
}}
style={{
color: isMailSend ? "gray" : themeColor,
border: "none",
fontWeight: "650",
fontSize: "16px"
}}
className="border-none font-[650] text-[14px]"
style={{ color: isMailSend ? "gray" : themeColor }}
data-tut={dataTut4}
>
{completeBtnTitle ? completeBtnTitle : "Send"}
@@ -358,17 +255,49 @@ function Header({
embedWidgetsData();
}
}}
style={{
color: themeColor,
border: "none",
fontWeight: "650",
fontSize: "16px"
}}
style={{ color: themeColor }}
className="border-none font-[650] text-[14px]"
>
Finish
</div>
)}
</>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<div
className="border-none font-[650] text-[18px] px-2 ml-[4px]"
style={{ color: themeColor }}
>
<i
className="fa fa-ellipsis-v"
aria-hidden="true"
></i>
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="bg-white shadow-md rounded px-2 py-1"
sideOffset={5}
>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i
className="fa fa-arrow-down mr-[5px]"
aria-hidden="true"
></i>
<span className="font-[500]">Download</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</div>
)}
</div>
)}
@@ -433,38 +362,32 @@ function Header({
{completeBtnTitle
? completeBtnTitle
: isMailSend
? "Sent"
: "Send"}
? "Sent"
: "Send"}
</button>
</div>
</>
) : isPdfRequestFiles ? (
alreadySign ? (
<div style={{ display: "flex", flexDirection: "row" }}>
<div className="flex flex-row">
{isCompleted && (
<>
{pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
<button
type="button"
onClick={() => handleDownloadCertificate()}
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>
) : (
<GenerateCertificate />
)}
</>
<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={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]"
>
@@ -475,14 +398,16 @@ 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>
</button>
</div>
) : (
<div className="flex">
<div className="flex" data-tut="reactourFifth">
<button
onClick={() => navigate(-1)}
type="button"
@@ -493,53 +418,77 @@ function Header({
{currentSigner && (
<>
<button
style={{ background: "#de4337" }}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() => {
handleDeclinePdfAlert();
}}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#de4337]"
onClick={() => handleDeclinePdfAlert()}
>
Decline
</button>
<button
style={{
background: "#188ae2"
}}
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
onClick={() => {
embedWidgetsData();
}}
onClick={() => embedWidgetsData()}
>
Finish
</button>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<div
className="border-none font-[650] text-[16px] py-[3px] px-[11px] shadow rounded bg-white"
style={{ color: themeColor }}
>
<i
className="fa fa-ellipsis-v"
aria-hidden="true"
></i>
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="bg-white shadow-md rounded px-2 py-1"
sideOffset={5}
>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i
className="fa fa-arrow-down mr-[5px]"
aria-hidden="true"
></i>
<span className="font-[500]">Download</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</>
)}
</div>
)
) : isCompleted ? (
<div style={{ display: "flex", flexDirection: "row" }}>
<div className="flex flex-row">
{isCompleted && (
<>
{pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
<button
type="button"
onClick={() => handleDownloadCertificate()}
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>
) : (
<GenerateCertificate />
)}
</>
<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={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]"
>
@@ -549,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>
@@ -575,9 +526,6 @@ function Header({
Back
</button>
<button
style={{
background: "#188ae2"
}}
type="button"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
onClick={() => {
@@ -592,6 +540,34 @@ 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={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>
</div>
);
}
@@ -38,9 +38,9 @@ function PlaceholderType(props) {
"December"
];
const validateExpression = (regexValidation) => {
if (textValue) {
if (textValue && regexValidation) {
let regexObject = regexValidation;
if (props.pos?.options.validation.type === "regex") {
if (props.pos?.options?.validation?.type === "regex") {
regexObject = RegexParser(regexValidation);
}
// new RegExp(regexValidation);
@@ -56,7 +56,7 @@ function PlaceholderType(props) {
props.setDraggingEnabled(true);
const validateType = props.pos?.options?.validation?.type;
let regexValidation;
if (validateType) {
if (validateType && validateType !== "text") {
switch (validateType) {
case "email":
regexValidation = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
@@ -66,12 +66,8 @@ function PlaceholderType(props) {
regexValidation = /^[0-9\s]*$/;
validateExpression(regexValidation);
break;
case "text":
regexValidation = /^[a-zA-Z\s]+$/;
validateExpression(regexValidation);
break;
default:
regexValidation = props.pos?.options.validation.pattern;
regexValidation = props.pos?.options?.validation?.pattern || "";
validateExpression(regexValidation);
}
}
@@ -89,11 +85,11 @@ function PlaceholderType(props) {
case "number":
setValidatePlaceholder("12345");
break;
case textInputWidget:
setValidatePlaceholder("enter text");
case "text":
setValidatePlaceholder("please enter text");
break;
default:
setValidatePlaceholder("enter text");
setValidatePlaceholder("please enter value");
}
}
@@ -407,7 +403,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={validatePlaceholder}
placeholder={validatePlaceholder || "please enter value"}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -710,7 +706,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={"email"}
placeholder={"enter email"}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -782,7 +778,7 @@ function PlaceholderType(props) {
case textWidget:
return (
<textarea
placeholder="Enter label"
placeholder="Enter text"
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import RSC from "react-scrollbars-custom";
import { Document, Page } from "react-pdf";
import { themeColor } from "../../constant/const";
@@ -9,13 +9,54 @@ function RenderAllPdfPage({
setAllPages,
setPageNumber,
setSignBtnPosition,
pageNumber
pageNumber,
signerPos,
signerObjectId
}) {
const [signPageNumber, setSignPageNumber] = useState([]);
const [bookmarkColor, setBookmarkColor] = useState("");
//set all number of pages after load pdf
function onDocumentLoad({ numPages }) {
setAllPages(numPages);
//check if signerPos array exist then save page number exist in signerPos array to show bookmark icon
if (signerPos) {
const checkUser = signerPos.filter(
(data) => data.signerObjId === signerObjectId
);
setBookmarkColor(checkUser[0]?.blockColor);
let pageNumberArr = [];
if (checkUser?.length > 0) {
checkUser[0]?.placeHolder?.map((data) => {
pageNumberArr.push(data?.pageNumber);
});
setSignPageNumber(pageNumberArr);
}
}
}
//'function `addSignatureBookmark` is used to display the page where the user's signature is located.
const addSignatureBookmark = (index) => {
const ispageNumber = signPageNumber.includes(index + 1);
return (
ispageNumber && (
<div
style={{
position: "absolute",
zIndex: 2,
top: -12,
right: -7,
transform: "translate(50% -50%)"
}}
>
<i
style={{ color: bookmarkColor || "red" }}
className="fa-solid fa-bookmark"
></i>
</div>
)
);
};
return (
<div className="showPages">
<div
@@ -66,7 +107,8 @@ function RenderAllPdfPage({
display: "flex",
justifyContent: "center",
alignItems: "center",
contain: "content"
position: "relative"
}}
onClick={() => {
setPageNumber(index + 1);
@@ -75,15 +117,25 @@ function RenderAllPdfPage({
}
}}
>
<Page
key={`page_${index + 1}`}
pageNumber={index + 1}
width={100}
height={100}
scale={1}
renderAnnotationLayer={false}
renderTextLayer={false}
/>
{signerPos && addSignatureBookmark(index)}
<div
style={{
position: "relative",
zIndex: 1,
overflow: "hidden"
}}
>
<Page
key={`page_${index + 1}`}
pageNumber={index + 1}
width={100}
height={100}
scale={1}
renderAnnotationLayer={false}
renderTextLayer={false}
/>
</div>
</div>
))}
</Document>
+25 -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 (
@@ -334,6 +334,7 @@ function RenderPdf({
{successEmail && <Alert type={"success"}>Email sent successfully!</Alert>}
{isMobile && scale ? (
<div
data-tut="reactourForth"
style={{
border: "0.1px solid #ebe8e8",
marginTop: isGuestSigner && "30px"
@@ -517,6 +518,7 @@ function RenderPdf({
noScrollX={pdfNewWidth < pdfOriginalWidth ? false : true}
>
<div
data-tut="reactourForth"
style={{
border: "0.1px solid #ebe8e8",
width: pdfOriginalWidth
+7 -8
View File
@@ -214,8 +214,8 @@ function SignPad({
const fontfamily = fontStyle
? fontStyle
: fontSelect
? fontSelect
: "Fasthand";
? fontSelect
: "Fasthand";
//creating span for getting text content width
const span = document.createElement("span");
@@ -267,10 +267,10 @@ function SignPad({
key === 0 && penColor === "blue"
? "2px solid blue"
: key === 1 && penColor === "red"
? "2px solid red"
: key === 2 && penColor === "black"
? "2px solid black"
: "2px solid white"
? "2px solid red"
: key === 2 && penColor === "black"
? "2px solid black"
: "2px solid white"
}}
onClick={() => {
props?.convertToImg &&
@@ -536,7 +536,6 @@ function SignPad({
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
@@ -553,7 +552,7 @@ function SignPad({
type="file"
onChange={onImageChange}
className="filetype"
accept="image/*"
accept="image/png,image/jpeg"
ref={imageRef}
hidden
/>
@@ -0,0 +1,85 @@
import React from "react";
import { rejectBtn, submitBtn, themeColor } from "../../constant/const";
function VerifyEmail(props) {
return (
<div className="bg-black bg-opacity-[75%] absolute z-[999] flex flex-col items-center justify-center w-full h-full rounded">
<div className="bg-white rounded outline-none md:w-[40%] w-[80%]">
<div
style={{ backgroundColor: themeColor }}
className=" text-white p-[10px] rounded-t"
>
OTP verification
</div>
{props.isVerifyModal ? (
<form
onSubmit={(e) => {
props.setIsVerifyModal(false);
props.handleVerifyEmail(e);
}}
>
<div className="px-6 py-3">
<label className="mb-2">Enter OTP</label>
<input
required
type="tel"
pattern="[0-9]{4}"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
placeholder="Enter OTP received over email"
value={props.otp}
onChange={(e) => props.setOtp(e.target.value)}
/>
</div>
<hr />
<div className="px-6 my-3">
<button type="submit" className={submitBtn}>
Verify
</button>
<button
className={`${rejectBtn} ml-2`}
onClick={(e) => props.handleResend(e)}
>
Resend
</button>
</div>
</form>
) : props.otpLoader ? (
<div
style={{
height: "150px",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
<div
style={{
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37"
></div>
</div>
) : (
<div className="px-6 py-3">
<p className="mb-2">Please verify your email !</p>
<hr />
<div className="px-0 mt-3">
<button
className={submitBtn}
type="submit"
onClick={() => {
props.handleVerifyBtn();
}}
>
Send OTP
</button>
</div>
</div>
)}
</div>
</div>
);
}
export default VerifyEmail;
@@ -6,6 +6,7 @@ import { textInputWidget } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
import Tooltip from "../../primitives/Tooltip";
const WidgetNameModal = (props) => {
const [formdata, setFormdata] = useState({
@@ -13,7 +14,7 @@ const WidgetNameModal = (props) => {
defaultValue: "",
status: "required",
hint: "",
textvalidate: "text"
textvalidate: ""
});
const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"];
@@ -48,12 +49,16 @@ const WidgetNameModal = (props) => {
defaultValue: "",
status: "required",
hint: "",
textvalidate: "text"
textvalidate: ""
});
}
};
const handleChange = (e) => {
setFormdata({ ...formdata, [e.target.name]: e.target.value });
if (e) {
setFormdata({ ...formdata, [e.target.name]: e.target.value });
} else {
setFormdata({ ...formdata, textvalidate: "" });
}
};
const handledefaultChange = (e) => {
@@ -82,7 +87,7 @@ const WidgetNameModal = (props) => {
function handleBlurRegex() {
if (!formdata.textvalidate) {
setFormdata({ ...formdata, textvalidate: "text" });
setFormdata({ ...formdata, textvalidate: "" });
} else {
if (formdata.defaultValue) {
const regexObject = RegexParser(
@@ -136,16 +141,21 @@ const WidgetNameModal = (props) => {
props.widgetName === textInputWidget) && (
<>
<div className="form-section">
<label
htmlFor="textvalidate"
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{ fontSize: 13 }}
>
Validation
</label>
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
<div className="flex items-center gap-1">
<label
htmlFor="textvalidate"
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{ fontSize: 13 }}
>
Validation
</label>
<Tooltip
url={"https://www.w3schools.com/jsref/jsref_obj_regexp.asp"}
/>
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
</div>
<div
className={
!props.isSubscribe && isEnableSubscription && "disabled"
@@ -161,7 +171,7 @@ const WidgetNameModal = (props) => {
<input
className="relative z-20 w-[87%] md:w-[92%] p-2.5 border-[1px] border-[#d1d5db] rounded-l-md outline-none text-xs"
name="textvalidate"
placeholder="Enter custom expression"
placeholder="Enter custom regular expression"
value={formdata.textvalidate}
onChange={(e) => handleChange(e)}
// onBlur={() => handleBlurRegex()}
@@ -173,7 +183,10 @@ const WidgetNameModal = (props) => {
onChange={(e) => handleChange(e)}
onBlur={() => handleBlurRegex()}
>
<option disabled style={{ fontSize: "13px" }}>
<option
disabled={formdata?.textvalidate}
style={{ fontSize: "13px" }}
>
Select...
</option>
{inputOpt.map((data, ind) => {
@@ -25,9 +25,11 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
if (parentFolderId) {
FolderQuery.equalTo("Folder", folderPtr);
FolderQuery.equalTo("Type", "Folder");
FolderQuery.notEqualTo("IsArchive", true);
} else {
FolderQuery.doesNotExist("Folder");
FolderQuery.equalTo("Type", "Folder");
FolderQuery.notEqualTo("IsArchive", true);
}
const res = await FolderQuery.find();
@@ -49,6 +51,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
const exsitQuery = new Parse.Query(folderCls);
exsitQuery.equalTo("Name", name);
exsitQuery.equalTo("Type", "Folder");
exsitQuery.notEqualTo("IsArchive", true);
if (parentFolderId) {
exsitQuery.equalTo("Folder", folderPtr);
}
@@ -25,9 +25,11 @@ const FolderModal = (props) => {
if (folderPtr) {
FolderQuery.equalTo("Folder", folderPtr);
FolderQuery.equalTo("Type", "Folder");
FolderQuery.notEqualTo("IsArchive", true);
} else {
FolderQuery.doesNotExist("Folder");
FolderQuery.equalTo("Type", "Folder");
FolderQuery.notEqualTo("IsArchive", true);
}
const res = await FolderQuery.find();
@@ -45,9 +45,11 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
if (folderPtr) {
FolderQuery.equalTo("Folder", folderPtr);
FolderQuery.equalTo("Type", "Folder");
FolderQuery.notEqualTo("IsArchive", true);
} else {
FolderQuery.doesNotExist("Folder");
FolderQuery.equalTo("Type", "Folder");
FolderQuery.notEqualTo("IsArchive", true);
}
const res = await FolderQuery.find();
@@ -169,32 +171,40 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
{required && <span className="text-red-500 text-[13px]">*</span>}
</label>
</div>
<div className="relative rounded px-[20px] py-[20px] bg-white border border-gray-200 shadow flex max-w-sm gap-8 items-center">
<div>
<i
className="far fa-folder-open text-[40px] text-[#33bbff]"
style={{ fontSize: "40px" }}
aria-hidden="true"
></i>
</div>
<div className="font-semibold ">
<div className="flex items-center gap-2">
<p>
{selectFolder && selectFolder.Name
? selectFolder.Name
: "OpenSign™ Drive"}
</p>
<div className="text-black text-sm" onClick={() => SetIsOpen(true)}>
<i
className="fa fa-pencil"
title="Select Folder"
aria-hidden="true"
></i>
</div>
<div className="relative max-w-sm">
<div
onClick={() => SetIsOpen(true)}
className=" cursor-pointer rounded px-[20px] py-[20px] bg-white border border-gray-200 shadow flex max-w-sm gap-8 items-center"
>
<div>
<i
className="far fa-folder-open text-[40px] text-[#33bbff]"
style={{ fontSize: "40px" }}
aria-hidden="true"
></i>
</div>
<div className="font-semibold ">
<div className="flex items-center gap-2">
<p>
{selectFolder && selectFolder.Name
? selectFolder.Name
: "OpenSign™ Drive"}
</p>
<div
className="text-black text-sm"
// onClick={() => SetIsOpen(true)}
>
<i
className="fa fa-pencil cursor-pointer"
title="Select Folder"
aria-hidden="true"
></i>
</div>
</div>
<p className="text-[10px] text-gray-400">
{selectFolder && selectFolder.Name ? `(${folderPath})` : ""}
</p>
</div>
<p className="text-[10px] text-gray-400">
{selectFolder && selectFolder.Name ? `(${folderPath})` : ""}
</p>
</div>
<div className="absolute top-2 right-1 cursor-pointer">
<Tooltip
@@ -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"
+328 -60
View File
@@ -4,22 +4,34 @@ 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(extUserId) {
export async function fetchSubscription(
extUserId,
contactObjId,
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) {
const jsonSender = JSON.parse(extClass);
extUser = jsonSender[0].objectId;
if (extClass && extClass.length > 0) {
extUser = extClass[0].objectId;
} else {
extUser = extUserId;
}
@@ -30,11 +42,19 @@ export async function fetchSubscription(extUserId) {
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const params = { extUserId: extUser };
const params = isGuestSign
? { contactId: contactObjId }
: { extUserId: extUser };
const tenatRes = await axios.post(url, params, { headers: headers });
const plan = tenatRes.data?.result?.result?.PlanCode;
const billingDate = tenatRes.data?.result?.result?.Next_billing_date?.iso;
return { plan, billingDate };
let plan, status, billingDate;
if (isGuestSign) {
plan = tenatRes.data?.result?.result?.plan;
status = tenatRes.data?.result?.result?.isSubscribed;
} else {
plan = tenatRes.data?.result?.result?.PlanCode;
billingDate = tenatRes.data?.result?.result?.Next_billing_date?.iso;
}
return { plan, billingDate, status };
} catch (err) {
console.log("Err in fetch subscription", err);
return { plan: "", billingDate: "" };
@@ -159,11 +179,10 @@ export const contractUsers = async (email) => {
.then((Listdata) => {
const json = Listdata.data;
let data = [];
if (json && json.result) {
data.push(json.result);
return data;
}
return data;
})
.catch((err) => {
console.log("Err ", err);
@@ -323,15 +342,15 @@ export const addWidgetOptions = (type) => {
options: { isReadOnly: false, isHideLabel: false }
};
case textInputWidget:
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
return { ...defaultOpt };
case "initials":
return defaultOpt;
case "name":
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
return { ...defaultOpt };
case "company":
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
return { ...defaultOpt };
case "job title":
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
return { ...defaultOpt };
case "date":
return {
...defaultOpt,
@@ -528,53 +547,94 @@ export const signPdfFun = async (
signerObjectId,
setIsAlert,
objectId,
isSubscribed
isSubscribed,
activeMailAdapter,
xyPosition
) => {
let singleSign,
isCustomCompletionMail = false;
//get tenant details
const tenantDetails = await getTenantDetails(objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert("User does not exist");
} else {
if (
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject &&
(!isEnableSubscription || isSubscribed)
) {
isCustomCompletionMail = true;
}
}
singleSign = {
pdfFile: base64Url,
docId: documentId,
userId: signerObjectId,
isCustomCompletionMail: isCustomCompletionMail
};
const response = await axios
.post(`${localStorage.getItem("baseUrl")}functions/signPdf`, singleSign, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
try {
//get tenant details
const tenantDetails = await getTenantDetails(objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert("User does not exist");
} else {
if (
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject &&
(!isEnableSubscription || isSubscribed)
) {
isCustomCompletionMail = true;
}
})
.then((Listdata) => {
const json = Listdata.data;
const res = json.result;
return res;
})
.catch((err) => {
console.log("Err ", err);
setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
});
}
return response;
// below for loop is used to get first signature of user to send if to signpdf
// for adding it in completion certificate
let getSignature;
for (let item of xyPosition) {
if (!getSignature) {
const typeExist = item.pos.some((data) => data?.type);
if (typeExist) {
getSignature = item.pos.find((data) => data?.type === "signature");
} else {
getSignature = item.pos.find((data) => !data.isStamp);
}
}
}
let base64Sign = getSignature.SignUrl;
//check https type signature (default signature exist) then convert in base64
const isUrl = base64Sign.includes("https");
if (isUrl) {
try {
base64Sign = await fetchImageBase64(base64Sign);
} catch (e) {
console.log("error", e);
}
}
//change image width and height to 104/44 in png base64
const getNewse64 = await changeImageWH(base64Sign);
//remove suffiix of base64
const suffixbase64 = getNewse64 && getNewse64.split(",").pop();
singleSign = {
mailProvider: activeMailAdapter,
pdfFile: base64Url,
docId: documentId,
userId: signerObjectId,
isCustomCompletionMail: isCustomCompletionMail,
signature: suffixbase64
};
const response = await axios
.post(`${localStorage.getItem("baseUrl")}functions/signPdf`, singleSign, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
// sessionToken: localStorage.getItem("accesstoken")
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
})
.then((Listdata) => {
const json = Listdata.data;
const res = json.result;
return res;
})
.catch((err) => {
console.log("Err ", err);
setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
});
return response;
} catch (e) {
setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
}
};
export const randomId = () => {
@@ -609,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,
@@ -623,7 +684,9 @@ export const createDocument = async (template, placeholders, signerData) => {
objectId: Doc.CreatedBy.objectId
},
Signers: signers,
SendinOrder: Doc?.SendinOrder || false
SendinOrder: Doc?.SendinOrder || false,
AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5)
};
try {
@@ -1111,7 +1174,6 @@ export const onImageSelect = (event, setImgWH, setImage) => {
const imageType = event.target.files[0].type;
const reader = new FileReader();
reader.readAsDataURL(event.target.files[0]);
reader.onloadend = function (e) {
let width, height;
const image = new Image();
@@ -1139,6 +1201,50 @@ export const onImageSelect = (event, setImgWH, setImage) => {
};
};
//convert https url to base64
export const fetchImageBase64 = async (imageUrl) => {
try {
const response = await fetch(imageUrl);
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
const base64data = reader.result;
resolve(base64data);
};
reader.onerror = (error) => {
reject(error);
};
});
} catch (error) {
throw new Error("Error converting URL to base64:", error);
}
};
//function for select image and upload image
export const changeImageWH = async (base64Image) => {
const newWidth = 100;
const newHeight = 40;
return new Promise((resolve, reject) => {
const img = new Image();
img.src = base64Image;
img.onload = async () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = newWidth;
canvas.height = newHeight;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 0, 0, newWidth, newHeight);
const resizedBase64 = canvas.toDataURL("image/png", 1);
resolve(resizedBase64);
};
img.onerror = (error) => {
reject(error);
};
});
};
//function for embed multiple signature using pdf-lib
export const multiSignEmbed = async (
pngUrl,
@@ -1917,3 +2023,165 @@ export const convertPdfArrayBuffer = async (url) => {
return "Error";
}
};
//`handleSendOTP` function is used to send otp on user's email using `SendOTPMailV1` cloud function
export const handleSendOTP = async (email) => {
try {
let url = `${localStorage.getItem("baseUrl")}functions/SendOTPMailV1`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
};
const body = { email: email };
await axios.post(url, body, { headers: headers });
} catch (error) {
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);
}
}
+10 -7
View File
@@ -1,6 +1,10 @@
import axios from "axios";
const parseAppId = localStorage.getItem("parseAppId");
const serverUrl = localStorage.getItem("baseUrl");
const parseAppId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID
: "opensign";
const serverUrl = process.env.REACT_APP_SERVERURL
? process.env.REACT_APP_SERVERURL
: window.location.origin + "/api/app";
export const SaveFileSize = async (size, imageUrl, tenantId) => {
//checking server url and save file's size
@@ -12,7 +16,7 @@ export const SaveFileSize = async (size, imageUrl, tenantId) => {
const _tenantPtr = JSON.stringify(tenantPtr);
try {
const res = await axios.get(
`${serverUrl}classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
`${serverUrl}/classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
{
headers: {
"Content-Type": "application/json",
@@ -30,7 +34,7 @@ export const SaveFileSize = async (size, imageUrl, tenantId) => {
: size
};
await axios.put(
`${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`,
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
data,
{
headers: {
@@ -41,7 +45,7 @@ export const SaveFileSize = async (size, imageUrl, tenantId) => {
);
} else {
data = { usedStorage: size, PartnersTenant: tenantPtr };
await axios.post(`${serverUrl}classes/partners_TenantCredits`, data, {
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
@@ -64,7 +68,7 @@ const saveDataFile = async (size, imageUrl, tenantPtr) => {
// console.log("data save",file, data)
try {
await axios.post(`${serverUrl}classes/partners_DataFiles`, data, {
await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
@@ -74,4 +78,3 @@ const saveDataFile = async (size, imageUrl, tenantPtr) => {
console.log("err in save usage ", err);
}
};
+29 -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":
@@ -85,6 +85,14 @@ export default function reportJson(id) {
btnIcon: "fa-solid fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "1630",
btnLabel: "Resend",
hoverLabel: "Resend",
btnIcon: "fa-solid fa-envelope",
redirectUrl: "",
action: "resend"
},
{
btnId: "1688",
btnLabel: "Revoke",
@@ -223,6 +231,14 @@ export default function reportJson(id) {
btnIcon: "fa-solid fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "1631",
btnLabel: "Resend",
hoverLabel: "Resend",
btnIcon: "fa-solid fa-envelope",
redirectUrl: "",
action: "resend"
},
{
btnId: "6788",
btnLabel: "Revoke",
@@ -310,7 +326,7 @@ export default function reportJson(id) {
case "6TeaPr321t":
return {
reportName: "Templates",
heading: head,
heading: templateReport,
actions: [
{
btnId: "2234",
@@ -325,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",
+3 -2
View File
@@ -43,7 +43,7 @@ const plans = [
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly",
target: "_self",
benefits: [
"Everything in OpenSign™ professional",
"Everything in OpenSign™ free",
"Field validations",
"Regular expression validations",
"Organize docs in OpenSign™ Drive",
@@ -52,7 +52,8 @@ const plans = [
"API Access",
"100 API signatures included",
"DocumentId removal from signed docs",
"Custom email templates(coming soon)"
"Custom email templates",
"Auto reminders"
]
},
{
+23 -18
View File
@@ -28,29 +28,34 @@ const HomeLayout = () => {
const [tourConfigs, setTourConfigs] = useState([]);
const [, setCookie] = useCookies(["accesstoken", "main_Domain"]);
const tenantId = localStorage.getItem("TenantId");
useEffect(() => {
(async () => {
try {
// Use the session token to validate the user
const userQuery = new Parse.Query(Parse.User);
const user = await userQuery.get(Parse.User.current().id, {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
checkIsSubscribed();
} else {
if (!tenantId) {
setIsUserValid(false);
} else {
(async () => {
try {
// Use the session token to validate the user
const userQuery = new Parse.Query(Parse.User);
const user = await userQuery.get(Parse.User.current().id, {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
checkIsSubscribed();
} else {
setIsUserValid(false);
}
} catch (error) {
// Session token is invalid or there was an error
setIsUserValid(false);
}
} catch (error) {
// Session token is invalid or there was an error
setIsUserValid(false);
}
})();
saveCookies();
})();
saveCookies();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [tenantId]);
//function to use save data in cookies storage
const saveCookies = () => {
const main_Domain = window.location.origin;
+1 -1
View File
@@ -24,7 +24,7 @@ const Dashboard = () => {
getDashboard(localStorage.getItem("PageLanding"));
}
} else {
navigate("/", { replace: true, state: { from: location } });
navigate("/", { replace: true, state: { from: "" } });
}
// eslint-disable-next-line
}, [id]);
+1 -1
View File
@@ -108,7 +108,7 @@ function ForgotPassword() {
</button>
<NavLink
to="/"
className="rounded-sm bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] px-4 py-3 text-xs font-semibold shadow uppercase"
className="rounded-sm bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] px-4 py-3 text-xs font-semibold shadow uppercase cursor-pointer"
>
Login
</NavLink>
+675 -203
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { formJson } from "../json/FormJson";
import AddUser from "../components/AddUser";
@@ -11,7 +11,14 @@ import SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
import { SaveFileSize } from "../constant/saveFileSize";
import { getFileName } from "../constant/Utils";
import { checkIsSubscribed, getFileName, toDataUrl } from "../constant/Utils";
import { PDFDocument } from "pdf-lib";
import axios from "axios";
import { isEnableSubscription, submitBtn } from "../constant/const";
import ModalUi from "../primitives/ModalUi";
import { Tooltip } from "react-tooltip";
import Upgrade from "../primitives/Upgrade";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
// `Form` render all type of Form on this basis of their provided in path
function Form() {
@@ -31,6 +38,8 @@ function Form() {
const Forms = (props) => {
const maxFileSize = 20;
const abortController = new AbortController();
const inputFileRef = useRef(null);
const navigate = useNavigate();
const [signers, setSigners] = useState([]);
const [folder, setFolder] = useState({ ObjectId: "", Name: "" });
@@ -39,37 +48,248 @@ const Forms = (props) => {
Description: "",
Note: "",
TimeToCompleteDays: 15,
SendinOrder: "false"
SendinOrder: "false",
password: "",
file: "",
remindOnceInEvery: 5,
autoreminder: false
});
const [fileupload, setFileUpload] = useState([]);
const [fileupload, setFileUpload] = useState("");
const [fileload, setfileload] = useState(false);
const [percentage, setpercentage] = useState(0);
const [isReset, setIsReset] = useState(false);
const [isAlert, setIsAlert] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [isErr, setIsErr] = useState("");
const [isPassword, setIsPassword] = useState(false);
const [isDecrypting, setIsDecrypting] = useState(false);
const [isCorrectPass, setIsCorrectPass] = useState(true);
const [isSubscribe, setIsSubscribe] = useState(false);
const handleStrInput = (e) => {
setIsCorrectPass(true);
setFormData({ ...formData, [e.target.name]: e.target.value });
};
useEffect(() => {
handleReset();
return () => abortController.abort();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.title]);
const handleFileInput = (e) => {
useEffect(() => {
fetchSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const fetchSubscription = async () => {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
};
function getFileAsArrayBuffer(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
resolve(e.target.result);
};
reader.onerror = (e) => {
reject(e.target.error);
};
reader.readAsArrayBuffer(file);
});
}
const handleFileInput = async (e) => {
setpercentage(0);
try {
let files = e.target.files;
setFormData((prev) => ({ ...prev, file: e.target.files[0] }));
if (typeof files[0] !== "undefined") {
const mb = Math.round(files[0].size / Math.pow(1024, 2));
if (mb > maxFileSize) {
alert(
`The selected file size is too large. Please select a file less than ${maxFileSize} MB`
);
setFileUpload([]);
setFileUpload("");
e.target.value = "";
return;
} else {
handleFileUpload(files[0]);
if (files?.[0]?.type === "application/pdf") {
try {
const res = await getFileAsArrayBuffer(files[0]);
await PDFDocument.load(res);
handleFileUpload(files[0]);
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try {
await Parse.Cloud.run("encryptedpdf", {
email: Parse.User.current().getEmail()
});
} catch (err) {
console.log("err in sending posthog encryptedpdf", err);
}
// console.log("err ", err);
try {
setIsDecrypting(true);
const fileName = files?.[0].name;
const size = files?.[0].size;
const name = sanitizeFileName(fileName);
const url = "https://ai.nxglabs.in/decryptpdf"; //"https://ai.nxglabs.in/decryptpdf"; //
let formData = new FormData();
formData.append("file", files[0]);
formData.append("password", "");
const config = {
headers: {
"content-type": "multipart/form-data"
// sessiontoken: Parse.User.current().getSessionToken()
},
responseType: "blob"
};
const response = await axios.post(url, formData, config);
const pdfBlob = new Blob([response.data], {
type: "application/pdf"
});
const pdfFile = new File([pdfBlob], name, {
type: "application/pdf"
});
setIsDecrypting(false);
setfileload(true);
// Upload the file to Parse Server
const parseFile = new Parse.File(
name,
pdfFile,
"application/pdf"
);
await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round(
(loaded * 100) / total
);
setpercentage(percentCompleted);
}
}
});
// Retrieve the URL of the uploaded file
if (parseFile.url()) {
setFileUpload(parseFile.url());
setfileload(false);
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, parseFile.url(), tenantId);
return parseFile.url();
}
} catch (err) {
setfileload(false);
setpercentage(0);
if (err?.response?.status === 401) {
setIsPassword(true);
} else {
console.log("Error uploading file: ", err?.response);
setIsDecrypting(false);
e.target.value = "";
}
}
} else {
console.log("err ", err);
setFileUpload("");
e.target.value = "";
}
}
} else {
const isImage = files?.[0]?.type.includes("image/");
if (isImage) {
const image = await toDataUrl(files[0]);
const pdfDoc = await PDFDocument.create();
let embedImg;
if (files?.[0]?.type === "image/png") {
embedImg = await pdfDoc.embedPng(image);
} else {
embedImg = await pdfDoc.embedJpg(image);
}
// Get image dimensions
const imageWidth = embedImg.width;
const imageHeight = embedImg.height;
const page = pdfDoc.addPage([imageWidth, imageHeight]);
page.drawImage(embedImg, {
x: 0,
y: 0,
width: imageWidth,
height: imageHeight
});
const getFile = await pdfDoc.save({
useObjectStreams: false
});
setfileload(true);
const fileName = files[0].name;
const size = files[0].size;
const name = sanitizeFileName(fileName);
const pdfName = `${name?.split(".")[0]}.pdf`;
const parseFile = new Parse.File(
pdfName,
[...getFile],
"application/pdf"
);
try {
const response = await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round(
(loaded * 100) / total
);
setpercentage(percentCompleted);
}
}
});
// The response object will contain information about the uploaded file
// You can access the URL of the uploaded file using response.url()
setFileUpload(response.url());
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
} catch (error) {
e.target.value = "";
setfileload(false);
setpercentage(0);
console.error("Error uploading file:", error);
}
} else {
if (isEnableSubscription) {
try {
setfileload(true);
const url = "https://ai.nxglabs.in/docxtopdf";
let formData = new FormData();
formData.append("file", files[0]);
const config = {
headers: {
"content-type": "multipart/form-data",
sessiontoken: Parse.User.current().getSessionToken()
},
signal: abortController.signal
};
const res = await axios.post(url, formData, config);
if (res.data) {
setFileUpload(res.data.url);
setfileload(false);
}
} catch (err) {
e.target.value = "";
setfileload(false);
setpercentage(0);
console.log("err in libreconverter ", err);
}
}
}
}
}
} else {
alert("Please select file.");
@@ -162,64 +382,69 @@ const Forms = (props) => {
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmit(true);
try {
const currentUser = Parse.User.current();
const object = new Parse.Object(props.Cls);
object.set("Name", formData?.Name);
object.set("Description", formData?.Description);
object.set("Note", formData?.Note);
if (props.title === "Request Signatures") {
object.set(
"TimeToCompleteDays",
parseInt(formData?.TimeToCompleteDays)
);
}
if (props.title !== "Sign Yourself") {
const isChecked = formData.SendinOrder === "false" ? false : true;
object.set("SendinOrder", isChecked);
}
object.set("URL", fileupload);
object.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
if (folder && folder.ObjectId) {
object.set("Folder", {
if (fileupload) {
setIsSubmit(true);
try {
const currentUser = Parse.User.current();
const object = new Parse.Object(props.Cls);
object.set("Name", formData?.Name);
object.set("Description", formData?.Description);
object.set("Note", formData?.Note);
if (props.title === "Request Signatures") {
object.set(
"TimeToCompleteDays",
parseInt(formData?.TimeToCompleteDays)
);
}
if (props.title !== "Sign Yourself") {
const isChecked = formData.SendinOrder === "false" ? false : true;
object.set("SendinOrder", isChecked);
object.set("AutomaticReminders", formData.autoreminder);
object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery));
}
object.set("URL", fileupload);
object.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
if (folder && folder.ObjectId) {
object.set("Folder", {
__type: "Pointer",
className: props.Cls,
objectId: folder.ObjectId
});
}
if (signers && signers.length > 0) {
object.set("Signers", signers);
}
const ExtCls = JSON.parse(localStorage.getItem("Extand_Class"));
object.set("ExtUserPtr", {
__type: "Pointer",
className: props.Cls,
objectId: folder.ObjectId
className: "contracts_Users",
objectId: ExtCls[0].objectId
});
const res = await object.save();
if (res) {
setSigners([]);
setFolder({ ObjectId: "", Name: "" });
setFormData({
Name: "",
Description: "",
Note: ""
});
setFileUpload("");
setpercentage(0);
navigate(`/${props?.redirectRoute}/${res.id}`);
}
} catch (err) {
console.log("err ", err);
setIsErr(true);
} finally {
setIsAlert(true);
setTimeout(() => {
setIsAlert(false);
}, 1000);
setIsSubmit(false);
}
if (signers && signers.length > 0) {
object.set("Signers", signers);
}
const ExtCls = JSON.parse(localStorage.getItem("Extand_Class"));
object.set("ExtUserPtr", {
__type: "Pointer",
className: "contracts_Users",
objectId: ExtCls[0].objectId
});
const res = await object.save();
if (res) {
setSigners([]);
setFolder({ ObjectId: "", Name: "" });
setFormData({
Name: "",
Description: "",
Note: ""
});
setFileUpload([]);
setpercentage(0);
navigate(`/${props?.redirectRoute}/${res.id}`);
}
} catch (err) {
console.log("err ", err);
setIsErr(true);
} finally {
setIsAlert(true);
setTimeout(() => {
setIsAlert(false);
}, 1000);
setIsSubmit(false);
} else {
alert("Please wait until the PDF has been uploading.");
}
};
@@ -249,13 +474,92 @@ const Forms = (props) => {
? "Note to myself"
: "Please review and sign this document",
TimeToCompleteDays: 15,
SendinOrder: "true"
SendinOrder: "true",
password: "",
file: "",
remindOnceInEvery: 5,
autoreminder: false
});
setFileUpload([]);
setFileUpload("");
setpercentage(0);
setTimeout(() => setIsReset(false), 50);
};
const handlePasswordSubmit = async (e) => {
e.preventDefault();
setIsPassword(false);
setfileload(true);
try {
const fileName = formData?.file?.name;
const size = formData?.file?.size;
const name = sanitizeFileName(fileName);
const url = "https://ai.nxglabs.in/decryptpdf"; //
let Data = new FormData();
Data.append("file", formData?.file);
Data.append("password", formData.password);
const config = {
headers: {
"content-type": "multipart/form-data"
// sessiontoken: Parse.User.current().getSessionToken()
},
responseType: "blob"
};
const response = await axios.post(url, Data, config);
const pdfBlob = new Blob([response.data], {
type: "application/pdf"
});
const pdfFile = new File([pdfBlob], name, {
type: "application/pdf"
});
setIsDecrypting(false);
// Upload the file to Parse Server
const parseFile = new Parse.File(name, pdfFile, "application/pdf");
await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round((loaded * 100) / total);
setpercentage(percentCompleted);
}
}
});
// Retrieve the URL of the uploaded file
if (parseFile.url()) {
setFormData((prev) => ({ ...prev, password: "" }));
setFileUpload(parseFile.url());
setfileload(false);
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, parseFile.url(), tenantId);
return parseFile.url();
}
} catch (err) {
setfileload(false);
setpercentage(0);
if (err?.response?.status === 401) {
setIsPassword(true);
setIsCorrectPass(false);
} else {
console.log("Error uploading file: ", err?.response);
setFormData((prev) => ({ ...prev, password: "" }));
setIsDecrypting(false);
if (inputFileRef.current) {
inputFileRef.current.value = ""; // Set file input value to empty string
}
}
}
};
const handeCloseModal = () => {
setIsPassword(false);
setFormData((prev) => ({ ...prev, file: "", password: "" }));
setIsDecrypting(false);
setfileload(false);
if (inputFileRef.current) {
inputFileRef.current.value = ""; // Set file input value to empty string
}
};
const handleAutoReminder = () => {
setFormData((prev) => ({ ...prev, autoreminder: !formData.autoreminder }));
};
return (
<div className="shadow-md rounded my-2 p-3 bg-[#ffffff] md:border-[1px] md:border-gray-600/50">
<Title title={props?.title} />
@@ -279,166 +583,334 @@ const Forms = (props) => {
></div>
</div>
) : (
<form onSubmit={handleSubmit}>
<h1 className="text-[20px] font-semibold mb-4">{props?.title}</h1>
{fileload && (
<div className="flex items-center gap-x-2">
<div className="h-2 rounded-full w-[200px] md:w-[400px] bg-gray-200">
<div
className="h-2 rounded-full bg-blue-500"
style={{ width: `${percentage}%` }}
></div>
</div>
<span className="text-black text-sm">{percentage}%</span>
</div>
)}
<div className="text-xs">
<label className="block">
File<span className="text-red-500 text-[13px]">*</span>
</label>
{fileupload.length > 0 ? (
<div className="flex gap-2 justify-center items-center">
<div className="flex justify-between items-center px-2 py-2 w-full font-bold rounded border-[1px] border-[#ccc] text-gray-500 bg-white text-[13px]">
<div className="break-all">
file selected : {getFileName(fileupload)}
</div>
<div
onClick={() => setFileUpload([])}
className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500"
>
<i className="fa-solid fa-xmark"></i>
</div>
</div>
{process.env.REACT_APP_DROPBOX_API_KEY && (
<DropboxChooser
onSuccess={dropboxSuccess}
onCancel={dropboxCancel}
/>
)}
</div>
) : (
<div className="flex gap-2 justify-center items-center">
<>
<ModalUi
isOpen={isPassword}
handleClose={() => handeCloseModal()}
title={"Enter Pdf Password"}
>
<form onSubmit={handlePasswordSubmit}>
<div className="px-6 pt-3 pb-2">
<label className="mb-2 text-xs">Password</label>
<input
type="file"
className="bg-white px-2 py-1.5 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
onChange={(e) => handleFileInput(e)}
accept="application/pdf"
type="text"
name="password"
value={formData.password}
onChange={(e) => handleStrInput(e)}
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
placeholder="Enter pdf password"
required
/>
{process.env.REACT_APP_DROPBOX_API_KEY && (
<DropboxChooser
onSuccess={dropboxSuccess}
onCancel={dropboxCancel}
/>
{!isCorrectPass ? (
<p className="ml-2 text-[11px] text-red-600">
Please provide correct password
</p>
) : (
<p className="ml-2 text-[11px] text-transparent text-red-600">
.
</p>
)}
</div>
<hr />
<div className="px-6 my-3">
<button type="submit" className={submitBtn}>
Submit
</button>
</div>
</form>
</ModalUi>
<form onSubmit={handleSubmit}>
<h1 className="text-[20px] font-semibold mb-4">{props?.title}</h1>
{fileload && (
<div className="flex items-center gap-x-2">
<div className="h-2 rounded-full w-[200px] md:w-[400px] bg-gray-200">
<div
className="h-2 rounded-full bg-blue-500"
style={{ width: `${percentage}%` }}
></div>
</div>
<span className="text-black text-sm">{percentage}%</span>
</div>
)}
</div>
<div className="text-xs mt-2">
<label className="block">
{props.title === "New Template"
? "Template Title"
: "Document Title"}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
name="Name"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
value={formData.Name}
onChange={(e) => handleStrInput(e)}
required
/>
</div>
<div className="text-xs mt-2">
<label className="block">Description</label>
<input
name="Description"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
value={formData.Description}
onChange={(e) => handleStrInput(e)}
/>
</div>
{props.signers && (
<SignersInput onChange={handleSigners} isReset={isReset} required />
)}
<div className="text-xs mt-2">
<label className="block">
Note<span className="text-red-500 text-[13px]">*</span>
</label>
<input
name="Note"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
value={formData.Note}
onChange={(e) => handleStrInput(e)}
required
/>
</div>
<SelectFolder
onSuccess={handleFolder}
folderCls={props.Cls}
isReset={isReset}
/>
{props.title === "Request Signatures" && (
{isDecrypting && (
<div className="flex items-center gap-x-2">
<span className="text-black text-sm">
Decrypting pdf please wait...
</span>
</div>
)}
<div className="text-xs">
<label className="block">
{`File (pdf, png, jpg, jpeg${
isEnableSubscription ? ", docx)" : ")"
}`}
<span className="text-red-500 text-[13px]">*</span>
</label>
{fileupload.length > 0 ? (
<div className="flex gap-2 justify-center items-center">
<div className="flex justify-between items-center px-2 py-2 w-full font-bold rounded border-[1px] border-[#ccc] text-gray-500 bg-white text-[13px]">
<div className="break-all">
file selected : {getFileName(fileupload)}
</div>
<div
onClick={() => setFileUpload("")}
className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500"
>
<i className="fa-solid fa-xmark"></i>
</div>
</div>
{process.env.REACT_APP_DROPBOX_API_KEY && (
<DropboxChooser
onSuccess={dropboxSuccess}
onCancel={dropboxCancel}
/>
)}
</div>
) : (
<div className="flex gap-2 justify-center items-center">
<input
type="file"
className="bg-white px-2 py-1.5 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
onChange={(e) => handleFileInput(e)}
ref={inputFileRef}
accept={
isEnableSubscription
? "application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/png,image/jpeg"
: "application/pdf,image/png,image/jpeg"
}
required
/>
{process.env.REACT_APP_DROPBOX_API_KEY && (
<DropboxChooser
onSuccess={dropboxSuccess}
onCancel={dropboxCancel}
/>
)}
</div>
)}
</div>
<div className="text-xs mt-2">
<label className="block">
Time To Complete (Days)
{props.title === "New Template"
? "Template Title"
: "Document Title"}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
name="TimeToCompleteDays"
name="Name"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
value={formData.TimeToCompleteDays}
value={formData.Name}
onChange={(e) => handleStrInput(e)}
required
/>
</div>
)}
{props.title !== "Sign Yourself" && (
<div className="text-xs mt-2">
<label className="block">Send In Order</label>
<div className="flex items-center gap-2 ml-2 mb-1">
<label className="block">Description</label>
<input
name="Description"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
value={formData.Description}
onChange={(e) => handleStrInput(e)}
/>
</div>
{props.signers && (
<SignersInput
onChange={handleSigners}
isReset={isReset}
required
/>
)}
<div className="text-xs mt-2">
<label className="block">
Note<span className="text-red-500 text-[13px]">*</span>
</label>
<input
name="Note"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
value={formData.Note}
onChange={(e) => handleStrInput(e)}
required
/>
</div>
{props.title !== "New Template" && (
<SelectFolder
onSuccess={handleFolder}
folderCls={props.Cls}
isReset={isReset}
/>
)}
{props.title === "Request Signatures" && (
<div className="text-xs mt-2">
<label className="block">
Time To Complete (Days)
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="radio"
value={"true"}
name="SendinOrder"
checked={formData.SendinOrder === "true"}
className=""
onChange={handleStrInput}
type="number"
name="TimeToCompleteDays"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
value={formData.TimeToCompleteDays}
onChange={(e) => handleStrInput(e)}
required
/>
<div className="text-center">Yes</div>
</div>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"false"}
name="SendinOrder"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-center">No</div>
)}
{props.title !== "Sign Yourself" && (
<>
<div className="text-xs mt-2">
<label className="block">
Send In Order
<a data-tooltip-id="sendInOrder-tooltip" className="ml-1">
<sup>
<i
className="fa-solid fa-question rounded-full"
style={{
borderColor: "#33bbff",
color: "#33bbff",
fontSize: 13,
borderWidth: 1.5,
padding: "1.5px 4px"
}}
></i>
</sup>
</a>
<Tooltip id="sendInOrder-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">Send in Order</p>
<p>
Choose how you want the signing requests to be sent to
the document signers:
</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">Yes:</span>
<span>
{" "}
Selecting this option will send the signing
request to the first signer initially. Once the
first signer completes their part, the next
signer in the sequence will receive the request.
This process continues until all signers have
signed the document. This method ensures that
the document is signed in a specific order.
</span>
</li>
<li>
<span className="font-bold">No: </span>
<span>
Selecting this option will send the signing
links to all signers simultaneously. Every
signer can sign the document at their
convenience, regardless of whether other signers
have completed their signatures. This method is
faster but does not enforce any signing order
among the participants.
</span>
</li>
</ol>
</p>
<p>
Select the option that best suits the needs of your
document processing.
</p>
</div>
</Tooltip>
</label>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"true"}
name="SendinOrder"
checked={formData.SendinOrder === "true"}
onChange={handleStrInput}
/>
<div className="text-center">Yes</div>
</div>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"false"}
name="SendinOrder"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-center">No</div>
</div>
</div>
<div className="text-xs mt-2">
{!isEnableSubscription && (
<PremiumAlertHeader
message={
"Disable Auto reminder is free in beta, this feature will incur a fee later."
}
/>
)}
<span
className={
isSubscribe || !isEnableSubscription
? "font-semibold"
: "font-semibold text-gray-300"
}
>
Auto reminder{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<label
className={`${
isSubscribe || !isEnableSubscription
? "cursor-pointer "
: "pointer-events-none opacity-50"
} relative block items-center mb-0`}
>
<input
checked={formData.autoreminder}
onChange={handleAutoReminder}
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 mt-2"></div>
</label>
</div>
{formData?.autoreminder === true && (
<div className="text-xs mt-2">
<label className="block">
Remind once in every (Days)
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
value={formData.remindOnceInEvery}
name="remindOnceInEvery"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
onChange={handleStrInput}
required
/>
</div>
)}
</>
)}
<div className="flex items-center mt-3 gap-2 text-white">
<button
className={`${
isSubmit && "cursor-progress"
} bg-[#1ab6ce] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 focus:outline-none`}
type="submit"
disabled={isSubmit}
>
Next
</button>
<div
className="cursor-pointer bg-[#188ae2] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 text-center ml-[2px] focus:outline-none"
onClick={() => handleReset()}
>
Reset
</div>
</div>
)}
<div className="flex items-center mt-3 gap-2 text-white">
<button
className={`${
isSubmit && "cursor-progress"
} bg-[#1ab6ce] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 focus:outline-none`}
type="submit"
disabled={isSubmit}
>
Next
</button>
<div
className="cursor-pointer bg-[#188ae2] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 text-center ml-[2px] focus:outline-none"
onClick={() => handleReset()}
>
Reset
</div>
</div>
</form>
</form>
</>
)}
</div>
);
+16 -13
View File
@@ -144,29 +144,32 @@ function GenerateToken() {
: "w-full flex flex-col p-2 text-sm opacity-20 pointer-events-none select-none"
}
>
<li
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
>
<div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
<li className="flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2">
<div className="w-full md:w-[70%] flex-col md:flex-row text-xs md:text-[15px] flex items-center gap-x-5 ">
<span className="">Api Token:</span>{" "}
<span
id="token"
className=" md:text-end cursor-pointer"
onClick={() => copytoclipboard(apiToken)}
>
{apiToken ? apiToken : "_____"}
<span id="token" className="md:text-end py-2 md:py-0">
<span
className="cursor-pointer"
onClick={() => copytoclipboard(apiToken)}
>
{apiToken ? apiToken : "_____"}
</span>
<button
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-2.5 py-2 focus:outline-none ml-2"
onClick={() => copytoclipboard(apiToken)}
>
<i className="fa-solid fa-copy"></i>
</button>
</span>
</div>
<button
type="button"
onClick={apiToken ? handleModal : handleSubmit}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white text-xs md:text-sm lg:text-base px-2 py-2 focus:outline-none"
>
{apiToken ? "Regenerate Token" : "Generate Token"}
</button>
</li>
</ul>
<div className="flex items-center justify-center ">
<button
type="button"
+206 -172
View File
@@ -1,25 +1,32 @@
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, serverUrl } = useParams();
let navigate = useNavigate();
const { id, userMail, contactBookId, base64url } = useParams();
const navigate = useNavigate();
const [email, setEmail] = useState(userMail);
const [OTP, setOTP] = useState("");
const [EnterOTP, setEnterOtp] = useState(false);
const [loading, setLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [appLogo, setAppLogo] = useState("");
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
}, []);
@@ -36,60 +43,65 @@ function GuestLogin() {
setAppLogo(logo);
}
//split url in array from '&'
localStorage.clear();
const checkSplit = serverUrl.split("&");
const server = checkSplit[0];
const parseId = checkSplit[1];
const appName = checkSplit[2];
const newServer = server.replaceAll("%2F", "/");
const parseId = appInfo.appId;
const newServer = `${appInfo.baseUrl}/`;
const appName = appInfo.appname;
localStorage.setItem("baseUrl", newServer);
localStorage.setItem("parseAppId", parseId);
localStorage.setItem("_appName", appName);
//this condition is used decode base64 to string and get userEmail,documentId, contactBoookId data.
if (!id) {
//`atob` function is used to decode base64
const decodebase64 = atob(base64url);
//split url in array from '/'
const checkSplit = decodebase64.split("/");
setDocumentId(checkSplit[0]);
setContact((prev) => ({ ...prev, email: checkSplit[1] }));
setEmail(checkSplit[1]);
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: id
};
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();
@@ -125,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)
@@ -137,7 +150,13 @@ function GuestLogin() {
//save isGuestSigner true in local to handle login flow header in mobile view
localStorage.setItem("isGuestSigner", true);
setLoading(false);
navigate(`/load/recipientSignPdf/${id}/${contactBookId}`);
if (sendmail === "false") {
navigate(
`/load/recipientSignPdf/${documentId}/${contactId}?sendmail=${sendmail}`
);
} else {
navigate(`/load/recipientSignPdf/${documentId}/${contactId}`);
}
}
} catch (error) {
console.log("err ", error);
@@ -146,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>
+158 -149
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(
@@ -422,7 +423,7 @@ function Login() {
alert("Somenthing went wrong, please try again later!");
}
};
const thirdpartyLoginfn = async (sessionToken, billingDate) => {
const thirdpartyLoginfn = async (sessionToken) => {
const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
const res = await axios.get(baseUrl + "users/me", {
@@ -521,7 +522,7 @@ function Login() {
await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
}).then(
(result) => {
async (result) => {
let tenentInfo = [];
const results = [result];
if (results) {
@@ -571,7 +572,7 @@ function Login() {
setState({ ...state, loading: false });
navigate("/");
} else {
extendedInfo.forEach((x) => {
extendedInfo.forEach(async (x) => {
if (x.TenantId) {
let obj = {
tenentId: x.TenantId.objectId,
@@ -608,8 +609,13 @@ function Login() {
setThirdpartyLoader(false);
setState({ ...state, loading: false });
if (isEnableSubscription) {
if (billingDate) {
if (billingDate > new Date()) {
const res = await fetchSubscription();
const freeplan = res.plan;
const billingDate = res.billingDate;
if (freeplan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
navigate(redirectUrl);
} else {
@@ -644,8 +650,13 @@ function Login() {
setState({ ...state, loading: false });
setThirdpartyLoader(false);
if (isEnableSubscription) {
if (billingDate) {
if (billingDate > new Date()) {
const res = await fetchSubscription();
const freeplan = res.plan;
const billingDate = res.billingDate;
if (freeplan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
@@ -858,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(
@@ -895,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(
@@ -972,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
@@ -984,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
};
@@ -1006,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");
@@ -1030,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"} />
@@ -1077,154 +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>
</fieldset>
<div className="flex flex-row justify-between items-center ml-4 py-2">
<div>
<label
className="form-check-label inline-block cursor-pointer"
htmlFor="rememberpassword"
>
<input
type="checkbox"
className="form-check-input mr-1"
value=""
id="rememberpassword"
/>
<span className="text-[13px]">
Remember Password
</span>
</label>
</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">
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] underline focus:outline-none focus:ring-2 focus:ring-blue-600"
className="text-[13px] text-[#002864] hover:underline underline-offset-1 focus:outline-none cursor-pointer ml-1"
>
Forgot Password ?
Forgot Password?
</NavLink>
</div>
</div>
<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}
+18 -7
View File
@@ -11,8 +11,8 @@ import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Tour from "reactour";
import axios from "axios";
const DriveBody = React.lazy(
() => import("../components/opensigndrive/DriveBody")
const DriveBody = React.lazy(() =>
import("../components/opensigndrive/DriveBody")
);
const Loader = () => {
return (
@@ -64,6 +64,7 @@ function Opensigndrive() {
const sortingValue = ["Name", "Date"];
const [isDontShow, setIsDontShow] = useState(false);
const [tourData, setTourData] = useState();
const [showTourFirstTIme, setShowTourFirstTime] = useState(true);
const orderName = {
Ascending: "Ascending",
Descending: "Descending",
@@ -134,7 +135,9 @@ function Opensigndrive() {
//function for get all pdf document list
const getPdfDocumentList = async (disbaleLoading) => {
setLoading(true);
checkTourStatus();
if (showTourFirstTIme) {
checkTourStatus();
}
if (!disbaleLoading) {
setIsLoading({ isLoad: true, message: "This might take some time" });
}
@@ -250,17 +253,23 @@ function Opensigndrive() {
}, [loading, sortingOrder, selectedSort]); // Add/remove scroll event listener when loading changes
//function for handle folder name path
const handleRoute = (index) => {
setPdfData([]);
const handleRoute = (index, folderData) => {
setSkip(0);
// after onclick on route filter route from that index
const updateFolderName = folderName.filter((x, i) => {
if (i <= index) {
return x;
}
});
setFolderName(updateFolderName);
//get route details after onclick path of folder name
const getCurrentId = folderData[index];
const getLastId = updateFolderName[updateFolderName.length - 1];
setDocId(getLastId.objectId);
//below condition is used to check if user click on same route which already open then don't change any thing
if (docId !== getCurrentId.objectId) {
setPdfData([]);
setDocId(getLastId.objectId);
}
};
//function for add new folder name
@@ -291,6 +300,7 @@ function Opensigndrive() {
const exsitQuery = new Parse.Query(foldercls);
exsitQuery.equalTo("Name", newFolderName);
exsitQuery.equalTo("Type", "Folder");
exsitQuery.notEqualTo("IsArchive", true);
if (parentId) {
exsitQuery.equalTo("Folder", folderPtr);
}
@@ -439,7 +449,7 @@ function Opensigndrive() {
return (
<React.Fragment key={id}>
<span
onClick={() => handleRoute(id)}
onClick={() => handleRoute(id, folderData)}
style={{
color: "#a64b4e",
fontWeight: "400",
@@ -474,6 +484,7 @@ function Opensigndrive() {
const closeTour = async () => {
setIsTour(false);
setShowTourFirstTime(false);
if (isDontShow) {
const serverUrl = localStorage.getItem("baseUrl");
const appId = localStorage.getItem("parseAppId");
File diff suppressed because it is too large Load Diff
+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
+125 -97
View File
@@ -123,6 +123,7 @@ function PlaceHolderSign() {
const [requestSubject, setRequestSubject] = useState("");
const [requestBody, setRequestBody] = useState("");
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
const [activeMailAdapter, setActiveMailAdapter] = useState("");
const [isAlreadyPlace, setIsAlreadyPlace] = useState({
status: false,
message: ""
@@ -172,15 +173,6 @@ function PlaceHolderSign() {
: rowLevel?.objectId && rowLevel.objectId;
const documentId = docId ? docId : objectId && objectId;
const senderUser =
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
) &&
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
useEffect(() => {
if (documentId) {
getDocumentDetails();
@@ -209,16 +201,16 @@ function PlaceHolderSign() {
setDefaultBody(defaultRequestBody);
setDefaultSubject(
`{{sender_name}} has requested you to sign {{document_title}}`
`{{sender_name}} has requested you to sign "{{document_title}}"`
);
} else {
setRequestBody(defaultRequestBody);
setRequestSubject(
`{{sender_name}} has requested you to sign {{document_title}}`
`{{sender_name}} has requested you to sign "{{document_title}}"`
);
setDefaultBody(defaultRequestBody);
setDefaultSubject(
`{{sender_name}}has requested you to sign {{document_title}}`
`{{sender_name}}has requested you to sign "{{document_title}}"`
);
}
}
@@ -405,11 +397,22 @@ function PlaceHolderSign() {
};
setIsLoading(loadObj);
}
const res = await contractUsers(jsonSender.email);
if (res[0] && res.length) {
const currentUserEmail = documentData[0]?.ExtUserPtr?.Email;
const res = await contractUsers(currentUserEmail);
if (res === "Error: Something went wrong!") {
const loadObj = {
isLoad: false
};
setHandleError("Error: Something went wrong!");
setIsLoading(loadObj);
} else if (res.length && res[0]?.objectId) {
setActiveMailAdapter(res[0]?.active_mail_adapter);
setSignerUserId(res[0].objectId);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
const alreadyPlaceholder = documentData[0]?.SignedUrl;
if (alreadyPlaceholder) {
setCheckTourStatus(true);
} else if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRecipients = tourstatus.filter(
(data) => data.placeholder
@@ -422,12 +425,6 @@ function PlaceHolderSign() {
isLoad: false
};
setIsLoading(loadObj);
} else if (res === "Error: Something went wrong!") {
const loadObj = {
isLoad: false
};
setHandleError("Error: Something went wrong!");
setIsLoading(loadObj);
} else if (res.length === 0) {
setHandleError("No Data Found!");
const loadObj = {
@@ -836,11 +833,11 @@ function PlaceHolderSign() {
return pdfDetails[0].URL;
}
};
const alertSendEmail = async () => {
const filterPrefill = signerPos?.filter((data) => data.Role !== "prefill");
const getPrefill = signerPos?.filter((data) => data.Role === "prefill");
let isLabel = false;
//condition is used to check text widget data is empty or have response
if (getPrefill && getPrefill.length > 0) {
const prefillPlaceholder = getPrefill[0].placeHolder;
if (prefillPlaceholder) {
@@ -851,6 +848,29 @@ function PlaceHolderSign() {
});
}
}
let isSignatureExist = true; // variable is used to check a signature widget exit or not then execute other code
//for loop is used to check signature widget exist or not
for (let item of filterPrefill) {
let signatureExist = false; // Reset for each iteration
for (let x of item.placeHolder) {
if (!signatureExist) {
const typeExist = x.pos.some((data) => data?.type);
if (typeExist) {
signatureExist = x.pos.some((data) => data?.type === "signature");
} else {
signatureExist = x.pos.some((data) => !data.isStamp);
}
}
}
if (!signatureExist) {
isSignatureExist = false;
const alert = {
mssg: "sure",
alert: true
};
setIsSendAlert(alert);
}
}
if (getPrefill && isLabel) {
const alert = {
@@ -858,23 +878,26 @@ function PlaceHolderSign() {
alert: true
};
setIsSendAlert(alert);
} else if (filterPrefill.length === signersdata.length) {
const IsSignerNotExist = filterPrefill?.filter((x) => !x.signerObjId);
if (IsSignerNotExist && IsSignerNotExist?.length > 0) {
setSignerExistModal(true);
setSelectWidgetId(IsSignerNotExist[0]?.placeHolder?.[0]?.pos?.[0]?.key);
} else if (isSignatureExist) {
if (filterPrefill.length === signersdata.length) {
const IsSignerNotExist = filterPrefill?.filter((x) => !x.signerObjId);
if (IsSignerNotExist && IsSignerNotExist?.length > 0) {
setSignerExistModal(true);
setSelectWidgetId(
IsSignerNotExist[0]?.placeHolder?.[0]?.pos?.[0]?.key
);
} else {
saveDocumentDetails();
}
} else {
saveDocumentDetails();
const alert = {
mssg: "sure",
alert: true
};
setIsSendAlert(alert);
}
} else {
const alert = {
mssg: "sure",
alert: true
};
setIsSendAlert(alert);
}
};
//function to use save placeholder details in contracts_document
const saveDocumentDetails = async () => {
setIsUiLoading(true);
@@ -913,6 +936,7 @@ function PlaceHolderSign() {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
SentToOthers: true,
ExpiryDate: {
iso: updateExpiryDate,
__type: "Date"
@@ -922,7 +946,8 @@ function PlaceHolderSign() {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers
Signers: signers,
SentToOthers: true
};
}
await axios
@@ -973,15 +998,14 @@ function PlaceHolderSign() {
const shareLinkList = [];
let signerMail = signersdata;
for (let i = 0; i < signerMail.length; i++) {
const serverUrl = localStorage.getItem("baseUrl");
const newServer = serverUrl.replaceAll("/", "%2F");
const objectId = signerMail[i].objectId;
const serverParams = `${newServer}&${localStorage.getItem(
"parseAppId"
)}&${localStorage.getItem("_appName")}`;
const hostUrl = window.location.origin;
let signPdf = `${hostUrl}/login/${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}/${serverParams}`;
const sendMail = false;
//encode this url value `${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}` to base64 using `btoa` function
const encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}/${sendMail}`
);
let signPdf = `${hostUrl}/login/${encodeBase64}`;
shareLinkList.push({ signerEmail: signerMail[i].Email, url: signPdf });
}
return shareLinkList.map((data, ind) => {
@@ -1055,15 +1079,13 @@ function PlaceHolderSign() {
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const serverUrl = localStorage.getItem("baseUrl");
const newServer = serverUrl.replaceAll("/", "%2F");
const objectId = signerMail[i].objectId;
const serverParams = `${newServer}&${localStorage.getItem(
"parseAppId"
)}&${localStorage.getItem("_appName")}`;
const hostUrl = window.location.origin;
let signPdf = `${hostUrl}/login/${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}/${serverParams}`;
//encode this url value `${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}` to base64 using `btoa` function
const encodeBase64 = btoa(
`${pdfDetails?.[0].objectId}/${signerMail[i].Email}/${objectId}`
);
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl = "https://www.opensignlabs.com/";
const orgName = pdfDetails[0]?.ExtUserPtr.Company
? pdfDetails[0].ExtUserPtr.Company
@@ -1088,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>`
@@ -1104,11 +1126,12 @@ function PlaceHolderSign() {
}
let params = {
mailProvider: activeMailAdapter,
extUserId: extUserId,
recipient: signerMail[i].Email,
subject: isCustomize
? replaceVar?.subject
: `${senderName} has requested you to sign ${documentName}`,
: `${senderName} has requested you to sign "${documentName}"`,
from: senderEmail,
html: isCustomize
? replaceVar?.body
@@ -1142,40 +1165,45 @@ function PlaceHolderSign() {
}
if (sendMail.data.result.status === "success") {
setMailStatus("success");
if (
requestBody &&
requestSubject &&
isCustomize &&
(isSubscribe || !isEnableSubscription)
) {
try {
const data = {
try {
let data;
if (
requestBody &&
requestSubject &&
isCustomize &&
(isSubscribe || !isEnableSubscription)
) {
data = {
RequestBody: htmlReqBody,
RequestSubject: requestSubject
RequestSubject: requestSubject,
SendMail: true
};
} else {
data = {
SendMail: true
};
await axios
.put(
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
"_appName"
)}_Document/${documentId}`,
data,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
)
.then(() => {})
.catch((err) => {
console.log("axois err ", err);
});
} catch (e) {
console.log("error", e);
}
await axios
.put(
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
"_appName"
)}_Document/${documentId}`,
data,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
)
.then(() => {})
.catch((err) => {
console.log("axois err ", err);
});
} catch (e) {
console.log("error", e);
}
setIsSend(true);
@@ -1375,10 +1403,7 @@ function PlaceHolderSign() {
inputype = options.includes(defaultdata.textvalidate)
? defaultdata.textvalidate
: "regex";
} else {
inputype = "text";
}
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
if (filterSignerPos.length > 0) {
const getPlaceHolder = filterSignerPos[0].placeHolder;
@@ -1401,11 +1426,14 @@ function PlaceHolderSign() {
status: defaultdata?.status || "required",
hint: defaultdata?.hint || "",
defaultValue: defaultdata?.defaultValue || "",
validation: {
type: inputype,
pattern:
inputype === "regex" ? defaultdata.textvalidate : ""
}
validation:
(isSubscribe || !isEnableSubscription) && inputype
? {
type: inputype,
pattern:
inputype === "regex" ? defaultdata.textvalidate : ""
}
: {}
}
};
} else {
@@ -1695,7 +1723,10 @@ function PlaceHolderSign() {
style={{ padding: 20 }}
>
{isSendAlert.mssg === "sure" ? (
<span>Please add at least one signature field for all recipients.</span>
<span>
Please ensure there&apos;s at least one signature widget
added for all recipients.
</span>
) : isSendAlert.mssg === textWidget ? (
<p>Please confirm that you have filled the text field.</p>
) : (
@@ -1775,10 +1806,7 @@ function PlaceHolderSign() {
{!isSubscribe && isEnableSubscription && (
<div className="mt-2">
<Upgrade
message="Upgrade to customize Email"
newWindow={true}
/>
<Upgrade message="Upgrade to customize Email" />
</div>
)}
</div>
+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;
+32 -6
View File
@@ -11,7 +11,7 @@ import { useDispatch } from "react-redux";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import { isEnableSubscription } from "../constant/const";
import { getAppLogo } from "../constant/Utils";
import { getAppLogo, openInNewTab } from "../constant/Utils";
const Signup = () => {
const { width } = useWindowSize();
const navigate = useNavigate();
@@ -33,7 +33,7 @@ const Signup = () => {
const [lengthValid, setLengthValid] = useState(false);
const [caseDigitValid, setCaseDigitValid] = useState(false);
const [specialCharValid, setSpecialCharValid] = useState(false);
const [isAuthorize, setIsAuthorize] = useState(false);
const togglePasswordVisibility = () => setShowPassword(!showPassword);
const clearStorage = async () => {
@@ -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" />
@@ -658,10 +660,34 @@ const Signup = () => {
{specialCharValid ? "✓" : "✗"} Password should
contain special character
</p>
{/* </>
)} */}
</div>
)}
<div className="mt-2.5 ml-1 flex flex-row items-center">
<input
type="checkbox"
id="termsandcondition"
checked={isAuthorize}
onChange={(e) => setIsAuthorize(e.target.checked)}
required
/>
<label
className="text-xs cursor-pointer ml-1 mb-0"
htmlFor="termsandcondition"
>
I agree to the
</label>
<span
className="underline cursor-pointer ml-1"
onClick={() =>
openInNewTab(
"https://www.opensignlabs.com/terms-and-conditions"
)
}
>
Terms of Service
</span>
<span>.</span>
</div>
</div>
</div>
<div className="flex flex-col md:flex-row justify-between items-stretch gap-8 text-center text-xs font-bold mt-2">
+234 -101
View File
@@ -1,7 +1,9 @@
import React, { useState, useRef, useEffect } from "react";
import { PDFDocument } from "pdf-lib";
import "../styles/signature.css";
import Parse from "parse";
import { isEnableSubscription, themeColor } from "../constant/const";
import Confetti from "react-confetti";
import axios from "axios";
import Loader from "../primitives/LoaderWithMsg";
import loader from "../assets/images/loader2.gif";
@@ -29,7 +31,10 @@ import {
textWidget,
getTenantDetails,
checkIsSubscribed,
convertPdfArrayBuffer
convertPdfArrayBuffer,
fetchImageBase64,
changeImageWH,
handleSendOTP
} from "../constant/Utils";
import { useParams } from "react-router-dom";
import Tour from "reactour";
@@ -42,6 +47,7 @@ import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Title from "../components/Title";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import VerifyEmail from "../components/pdf/VerifyEmail";
//For signYourself inProgress section signer can add sign and complete doc sign.
function SignYourSelf() {
@@ -65,7 +71,6 @@ function SignYourSelf() {
const [dragKey, setDragKey] = useState();
const [signKey, setSignKey] = useState();
const [imgWH, setImgWH] = useState({});
const [isCeleb, setIsCeleb] = useState(false);
const [pdfNewWidth, setPdfNewWidth] = useState();
const [pdfOriginalWidth, setPdfOriginalWidth] = useState();
const [successEmail, setSuccessEmail] = useState(false);
@@ -89,6 +94,7 @@ function SignYourSelf() {
const [containerWH, setContainerWH] = useState({});
const [isPageCopy, setIsPageCopy] = useState(false);
const [selectWidgetId, setSelectWidgetId] = useState("");
const [otpLoader, setOtpLoader] = useState(false);
const [showAlreadySignDoc, setShowAlreadySignDoc] = useState({
status: false
});
@@ -103,7 +109,12 @@ function SignYourSelf() {
const [isDontShow, setIsDontShow] = useState(false);
const [extUserId, setExtUserId] = useState("");
const [isCompleted, setIsCompleted] = useState(false);
const [isCelebration, setIsCelebration] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
const [activeMailAdapter, setActiveMailAdapter] = useState("");
const [isEmailVerified, setIsEmailVerified] = useState(true);
const [isVerifyModal, setIsVerifyModal] = useState(false);
const [otp, setOtp] = useState("");
const divRef = useRef(null);
const nodeRef = useRef(null);
const [, drop] = useDrop({
@@ -193,13 +204,18 @@ function SignYourSelf() {
setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const url = documentData[0] && documentData[0]?.URL;
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
const arrayBuffer = await convertPdfArrayBuffer(url);
if (arrayBuffer === "Error") {
setHandleError("Error: Something went wrong!");
if (url) {
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
const arrayBuffer = await convertPdfArrayBuffer(url);
if (arrayBuffer === "Error") {
setHandleError("Error: Something went wrong!");
} else {
setPdfArrayBuffer(arrayBuffer);
}
} else {
setPdfArrayBuffer(arrayBuffer);
setHandleError("Error: Something went wrong!");
}
isCompleted = documentData[0].IsCompleted && documentData[0].IsCompleted;
if (isCompleted) {
setIsCompleted(true);
@@ -214,8 +230,12 @@ function SignYourSelf() {
setIsUiLoading(false);
setIsSignPad(false);
setIsEmail(true);
setIsCelebration(true);
setXyPostion([]);
setSignBtnPosition([]);
setTimeout(() => {
setIsCelebration(false);
}, 5000);
}
}
} else if (
@@ -266,7 +286,14 @@ function SignYourSelf() {
setIsLoading(loadObj);
});
const contractUsersRes = await contractUsers(jsonSender.email);
if (contractUsersRes[0] && contractUsersRes.length > 0) {
if (contractUsersRes === "Error: Something went wrong!") {
const loadObj = {
isLoad: false
};
setHandleError("Error: Something went wrong!");
setIsLoading(loadObj);
} else if (contractUsersRes[0] && contractUsersRes.length > 0) {
setActiveMailAdapter(contractUsersRes[0]?.active_mail_adapter);
setContractName("_Users");
setSignerUserId(contractUsersRes[0].objectId);
const tourstatuss =
@@ -286,12 +313,6 @@ function SignYourSelf() {
isLoad: false
};
setIsLoading(loadObj);
} else if (contractUsersRes === "Error: Something went wrong!") {
const loadObj = {
isLoad: false
};
setHandleError("Error: Something went wrong!");
setIsLoading(loadObj);
} else if (contractUsersRes.length === 0) {
const contractContactBook = await contactBook(jsonSender.objectId);
if (contractContactBook && contractContactBook.length > 0) {
@@ -525,76 +546,163 @@ function SignYourSelf() {
setSignKey(key);
};
//`handleResend` function is used to resend otp for email verification
const handleResend = async (e) => {
e.preventDefault();
setOtpLoader(true);
await handleSendOTP(Parse.User.current().getEmail());
setOtpLoader(false);
alert("OTP sent on you email");
};
//`handleVerifyEmail` function is used to verify email with otp
const handleVerifyEmail = async (e) => {
e.preventDefault();
setOtpLoader(true);
try {
const resEmail = await Parse.Cloud.run("verifyemail", {
otp: otp,
email: Parse.User.current().getEmail()
});
if (resEmail?.message === "Email is verified.") {
setIsEmailVerified(true);
} else if (resEmail?.message === "Email is already verified.") {
setIsEmailVerified(true);
}
setOtp("");
alert(resEmail.message);
setIsVerifyModal(false);
// handleRecipientSign();
} catch (error) {
alert(error.message);
} finally {
setOtpLoader(false);
}
};
//`handleVerifyBtn` function is used to send otp on user mail
const handleVerifyBtn = async () => {
setIsVerifyModal(true);
await handleSendOTP(Parse.User.current().getEmail());
};
//function for send placeholder's co-ordinate(x,y) position embed signature url or stamp url
async function embedWidgetsData() {
let showAlert = false;
for (let i = 0; i < xyPostion?.length; i++) {
const requiredWidgets = xyPostion[i].pos.filter(
(position) => position.type !== "checkbox"
);
if (requiredWidgets && requiredWidgets?.length > 0) {
let checkSigned;
for (let i = 0; i < requiredWidgets?.length; i++) {
checkSigned = requiredWidgets[i]?.options?.response;
if (!checkSigned) {
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
let checkDefaultSigned = requiredWidgets[i]?.options?.defaultValue;
if (!checkSignUrl) {
if (!checkDefaultSigned) {
if (!showAlert) {
showAlert = true;
//check current user email is verified or not
const currentUser = JSON.parse(JSON.stringify(Parse.User.current()));
let isEmailVerified;
isEmailVerified = currentUser?.emailVerified;
if (isEmailVerified) {
setIsEmailVerified(isEmailVerified);
} else {
try {
const userQuery = new Parse.Query(Parse.User);
const user = await userQuery.get(currentUser.objectId, {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
isEmailVerified = user?.get("emailVerified");
setIsEmailVerified(isEmailVerified);
}
} catch (e) {
setHandleError("Error: Something went wrong!");
}
}
if (isEmailVerified) {
let showAlert = false,
isSignatureExist = false;
try {
for (let i = 0; i < xyPostion?.length; i++) {
const requiredWidgets = xyPostion[i].pos.filter(
(position) => position.type !== "checkbox"
);
if (requiredWidgets && requiredWidgets?.length > 0) {
let checkSigned;
for (let i = 0; i < requiredWidgets?.length; i++) {
checkSigned = requiredWidgets[i]?.options?.response;
if (!checkSigned) {
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
let checkDefaultSigned =
requiredWidgets[i]?.options?.defaultValue;
if (!checkSignUrl) {
if (!checkDefaultSigned) {
if (!showAlert) {
showAlert = true;
}
}
}
}
}
}
//condition to check exist signature widget or not
if (!isSignatureExist) {
isSignatureExist = xyPostion[i].pos.some(
(data) => data?.type === "signature"
);
}
}
if (xyPostion.length === 0 || !isSignatureExist) {
setIsAlert({
header: "Fields required",
isShow: true,
alertMessage:
"Please ensure there's at least one signature widget added"
});
return;
} else if (showAlert) {
setIsAlert({
isShow: true,
alertMessage:
"Please ensure all field is accurately filled and meets all requirements."
});
return;
} else {
setIsUiLoading(true);
const existingPdfBytes = pdfArrayBuffer;
// Load a PDFDocument from the existing PDF bytes
try {
const pdfDoc = await PDFDocument.load(existingPdfBytes);
const isSignYourSelfFlow = true;
const extUserPtr = pdfDetails[0].ExtUserPtr;
const HeaderDocId = extUserPtr?.HeaderDocId;
//embed document's object id to all pages in pdf document
if (!HeaderDocId) {
await embedDocId(pdfDoc, documentId, allPages);
}
//embed multi signature in pdf
const pdfBytes = await multiSignEmbed(
xyPostion,
pdfDoc,
pdfOriginalWidth,
isSignYourSelfFlow,
containerWH
);
// console.log("pdf", pdfBytes);
//function for call to embed signature in pdf and get digital signature pdf
await signPdfFun(pdfBytes, documentId);
} catch (err) {
setIsUiLoading(false);
if (err && err.message.includes("is encrypted.")) {
setIsAlert({
isShow: true,
alertMessage: `Currently encrypted pdf files are not supported.`
});
} else {
console.log("err in signing", err);
setIsAlert({
isShow: true,
alertMessage: `Something went wrong.`
});
}
}
}
} catch (err) {
console.log("err in embedselfsign ", err);
setIsUiLoading(false);
setIsAlert({
isShow: true,
alertMessage: "something went wrong, please try again later."
});
}
}
if (xyPostion.length === 0) {
setIsAlert({
isShow: true,
alertMessage: "Please complete your signature!"
});
return;
} else if (showAlert) {
setIsAlert({
isShow: true,
alertMessage: "Please complete your signature!"
});
return;
} else {
setIsCeleb(true);
setTimeout(() => {
setIsCeleb(false);
}, 3000);
setIsUiLoading(true);
const existingPdfBytes = pdfArrayBuffer;
// Load a PDFDocument from the existing PDF bytes
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
ignoreEncryption: true
});
const isSignYourSelfFlow = true;
const extUserPtr = pdfDetails[0].ExtUserPtr;
const HeaderDocId = extUserPtr?.HeaderDocId;
//embed document's object id to all pages in pdf document
if (!HeaderDocId) {
await embedDocId(pdfDoc, documentId, allPages);
}
//embed multi signature in pdf
const pdfBytes = await multiSignEmbed(
xyPostion,
pdfDoc,
pdfOriginalWidth,
isSignYourSelfFlow,
containerWH
);
// console.log("pdf", pdfBytes);
//function for call to embed signature in pdf and get digital signature pdf
await signPdfFun(pdfBytes, documentId);
}
}
// console.log("signyourself", xyPostion);
//function for get digital signature
const signPdfFun = async (base64Url, documentId) => {
let isCustomCompletionMail = false;
@@ -611,11 +719,40 @@ function SignYourSelf() {
isCustomCompletionMail = true;
}
}
// below for loop is used to get first signature of user to send if to signpdf
// for adding it in completion certificate
let getSignature;
for (let item of xyPostion) {
if (!getSignature) {
const typeExist = item.pos.some((data) => data?.type);
if (typeExist) {
getSignature = item.pos.find((data) => data?.type === "signature");
} else {
getSignature = item.pos.find((data) => !data.isStamp);
}
}
}
let base64Sign = getSignature.SignUrl;
//check https type signature (default signature exist) then convert in base64
const isUrl = base64Sign.includes("https");
if (isUrl) {
try {
base64Sign = await fetchImageBase64(base64Sign);
} catch (e) {
console.log("error", e);
}
}
//change image width and height to 104/44 in png base64
const getNewse64 = await changeImageWH(base64Sign);
//remove suffiix of base64
const suffixbase64 = getNewse64 && getNewse64.split(",").pop();
let singleSign = {
pdfFile: base64Url,
docId: documentId,
isCustomCompletionMail: isCustomCompletionMail
isCustomCompletionMail: isCustomCompletionMail,
mailProvider: activeMailAdapter,
signature: suffixbase64
};
await axios
@@ -623,7 +760,8 @@ function SignYourSelf() {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
// sessionToken: localStorage.getItem("accesstoken")
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
})
.then((Listdata) => {
@@ -985,8 +1123,25 @@ function SignYourSelf() {
</span>
</div>
)}
{isCelebration && (
<div style={{ position: "relative", zIndex: "999" }}>
<Confetti width={window.innerWidth} height={window.innerHeight} />
</div>
)}
<div className="signatureContainer" ref={divRef}>
{!isEmailVerified && (
<VerifyEmail
isVerifyModal={isVerifyModal}
setIsVerifyModal={setIsVerifyModal}
handleVerifyEmail={handleVerifyEmail}
setOtp={setOtp}
otp={otp}
otpLoader={otpLoader}
handleVerifyBtn={handleVerifyBtn}
handleResend={handleResend}
/>
)}
{/* this component used for UI interaction and show their functionality */}
{pdfLoadFail && !checkTourStatus && (
<Tour
@@ -1021,7 +1176,7 @@ function SignYourSelf() {
<ModalUi
headerColor={"#dc3545"}
isOpen={isAlert.isShow}
title={"Alert"}
title={isAlert?.header || "Alert"}
handleClose={() => {
setIsAlert({
isShow: false,
@@ -1031,35 +1186,13 @@ function SignYourSelf() {
>
<div style={{ height: "100%", padding: 20 }}>
<p>{isAlert.alertMessage}</p>
<div
style={{
height: "1px",
backgroundColor: "#9f9f9f",
width: "100%",
marginTop: "15px",
marginBottom: "15px"
}}
></div>
<button
onClick={() => {
setIsAlert({
isShow: false,
alertMessage: ""
});
}}
type="button"
className="finishBtn cancelBtn"
>
Ok
</button>
</div>
</ModalUi>
{/* this modal is used show this document is already sign */}
<ModalUi
isOpen={showAlreadySignDoc.status}
title={"Sign Documents"}
title={"Document signed"}
handleClose={() => {
setShowAlreadySignDoc({ status: false });
}}
@@ -1131,13 +1264,13 @@ function SignYourSelf() {
<EmailComponent
isEmail={isEmail}
pdfUrl={pdfUrl}
isCeleb={isCeleb}
setIsEmail={setIsEmail}
pdfName={pdfDetails[0] && pdfDetails[0].Name}
setSuccessEmail={setSuccessEmail}
sender={jsonSender}
setIsAlert={setIsAlert}
extUserId={extUserId}
activeMailAdapter={activeMailAdapter}
/>
{/* pdf header which contain funish back button */}
<Header
+29 -33
View File
@@ -36,6 +36,7 @@ import AddRoleModal from "../components/pdf/AddRoleModal";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import Parse from "parse";
const TemplatePlaceholder = () => {
const navigate = useNavigate();
const { templateId } = useParams();
@@ -704,35 +705,29 @@ const TemplatePlaceholder = () => {
Name: pdfDetails[0]?.Name || "",
Note: pdfDetails[0]?.Note || "",
Description: pdfDetails[0]?.Description || "",
SendinOrder: pdfDetails[0]?.SendinOrder || false
SendinOrder: pdfDetails[0]?.SendinOrder || false,
AutomaticReminders: pdfDetails[0]?.AutomaticReminders,
RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery),
NextReminderDate: pdfDetails[0]?.NextReminderDate
};
const updateTemplate = new Parse.Object("contracts_Template");
updateTemplate.id = templateId;
for (const key in data) {
updateTemplate.set(key, data[key]);
}
await updateTemplate.save(null, {
sessionToken: localStorage.getItem("accesstoken")
});
await axios
.put(
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
"_appName"
)}_Template/${templateId}`,
data,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
)
.then(() => {
setIsCreateDocModal(true);
setIsMailSend(true);
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
})
.catch((err) => {
console.log("axois err ", err);
});
setIsCreateDocModal(true);
setIsMailSend(true);
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
} catch (e) {
setIsLoading(false);
alert("Something went wrong, please try again later.");
console.log("error", e);
}
} else {
@@ -1132,8 +1127,6 @@ const TemplatePlaceholder = () => {
inputype = options.includes(defaultdata.textvalidate)
? defaultdata.textvalidate
: "regex";
} else {
inputype = "text";
}
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
@@ -1158,11 +1151,14 @@ const TemplatePlaceholder = () => {
status: defaultdata?.status || "required",
hint: defaultdata?.hint || "",
defaultValue: defaultdata?.defaultValue || "",
validation: {
type: inputype,
pattern:
inputype === "regex" ? defaultdata.textvalidate : ""
}
validation:
(isSubscribe || !isEnableSubscription) && inputype
? {
type: inputype,
pattern:
inputype === "regex" ? defaultdata.textvalidate : ""
}
: {}
}
};
} else {
+272 -50
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";
@@ -8,9 +12,10 @@ import sanitizeFileName from "../primitives/sanitizeFileName";
import axios from "axios";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribed } from "../constant/Utils";
import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { checkIsSubscribed, handleSendOTP } from "../constant/Utils";
import Upgrade from "../primitives/Upgrade";
import ModalUi from "../primitives/ModalUi";
function UserProfile() {
const navigate = useNavigate();
@@ -26,18 +31,27 @@ 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
);
const [jobTitle, setJobTitle] = useState(
extendUser && extendUser?.[0]?.JobTitle
);
const [isVerifyModal, setIsVerifyModal] = useState(false);
const [otp, setOtp] = useState("");
const [otpLoader, setOtpLoader] = useState(false);
const [isEmailVerified, setIsEmailVerified] = useState(false);
// const [userNameError, setUserNameError] = useState("");
useEffect(() => {
getUserDetail();
}, []);
const getUserDetail = async () => {
setIsLoader(true);
const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass);
const HeaderDocId = jsonSender[0]?.HeaderDocId;
@@ -48,47 +62,93 @@ function UserProfile() {
if (HeaderDocId) {
setIsDisableDocId(HeaderDocId);
}
const currentUser = JSON.parse(JSON.stringify(Parse.User.current()));
let isEmailVerified = currentUser?.emailVerified || false;
if (isEmailVerified) {
setIsEmailVerified(isEmailVerified);
setIsLoader(false);
} else {
try {
const userQuery = new Parse.Query(Parse.User);
const user = await userQuery.get(currentUser.objectId, {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
isEmailVerified = user?.get("emailVerified");
setIsEmailVerified(isEmailVerified);
setIsLoader(false);
}
} catch (e) {
alert("something went wrong!");
}
}
};
//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);
}
}
};
@@ -97,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,
@@ -122,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);
};
@@ -177,6 +237,66 @@ function UserProfile() {
const handleDisableDocId = () => {
setIsDisableDocId((prevChecked) => !prevChecked);
};
//`handleVerifyBtn` function is used to send otp on user mail
const handleVerifyBtn = async () => {
setIsVerifyModal(true);
await handleSendOTP(Parse.User.current().getEmail());
};
const handleCloseVerifyModal = async () => {
setIsVerifyModal(false);
};
//`handleVerifyEmail` function is used to verify email with otp
const handleVerifyEmail = async (e) => {
e.preventDefault();
setOtpLoader(true);
try {
const resEmail = await Parse.Cloud.run("verifyemail", {
otp: otp,
email: Parse.User.current().getEmail()
});
if (resEmail?.message === "Email is verified.") {
setIsEmailVerified(true);
} else if (resEmail?.message === "Email is already verified.") {
setIsEmailVerified(true);
}
setOtp("");
alert(resEmail.message);
setIsVerifyModal(false);
} catch (error) {
alert(error.message);
} finally {
setOtpLoader(false);
}
};
//function to use resend otp for email verification
const handleResend = async (e) => {
e.preventDefault();
setOtpLoader(true);
await handleSendOTP();
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"} />
@@ -198,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">
@@ -310,11 +437,53 @@ function UserProfile() {
<li className="flex justify-between items-center border-t-[1px] border-gray-300 py-2 break-all">
<span className="font-semibold">Is Email verified:</span>{" "}
<span>
{UserProfile && UserProfile.emailVerified
? "Verified"
: "Not verified"}
{isEmailVerified ? (
"Verified"
) : (
<span>
Not verified(
<span
onClick={() => handleVerifyBtn()}
className="hover:underline text-blue-600 cursor-pointer"
>
verify
</span>
)
</span>
)}
</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
@@ -329,7 +498,6 @@ function UserProfile() {
url={
"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"
}
isSubscribe={isSubscribe}
/>{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>{" "}
@@ -386,7 +554,7 @@ function UserProfile() {
type="button"
onClick={() => {
if (editmode) {
setEditMode(false);
handleCancel();
} else {
navigate("/changepassword");
}
@@ -399,6 +567,60 @@ function UserProfile() {
</button>
</div>
</div>
{isVerifyModal && (
<ModalUi
isOpen
title={"OTP verification"}
handleClose={handleCloseVerifyModal}
>
{otpLoader ? (
<div
style={{
height: "150px",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
<div
style={{
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37"
></div>
</div>
) : (
<form onSubmit={(e) => handleVerifyEmail(e)}>
<div className="px-6 py-3">
<label className="mb-2">Enter OTP</label>
<input
required
type="tel"
pattern="[0-9]{4}"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
placeholder="Enter OTP received over email"
value={otp}
onChange={(e) => setOtp(e.target.value)}
/>
</div>
<hr />
<div className="px-6 my-3">
<button type="submit" className={submitBtn}>
Verify
</button>
<button
className={`${rejectBtn} ml-2`}
onClick={(e) => handleResend(e)}
>
Resend
</button>
</div>
</form>
)}
</ModalUi>
)}
</div>
)}
</React.Fragment>
@@ -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;
+1 -1
View File
@@ -18,7 +18,7 @@ const Alert = ({ children, type }) => {
<>
{children && (
<div
className={`z-40 fixed top-20 left-1/2 transform -translate-x-1/2 border-[1px] text-sm ${textcolor} rounded py-[.75rem] px-[1.25rem] z-50`}
className={`z-[1000] fixed top-20 left-1/2 transform -translate-x-1/2 border-[1px] text-sm ${textcolor} rounded py-[.75rem] px-[1.25rem] z-50`}
>
{children}
</div>
File diff suppressed because it is too large Load Diff
@@ -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>
);
};
+5 -7
View File
@@ -10,19 +10,17 @@ const ModalUi = ({
handleClose,
showHeader = true,
showClose = true,
styleClass,
reduceWidth,
showHeaderMessage
}) => {
return (
<>
{isOpen && (
<div className="fixed z-[999] 2 top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]">
<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
+2 -10
View File
@@ -1,17 +1,9 @@
import React from "react";
import { Tooltip as ReactTooltip } from "react-tooltip";
import { openInNewTab } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const";
const Tooltip = ({ id, message, url, iconColor, isSubscribe }) =>
const Tooltip = ({ id, message, url, iconColor }) =>
url ? (
<button
onClick={() => openInNewTab(url)}
className={
isSubscribe || !isEnableSubscription
? "text-center"
: "text-center opacity-20 pointer-events-none"
}
>
<button onClick={() => openInNewTab(url)} className={"text-center"}>
<sup>
<i
className="fa-solid fa-question rounded-full"
+3 -10
View File
@@ -1,20 +1,13 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { openInNewTab } from "../constant/Utils";
function Upgrade({ message, newWindow }) {
const navigation = useNavigate();
function Upgrade({ message }) {
return (
<sup>
<span
onClick={() => {
if (newWindow) {
const url = window.location.origin + "/subscription";
openInNewTab(url);
} else {
navigation("/subscription");
}
const url = window.location.origin + "/subscription";
openInNewTab(url);
}}
className="text-blue-800 text-sm cursor-pointer underline"
>
-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;
}
}
+4
View File
@@ -1,4 +1,6 @@
import axios from 'axios';
import dotenv from 'dotenv';
dotenv.config();
const appId = process.env.APP_ID;
const serverUrl = process.env.SERVER_URL;
@@ -243,3 +245,5 @@ export function sanitizeFileName(fileName) {
const removedot = file.replace(/\.(?=.*\.)/g, '');
return removedot.replace(/[^a-zA-Z0-9._-]/g, '');
}
export const useLocal = process.env.USE_LOCAL ? process.env.USE_LOCAL.toLowerCase() : '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();
},
};
@@ -0,0 +1,191 @@
import axios from 'axios';
// `replaceMailVaribles` is used to replace variables from mail with there actual values
function replaceMailVaribles(subject, body, variables) {
let replacedSubject = subject;
let replacedBody = body;
for (const variable in variables) {
const regex = new RegExp(`{{${variable}}}`, 'g');
if (subject) {
replacedSubject = replacedSubject.replace(regex, variables[variable]);
}
if (body) {
replacedBody = replacedBody.replace(regex, variables[variable]);
}
}
const result = {
subject: replacedSubject,
body: replacedBody,
};
return result;
}
// `sendmail` is used to signing reminder mail to signer
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`;
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': process.env.APP_ID,
};
const baseUrl = new URL(process.env.PUBLIC_URL);
const encodeBase64 = btoa(`${doc.objectId}/${signer.Email}/${signer.objectId}`);
const expireDate = doc?.ExpiryDate?.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
const signPdf = `${baseUrl.origin}/login/${encodeBase64}`;
const variables = {
document_title: doc.Name,
sender_name: doc.ExtUserPtr.Name,
sender_mail: doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr?.Phone || '',
receiver_name: signer.Name,
receiver_email: signer.Email,
receiver_phone: signer?.Phone || '',
expiry_date: localExpireDate,
company_name: doc?.ExtUserPtr?.Company || '',
signing_url: `<a href=${signPdf}>Sign here</a>`,
};
const mail = replaceMailVaribles(subject, body, variables);
let params = {
mailProvider: doc?.ExtUserPtr?.active_mail_adapter,
extUserId: doc?.ExtUserPtr?.objectId,
recipient: signer.Email,
subject: mail.subject,
from: doc?.ExtUserPtr?.Email,
html: mail.body,
};
try {
// The axios request is used to send a signing reminder email.
const res = await axios.post(url, params, { headers: headers });
// console.log('res ', res.data.result);
if (res.data.result.status === 'success') {
return { status: 'success' };
}
} catch (err) {
console.log('err in mail of sendreminder api', err);
return { status: 'error' };
}
}
export default async function autoReminder(request, response) {
// The query below is used to find documents where the reminder date is less than or equal to the current date, and which have existing signers and a signed URL.
try {
const docQuery = new Parse.Query('contracts_Document');
docQuery.limit(2000);
docQuery.lessThanOrEqualTo('NextReminderDate', new Date());
docQuery.equalTo('AutomaticReminders', true);
docQuery.exists('NextReminderDate');
docQuery.exists('Signers');
docQuery.exists('SignedUrl');
docQuery.descending('createdAt');
docQuery.include('Signers,AuditTrail.UserPtr,ExtUserPtr');
docQuery.notEqualTo('IsCompleted', true);
docQuery.notEqualTo('IsDeclined', true);
docQuery.notEqualTo('IsArchive', true);
docQuery.greaterThanOrEqualTo('ExpiryDate', new Date());
const docsArr = await docQuery.find({ useMasterKey: true });
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();
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;
if (SendInOrder) {
// The auditTrail variable is used to get the count of how many users have already signed the document.
const auditTrail = doc?.AuditTrail?.filter(x => x.Activity === 'Signed');
const count = auditTrail?.length || 0;
const signer = doc?.Signers?.[count];
if (signer) {
docCount += 1;
const mailRes = await sendMail(doc, signer);
if (mailRes && mailRes.status === 'success') {
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.
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = doc.objectId;
updateDoc.set('NextReminderDate', ReminderDate);
const updateRes = await updateDoc.save(null, { useMasterKey: true });
// console.log('updateRes ', updateRes);
} catch (err) {
console.log('err in update document', err);
}
}
} else {
// The AuditTrail variable is used to check if there is any user who has already signed the document.
const auditTrail = doc?.AuditTrail?.filter(x => x.Activity === 'Signed');
if (auditTrail?.length > 0) {
// The signers variable is used to get the signers who haven't signed the document.
const signers = doc?.Signers.filter(signer => {
const signedUser = auditTrail?.find(y => y.UserPtr.objectId === signer.objectId);
if (!signedUser) {
return signer;
}
});
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);
if (mailRes && mailRes.status === 'success') {
mailCount += 1;
}
}
}
} else {
// 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);
if (mailRes && mailRes.status === 'success') {
mailCount += 1;
}
}
}
}
// The code below is used to update the next reminder date of the document based on the "remind once every X days" setting.
try {
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = doc.objectId;
updateDoc.set('NextReminderDate', ReminderDate);
const updateRes = await updateDoc.save(null, { useMasterKey: true });
// console.log('updateRes ', updateRes);
} catch (err) {
console.log('err in sendmail', err);
}
}
}
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' });
}
} catch (err) {
console.log('err ', err);
const code = err?.code || 400;
const message = err?.message || 'Somehting went wrong!';
response.status(code).json({ error: message });
}
}
@@ -6,6 +6,8 @@ import uploadFile from './uploadFile.js';
import saveSubscription from './saveSubscription.js';
import saveInvoice from './saveInvoice.js';
import savePayments from './savePayments.js';
import gooogleauth from './googleauth.js';
import autoReminder from './autoReminder.js';
export const app = express();
dotenv.config();
@@ -15,8 +17,8 @@ app.use(express.urlencoded({ limit: '50mb', extended: true }));
app.post('/file_upload', uploadFile);
app.post('/savesubscription', saveSubscription)
app.post('/saveinvoice', saveInvoice)
app.post('/savepayment', savePayments)
app.post('/savesubscription', saveSubscription);
app.post('/saveinvoice', saveInvoice);
app.post('/savepayment', savePayments);
app.post('/googleauth', gooogleauth);
app.post('/sendreminder', autoReminder);
@@ -0,0 +1,59 @@
import axios from 'axios';
const appId = process.env.APP_ID;
const serverUrl = process.env.SERVER_URL;
export default async function gooogleauth(request, response) {
const code = request.body.code;
const baseUrl = new URL(process.env.SERVER_URL);
try {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
const userId = userRes.data && userRes.data.objectId;
if (userId) {
const clientId = process.env.GOOGLE_CLIENT_ID;
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
const redirectUri =
baseUrl?.hostname === 'localhost'
? 'http://localhost:3000'
: 'https://console.opensignlabs.com'; // Should match the redirect URI used in the authorization request
const tokenEndpoint = 'https://oauth2.googleapis.com/token';
const params = new URLSearchParams();
params.append('code', code);
params.append('client_id', clientId);
params.append('client_secret', clientSecret);
params.append('redirect_uri', redirectUri);
params.append('grant_type', 'authorization_code');
const res = await axios.post(tokenEndpoint, params);
// console.log('oauthres ', res.data);
const refresh_token = res.data.refresh_token;
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const extUserCls = new Parse.Object('contracts_Users');
extUserCls.id = extUser.id;
extUserCls.set('google_refresh_token', refresh_token);
extUserCls.set('active_mail_adapter', 'google');
const updateExtUser = await extUserCls.save(null, { useMasterKey: true });
// console.log('updateExtUser ', updateExtUser);
}
return response.status(200).json({ status: 'success' });
} else {
return response.status(404).json({ message: 'user not found!' });
}
} catch (err) {
console.log('err in google auth', err);
if (err?.response?.data?.error) {
return response.status(404).json({ message: err.response.data.error });
} else {
return response.status(404).json({ message: err.message });
}
}
}
@@ -3,6 +3,7 @@ import multer from 'multer';
import multerS3 from 'multer-s3';
import aws from 'aws-sdk';
import dotenv from 'dotenv';
import { useLocal } from '../../Utils.js';
dotenv.config();
function sanitizeFileName(fileName) {
@@ -51,7 +52,7 @@ async function uploadFile(req, res) {
const parseBaseUrl = process.env.SERVER_URL;
const parseAppId = process.env.APP_ID;
let fileStorage;
if (process.env.USE_LOCAL == 'TRUE') {
if (useLocal === 'true') {
fileStorage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'files/files');
@@ -147,7 +148,7 @@ async function uploadFile(req, res) {
const status = 'Success';
//res.header("Access-Control-Allow-Headers", "Content-Type");
//res.setHeader("Access-Control-Allow-Origin", "*");
if (process.env.USE_LOCAL == 'TRUE') {
if (useLocal === 'true') {
console.log(req.file);
var fileUrl = `${parseBaseUrl}/files/${parseAppId}/${req.file.filename}`;
} else {
@@ -17,7 +17,6 @@ import getTemplatetList from './routes/getTemplateList.js';
import updateTemplate from './routes/updateTemplate.js';
import createContact from './routes/createContact.js';
import multer from 'multer';
// import fs from 'node:fs';
import updateDocument from './routes/updateDocument.js';
import deleteDocument from './routes/deleteDocument.js';
import createDocumentWithTemplate from './routes/CreateDocumentWithTemplate.js';
@@ -26,6 +25,12 @@ import deleteWebhook from './routes/deleteWebhook.js';
import getWebhook from './routes/getWebhook.js';
import createDocumentwithCoordinate from './routes/createDocumentwithCoordinate.js';
import createTemplatewithCoordinate from './routes/createTemplatewithCoordinate.js';
import resendMail from './routes/resendMail.js';
import getFolder from './routes/getFolder.js';
import createFolder from './routes/createFolder.js';
import updateFolder from './routes/updateFolder.js';
import getFolderList from './routes/getFolderList.js';
import deleteFolder from './routes/deleteFolder.js';
dotenv.config();
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
@@ -102,3 +107,21 @@ app.post('/webhook', saveWebhook);
// set and update webhook
app.delete('/webhook', deleteWebhook);
// resend mail
app.post('/resendmail', resendMail);
// create folder
app.post('/createfolder', createFolder);
// update folder
app.put('/folder/:folder_id', updateFolder);
// get folder list
app.delete('/folder/:folder_id', deleteFolder);
// get folder
app.get('/folder/:folder_id', getFolder);
// get folder list
app.get('/folderlist', getFolderList);
@@ -50,11 +50,12 @@ export default async function createDocumentWithTemplate(request, response) {
const folderId = request.body.folderId;
const templateId = request.params.template_id;
const protocol = customAPIurl();
const baseUrl = new URL(process.env.SERVER_URL);
const baseUrl = new URL(process.env.PUBLIC_URL);
const send_email = request.body.send_email;
const email_subject = request.body.email_subject;
const email_body = request.body.email_body;
const sendInOrder = request.body.sendInOrder || false;
const TimeToCompleteDays = request.body.timeToCompleteDays || 15;
try {
const reqToken = request.headers['x-api-token'];
@@ -88,12 +89,20 @@ export default async function createDocumentWithTemplate(request, response) {
const updateSigners = placeholder.every(y => signers?.some(x => x.role === y.Role));
// console.log('isValid ', isValid);
if (isValid && updateSigners) {
//Check if every item's placeholders contain at least one placeholder with type 'signature'.
let isSignature = template?.Placeholders?.every(item =>
item?.placeHolder.some(x => x?.pos.some(data => data?.type === 'signature'))
);
if (!isSignature) {
return response
.status(400)
.json({ error: 'Please add at least one signature widget for all signers' });
}
const folderPtr = {
__type: 'Pointer',
className: 'contracts_Document',
objectId: folderId,
};
const template = JSON.parse(JSON.stringify(templateRes));
const object = new Parse.Object('contracts_Document');
object.set('Name', template.Name);
if (template?.Note) {
@@ -169,6 +178,11 @@ export default async function createDocumentWithTemplate(request, response) {
object.set('Signers', templateSigner);
}
object.set('URL', template.URL);
object.set('SignedUrl', template.URL);
object.set('SentToOthers', true);
if (TimeToCompleteDays) {
object.set('TimeToCompleteDays', TimeToCompleteDays);
}
object.set('CreatedBy', template.CreatedBy);
object.set('ExtUserPtr', {
__type: 'Pointer',
@@ -195,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 {
@@ -217,9 +228,10 @@ export default async function createDocumentWithTemplate(request, response) {
};
const objectId = contactMail[i].contactPtr.objectId;
const hostUrl = baseUrl.origin;
let signPdf = `${hostUrl}/login/${res.id}/${contactMail[i].email}/${objectId}/${serverParams}`;
//encode this url value `${res.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function
const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`);
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl = 'https://www.opensignlabs.com/contact-us';
const orgName = template.ExtUserPtr.Company ? template.ExtUserPtr.Company : '';
const themeBGcolor = '#47a3ad';
@@ -251,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,
@@ -266,13 +278,13 @@ export default async function createDocumentWithTemplate(request, response) {
replaceVar = { subject: replaceVar.subject, body: email_html };
} else if (email_body) {
replaceVar = replaceMailVaribles(
`${template.ExtUserPtr.Name} has requested you to sign ${template.Name}`,
`${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`,
email_body,
variables
);
} else {
replaceVar = {
subject: `${template.ExtUserPtr.Name} has requested you to sign ${template.Name}`,
subject: `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`,
body: email_html,
};
}
@@ -300,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) {
@@ -320,7 +336,9 @@ export default async function createDocumentWithTemplate(request, response) {
objectId: res.id,
signurl: contact.map(x => ({
email: x.email,
url: `${baseUrl.origin}/login/${res.id}/${x.email}/${x.contactPtr.objectId}/${serverParams}`,
url: `${baseUrl.origin}/login/${btoa(
`${res.id}/${x.email}/${x.contactPtr.objectId}`
)}`,
})),
message: 'Document sent successfully!',
});
@@ -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,
});
@@ -128,7 +132,7 @@ export default async function createContact(request, response) {
userId: userRes.objectId,
};
await axios.post(roleurl, body, { headers: headers });
contactQuery.set('CreatedBy', userPtr.objectId);
contactQuery.set('CreatedBy', userPtr);
contactQuery.set('UserId', {
__type: 'Pointer',
className: '_User',
@@ -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);
@@ -66,9 +66,10 @@ export default async function createDocumentwithCoordinate(request, response) {
const email_subject = request.body.email_subject;
const email_body = request.body.email_body;
const sendInOrder = request.body.sendInOrder || false;
const TimeToCompleteDays = request.body.timeToCompleteDays || 15;
// console.log('fileData ', fileData);
const protocol = customAPIurl();
const baseUrl = new URL(process.env.SERVER_URL);
const baseUrl = new URL(process.env.PUBLIC_URL);
const reqToken = request.headers['x-api-token'];
if (!reqToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
@@ -88,6 +89,15 @@ export default async function createDocumentwithCoordinate(request, response) {
objectId: parseUser.userId.objectId,
};
if (signers && signers.length > 0) {
// Check if at least one signature exists among all items in the signers array
let isSignExist = signers.every(item =>
item.widgets.some(data => data?.type === 'signature')
);
if (!isSignExist) {
return response
.status(400)
.json({ error: 'Please add at least one signature widget for all signers' });
}
let fileUrl;
if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
@@ -130,8 +140,13 @@ export default async function createDocumentwithCoordinate(request, response) {
object.set('SendinOrder', sendInOrder);
}
object.set('URL', fileUrl);
object.set('SignedUrl', fileUrl);
object.set('SentToOthers', true);
object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr);
if (TimeToCompleteDays) {
object.set('TimeToCompleteDays', TimeToCompleteDays);
}
object.set('IsSendMail', send_email);
let contact = [];
if (signers && signers.length > 0) {
@@ -241,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) {
@@ -255,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 {
@@ -278,7 +290,9 @@ export default async function createDocumentwithCoordinate(request, response) {
const objectId = contactMail[i].contactPtr.objectId;
const hostUrl = baseUrl.origin;
let signPdf = `${hostUrl}/login/${res.id}/${contactMail[i].email}/${objectId}/${serverParams}`;
//encode this url value `${response.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function
const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`);
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl = 'https://www.opensignlabs.com/contact-us';
const orgName = parseExtUser.Company ? parseExtUser.Company : '';
const themeBGcolor = '#47a3ad';
@@ -309,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,
@@ -324,13 +338,13 @@ export default async function createDocumentwithCoordinate(request, response) {
replaceVar = { subject: replaceVar.subject, body: email_html };
} else if (email_body) {
replaceVar = replaceMailVaribles(
`${parseExtUser.Name} has requested you to sign ${name}`,
`${parseExtUser.Name} has requested you to sign "${name}"`,
email_body,
variables
);
} else {
replaceVar = {
subject: `${parseExtUser.Name} has requested you to sign ${parseExtUser.Name}`,
subject: `${parseExtUser.Name} has requested you to sign "${name}"`,
body: email_html,
};
}
@@ -363,7 +377,7 @@ export default async function createDocumentwithCoordinate(request, response) {
objectId: res.id,
signurl: contact.map(x => ({
email: x.email,
url: `${baseUrl.origin}/login/${res.id}/${x.email}/${x.contactPtr.objectId}/${serverParams}`,
url: `${baseUrl.origin}/login/${btoa(`${res.id}/${x.email}/${x.contactPtr.objectId}`)}`,
})),
message: 'Document sent successfully!',
});
@@ -0,0 +1,73 @@
export default async function createFolder(request, response) {
const apiToken = request.headers['x-api-token'];
const folderName = request.body.folderName;
const parentFolderId = request.body.parentFolderId;
if (!apiToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
}
try {
const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', apiToken);
tokenQuery.include('userId');
const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) {
// Valid Token then proceed request
const parseUser = JSON.parse(JSON.stringify(token));
const userPtr = {
__type: 'Pointer',
className: '_User',
objectId: parseUser.userId.objectId,
};
const userQuery = new Parse.Query('contracts_Users');
userQuery.equalTo('UserId', userPtr);
const extUser = await userQuery.first({ useMasterKey: true });
const folderCls = new Parse.Object('contracts_Document');
folderCls.set('Name', folderName);
folderCls.set('CreatedBy', userPtr);
folderCls.set('Type', 'Folder');
folderCls.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
});
if (parentFolderId) {
folderCls.set('Folder', {
__type: 'Pointer',
className: 'contracts_Document',
objectId: parentFolderId,
});
}
const acl = new Parse.ACL();
acl.setReadAccess(parseUser.userId.objectId, true); // Allow read access to the userPtr
acl.setWriteAccess(parseUser.userId.objectId, true); // Allow write access to the userPtr
// Set the ACL to the document
folderCls.setACL(acl);
const _resFolder = await folderCls.save(null, { useMasterKey: true });
if (_resFolder) {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_folder',
properties: { response_code: 200 },
});
}
const parseRes = JSON.parse(JSON.stringify(_resFolder));
return response.json({
objectId: parseRes.objectId,
folderName: parseRes.Name,
parentFolderId: parseRes?.Folder?.objectId || '',
createdAt: parseRes.createdAt,
updatedAt: parseRes.updatedAt,
});
}
} else {
return response.status(405).json({ error: 'Invalid API Token!' });
}
} catch (err) {
console.log('err ', err);
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
}
}
@@ -8,7 +8,7 @@ export default async function createTemplate(request, response) {
const base64File = request.body.file;
const SendinOrder = request.body.sendInOrder || false;
const fileData = request.files?.[0] ? request.files[0].buffer : null;
const baseUrl = new URL(process.env.SERVER_URL);
const baseUrl = new URL(process.env.PUBLIC_URL);
try {
const reqToken = request.headers['x-api-token'];
@@ -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 || '',
@@ -0,0 +1,74 @@
export default async function deleteFolder(request, response) {
const apiToken = request.headers['x-api-token'];
const folderId = request.params.folder_id;
if (!apiToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
}
try {
const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', apiToken);
tokenQuery.include('userId');
const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) {
// Valid Token then proceed request
const parseUser = JSON.parse(JSON.stringify(token));
const userPtr = {
__type: 'Pointer',
className: '_User',
objectId: parseUser.userId.objectId,
};
const obj = new Parse.Query('contracts_Document');
obj.equalTo('objectId', folderId);
obj.equalTo('IsArchive', true);
obj.equalTo('CreatedBy', userPtr);
const isFolderDeleted = await obj.first({ useMasterKey: true });
if (!isFolderDeleted) {
const folder = new Parse.Query('contracts_Document');
folder.equalTo('Folder', {
__type: 'Pointer',
className: 'contracts_Document',
objectId: folderId,
});
folder.notEqualTo('IsArchive', true);
folder.equalTo('CreatedBy', userPtr);
folder.equalTo('Type', 'Folder');
const isSubItems = await folder.first({ useMasterKey: true });
// console.log('isSubItems ', isSubItems);
if (isSubItems) {
return response
.status(400)
.json({ error: 'folder is not empty, contains document or folder.' });
} else {
const deleteFolder = new Parse.Object('contracts_Document');
deleteFolder.id = folderId;
deleteFolder.set('IsArchive', true);
const deleteRes = await deleteFolder.save(null, { useMasterKey: true });
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_delete_folder',
properties: { response_code: 200 },
});
}
return response.json({ objectId: deleteRes.id, deletedAt: deleteRes.updatedAt });
}
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_delete_folder',
properties: { response_code: 404 },
});
}
return response.status(404).json({ error: 'folder not found.' });
}
} else {
return response.status(405).json({ error: 'Invalid API Token!' });
}
} catch (err) {
console.log('err is delete folder API', err);
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
}
}
@@ -1,5 +1,5 @@
import axios from 'axios';
import { customAPIurl } from '../../../../Utils.js';
import { customAPIurl, saveFileUsage } from '../../../../Utils.js';
// const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function draftDocument(request, response) {
@@ -14,7 +14,7 @@ export default async function draftDocument(request, response) {
const SendinOrder = request.body.sendInOrder || false;
// console.log('fileData ', fileData);
const protocol = customAPIurl();
const baseUrl = new URL(process.env.SERVER_URL);
const baseUrl = new URL(process.env.PUBLIC_URL);
try {
const reqToken = request.headers['x-api-token'];
@@ -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,
}));
@@ -40,6 +40,7 @@ export default async function getDocument(request, response) {
note: document.Note || '',
folder: { objectId: document?.Folder?.objectId, name: document?.Folder?.Name } || '',
file: document?.SignedUrl || document.URL,
certificate: document?.CertificateUrl || '',
owner: document?.ExtUserPtr?.Name,
signers:
document?.Placeholders?.map(y => ({
@@ -77,7 +77,7 @@ export default async function getDocumentList(request, response) {
title: x.Name,
note: x.Note || '',
folder: { objectId: x?.Folder?.objectId, name: x?.Folder?.Name } || '',
file: x?.SignedUrl || x.URL,
// file: x?.SignedUrl || x.URL,
owner: x?.ExtUserPtr?.Name,
signers:
x?.Placeholders?.map(y => ({
@@ -0,0 +1,64 @@
export default async function getFolder(request, response) {
const apiToken = request.headers['x-api-token'];
const folder_id = request.params.folder_id;
if (!apiToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
}
try {
const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', apiToken);
tokenQuery.include('userId');
const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) {
// Valid Token then proceed request
const parseUser = JSON.parse(JSON.stringify(token));
const userPtr = {
__type: 'Pointer',
className: '_User',
objectId: parseUser.userId.objectId,
};
const folderCls = new Parse.Query('contracts_Document');
folderCls.equalTo('CreatedBy', userPtr);
folderCls.equalTo('objectId', folder_id);
folderCls.equalTo('Type', 'Folder');
folderCls.notEqualTo('IsArchive', true);
folderCls.descending('createdAt');
folderCls.include('Folder');
const res = await folderCls.first({ useMasterKey: true });
if (res) {
const parseRes = JSON.parse(JSON.stringify(res));
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_get_folder',
properties: { response_code: 200 },
});
}
return response.json({
objectId: parseRes.objectId,
folderName: parseRes.Name,
parentFolderId: parseRes?.Folder?.objectId || '',
parentFolderName: parseRes?.Folder?.Name || '',
createdAt: parseRes.createdAt,
updatedAt: parseRes.updatedAt,
});
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_get_folder',
properties: { response_code: 404 },
});
}
return response.status(404).json({ error: 'folder not found.' });
}
} else {
return response.status(405).json({ error: 'Invalid API Token!' });
}
} catch (err) {
console.log('err ', err);
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
}
}
@@ -0,0 +1,76 @@
export default async function getFolderList(request, response) {
const apiToken = request.headers['x-api-token'];
const parentFolderId = request.query?.parentFolderId || '';
if (!apiToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
}
try {
const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', apiToken);
tokenQuery.include('userId');
const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) {
// Valid Token then proceed request
const parseUser = JSON.parse(JSON.stringify(token));
const userPtr = {
__type: 'Pointer',
className: '_User',
objectId: parseUser.userId.objectId,
};
const limit = request?.query?.limit ? parseInt(request.query.limit) : 100;
const skip = request?.query?.skip ? parseInt(request.query.skip) : 0;
const folderCls = new Parse.Query('contracts_Document');
folderCls.equalTo('CreatedBy', userPtr);
folderCls.equalTo('Type', 'Folder');
folderCls.notEqualTo('IsArchive', true);
folderCls.descending('createdAt');
folderCls.include('Folder');
if (parentFolderId) {
folderCls.equalTo('Folder', {
__type: 'Pointer',
className: 'contracts_Document',
objectId: parentFolderId,
});
} else {
folderCls.doesNotExist('Folder');
}
folderCls.descending('createdAt');
folderCls.limit(limit);
folderCls.skip(skip);
const res = await folderCls.find({ useMasterKey: true });
if (res && res.length > 0) {
const parseRes = JSON.parse(JSON.stringify(res));
const folderlist = parseRes.map(x => ({
objectId: x.objectId,
folderName: x.Name,
parentFolderId: x?.Folder?.objectId || '',
parentFolderName: x?.Folder?.Name || '',
createdAt: x.createdAt,
updatedAt: x.updatedAt,
}));
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_get_Folder_list',
properties: { response_code: 200 },
});
}
return response.json({ result: folderlist });
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_get_Folder_list',
properties: { response_code: 200 },
});
}
return response.json({ result: [] });
}
} else {
return response.status(405).json({ error: 'Invalid API Token!' });
}
} catch (err) {
console.log('err ', err);
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
}
}
@@ -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,
@@ -0,0 +1,157 @@
import axios from 'axios';
import { replaceMailVaribles } from '../../../../Utils.js';
export default async function resendMail(request, response) {
try {
const reqToken = request.headers['x-api-token'];
const docId = request.body.document_id;
const userMail = request.body.email;
const email_subject = request.body.email_subject;
const email_body = request.body.email_body;
const baseUrl = new URL(process.env.SERVER_URL);
if (!reqToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
}
const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', reqToken);
tokenQuery.include('userId');
const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) {
// Valid Token then proceed request
const parseUser = JSON.parse(JSON.stringify(token));
const userPtr = {
__type: 'Pointer',
className: '_User',
objectId: parseUser.userId.objectId,
};
const docQuery = new Parse.Query('contracts_Document');
docQuery.equalTo('objectId', docId);
docQuery.equalTo('CreatedBy', userPtr);
docQuery.include('Signers,ExtUserPtr');
docQuery.notEqualTo('IsCompleted', true);
docQuery.notEqualTo('IsDeclined', true);
docQuery.notEqualTo('IsArchive', true);
docQuery.greaterThanOrEqualTo('ExpiryDate', new Date());
docQuery.exists('SignedUrl');
const resDoc = await docQuery.first({ useMasterKey: true });
// console.log("resDoc ",resDoc)
if (resDoc) {
const _resDoc = resDoc.toJSON();
const contact = _resDoc.Signers.find(x => x.Email === userMail);
if (contact) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
let url = `${process.env.SERVER_URL}/functions/sendmailv3/`;
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Master-Key': process.env.MASTER_KEY,
};
const objectId = contact.objectId;
const hostUrl = baseUrl.origin;
const title = _resDoc.Name;
const receiverMail = contact.Email;
//encode this url value `${response.id}/${receiverMail}/${objectId}` to base64 using `btoa` function
const encodeBase64 = btoa(`${_resDoc.objectId}/${receiverMail}/${objectId}`);
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const openSignUrl = 'https://www.opensignlabs.com/contact-us';
const orgName = _resDoc.ExtUserPtr.Company ? _resDoc.ExtUserPtr.Company : '';
const newDate = new Date(_resDoc.ExpiryDate.iso);
newDate.setDate(newDate.getDate() + 15);
const sender = _resDoc.ExtUserPtr.Email;
const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
const themeBGcolor = '#47a3ad';
const email_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;'> " +
_resDoc.ExtUserPtr.Name +
' has requested you to review and sign <strong> ' +
title +
"</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>';
let replaceVar;
const variables = {
document_title: title,
sender_name: _resDoc.ExtUserPtr.Name,
sender_mail: _resDoc.ExtUserPtr.Email,
sender_phone: _resDoc.ExtUserPtr?.Phone || '',
receiver_name: contact.Name,
receiver_email: contact.Email,
receiver_phone: contact?.Phone || '',
expiry_date: localExpireDate,
company_name: orgName,
signing_url: signPdf,
};
if (email_subject && email_body) {
replaceVar = replaceMailVaribles(email_subject, email_body, variables);
} else if (email_subject) {
replaceVar = replaceMailVaribles(email_subject, '', variables);
replaceVar = { subject: replaceVar.subject, body: email_html };
} else if (email_body) {
replaceVar = replaceMailVaribles(
`${_resDoc.ExtUserPtr.Name} has requested you to sign "${title}"`,
email_body,
variables
);
} else {
replaceVar = {
subject: `${_resDoc.ExtUserPtr.Name} has requested you to sign "${title}"`,
body: email_html,
};
}
const subject = replaceVar.subject;
const html = replaceVar.body;
let params = {
recipient: contact.Email,
subject: subject,
from: sender,
html: html,
extUserId: _resDoc.ExtUserPtr.objectId,
};
const res = await axios.post(url, params, { headers: headers });
if (res.data.result && res.data.result.status === 'success') {
return response.json({ result: 'mail sent successfully.' });
}
} catch (error) {
console.log('error', error);
return response.status(400).json({ error: error.message });
}
} else {
return response.status(404).json({ error: 'user not found.' });
}
} else {
return response.status(404).json({ error: 'document not found.' });
}
} else {
return response.status(405).json({ error: 'Invalid API Token.' });
}
} catch (err) {
console.log('err in resendmail', err);
return response.status(400).json({ error: err.message || 'Something went wrong.' });
}
}
@@ -0,0 +1,67 @@
export default async function updateFolder(request, response) {
const apiToken = request.headers['x-api-token'];
const folderId = request.params.folder_id;
const name = request.body.folderName;
const parentFolderId = request.body.parentFolderId;
if (!apiToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
}
try {
const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', apiToken);
tokenQuery.include('userId');
const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) {
// Valid Token then proceed request
const parseUser = JSON.parse(JSON.stringify(token));
const userPtr = {
__type: 'Pointer',
className: '_User',
objectId: parseUser.userId.objectId,
};
const folderCls = new Parse.Query('contracts_Document');
folderCls.equalTo('CreatedBy', userPtr);
folderCls.equalTo('objectId', folderId);
folderCls.equalTo('Type', 'Folder');
folderCls.notEqualTo('IsArchive', true);
const res = await folderCls.first({ useMasterKey: true });
if (res) {
const updateFolder = new Parse.Object('contracts_Document');
updateFolder.id = res.id;
updateFolder.set('Name', name);
if (parentFolderId) {
updateFolder.set('Folder', {
__type: 'Pointer',
className: 'contracts_Document',
objectId: parentFolderId,
});
}
const updateRes = await updateFolder.save(null, { useMasterKey: true });
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_update_folder',
properties: { response_code: 200 },
});
}
return response.json({ objectId: updateRes.id, updatedAt: updateRes.updatedAt });
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_update_folder',
properties: { response_code: 404 },
});
}
return response.status(404).json({ error: 'folder not found.' });
}
} else {
return response.status(405).json({ error: 'Invalid API Token!' });
}
} catch (err) {
console.log('err ', err);
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
}
}
+41 -14
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';
@@ -33,8 +33,36 @@ import getInvoices from './parsefunction/getInvoices.js';
import getPayments from './parsefunction/getPayments.js';
import getSubscriptions from './parsefunction/getSubscriptions.js';
import TenantAterFind from './parsefunction/TenantAfterFind.js';
import saveSubscriptio from './parsefunction/saveSubscription.js';
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);
@@ -59,15 +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', saveSubscriptio);
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,23 +1,25 @@
import { useLocal } from '../../Utils.js';
import getPresignedUrl from './getSignedUrl.js';
async function DocumentAfterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
const Url = obj?.get('URL') && obj?.get('URL');
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
if (useLocal !== 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
const Url = obj?.get('URL') && obj?.get('URL');
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
}
if (Url) {
obj.set('URL', getPresignedUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
}
return [obj];
}
if (Url) {
obj.set('URL', getPresignedUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
}
return [obj];
}
}
}
@@ -4,6 +4,7 @@ async function DocumentAftersave(request) {
console.log('new entry is insert in contracts_Document');
const createdAt = request.object.get('createdAt');
const Folder = request.object.get('Type');
const ip = request?.headers?.['x-real-ip'] || '';
if (createdAt && Folder === undefined) {
// console.log("IN If condition")
const TimeToCompleteDays = request.object.get('TimeToCompleteDays');
@@ -12,18 +13,30 @@ async function DocumentAftersave(request) {
const documentQuery = new Parse.Query('contracts_Document');
const updateQuery = await documentQuery.get(request.object.id, { useMasterKey: true });
updateQuery.set('ExpiryDate', ExpiryDate);
updateQuery.set('OriginIp', ip);
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
if (AutoReminder) {
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
const ReminderDate = new Date(createdAt);
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
updateQuery.set('NextReminderDate', ReminderDate);
}
await updateQuery.save(null, { useMasterKey: true });
} else if (createdAt && Folder === 'AIDoc') {
const TimeToCompleteDays = request.object.get('TimeToCompleteDays');
const ExpiryDate = new Date(createdAt);
// console.log("ExpiryDate")
// console.log(ExpiryDate)
ExpiryDate.setDate(ExpiryDate.getDate() + TimeToCompleteDays);
// console.log("ExpiryDate date after update")
// console.log(ExpiryDate)
const documentQuery = new Parse.Query('contracts_Document');
const updateQuery = await documentQuery.get(request.object.id, { useMasterKey: true });
updateQuery.set('ExpiryDate', ExpiryDate);
updateQuery.set('OriginIp', ip);
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
if (AutoReminder) {
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
const ReminderDate = new Date(createdAt);
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
updateQuery.set('NextReminderDate', ReminderDate);
}
await updateQuery.save(null, { useMasterKey: true });
}
@@ -25,6 +25,10 @@ async function DocumentBeforesave(request) {
} catch (error) {
console.log('Error updating document count in contracts_users: ' + error.message);
}
if (document?.get('Signers') && document.get('Signers').length > 0) {
document.set('DocSentAt', new Date());
document.save(null, { useMasterKey: true });
}
}
} catch (err) {
console.log('err in document beforesave', err.message);
@@ -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;
@@ -26,7 +26,7 @@ export default async function GetTemplate(request) {
if (res) {
// console.log("res ",res)
const acl = res.getACL();
console.log("acl", acl.getReadAccess(userId))
// console.log("acl", acl.getReadAccess(userId))
if (acl && acl.getReadAccess(userId)) {
return res;
} else {
@@ -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' };
}
}
}
@@ -39,7 +39,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) {
@@ -1,21 +1,21 @@
import { useLocal } from '../../Utils.js';
import getPresignedUrl from './getSignedUrl.js';
async function SignatureAfterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ImageURL = obj?.get('ImageURL') && obj?.get('ImageURL');
const Initials = obj?.get('Initials') && obj?.get('Initials');
if (ImageURL) {
obj.set('ImageURL', getPresignedUrl(ImageURL));
if (useLocal !== 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ImageURL = obj?.get('ImageURL') && obj?.get('ImageURL');
const Initials = obj?.get('Initials') && obj?.get('Initials');
if (ImageURL) {
obj.set('ImageURL', getPresignedUrl(ImageURL));
}
if (Initials) {
obj.set('Initials', getPresignedUrl(Initials));
}
return [obj];
}
if (Initials) {
obj.set('Initials', getPresignedUrl(Initials));
}
return [obj];
}
}
}
@@ -1,23 +1,26 @@
import { useLocal } from '../../Utils.js';
import getPresignedUrl from './getSignedUrl.js';
async function TemplateAfterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
const Url = obj?.get('URL') && obj?.get('URL');
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
}
if (Url) {
obj.set('URL', getPresignedUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
}
if (useLocal !== 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
const Url = obj?.get('URL') && obj?.get('URL');
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
}
if (Url) {
obj.set('URL', getPresignedUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
}
return [obj];
return [obj];
}
}
}
}
@@ -4,7 +4,14 @@ export default async function TemplateAfterSave(request) {
console.log('new entry is insert in contracts_Template');
// update acl of New Document If There are signers present in array
const signers = request.object.get('Signers');
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
if (AutoReminder) {
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
const ReminderDate = new Date(request?.object?.get('createdAt'));
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
request.object.set('NextReminderDate', ReminderDate);
await request.object.save(null, { useMasterKey: true });
}
if (signers && signers.length > 0) {
await updateAclDoc(request.object.id);
} else {
@@ -1,14 +1,17 @@
import { useLocal } from '../../Utils.js';
import getPresignedUrl from './getSignedUrl.js';
async function TenantAterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const Logo = obj?.get('Logo') && obj?.get('Logo');
if (Logo) {
obj.set('Logo', getPresignedUrl(Logo));
if (useLocal !== 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const Logo = obj?.get('Logo') && obj?.get('Logo');
if (Logo) {
obj.set('Logo', getPresignedUrl(Logo));
}
return [obj];
}
return [obj];
}
}
}
@@ -1,16 +1,17 @@
import { useLocal } from '../../Utils.js';
import getPresignedUrl from './getSignedUrl.js';
async function UserAfterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ProfilePic = obj?.get('ProfilePic') && obj?.get('ProfilePic');
if (ProfilePic) {
obj.set('ProfilePic', getPresignedUrl(ProfilePic));
if (useLocal !== 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ProfilePic = obj?.get('ProfilePic') && obj?.get('ProfilePic');
if (ProfilePic) {
obj.set('ProfilePic', getPresignedUrl(ProfilePic));
}
return [obj];
}
return [obj];
}
}
}
@@ -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');
}
}

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