Compare commits

..
232 Commits
Author SHA1 Message Date
ZathielandGitHub c8ecd34bd3 Merge pull request #1148 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/lint-staged-15.2.10
build(deps-dev): bump lint-staged from 15.2.9 to 15.2.10 in /apps/OpenSign
2024-09-13 20:00:31 +02:00
ZathielandGitHub d4a4eb3248 Merge pull request #1182 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/mongoose-8.6.1
build(deps): bump mongoose from 8.6.0 to 8.6.1 in /apps/OpenSignServer
2024-09-13 20:00:01 +02:00
ZathielandGitHub f782733397 Merge pull request #1186 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/jasmine-5.3.0
build(deps-dev): bump jasmine from 5.2.0 to 5.3.0 in /apps/OpenSignServer
2024-09-13 19:59:13 +02:00
ZathielandGitHub 7720eddc47 Merge pull request #1183 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/nodemailer-6.9.15
build(deps): bump nodemailer from 6.9.14 to 6.9.15 in /apps/OpenSignServer
2024-09-13 19:58:58 +02:00
AndrewandGitHub 71170a373a Merge pull request #1205 from OpenSignLabs/validation 2024-09-13 12:58:49 +05:30
prafull-opensignlabs 8f080080a7 refactor: changes in text of modal 2024-09-13 12:57:23 +05:30
AndrewandGitHub 71b2958c14 Merge pull request #1204 from OpenSignLabs/validation 2024-09-13 10:56:58 +05:30
prafull-opensignlabs f69aafcbfa refactor: provide userdetails 2024-09-13 10:52:22 +05:30
AndrewandGitHub 5f6a423697 Merge pull request #1203 from OpenSignLabs/validation 2024-09-12 13:29:39 +05:30
prafull-opensignlabs 5d245927c4 fix: user count is not correct 2024-09-12 13:25:11 +05:30
prafull-opensignlabsandGitHub 17c96fb849 Merge pull request #1201 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/npm_and_yarn-d7ff1d4c1a
build(deps): bump the npm_and_yarn group in /apps/OpenSignServer with 2 updates
2024-09-12 12:07:50 +05:30
prafull-opensignlabsandGitHub b7c28cdf36 Merge pull request #1202 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/npm_and_yarn-cf87d80143
build(deps): bump the npm_and_yarn group in /apps/OpenSign with 2 updates
2024-09-12 12:07:17 +05:30
dependabot[bot]andGitHub d05aa0d350 build(deps): bump the npm_and_yarn group
Bumps the npm_and_yarn group in /apps/OpenSign with 2 updates: [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express).


Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.20.0 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.20.0...4.21.0)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-12 06:34:33 +00:00
prafull-opensignlabsandGitHub 875dc086bf Merge pull request #1195 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/npm_and_yarn-31414ddb3e
build(deps): bump the npm_and_yarn group in /apps/OpenSign with 4 updates
2024-09-12 12:02:55 +05:30
dependabot[bot]andGitHub 4719ebd52a build(deps): bump the npm_and_yarn group
Bumps the npm_and_yarn group in /apps/OpenSignServer with 2 updates: [express](https://github.com/expressjs/express) and [dset](https://github.com/lukeed/dset).


Updates `express` from 4.20.0 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.20.0...4.21.0)

Updates `dset` from 3.1.3 to 3.1.4
- [Release notes](https://github.com/lukeed/dset/releases)
- [Commits](https://github.com/lukeed/dset/compare/v3.1.3...v3.1.4)

---
updated-dependencies:
- dependency-name: express
  dependency-type: direct:production
  dependency-group: npm_and_yarn
- dependency-name: dset
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-11 23:46:04 +00:00
AndrewandGitHub c8a436a7df Merge pull request #1200 from OpenSignLabs/validation 2024-09-11 20:10:47 +05:30
prafull-opensignlabs 8d6f4dd64e fix: issue user count 2024-09-11 20:10:19 +05:30
AndrewandGitHub 63486927d6 Merge pull request #1199 from OpenSignLabs/validation 2024-09-11 17:54:59 +05:30
prafull-opensignlabs c36a70f8d3 refactor: add user validation 2024-09-11 17:50:39 +05:30
AndrewandGitHub 39472d6874 Merge pull request #1197 from OpenSignLabs/validation 2024-09-11 12:26:18 +05:30
prafull-opensignlabs 6207e99cc0 fix: pagination issue 2024-09-11 12:23:09 +05:30
prafull-opensignlabsandGitHub bdc163199d Merge pull request #1196 from OpenSignLabs/raktima-patch-3
fix: after submitting the request sign form a blank page appears on mobile view
2024-09-11 11:06:13 +05:30
RaktimaNXG e7a04ede65 fix: after submitting the request sign form a blank page appears on mobile view 2024-09-11 10:52:18 +05:30
prafull-opensignlabsandGitHub f6aee4148d Merge pull request #1194 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/npm_and_yarn-7a84200127
build(deps): bump express from 4.19.2 to 4.20.0 in /apps/OpenSignServer in the npm_and_yarn group
2024-09-11 10:51:13 +05:30
dependabot[bot]andGitHub 6cb30a6597 build(deps): bump the npm_and_yarn group
Bumps the npm_and_yarn group in /apps/OpenSign with 4 updates: [body-parser](https://github.com/expressjs/body-parser), [express](https://github.com/expressjs/express), [path-to-regexp](https://github.com/pillarjs/path-to-regexp) and [serve-static](https://github.com/expressjs/serve-static).


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

Updates `express` from 4.19.2 to 4.20.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

Updates `express` from 4.19.2 to 4.20.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

Updates `path-to-regexp` from 0.1.7 to 0.1.10
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.7...v0.1.10)

Updates `serve-static` from 1.15.0 to 1.16.0
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...1.16.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: express
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: path-to-regexp
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: serve-static
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-11 04:58:46 +00:00
dependabot[bot]andGitHub 34fcfc8f30 build(deps): bump express
Bumps the npm_and_yarn group in /apps/OpenSignServer with 1 update: [express](https://github.com/expressjs/express).


Updates `express` from 4.19.2 to 4.20.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-11 04:58:43 +00:00
dependabot[bot]andGitHub bc89b33468 build(deps-dev): bump lint-staged in /apps/OpenSign
Bumps [lint-staged](https://github.com/lint-staged/lint-staged) from 15.2.9 to 15.2.10.
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/master/CHANGELOG.md)
- [Commits](https://github.com/lint-staged/lint-staged/compare/v15.2.9...v15.2.10)

---
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-09-11 04:58:17 +00:00
prafull-opensignlabsandGitHub af9af347e6 Merge pull request #1192 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/npm_and_yarn-7ee3b15cf5
build(deps-dev): bump postcss from 8.4.44 to 8.4.45 in /apps/OpenSign in the npm_and_yarn group
2024-09-11 10:27:04 +05:30
dependabot[bot]andGitHub 6f46279b52 build(deps-dev): bump postcss
Bumps the npm_and_yarn group in /apps/OpenSign with 1 update: [postcss](https://github.com/postcss/postcss).


Updates `postcss` from 8.4.44 to 8.4.45
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.4.44...8.4.45)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-10 12:53:18 +00:00
AndrewandGitHub 8f93ed8b8e Merge pull request #1191 from OpenSignLabs/validation 2024-09-10 16:41:59 +05:30
prafull-opensignlabs df91363ff4 fix: pagination issue 2024-09-10 16:41:08 +05:30
AndrewandGitHub e40e59714d Merge pull request #1190 from OpenSignLabs/validation 2024-09-10 13:43:28 +05:30
prafull-opensignlabs a43d95afc3 refactor: validation 2024-09-10 13:42:35 +05:30
AndrewandGitHub 8fa4b5ab22 Merge pull request #1189 from OpenSignLabs/validation 2024-09-09 15:46:44 +05:30
prafull-opensignlabs 68a030145f fix: handle invalid session token error while logging out 2024-09-09 15:44:01 +05:30
AndrewandGitHub 4f48657623 Merge pull request #1175 from OpenSignLabs/validation 2024-09-09 10:19:08 +05:30
dependabot[bot]andGitHub ff2f7f6586 build(deps-dev): bump jasmine in /apps/OpenSignServer
Bumps [jasmine](https://github.com/jasmine/jasmine-npm) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/jasmine/jasmine-npm/releases)
- [Changelog](https://github.com/jasmine/jasmine-npm/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine-npm/compare/v5.2.0...v5.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 00:12:15 +00:00
dependabot[bot]andGitHub 93f33d73d9 build(deps): bump nodemailer in /apps/OpenSignServer
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 6.9.14 to 6.9.15.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v6.9.14...v6.9.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 00:11:47 +00:00
dependabot[bot]andGitHub 402c176277 build(deps): bump mongoose from 8.6.0 to 8.6.1 in /apps/OpenSignServer
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.6.0 to 8.6.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.6.0...8.6.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-09-09 00:11:41 +00:00
prafull-opensignlabs 79b05b4a66 refactor: handle tab in menu 2024-09-06 20:10:56 +05:30
AndrewandGitHub fb9e37e71c Merge pull request #1173 from OpenSignLabs/validation 2024-09-06 19:37:54 +05:30
prafull-opensignlabs cd7a741d53 fix: size of tour 2024-09-06 19:36:27 +05:30
AmolandGitHub 5fdc90b4f9 Merge pull request #1172 from OpenSignLabs/validation
fix: quick send not working
2024-09-06 18:51:45 +05:30
prafull-opensignlabs e775840abb fix: quicksend not working 2024-09-06 18:50:15 +05:30
prafull-opensignlabs d7df54dfa1 fix: quick send not working 2024-09-06 18:45:35 +05:30
AndrewandGitHub 646d165d37 Merge pull request #1171 from OpenSignLabs/validation 2024-09-06 18:04:14 +05:30
prafull-opensignlabs ba0a80cca4 refactor: text change 2024-09-06 18:01:47 +05:30
AndrewandGitHub 9d59858542 Merge pull request #1170 from OpenSignLabs/validation 2024-09-06 17:59:59 +05:30
prafull-opensignlabs aaff0a5697 refactor: text translation 2024-09-06 17:58:18 +05:30
AndrewandGitHub 1bb7cee238 Merge pull request #1169 from OpenSignLabs/validation 2024-09-06 17:46:22 +05:30
prafull-opensignlabs d330e662d3 refactor: correct keys in translation files 2024-09-06 17:14:53 +05:30
prafull-opensignlabs b530ff76c4 refactor: translate text 2024-09-06 16:41:56 +05:30
AndrewandGitHub e59a166445 Merge pull request #1168 from OpenSignLabs/validation 2024-09-06 14:48:47 +05:30
prafull-opensignlabs 980e713735 refactor: validation 2024-09-06 14:47:57 +05:30
prafull-opensignlabsandGitHub 7a5228bbab Merge pull request #1167 from OpenSignLabs/validation
refactor: validation
2024-09-06 12:00:26 +05:30
prafull-opensignlabs 5a26e5c2ee refactor: validaiton 2024-09-06 11:56:54 +05:30
prafull-opensignlabsandGitHub c2dccf17ce Merge pull request #1166 from OpenSignLabs/validation
fix: email count not update until re-login
2024-09-05 19:21:06 +05:30
prafull-opensignlabs 181d6bf1e4 fix: email count not update until re-login 2024-09-05 19:19:15 +05:30
prafull-opensignlabsandGitHub a07be3b5dc Merge pull request #1165 from OpenSignLabs/validation
feat: show email used count
2024-09-05 18:38:23 +05:30
prafull-opensignlabs af2666c83d feat: show email used count 2024-09-05 18:36:30 +05:30
prafull-opensignlabsandGitHub cad9929b56 Merge pull request #1164 from OpenSignLabs/raktima-patch-3
fix: display incorrect count of document on dashboard
2024-09-05 18:35:48 +05:30
RaktimaNXG 3b79da527d fix: display incorrect count of document on dashboard 2024-09-05 18:32:42 +05:30
AndrewandGitHub 3eaba55823 Merge pull request #1163 from OpenSignLabs/validation 2024-09-05 17:00:57 +05:30
prafull-opensignlabs e2eddb6362 refactor: validation 2024-09-05 16:59:47 +05:30
prafull-opensignlabsandGitHub d885570dd8 Merge pull request #1162 from OpenSignLabs/raktima-patch-2
fix: drop widgets in center of document in mobile view
2024-09-05 10:10:27 +05:30
RaktimaNXG f3de883e39 fix: increase close button size of react tour in mobile view 2024-09-05 09:59:07 +05:30
RaktimaNXG 6da22d23a8 fix: remove console 2024-09-04 19:41:39 +05:30
RaktimaNXG 49461a43bb fix: drop widgets in center of document in mobile view 2024-09-04 19:39:10 +05:30
prafull-opensignlabsandGitHub d4414f99af Merge pull request #1161 from OpenSignLabs/raktima-patch-2
fix: resolve issue to allow edits options on completed documents in the SignYourself flow
2024-09-04 18:52:06 +05:30
RaktimaNXG 52f37b0a37 fix: resolve issue to allow edits options on completed documents in the SignYourself flow 2024-09-04 18:31:19 +05:30
prafull-opensignlabsandGitHub ce949e4029 Merge pull request #1160 from OpenSignLabs/raktima-patch-2
fix: Issue with changing signers list order in report view button
2024-09-04 17:32:09 +05:30
RaktimaNXG 19d3465a13 add comment 2024-09-04 16:03:13 +05:30
RaktimaNXG a0e1425c74 fix: handle condition to send document for signing 2024-09-04 15:58:50 +05:30
prafull-opensignlabsandGitHub a5d8379b18 Merge pull request #1159 from OpenSignLabs/raktima-patch-3
fix: issue while rename document/folder in opensign-drive
2024-09-04 15:44:15 +05:30
RaktimaNXG 9ac487ea5b fix: unexpected view button of signers for signyourself document in report 2024-09-04 15:41:25 +05:30
RaktimaNXG 9cc9a93c3d fix: handle error 2024-09-04 15:13:10 +05:30
RaktimaNXG 4f2b0313de refactor code 2024-09-04 15:09:38 +05:30
RaktimaNXG 806894b24b fix : issue while rename the document/folder in opensign-drive 2024-09-04 14:48:49 +05:30
RaktimaNXG cbb737f96b Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-04 11:53:22 +05:30
prafull-opensignlabsandGitHub 5407f8156a Merge pull request #1157 from OpenSignLabs/raktima-patch-3
refactor: change order in decline modal
2024-09-04 10:08:47 +05:30
RaktimaNXG a0c2fd09ae fix: resolve issue of disappear widgets when adding it on multiple pages 2024-09-03 21:05:56 +05:30
RaktimaNXG 48b744c8ba refactor code 2024-09-03 20:59:41 +05:30
prafull-opensignlabsandGitHub 812b946d85 Merge pull request #1156 from OpenSignLabs/raktima-patch-3
feat: display the email of the user who declined the document along with the decline reason in the document decline popup.
2024-09-03 20:47:59 +05:30
RaktimaNXG 27eca2b470 fix: add error iin alert 2024-09-03 20:43:45 +05:30
RaktimaNXG ab6c88bebd feat: save declined by user details in-progress report 2024-09-03 20:23:54 +05:30
RaktimaNXG d547e0552a fix: handle error 2024-09-03 20:11:14 +05:30
RaktimaNXG 03fc0b84cb fix: remove console 2024-09-03 19:53:43 +05:30
RaktimaNXG b0825634ee resolve conflict 2024-09-03 19:50:42 +05:30
RaktimaNXG 9675e051bd feat: display the email of the user who declined the document along with the decline reason in the document decline popup. 2024-09-03 19:48:21 +05:30
AndrewandGitHub 981654a2aa Merge pull request #1155 from OpenSignLabs/validation 2024-09-03 19:32:05 +05:30
prafull-opensignlabs 8c97ea0da0 fix: duplicate entry issue 2024-09-03 19:30:51 +05:30
prafull-opensignlabsandGitHub 6c80911c90 Merge pull request #1154 from OpenSignLabs/rotatepage_document
feat: introduce a guide video in profile page for setting up a public template and managing the public profile
2024-09-03 16:57:38 +05:30
RaktimaNXG 740b5ea719 fix: placeholder disappear issue in template flow 2024-09-03 15:49:37 +05:30
RaktimaNXG 76c958fb7a fix: remove unnecessary variable from locales en and fr file 2024-09-03 13:54:08 +05:30
RaktimaNXG 28c53fd115 fix: update text of demo video 2024-09-03 13:31:19 +05:30
RaktimaNXG 6d9fc8b3df fix: resolve error 2024-09-03 13:13:49 +05:30
RaktimaNXG a044f4eceb refactor code 2024-09-03 12:33:23 +05:30
RaktimaNXG 1f35efcd51 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into rotatepage_document 2024-09-03 12:27:37 +05:30
RaktimaNXG c587abddae fix: on edit button show upgrade now validation issue in profile page 2024-09-03 12:26:49 +05:30
prafull-opensignlabsandGitHub d7f4c9a329 Merge pull request #1153 from OpenSignLabs/validation
build(deps): update dependencies
2024-09-03 12:17:00 +05:30
prafull-opensignlabs 8a3e6f5781 build(deps): update dependencies 2024-09-03 12:15:06 +05:30
RaktimaNXG cfbebc4989 refactor code 2024-09-02 21:10:15 +05:30
RaktimaNXG 99ec772d64 feat: embed demo video to create public profile 2024-09-02 21:01:44 +05:30
RaktimaNXG 1f60b0bbcb Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-02 20:03:47 +05:30
RaktimaNXG c8fb9a5b09 fix: signers list order change issue in reports 2024-09-02 20:01:18 +05:30
AndrewandGitHub d6f117c7bf Merge pull request #1152 from OpenSignLabs/validation
fix: failed to load pdf issue on self-host with docker
2024-09-02 19:34:05 +05:30
prafull-opensignlabs a765668354 fix: failed to load pdf issue on self-host with docker 2024-09-02 19:31:38 +05:30
prafull-opensignlabs da053658e8 refactor: handle uncuaght err in send mail 2024-09-02 19:29:36 +05:30
prafull-opensignlabsandGitHub cb5bcd72e4 Merge pull request #1151 from OpenSignLabs/validation
refactor: add logout btn
2024-09-02 13:07:43 +05:30
prafull-opensignlabs d4d53623ef refactor: add logout btn 2024-09-02 13:03:47 +05:30
RaktimaNXG 8a31b008d2 fix: ui design of mobile view on public script load 2024-09-02 12:55:53 +05:30
prafull-opensignlabsandGitHub fa4541b59f Merge pull request #1150 from OpenSignLabs/validation
fix: get is not function
2024-09-02 11:14:08 +05:30
prafull-opensignlabs aae6b32828 fix: get is not function 2024-09-02 11:09:23 +05:30
RaktimaNXG 7158d6735b resolve conflict 2024-09-02 09:51:26 +05:30
AmolandGitHub f4b896d5e6 Merge pull request #1141 from OpenSignLabs/validation 2024-09-01 18:45:59 +05:30
prafull-opensignlabs d2f814e6f8 refactor: prepare for v2.3.0 2024-09-01 18:45:07 +05:30
prafull-opensignlabs 87cec67e60 refactor: add help test, remove uncessary code 2024-09-01 17:51:56 +05:30
AmolandGitHub 041c46f38e Merge pull request #1140 from OpenSignLabs/validation 2024-08-31 23:30:23 +05:30
prafull-opensignlabs 54a7628f75 fix: quick send not working 2024-08-31 23:28:39 +05:30
AmolandGitHub 7cbbc16390 Merge pull request #1139 from OpenSignLabs/validation 2024-08-31 23:02:51 +05:30
prafull-opensignlabs 14532bf6db refactor: change help text 2024-08-31 22:59:45 +05:30
AmolandGitHub 7bfe18e718 Merge pull request #1137 from OpenSignLabs/validation 2024-08-30 20:04:58 +05:30
prafull-opensignlabs 07903a07a8 refactor: add help text for test token 2024-08-30 19:53:17 +05:30
RaktimaNXG 14fe3b2b98 fix: resolve conflict 2024-08-30 17:55:27 +05:30
AndrewandGitHub fa2756ff3f Merge pull request #1136 from OpenSignLabs/validation 2024-08-30 17:50:58 +05:30
prafull-opensignlabs 9d4841b4dc feat: add api testing token 2024-08-30 17:47:40 +05:30
RaktimaNXG 4b74b08825 fix: update tour messages of show public profile setup 2024-08-30 17:44:03 +05:30
RaktimaNXG 6457cfc958 refactor code 2024-08-30 12:02:59 +05:30
RaktimaNXG 30081c893b fix: tagline input box design 2024-08-30 11:59:34 +05:30
RaktimaNXG e69890a47e feat: add tour message in profile page 2024-08-30 11:42:11 +05:30
prafull-opensignlabsandGitHub 9d31162eec Merge pull request #1093 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/posthog-node-4.1.1
build(deps): bump posthog-node from 4.0.1 to 4.1.1 in /apps/OpenSignServer
2024-08-29 14:18:44 +05:30
prafull-opensignlabsandGitHub 0031cc7418 Merge pull request #1094 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/eslint-9.9.1
build(deps-dev): bump eslint from 9.9.0 to 9.9.1 in /apps/OpenSignServer
2024-08-29 10:57:32 +05:30
prafull-opensignlabsandGitHub 57132892b5 Merge pull request #1130 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/npm_and_yarn-271b7fd07e
build(deps): bump the npm_and_yarn group in /apps/OpenSign with 2 updates
2024-08-29 10:56:19 +05:30
prafull-opensignlabsandGitHub be084d1570 Merge pull request #1129 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/npm_and_yarn-07a849e1ae
build(deps): bump micromatch from 4.0.7 to 4.0.8 in /apps/OpenSignServer in the npm_and_yarn group
2024-08-29 10:55:41 +05:30
prafull-opensignlabsandGitHub 03d29a9ebd Merge pull request #1096 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/mongoose-8.5.4
build(deps): bump mongoose from 8.5.3 to 8.5.4 in /apps/OpenSignServer
2024-08-29 10:49:14 +05:30
dependabot[bot]andGitHub e9a837b534 build(deps): bump the npm_and_yarn group
Bumps the npm_and_yarn group in /apps/OpenSign with 2 updates: [micromatch](https://github.com/micromatch/micromatch) and [webpack](https://github.com/webpack/webpack).


Updates `micromatch` from 4.0.7 to 4.0.8
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

Updates `webpack` from 5.93.0 to 5.94.0
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.93.0...v5.94.0)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: webpack
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-29 05:18:15 +00:00
prafull-opensignlabsandGitHub 36c601f9fa Merge pull request #1095 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/axios-1.7.5
build(deps): bump axios from 1.7.4 to 1.7.5 in /apps/OpenSignServer
2024-08-29 10:48:06 +05:30
dependabot[bot]andGitHub af41234484 build(deps): bump micromatch
Bumps the npm_and_yarn group in /apps/OpenSignServer with 1 update: [micromatch](https://github.com/micromatch/micromatch).


Updates `micromatch` from 4.0.7 to 4.0.8
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.7...4.0.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-29 05:17:14 +00:00
prafull-opensignlabsandGitHub bcaf03a770 Merge pull request #1091 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/axios-1.7.5
build(deps): bump axios from 1.7.4 to 1.7.5 in /apps/OpenSign
2024-08-29 10:45:59 +05:30
prafull-opensignlabsandGitHub 9429b009ce Merge pull request #1120 from OpenSignLabs/rotatepage_document
fix:  handle conditions and throw errors for public template script loading
2024-08-29 10:44:55 +05:30
RaktimaNXG 3395785cbc refactor code 2024-08-28 17:12:17 +05:30
RaktimaNXG 97c048c7af fix: remove share option from public templateId 2024-08-28 17:07:22 +05:30
RaktimaNXG 726eb2ffe6 fix: templateid share option in mobile view 2024-08-28 11:54:55 +05:30
RaktimaNXG a9f22d3a10 fix: share templatId issue 2024-08-28 11:08:15 +05:30
prafull-opensignlabsandGitHub 5a6437e371 Merge pull request #1116 from OpenSignLabs/validation
fix: all contacts are not visible in request sign form's signers inputbox
2024-08-28 10:59:29 +05:30
prafull-opensignlabs 4ffc98b371 fix: all contacts are not visible in request sign form's signers inputbox 2024-08-28 10:54:56 +05:30
RaktimaNXG 16c73d4f91 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into rotatepage_document 2024-08-27 17:22:53 +05:30
RaktimaNXG 96aff5230f fix: handle condition to load public-template script without templatId 2024-08-27 17:16:21 +05:30
prafull-opensignlabsandGitHub b298571be8 Merge pull request #1106 from OpenSignLabs/validation
fix: api token not visible
2024-08-27 13:26:09 +05:30
prafull-opensignlabs 7b7b10c9a5 fix: api token visible 2024-08-27 13:22:48 +05:30
dependabot[bot]andGitHub 56beed5ad5 build(deps): bump mongoose from 8.5.3 to 8.5.4 in /apps/OpenSignServer
Bumps [mongoose](https://github.com/Automattic/mongoose) from 8.5.3 to 8.5.4.
- [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.5.3...8.5.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 00:37:01 +00:00
dependabot[bot]andGitHub 8c8ef0ea53 build(deps): bump axios from 1.7.4 to 1.7.5 in /apps/OpenSignServer
Bumps [axios](https://github.com/axios/axios) from 1.7.4 to 1.7.5.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.4...v1.7.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 00:36:48 +00:00
dependabot[bot]andGitHub 90377d98c0 build(deps-dev): bump eslint from 9.9.0 to 9.9.1 in /apps/OpenSignServer
Bumps [eslint](https://github.com/eslint/eslint) from 9.9.0 to 9.9.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.9.0...v9.9.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 00:36:40 +00:00
dependabot[bot]andGitHub d167e474d8 build(deps): bump posthog-node in /apps/OpenSignServer
Bumps [posthog-node](https://github.com/PostHog/posthog-js-lite/tree/HEAD/posthog-node) from 4.0.1 to 4.1.1.
- [Release notes](https://github.com/PostHog/posthog-js-lite/releases)
- [Changelog](https://github.com/PostHog/posthog-js-lite/blob/main/posthog-node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js-lite/commits/posthog-node-v4.1.1/posthog-node)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 00:36:32 +00:00
dependabot[bot]andGitHub 0cfacfe057 build(deps): bump axios from 1.7.4 to 1.7.5 in /apps/OpenSign
Bumps [axios](https://github.com/axios/axios) from 1.7.4 to 1.7.5.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.4...v1.7.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 00:32:26 +00:00
RaktimaandGitHub fbda5231d0 Merge pull request #1089 from OpenSignLabs/validation
build(deps): update dependencies
2024-08-23 17:36:40 +05:30
prafull-opensignlabs bd4d333d59 build(deps): update dependencies 2024-08-23 17:32:53 +05:30
prafull-opensignlabs 07878207de fix: validation of api token 2024-08-23 17:06:53 +05:30
RaktimaandGitHub b2352877b9 Merge pull request #1088 from OpenSignLabs/validation
feat: add download btn in expired document
2024-08-23 13:08:36 +05:30
prafull-opensignlabs 669ba9e8cb feat: add download btn in expired document 2024-08-23 12:40:38 +05:30
AmolandGitHub 5fa8a76219 Merge pull request #1087 from OpenSignLabs/validation 2024-08-22 21:07:40 +05:30
prafull-opensignlabs 476dcf051c feat: implement autosave feature in signyourself 2024-08-22 20:28:49 +05:30
prafull-opensignlabsandGitHub db96dbba6a Merge pull request #1086 from OpenSignLabs/validation
feat: autosave document and template while drafting
2024-08-22 17:38:04 +05:30
prafull-opensignlabs e9c6509970 feat: autosave document and template while drafting 2024-08-22 17:23:53 +05:30
prafull-opensignlabsandGitHub b26c7dfa8f Merge pull request #1085 from OpenSignLabs/rotatepage_document
fix: resolve daisy-ui theme issue for public-sign script loading
2024-08-22 15:10:37 +05:30
RaktimaNXG 3f251761d6 fix: resolve daisy-ui theme issue for public sign script loading 2024-08-22 14:53:45 +05:30
prafull-opensignlabsandGitHub bc9d489ee8 Merge pull request #1084 from OpenSignLabs/validation
fix: wrong alert is visible after loader in quicksend popup
2024-08-22 13:03:10 +05:30
prafull-opensignlabs 271563ba9f refactor: correct variable 2024-08-22 13:01:17 +05:30
prafull-opensignlabs 82aba20e83 refactor: remove unnecessary code from add user 2024-08-22 12:56:18 +05:30
prafull-opensignlabs 12f60c9be5 fix: wrong alert is visible after loader in quicksend popup 2024-08-22 12:49:48 +05:30
AndrewandGitHub 1c98a354c7 Merge pull request #1082 from OpenSignLabs/validation 2024-08-21 18:53:30 +05:30
prafull-opensignlabs 9ca7aee859 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into validation 2024-08-21 18:14:05 +05:30
prafull-opensignlabs 937b306128 fix: remove unncessary query from buyaddonusers 2024-08-21 18:10:57 +05:30
prafull-opensignlabsandGitHub 69a2ab0ce2 Merge pull request #1079 from OpenSignLabs/rotatepage_document
feat: implement PDF page rotation feature
2024-08-21 17:16:58 +05:30
RaktimaNXG b720711217 refactor code 2024-08-21 16:20:25 +05:30
prafull-opensignlabs bf56348c62 fix: add users count 2024-08-21 14:39:36 +05:30
RaktimaNXG f993817cb6 fix: issue of onclick yes button rotate pdf page 2024-08-21 14:36:01 +05:30
RaktimaNXG 1c2b4794f3 fix: add copy option eside to widget for signature,stamp and text widgets 2024-08-21 13:55:54 +05:30
prafull-opensignlabs 523d210503 fix: quicksend validation 2024-08-21 12:57:08 +05:30
RaktimaNXG 39da8bd636 refactor code 2024-08-21 10:02:46 +05:30
RaktimaNXG fbea98bbe6 fix: add validation of rotation functionality, add rotation functionality for mobile view 2024-08-20 20:41:14 +05:30
prafull-opensignlabs 5d89f724f4 feat: validtion of quicksend 2024-08-20 15:56:00 +05:30
prafull-opensignlabsandGitHub 0313b6f3ab Merge pull request #1077 from OpenSignLabs/raktima-patch-1
fix: owner's subscription expired alert issue during sign document
2024-08-20 12:27:29 +05:30
RaktimaNXG fe92111dc9 fix: owner's subscription expired alert issue during sign document 2024-08-20 12:08:55 +05:30
prafull-opensignlabs 9a03bfb9e7 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into validation 2024-08-19 12:10:27 +05:30
prafull-opensignlabs 882db6acc9 fix: validation of generate token 2024-08-19 11:28:22 +05:30
RaktimaNXG 5596468b03 refactor code 2024-08-16 18:06:52 +05:30
RaktimaNXG eb66de1220 fix: issue in template flow using roate pdf page functionality 2024-08-16 18:02:29 +05:30
RaktimaNXG f97896f6a6 fix: translate rotate icon title 2024-08-16 17:26:14 +05:30
RaktimaNXG cfb47f880e fix: translate rotate icon title 2024-08-16 17:25:58 +05:30
RaktimaNXG eba87776a0 fix: resolve conflict 2024-08-16 17:14:30 +05:30
RaktimaNXG dda0e6d676 fix: handle pdfRequest sign flow rotate pdf page not allowed 2024-08-16 17:11:51 +05:30
prafull-opensignlabsandGitHub 8d514644c1 Merge pull request #1066 from OpenSignLabs/raktima-opensignlabs-patch-12
feat: add customizable font and color options for Date, Dropdown, Checkbox, and Radio Button widgets.
2024-08-16 15:07:14 +05:30
prafull-opensignlabsandGitHub 0870a34391 Merge pull request #1065 from OpenSignLabs/header_strip
feat: add a header strip to display an announcement about the public profile feature launch
2024-08-16 15:00:21 +05:30
RaktimaNXG 38118986a3 feat: add a header strip to display an announcement about the public profile feature launch 2024-08-16 14:13:51 +05:30
RaktimaNXG 1b4e5247ad feat: implement PDF page rotation feature 2024-08-16 14:10:29 +05:30
AmolandGitHub 3ef2eeef93 Merge pull request #1063 from OpenSignLabs/validation 2024-08-14 20:24:25 +05:30
prafull-opensignlabs 4d5682e79b fix: add user is not working 2024-08-14 20:09:19 +05:30
RaktimaNXG 24100d203f fix: setting icon space issue on widgets 2024-08-14 20:07:14 +05:30
RaktimaNXG 86946a2c21 fix: date setting modal design in mobile view 2024-08-14 20:01:11 +05:30
RaktimaNXG 54f3e4cc6f fix: remove comment from signPdf function 2024-08-14 19:34:55 +05:30
RaktimaNXG d9360b2d8c add comment 2024-08-14 19:29:19 +05:30
RaktimaNXG abb47118c0 fix: dropdown font size issue using pdf-lib 2024-08-14 19:24:23 +05:30
RaktimaNXG 9e4e55bb8d Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-opensignlabs-patch-12 2024-08-14 16:41:30 +05:30
AmolandGitHub 498f720507 Merge pull request #1058 from OpenSignLabs/validation 2024-08-14 16:36:05 +05:30
RaktimaNXG b9bc9cb75e Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-opensignlabs-patch-12 2024-08-14 14:34:40 +05:30
RaktimaNXG 3b65917422 fix: checkbox options position 2024-08-14 14:18:32 +05:30
prafull-opensignlabs 862dd78cd5 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into validation 2024-08-14 12:21:35 +05:30
prafull-opensignlabsandGitHub d0bc48d119 Merge pull request #1054 from OpenSignLabs/deps
build(deps): update dependencies
2024-08-14 11:49:27 +05:30
prafull-opensignlabs 37462ba282 build(deps): update dependencies 2024-08-14 11:46:22 +05:30
prafull-opensignlabs 99e7f5862a build(deps): update dependencies 2024-08-14 11:46:14 +05:30
RaktimaNXG 048e529bb5 fix: checkbox & radio button labels font color issue 2024-08-14 11:41:18 +05:30
AmolandGitHub 5b8b06f3a1 Merge pull request #1051 from OpenSignLabs/prafull-opensignlabs-patch-28 2024-08-13 19:27:42 +05:30
prafull-opensignlabsandGitHub 3c7d025802 refactor: Update env 2024-08-13 19:26:00 +05:30
prafull-opensignlabsandGitHub 4f8b46da14 Merge pull request #1049 from OpenSignLabs/prafull-opensignlabs-patch-27
refactor: Update env
2024-08-13 19:20:25 +05:30
prafull-opensignlabsandGitHub 0b386a882e refactor: Update env 2024-08-13 19:17:28 +05:30
RaktimaNXG 6329d58a67 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-opensignlabs-patch-12 2024-08-13 17:00:15 +05:30
RaktimaNXG af0d780ac0 fix: add customiize font size and color in checkbox, radio button,dropdown widgets 2024-08-13 15:25:06 +05:30
prafull-opensignlabs 45c2c863ef Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into validation 2024-08-13 15:13:43 +05:30
prafull-opensignlabs 3372e16409 Fix: issue of unccesary alert is occured in request sign flow 2024-08-13 15:02:26 +05:30
prafull-opensignlabs a04fad9c1f Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into validation 2024-08-13 13:32:42 +05:30
prafull-opensignlabs a8a89dc9bd Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into validation 2024-08-09 18:03:34 +05:30
prafull-opensignlabs a0305a745a fix: addon not updated correctly 2024-08-09 18:03:14 +05:30
prafull-opensignlabs f06b702360 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into validation 2024-08-09 16:38:29 +05:30
prafull-opensignlabs 6f104c4ee6 fix: langauage support in add user 2024-08-09 13:05:16 +05:30
prafull-opensignlabs c721580516 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into validation 2024-08-09 11:23:10 +05:30
prafull-opensignlabs 1fe1381fe2 refactor: user valdation 2024-08-08 20:40:13 +05:30
prafull-opensignlabs 870a25dad1 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into validation 2024-08-08 13:02:39 +05:30
prafull-opensignlabs e4e5d5c6d7 Revert "refactor: change url"
This reverts commit 7e45f23b6c.
2024-08-08 12:11:18 +05:30
prafull-opensignlabs 7e45f23b6c refactor: change url 2024-08-08 12:03:46 +05:30
prafull-opensignlabs 719f61f612 refactor: change url 2024-08-08 12:00:07 +05:30
prafull-opensignlabs 908edda14b feat: validation to add user 2024-08-07 20:02:41 +05:30
99 changed files with 10759 additions and 7494 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
# Note that any data in staging will get cleared from time to time and you may have to sign up again and again
# Set it to the URL form where the app home page will be accessed
PUBLIC_URL=http://localhost:3000
PUBLIC_URL=https://localhost:3001
# Set it to true if you want to generate the Sourcemap for debugging
GENERATE_SOURCEMAP=false
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
+462 -613
View File
File diff suppressed because it is too large Load Diff
+15 -15
View File
@@ -4,16 +4,16 @@
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.8.7",
"@lottiefiles/dotlottie-react": "^0.8.9",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.1.1",
"@radix-ui/themes": "^3.1.3",
"@react-pdf/renderer": "^3.4.4",
"@reduxjs/toolkit": "^2.2.7",
"axios": "^1.7.3",
"axios": "^1.7.5",
"file-saver": "^2.0.5",
"i18next": "^23.12.2",
"i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.2",
"i18next-http-backend": "^2.6.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"nth-check": "^2.1.1",
@@ -36,10 +36,10 @@
"react-i18next": "^15.0.1",
"react-konva": "^18.2.10",
"react-pdf": "^9.1.0",
"react-quill-new": "^3.2.2",
"react-quill-new": "^3.3.0",
"react-redux": "^9.1.2",
"react-rnd": "^10.4.11",
"react-router-dom": "^6.26.0",
"react-router-dom": "^6.26.1",
"react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.8.0",
@@ -52,7 +52,7 @@
"regex-parser": "^2.3.0",
"serve": "^14.2.3",
"styled-components": "^5.3.0",
"web-vitals": "^4.2.2",
"web-vitals": "^4.2.3",
"ws": "^8.18.0"
},
"scripts": {
@@ -94,10 +94,10 @@
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/preset-env": "^7.25.4",
"@babel/preset-react": "^7.24.7",
"@babel/runtime-corejs2": "^7.25.0",
"autoprefixer": "^10.4.19",
"@babel/runtime-corejs2": "^7.25.4",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.1.3",
"clean-webpack-plugin": "^4.0.0",
"commitizen": "^4.3.0",
@@ -109,12 +109,12 @@
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.34.3",
"lint-staged": "^15.2.8",
"mini-css-extract-plugin": "^2.9.0",
"postcss": "^8.4.41",
"lint-staged": "^15.2.10",
"mini-css-extract-plugin": "^2.9.1",
"postcss": "^8.4.45",
"prettier": "^3.3.3",
"pretty-quick": "^4.0.0",
"tailwindcss": "^3.4.9",
"tailwindcss": "^3.4.10",
"terser-webpack-plugin": "^5.3.10",
"webpack-cli": "^5.1.4"
},
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+319 -185
View File
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
import { copytoData } from "../constant/Utils";
import { copytoData, fetchSubscriptionInfo } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const";
import { useTranslation } from "react-i18next";
function generatePassword(length) {
@@ -27,15 +27,64 @@ const AddUser = (props) => {
password: "",
role: ""
});
const [amount, setAmount] = useState({ quantity: 1, price: 0 });
const [planInfo, setPlanInfo] = useState({
priceperUser: 0,
price: 0,
totalAllowedUser: 0,
adminId: ""
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
const [allowedUser, setAllowedUser] = useState(0);
const [err, setErr] = useState("");
const role = ["OrgAdmin", "Editor", "User"];
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
useEffect(() => {
getTeamList();
// eslint-disable-next-line
}, []);
const getTeamList = async () => {
if (isEnableSubscription) {
try {
setIsLoader(true);
const resSub = await fetchSubscriptionInfo();
if (!resSub.error) {
setPlanInfo((prev) => ({
...prev,
priceperUser: resSub.price,
totalAllowedUser: resSub.totalAllowedUser,
adminId: resSub.adminId
}));
setAmount((prev) => ({ ...prev, price: resSub.price }));
const res = await Parse.Cloud.run("allowedusers");
if (props.setFormHeader) {
if (res > 0) {
props.setFormHeader(t("add-user"));
} else {
props.setFormHeader(
resSub.adminId !== extUser?.objectId
? "Unauthorized"
: t("Add-seats")
);
}
}
setAllowedUser(res);
} else {
setAllowedUser(0);
}
} catch (err) {
setAllowedUser(0);
console.log("Err in alloweduser", err);
setErr(err.message);
} finally {
setIsLoader(false);
}
}
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
const teamRes = await Parse.Cloud.run("getteams", { active: true });
if (teamRes.length > 0) {
@@ -67,14 +116,12 @@ const AddUser = (props) => {
e.preventDefault();
e.stopPropagation();
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
setIsLoader(true);
setIsFormLoader(true);
const res = await checkUserExist();
if (res) {
props.setIsAlert({ type: "danger", msg: t("user-already-exist") });
setIsLoader(false);
setTimeout(() => {
props.setIsAlert({ type: "success", msg: "" });
}, 1000);
setIsFormLoader(false);
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1000);
} else {
try {
const extUser = new Parse.Object("contracts_Users");
@@ -157,14 +204,8 @@ const AddUser = (props) => {
props.handleUserData(parseData);
}
setIsLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
setIsFormLoader(false);
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
}
} catch (err) {
console.log("err ", err);
@@ -204,19 +245,13 @@ const AddUser = (props) => {
}
props.handleUserData(parseData);
}
setIsLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
setIsFormLoader(false);
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
}
}
} catch (err) {
console.log("err", err);
setIsLoader(false);
setIsFormLoader(false);
props.setIsAlert({
type: "danger",
msg: t("something-went-wrong-mssg")
@@ -229,13 +264,7 @@ const AddUser = (props) => {
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
if (props.closePopup) {
props.closePopup();
}
@@ -245,10 +274,7 @@ const AddUser = (props) => {
if (name === "email") {
value = value?.toLowerCase()?.replace(/\s/g, "");
}
setFormdata((prev) => ({
...prev,
[name]: value
}));
setFormdata((prev) => ({ ...prev, [name]: value }));
};
const copytoclipboard = (text) => {
@@ -256,163 +282,271 @@ const AddUser = (props) => {
props.setIsAlert({ type: "success", msg: t("copied") });
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
};
const handlePricePerUser = (e) => {
const quantity = e.target.value;
const price = e.target?.value > 0 ? planInfo.priceperUser * quantity : 0;
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleAddOnSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsFormLoader(true);
try {
const resAddon = await Parse.Cloud.run("buyaddonusers", {
users: amount.quantity
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setAllowedUser(amount.quantity);
setPlanInfo((obj) => ({ ...obj, totalAllowedUser: _resAddon.addon }));
}
if (props.handleBuyUsers) {
props.handleBuyUsers(amount.quantity, _resAddon.addon);
}
}
} catch (err) {
console.log("Err in buy addon", err);
props.setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 2000);
setIsFormLoader(false);
}
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-[#ffffff]">
<Title title={"Add User"} />
{isLoader && (
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50 rounded-box">
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title={t("add-user")} />
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<div className="w-full mx-auto">
<form onSubmit={handleSubmit}>
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */}
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
{t("password")}
</label>
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
{!isLoader ? (
<>
{err ? (
<div className="flex justify-center items-center h-[70px] text-[18px]">
{err}
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
<input
type="text"
name="phone"
placeholder="optional"
value={formdata.phone}
onChange={(e) => handleChange(e)}
// required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
{isEnableSubscription && (
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
Team<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.team}
onChange={(e) => handleChange(e)}
name="team"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
select
</option>
{teamList.length > 0 &&
teamList.map((x) => (
<option key={x.objectId} value={x.objectId}>
{x.Name}
</option>
))}
</select>
) : (
<div className="w-full mx-auto">
{!isEnableSubscription || allowedUser > 0 ? (
<form onSubmit={handleSubmit}>
{isEnableSubscription && (
<p className="flex justify-center mb-1">
{t("remaing-users")}{" "}
<span
className={`${
allowedUser < 2 ? "op-text-accent" : "op-text-primary"
} font-medium ml-1`}
>
{allowedUser} {t("of")} {planInfo.totalAllowedUser}
</span>
</p>
)}
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
required
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label className="block text-xs text-gray-700 font-semibold">
{t("password")}
</label>
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
<input
type="text"
name="phone"
placeholder="optional"
value={formdata.phone}
onChange={(e) => handleChange(e)}
// required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
{isEnableSubscription && (
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("team")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
name="team"
value={formdata.team}
onChange={(e) => handleChange(e)}
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{teamList.length > 0 &&
teamList.map((x) => (
<option key={x.objectId} value={x.objectId}>
{x.Name}
</option>
))}
</select>
</div>
)}
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("cancel")}
</div>
</div>
</form>
) : (
<>
{planInfo.adminId !== extUser?.objectId ? (
<div className="mb-3 mt-1 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", {
adminEmail: extUser?.TenantId?.EmailAddress
})}
</div>
) : (
<form onSubmit={handleAddOnSubmit}>
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
{t("additional-users")}
</p>
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("Quantity-of-users")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="number"
name="quantity"
value={amount.quantity}
onChange={(e) => handlePricePerUser(e)}
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
required
/>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {planInfo.priceperUser})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full">
{t("Proceed")}
</button>
</form>
)}
</>
)}
</div>
)}
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
Role<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
select
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
Submit
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
Cancel
</div>
</div>
</form>
</div>
</>
) : (
<div className="w-full h-[200px] inset-0 flex justify-center items-center z-50">
<Loader />
</div>
)}
</div>
);
};
+335 -111
View File
@@ -3,9 +3,14 @@ import axios from "axios";
import SuggestionInput from "./shared/fields/SuggestionInput";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import { isEnableSubscription } from "../constant/const";
import { fetchSubscription } from "../constant/Utils";
import { useNavigate } from "react-router-dom";
const BulkSendUi = (props) => {
const { t } = useTranslation();
const navigate = useNavigate();
const [forms, setForms] = useState([]);
const [formId, setFormId] = useState(2);
const formRef = useRef(null);
@@ -13,6 +18,22 @@ const BulkSendUi = (props) => {
const [isSubmit, setIsSubmit] = useState(false);
const [allowedForm, setAllowedForm] = useState(0);
const [isSignatureExist, setIsSignatureExist] = useState();
const [isBulkAvailable, setIsBulkAvailable] = useState(false);
const quantityList = [500, 1000, 5000, 50000];
const [amount, setAmount] = useState({
price: (75.0).toFixed(2),
quantity: 500,
priceperbulksend: 0.15,
totalcredits: 0
});
const [isQuotaReached, setIsQuotaReached] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [isFreePlan, setIsFreePlan] = useState(false);
const [admin, setAdmin] = useState({
objectId: "",
email: "",
isAdmin: true
});
const allowedSigners = 50;
useEffect(() => {
signatureExist();
@@ -20,14 +41,71 @@ const BulkSendUi = (props) => {
}, []);
//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);
const signatureExist = async () => {
if (isEnableSubscription) {
setIsLoader(true);
try {
const subscription = await fetchSubscription();
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (
subscription?.adminId &&
extUser?.objectId === subscription?.adminId
) {
setAdmin((obj) => ({
...obj,
objectId: subscription?.adminId,
isAdmin: true
}));
} else {
setAdmin((obj) => ({
...obj,
isAdmin: false,
email: extUser?.TenantId?.EmailAddress
}));
}
setAdmin((obj) => ({ ...obj, objectId: subscription?.adminId }));
if (subscription?.plan === "freeplan") {
setIsFreePlan(true);
}
const resCredits = await Parse.Cloud.run("allowedcredits");
if (resCredits) {
const allowedcredits = resCredits?.allowedcredits || 0;
const addoncredits = resCredits?.addoncredits || 0;
const totalcredits = allowedcredits + addoncredits;
if (totalcredits > 0) {
if (subscription?.plan !== "freeplan") {
setIsBulkAvailable(true);
}
}
setAmount((obj) => ({ ...obj, totalcredits: totalcredits }));
}
const getPlaceholder = props.item?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
} catch (err) {
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
console.log("Err", err);
}
} else {
setIsBulkAvailable(true);
setAdmin((obj) => ({ ...obj, isAdmin: true }));
const getPlaceholder = props.item?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
}
};
useEffect(() => {
if (scrollOnNextUpdate && formRef.current) {
@@ -76,29 +154,33 @@ const BulkSendUi = (props) => {
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);
if (isEnableSubscription && forms.length >= amount.totalcredits) {
setIsQuotaReached(true);
} else {
// If the limit has been reached, throw an error with the appropriate message
alert(t("quick-send-alert-4"));
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(t("quick-send-alert-4"));
}
}
};
@@ -162,13 +244,9 @@ const BulkSendUi = (props) => {
: [...existSigner]
});
} else {
Documents.push({
...props.item,
Placeholders: updatedPlaceholders
});
Documents.push({ ...props.item, Placeholders: updatedPlaceholders });
}
});
//console.log("Documents ", Documents);
await batchQuery(Documents);
};
@@ -180,9 +258,7 @@ const BulkSendUi = (props) => {
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const params = {
Documents: JSON.stringify(Documents)
};
const params = { Documents: JSON.stringify(Documents) };
try {
const res = await axios.post(functionsUrl, params, { headers: headers });
// console.log("res ", res);
@@ -196,83 +272,231 @@ const BulkSendUi = (props) => {
setIsSubmit(false);
}
};
const handleAddOnQuickSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmit(true);
try {
const resAddon = await Parse.Cloud.run("buycredits", {
credits: amount.quantity
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setIsBulkAvailable(true);
handleCloseQuotaReached();
setAmount((obj) => ({
...obj,
quantity: 500,
priceperbulksend: 0.15,
price: (75.0).toFixed(2),
totalcredits: obj?.totalcredits + _resAddon.addon
}));
}
}
} catch (err) {
console.log("Err in buy addon", err);
alert(t("something-went-wrong-mssg"));
} finally {
setIsSubmit(false);
}
};
const handlePricePerQuick = async (e) => {
const quantity = e.target?.value;
const price =
quantity > 0
? (Math.round(quantity * amount.priceperbulksend * 100) / 100).toFixed(
2
)
: 500 * amount.priceperbulksend;
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleCloseQuotaReached = () => {
setIsQuotaReached(false);
};
const handleNavigation = () => {
navigate("/subscription");
};
return (
<>
{isSubmit && (
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
{isLoader ? (
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
<Loader />
</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 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
>
{form?.fields?.map((field, fieldIndex) => (
<div className="flex flex-col" key={field.fieldId}>
<label>{field.label}</label>
<SuggestionInput
required
type="email"
value={field.value}
index={fieldIndex}
onChange={(signer) =>
handleInputChange(index, signer, fieldIndex)
}
/>
</div>
))}
{forms?.length > 1 && (
<button
onClick={() => handleRemoveForm(index)}
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
>
<i className="fa-light fa-trash"></i>
</button>
)}
<div ref={formRef}></div>
</div>
))}
</div>
<div className="flex flex-col mx-4 mb-4 gap-3">
<button
onClick={handleAddForm}
className="op-btn op-btn-primary focus:outline-none"
>
<i className="fa-light fa-plus"></i>{" "}
<span>{t("add-new")}</span>
</button>
<button
type="submit"
className="op-btn op-btn-secondary focus:outline-none"
>
<i className="fa-light fa-paper-plane"></i>{" "}
<span>{t("send")}</span>
</button>
</div>
</form>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-1")}
</div>
)}
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-2")}
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-3")}
</div>
<>
{isSubmit && (
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
{isBulkAvailable ? (
<>
{props.Placeholders?.length > 0 ? (
isSignatureExist ? (
<>
{props.Placeholders?.some((x) => !x.signerObjId) ? (
<div>
<form onSubmit={handleSubmit}>
<div className="min-h-max max-h-[250px] overflow-y-auto">
{forms?.map((form, index) => (
<div
key={form.Id}
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
>
{form?.fields?.map((field, fieldIndex) => (
<div
className="flex flex-col"
key={field.fieldId}
>
<label>{field.label}</label>
<SuggestionInput
required
type="email"
value={field.value}
index={fieldIndex}
onChange={(signer) =>
handleInputChange(
index,
signer,
fieldIndex
)
}
/>
</div>
))}
{forms?.length > 1 && (
<button
onClick={() => handleRemoveForm(index)}
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
>
<i className="fa-light fa-trash"></i>
</button>
)}
<div ref={formRef}></div>
</div>
))}
</div>
<div className="flex flex-col mx-4 mb-4 gap-3">
{isEnableSubscription && (
<button
onClick={handleAddForm}
className="op-btn op-btn-primary focus:outline-none"
>
<i className="fa-light fa-plus"></i>{" "}
<span>{t("add-new")}</span>
</button>
)}
<button
type="submit"
className="op-btn op-btn-accent focus:outline-none"
>
<i className="fa-light fa-paper-plane"></i>{" "}
<span>{t("send")}</span>
</button>
</div>
</form>
<ModalUi
isOpen={isQuotaReached}
handleClose={() => handleCloseQuotaReached()}
>
<div className="p-4 flex justify-center items-center flex-col gap-y-3">
<p className="text-center text-base-content">
{t("quota-err-quicksend")}
</p>
<button
onClick={() => setIsBulkAvailable(false)}
className=" op-btn op-btn-primary w-[200px]"
>
{t("buy-credits")}
</button>
</div>
</ModalUi>
</div>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-1")}
</div>
)}
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-2")}
</div>
)
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-3")}
</div>
)}
</>
) : (
<>
{isFreePlan ? (
<div className="w-full h-[130px] flex flex-col justify-center items-center text-center p-4">
<p className="text-base font-medium mb-2.5">
{t("bulk-send-subcription-alert")}
</p>
<button
onClick={() => handleNavigation()}
className="op-btn op-btn-primary"
>
{t("upgrade-now")}
</button>
</div>
) : (
<>
{admin?.isAdmin ? (
<form onSubmit={handleAddOnQuickSubmit} className="p-3">
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
{t("additional-credits")}
</p>
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("quantity-of-credits")}
<span className="text-[red] text-[13px]">*</span>
</label>
<select
value={amount.quantity}
onChange={(e) => handlePricePerQuick(e)}
name="quantity"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs"
required
>
{quantityList.length > 0 &&
quantityList.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {amount.priceperbulksend})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full mt-2">
{t("Proceed")}
</button>
</form>
) : (
<div className="mx-8 mt-4 mb-8 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", { adminEmail: admin?.email })}
</div>
)}
</>
)}
</>
)}
</>
)}
</>
);
+162 -128
View File
@@ -6,27 +6,30 @@ import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize";
import {
checkIsSubscribed,
checkIsSubscribedTeam,
getAppLogo,
openInNewTab,
saveLanguageInLocal
} from "../constant/Utils";
import { isEnableSubscription, isStaging } from "../constant/const";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next";
import ModalUi from "../primitives/ModalUi";
import QuotaCard from "../primitives/QuotaCard";
const Header = ({ showSidebar }) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { width } = useWindowSize();
let username = localStorage.getItem("username");
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true);
const [isPro, setIsPro] = useState(false);
const [isTeam, setIsTeam] = useState(false);
const [isTeam, setIsTeam] = useState({ plan: "", isValid: false });
const [applogo, setAppLogo] = useState(
localStorage.getItem("appLogo") || " "
);
const [emailUsed, setEmailUsed] = useState(0);
const [isModal, setIsModal] = useState(false);
const toggleDropdown = () => {
setIsOpen(!isOpen);
@@ -37,9 +40,8 @@ const Header = ({ showSidebar }) => {
}, []);
async function checkSubscription() {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
const getIsTeam = await checkIsSubscribedTeam();
if (getIsSubscribe) {
const subscribe = await checkIsSubscribed();
if (subscribe.isValid) {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
@@ -47,20 +49,24 @@ const Header = ({ showSidebar }) => {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
setIsPro(getIsSubscribe);
setIsTeam(getIsTeam);
setIsSubscribe(getIsSubscribe);
setIsTeam(subscribe);
setIsSubscribe(subscribe.isValid);
try {
const extUser = await Parse.Cloud.run("getUserDetails");
const MonthlyFreeEmails = extUser?.get("MonthlyFreeEmails") || 0;
setEmailUsed(MonthlyFreeEmails);
} catch (err) {
console.log("err in while fetching monthlyfreeEmails", err);
}
}
}
const closeDropdown = () => {
const closeDropdown = async () => {
setIsOpen(false);
if (Parse?.User?.current()) {
try {
Parse.User.logOut();
} catch (err) {
console.log("Err", err);
}
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
@@ -103,129 +109,157 @@ const Header = ({ showSidebar }) => {
window.open("https://console.opensignlabs.com/");
}
};
const handleNavigation = () => {
navigate("/subscription");
};
const handleMailUsed = () => {
setIsModal(!isModal);
};
return (
<div className="op-navbar bg-base-100 shadow">
<div className="flex-none">
<button
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
onClick={showSidebar}
>
<i className="fa-light fa-bars text-xl text-base-content"></i>
</button>
</div>
<div className="flex-1 ml-2">
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
<img
className="object-contain h-full w-auto"
src={applogo}
alt="img"
/>
</div>
</div>
<div id="profile-menu" className="flex-none gap-2">
{!isSubscribe && (
<div>
<button
className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
onClick={() => navigate("/subscription")}
>
{t("upgrade-now")}
</button>
</div>
)}
{!isTeam && isPro && (
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
{t("pro")}
</div>
)}
{isTeam && (
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] text-[13px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
{t("team")}
</div>
)}
<div>
<FullScreenButton />
</div>
{width >= 768 && (
<div
onClick={toggleDropdown}
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
<div>
{isEnableSubscription && (
<div className="shadow py-1 text-center bg-[#CAE4FA] text-[14px] p-1">
{t("header-news")}
<span
className="cursor-pointer font-medium underline text-blue-800"
onClick={() => navigate("/profile")}
>
{" " + t("header-news-btn") + "."}
</span>
</div>
)}
<div className="op-navbar bg-base-100 shadow">
<div className="flex-none">
<button
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation"
onClick={showSidebar}
>
<i className="fa-light fa-bars text-xl text-base-content"></i>
</button>
</div>
<div className="flex-1 ml-2">
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
<img
className="w-[35px] h-[35px] object-contain"
src={image}
className="object-contain h-full w-auto"
src={applogo}
alt="img"
/>
</div>
)}
{width >= 768 && (
<div
onClick={toggleDropdown}
className="cursor-pointer text-base-content text-sm"
>
{username && username}
</div>
<div id="profile-menu" className="flex-none gap-2">
{!isSubscribe && (
<div>
<button
className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
onClick={() => handleNavigation()}
>
{t("upgrade-now")}
</button>
</div>
)}
{isTeam.isValid && !validplan[isTeam.plan] && (
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
{t("pro")}
</div>
)}
{validplan[isTeam.plan] && (
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] text-[13px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
{t("team")}
</div>
)}
<div>
<FullScreenButton />
</div>
)}
<div className="op-dropdown op-dropdown-end" id="profile-menu">
<div
tabIndex={0}
role="button"
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
>
<i
{width >= 768 && (
<div
onClick={toggleDropdown}
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
>
<img
className="w-[35px] h-[35px] object-contain"
src={image}
alt="img"
/>
</div>
)}
{width >= 768 && (
<div
onClick={toggleDropdown}
className="cursor-pointer text-base-content text-sm"
>
{username && username}
</div>
)}
<div className="op-dropdown op-dropdown-end" id="profile-menu">
<div
tabIndex={0}
role="button"
onClick={toggleDropdown}
className="fa-light fa-angle-down text-base-content"
></i>
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
>
<i
tabIndex={0}
role="button"
onClick={toggleDropdown}
className="fa-light fa-angle-down text-base-content"
></i>
</div>
<ul
tabIndex={0}
className="mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52"
>
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/profile");
}}
>
<span>
<i className="fa-light fa-user"></i> {t("profile")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
}}
>
<span>
<i className="fa-light fa-lock"></i> {t("change-password")}
</span>
</li>
{isEnableSubscription && (
<li onClick={() => handleConsoleRedirect()}>
<span>
<i className="fa-light fa-id-card"></i> Console
</span>
</li>
)}
{isEnableSubscription && isTeam?.plan === "freeplan" && (
<li className="cursor-pointer" onClick={handleMailUsed}>
<span>
<i className="fa-light fa-envelope"></i>
{emailUsed}/15 {t("sent-this-month")}
</span>
</li>
)}
<li onClick={closeDropdown}>
<span>
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
{t("log-out")}
</span>
</li>
</ul>
</div>
<ul
tabIndex={0}
className="mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52"
>
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
<span>
<i className="fa-light fa-book"></i> {t("docs")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/profile");
}}
>
<span>
<i className="fa-light fa-user"></i> {t("profile")}
</span>
</li>
<li
onClick={() => {
setIsOpen(false);
navigate("/changepassword");
}}
>
<span>
<i className="fa-light fa-lock"></i> {t("change-password")}
</span>
</li>
<li
onClick={() => {
handleConsoleRedirect();
}}
>
<span>
<i className="fa-light fa-id-card"></i> Console
</span>
</li>
<li onClick={closeDropdown}>
<span>
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
{t("log-out")}
</span>
</li>
</ul>
</div>
</div>
<ModalUi isOpen={isModal}>
<QuotaCard isPaidInfo={true} handlClose={handleMailUsed} />
</ModalUi>
</div>
);
};
+9 -4
View File
@@ -47,8 +47,10 @@ const GoogleSignInBtn = ({
});
});
const clearStorage = async () => {
if (Parse.User.current()) {
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
@@ -160,10 +162,13 @@ const GoogleSignInBtn = ({
alert(t("fill-required-details!"));
}
};
const handleCloseModal = () => {
const handleCloseModal = async () => {
setIsModal(false);
Parse.User.logOut();
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
@@ -0,0 +1,38 @@
import React from "react";
import ModalUi from "../primitives/ModalUi";
import { useTranslation } from "react-i18next";
function RotateAlert(props) {
const { t } = useTranslation();
return (
<ModalUi
isOpen={props.isRotate}
title={t("Rotation-alert")}
handleClose={() => props.setIsRotate({ status: false, degree: 0 })}
>
{" "}
<div className="p-[20px] h-full">
<p>{t("rotate-alert-mssg")}</p>
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<div className="flex gap-1">
<button
onClick={() => props.setIsRotate({ status: false, degree: 0 })}
type="button"
className="op-btn op-btn-ghost shadow-md"
>
{t("no")}
</button>
<button
onClick={() => props.handleRemoveWidgets()}
type="button"
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
</div>
</div>
</ModalUi>
);
}
export default RotateAlert;
@@ -357,7 +357,7 @@ function DriveBody(props) {
e.stopPropagation();
handleMenuItemClick("Download", data);
}}
className="fa-light fa-download mr-[8px] op-text-primary"
className="fa-light fa-download mr-[8px] op-text-primary cursor-pointer"
aria-hidden="true"
></i>
</td>
@@ -370,7 +370,11 @@ function DriveBody(props) {
{/* folder */}
<div
data-tut={props.dataTutSeventh}
onClick={() => handleOnclikFolder(data)}
onClick={() => {
if (!rename) {
handleOnclikFolder(data);
}
}}
className="cursor-pointer"
>
<svg
@@ -383,7 +387,6 @@ function DriveBody(props) {
{rename === data.objectId ? (
<input
onFocus={() => {
inputRef.current.setSelectionRange(0, 0);
const input = inputRef.current;
if (input) {
input.select();
@@ -395,9 +398,8 @@ function DriveBody(props) {
onKeyDown={(e) => handleEnterPress(e, data)}
ref={inputRef}
defaultValue={renameValue}
// value={renameValue}
onChange={(e) => setRenameValue(e.target.value)}
className="w-[100px] border-[1.5px] border-black rounded-sm text-[10px]"
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
/>
) : (
<span className="fileName">{data.Name}</span>
@@ -423,7 +425,11 @@ function DriveBody(props) {
</ContextMenu.Root>
</div>
) : (
<HoverCard.Root openDelay={0} closeDelay={100}>
<HoverCard.Root
open={rename ? false : undefined}
openDelay={0}
closeDelay={100}
>
<HoverCard.Trigger asChild>
<div>
<ContextMenu.Root>
@@ -432,7 +438,11 @@ function DriveBody(props) {
{/* pdf */}
<div
data-tut={props.dataTutSixth}
onClick={() => checkPdfStatus(data)}
onClick={() => {
if (!rename) {
checkPdfStatus(data);
}
}}
className="cursor-pointer"
>
<svg
@@ -447,7 +457,6 @@ function DriveBody(props) {
autoFocus={true}
type="text"
onFocus={() => {
inputRef.current.setSelectionRange(0, 0);
const input = inputRef.current;
if (input) {
input.select();
@@ -457,9 +466,8 @@ function DriveBody(props) {
onKeyDown={(e) => handleEnterPress(e, data)}
ref={inputRef}
defaultValue={renameValue}
// value={renameValue}
onChange={(e) => setRenameValue(e.target.value)}
className="w-[100px] border-[1.5px] border-black rounded-sm text-[10px]"
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
/>
) : (
<span className="fileName">{data.Name}</span>
@@ -54,7 +54,7 @@ function DraftDocument() {
navigate(`/recipientSignPdf/${data.objectId}`);
}
//checking if document has completed and signyour-self flow
else if (!signerExist && !isPlaceholder) {
else if ((!signerExist && !isPlaceholder) || data?.IsSignyourself) {
navigate(`/signaturePdf/${data.objectId}`);
}
//checking if document has declined by someone
@@ -4,6 +4,8 @@ import ModalUi from "../../primitives/ModalUi";
import { radioButtonWidget } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade";
import { useTranslation } from "react-i18next";
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
function DropdownWidgetOption(props) {
const { t } = useTranslation();
const [dropdownOptionList, setDropdownOptionList] = useState([
@@ -330,6 +332,55 @@ function DropdownWidgetOption(props) {
</div>
</>
)}
<div className="flex items-center mt-3 mb-3">
<span>{t("font-size")} :</span>
<select
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={
props.fontSize ||
props.currWidgetsDetails?.options?.fontSize ||
"12"
}
onChange={(e) => props.setFontSize(e.target.value)}
>
{fontsizeArr.map((size, ind) => {
return (
<option className="text-[13px]" value={size} key={ind}>
{size}
</option>
);
})}
</select>
<div className="flex flex-row gap-1 items-center ml-4 ">
<span>{t("color")} : </span>
<select
value={
props.fontColor ||
props.currWidgetsDetails?.options?.fontColor ||
"black"
}
onChange={(e) => props.setFontColor(e.target.value)}
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
>
{fontColorArr.map((color, ind) => {
return (
<option value={color} key={ind}>
{t(`color-type.${color}`)}
</option>
);
})}
</select>
<span
style={{
background:
props.fontColor ||
props.currWidgetsDetails?.options?.fontColor ||
"black"
}}
className="w-5 h-[19px] ml-1"
></span>
</div>
</div>
{["checkbox", radioButtonWidget].includes(props.type) && (
<div className="flex flex-row gap-5 my-2 items-center text-center">
{!props.isSignYourself && (
@@ -362,6 +413,7 @@ function DropdownWidgetOption(props) {
</div>
)}
</div>
<div
className={`${
props.type === "checkbox" && !props.isSignYourself
@@ -23,8 +23,8 @@ const EditTemplate = ({ template, onSuccess }) => {
}, []);
const fetchSubscription = async () => {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe.isValid);
}
};
const handleStrInput = (e) => {
@@ -34,7 +34,7 @@ function EmailComponent({
const imgPng =
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3/`;
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
@@ -74,26 +74,15 @@ function EmailComponent({
});
}
}
if (sendMail && sendMail.data.result.status === "success") {
if (sendMail?.data?.result?.status === "success") {
setSuccessEmail(true);
setIsEmail(false);
setTimeout(() => {
setSuccessEmail(false);
setIsEmail(false);
setEmailValue("");
setEmailList([]);
}, 1500);
setIsLoading(false);
} else if (sendMail && sendMail.data.result.status === "error") {
setIsLoading(false);
setIsEmail(false);
setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
setEmailValue("");
setEmailList([]);
} else {
setIsLoading(false);
setIsEmail(false);
+98 -37
View File
@@ -34,7 +34,12 @@ function Header({
setIsEmail,
completeBtnTitle,
setIsEditTemplate,
isPublicTemplate
isPublicTemplate,
clickOnZoomIn,
clickOnZoomOut,
handleRotationFun,
isDisableRotate,
templateId
}) {
const { t } = useTranslation();
const filterPrefill =
@@ -48,7 +53,6 @@ function Header({
const currentDecline = { currnt: "Sure", isDeclined: true };
setIsDecline(currentDecline);
};
return (
<div className="flex py-[5px]">
{isMobile && isShowHeader ? (
@@ -73,7 +77,7 @@ function Header({
allPages={allPages}
changePage={changePage}
/>
{pdfUrl && alreadySign ? (
{isCompleted || alreadySign ? (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<div className="op-link op-link-primary no-underline text-[16px] font-semibold pr-[3px] pl-[5px]">
@@ -184,13 +188,7 @@ function Header({
) : (
<div
data-tut="reactourThird"
onClick={() => {
if (!pdfUrl) {
embedWidgetsData();
} else if (isPdfRequestFiles) {
embedWidgetsData();
}
}}
onClick={() => embedWidgetsData()}
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
>
{t("finish")}
@@ -207,7 +205,7 @@ function Header({
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="bg-white shadow-md rounded-full px-3 py-2"
className="bg-white shadow-md rounded-md px-3 py-2"
sideOffset={5}
>
<DropdownMenu.Item
@@ -215,7 +213,7 @@ function Header({
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
pdfDetails[0]?.URL,
setIsDownloading
)
}
@@ -226,11 +224,57 @@ function Header({
></i>
<span className="font-[500]">{t("download")}</span>
</DropdownMenu.Item>
{!isDisableRotate && (
<>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() => handleRotationFun(90)}
>
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-right")}
</span>
</DropdownMenu.Item>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() => handleRotationFun(-90)}
>
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-left")}
</span>
</DropdownMenu.Item>
</>
)}
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() => clickOnZoomIn()}
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
<span className="font-[500]">{t("zoom-in")}</span>
</DropdownMenu.Item>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
onClick={() => clickOnZoomOut()}
>
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
<span className="font-[500]">{t("zoom-out")}</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
</DropdownMenu.Root>
</div>
)}
{isPublicTemplate && (
<div
data-tut="reactourThird"
onClick={() => embedWidgetsData()}
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
>
{t("sign-now")}
</div>
)}
</div>
)}
</div>
@@ -339,15 +383,32 @@ function Header({
</div>
) : (
<div className="flex" data-tut="reactourFifth">
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
{t("back")}
</button>
{!templateId && (
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
{t("back")}
</button>
)}
{currentSigner && (
<>
{templateId && (
<button
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
<span className="hidden lg:block">{t("download")}</span>
</button>
)}
<button
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
onClick={() => handleDeclinePdfAlert()}
@@ -361,16 +422,22 @@ function Header({
>
{t("finish")}
</button>
<button
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
{!templateId && (
<button
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
)}
</>
)}
</div>
@@ -423,9 +490,7 @@ function Header({
<button
type="button"
className="op-btn op-btn-primary op-btn-sm shadow"
onClick={() => {
embedWidgetsData();
}}
onClick={() => embedWidgetsData()}
>
{t("sign-now")}
</button>
@@ -442,11 +507,7 @@ function Header({
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => {
if (!pdfUrl) {
embedWidgetsData();
}
}}
onClick={() => embedWidgetsData()}
>
{t("finish")}
</button>
+22 -29
View File
@@ -3,44 +3,37 @@ import { useTranslation } from "react-i18next";
function PdfZoom(props) {
const { t } = useTranslation();
const onClickZoomIn = () => {
props.setScale(props.scale + 0.1 * props.scale);
props.setZoomPercent(props.zoomPercent + 10);
};
const onClickZoomOut = () => {
if (props.zoomPercent > 0) {
if (props.zoomPercent === 10) {
props.setScale(1);
} else {
props.setScale(props.scale - 0.1 * props.scale);
}
props.setZoomPercent(props.zoomPercent - 10);
}
};
const handleReset = () => {
props.setScale(1);
props.setZoomPercent(0);
};
return (
// md:mt-[41px] xl:mt-[63px]mt=[]
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[50px] 2xl:mt-[63px]">
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[43px] 2xl:mt-[63px]">
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={onClickZoomIn}
onClick={() => props.clickOnZoomIn()}
title={t("zoom-in")}
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={handleReset}
title={t("reset")}
>
<i className="fa-light fa-magnifying-glass-arrows-rotate text-gray-500 2xl:text-[30px]"></i>
</span>
{!props?.isDisableRotate && (
<>
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={() => props.handleRotationFun(90)}
title={t("rotate-right")}
>
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
title={t("rotate-left")}
onClick={() => props.handleRotationFun(-90)}
>
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px]"></i>
</span>
</>
)}
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
onClick={onClickZoomOut}
onClick={() => props.clickOnZoomOut()}
style={{
cursor: props.zoomPercent > 0 ? "pointer" : "default"
}}
+334 -282
View File
@@ -4,6 +4,8 @@ import PlaceholderBorder from "./PlaceholderBorder";
import { Rnd } from "react-rnd";
import {
defaultWidthHeight,
fontColorArr,
fontsizeArr,
getContainerScale,
handleCopyNextToWidget,
isTabAndMobile,
@@ -15,6 +17,8 @@ import {
import PlaceholderType from "./PlaceholderType";
import moment from "moment";
import "../../styles/opensigndrive.css";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
const selectFormat = (data) => {
switch (data) {
@@ -89,9 +93,10 @@ const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
function Placeholder(props) {
const { t } = useTranslation();
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
const [isDraggingEnabled, setDraggingEnabled] = useState(true);
const [isShowDateFormat, setIsShowDateFormat] = useState(false);
const [isDateModal, setIsDateModal] = useState(false);
const [containerScale, setContainerScale] = useState();
const [selectDate, setSelectDate] = useState({
date:
@@ -111,6 +116,7 @@ function Placeholder(props) {
: ""
});
const [dateFormat, setDateFormat] = useState([]);
const [clickonWidget, setClickonWidget] = useState({});
const [startDate, setStartDate] = useState(
props.pos.type === "date" &&
getDefaultdate(
@@ -200,20 +206,6 @@ function Placeholder(props) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectDate]);
//it detect outside click of date dropdown menu
useEffect(() => {
const closeMenuOnOutsideClick = (e) => {
if (isShowDateFormat && !e.target.closest("#menu-container")) {
setIsShowDateFormat(!isShowDateFormat);
}
};
document.addEventListener("click", closeMenuOnOutsideClick);
return () => {
// Cleanup the event listener when the component unmounts
document.removeEventListener("click", closeMenuOnOutsideClick);
};
}, [isShowDateFormat]);
//`handleWidgetIdandPopup` is used to set current widget id and open relative popup
const handleWidgetIdandPopup = () => {
if (props.setSelectWidgetId) {
@@ -338,7 +330,6 @@ function Placeholder(props) {
//condition for only placeholder and template flow
if (props.data && props?.pos?.type !== textWidget) {
props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
@@ -369,7 +360,6 @@ function Placeholder(props) {
//and on save or cancel button of copy all page popup i have set this temp signer Id in unique id
if (props.data && props?.pos?.type !== textWidget) {
props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
@@ -378,7 +368,6 @@ function Placeholder(props) {
props.setIsSelectId(checkIndex || 0);
} else if (props.data && props.pos.type === textWidget) {
props.setTempSignerId(props.uniqueId);
props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
@@ -430,7 +419,11 @@ function Placeholder(props) {
props.setXyPostion,
props.data && props.data.Id,
false,
data?.format
data?.format,
null,
null,
props.fontSize || props.pos?.options?.fontSize || "12",
props.fontColor || props.pos?.options?.fontColor || "black"
);
setSelectDate({ date: date, format: data?.format });
};
@@ -522,17 +515,12 @@ function Placeholder(props) {
)}
{/* setting icon only for date widgets */}
{props.pos.type === "date" && selectDate && (
<div
id="menu-container"
style={{
zIndex: "99",
top: "-19px",
right: props.isPlaceholder ? "60px" : "44px"
}}
className={`${isShowDateFormat ? "show" : ""} dropdown icon`}
<i
onClick={(e) => {
setIsShowDateFormat(!isShowDateFormat);
props.setCurrWidgetsDetails(props.pos);
setIsDateModal(!isDateModal);
e.stopPropagation();
setClickonWidget(props.pos);
if (props.data) {
props.setSignKey(props.pos.key);
props.setUniqueId(props.data.Id);
@@ -543,8 +531,9 @@ function Placeholder(props) {
}
}}
onTouchEnd={(e) => {
props.setCurrWidgetsDetails(props.pos);
e.stopPropagation();
setIsShowDateFormat(!isShowDateFormat);
setIsDateModal(!isDateModal);
if (props.data) {
props.setSignKey(props.pos.key);
props.setUniqueId(props.data.Id);
@@ -554,44 +543,15 @@ function Placeholder(props) {
props.setIsSelectId(checkIndex || 0);
}
}}
>
<i
className="fa-light fa-gear icon"
style={{
color: "#188ae2",
fontSize: "14px"
}}
></i>
<div
className={
isShowDateFormat ? "dropdown-menu show" : "dropdown-menu"
}
aria-labelledby="dropdownMenuButton"
aria-expanded={isShowDateFormat ? "true" : "false"}
>
{dateFormat.map((data, ind) => {
return (
<span
key={ind}
onTouchEnd={(e) => {
e.stopPropagation();
setIsShowDateFormat(!isShowDateFormat);
setSelectDate(data);
handleSaveDate(data);
}}
onClick={() => {
setIsShowDateFormat(!isShowDateFormat);
setSelectDate(data);
handleSaveDate(data);
}}
className="dropdown-item text-[13px]"
>
{data?.date ? data?.date : "nodata"}
</span>
);
})}
</div>
</div>
style={{
zIndex: "99",
top: "-18px",
right: props.isPlaceholder ? "50px" : "30px",
color: "#188ae2",
fontSize: "14px"
}}
className="fa-light fa-gear icon"
></i>
)}
{/* copy icon for all widgets */}
<i
@@ -705,228 +665,320 @@ function Placeholder(props) {
return "all-scroll";
}
};
return (
<Rnd
id={props.pos.key}
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key}
lockAspectRatio={
![
textWidget,
"email",
"name",
"company",
"job title",
textInputWidget
].includes(props.pos.type) &&
(props.pos.Width
? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width /
defaultWidthHeight(props.pos.type).height)
}
enableResizing={{
top: false,
right: false,
bottom: false,
left: false,
topRight: false,
bottomRight:
props.data && props.isNeedSign
? props.data?.signerObjId === props.signerObjId &&
props.pos.type !== "checkbox" &&
props.pos.type !== radioButtonWidget
? true
: false
: props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId &&
true,
bottomLeft: false,
topLeft: false
}}
bounds="parent"
className="signYourselfBlock"
style={{
border: "1px solid #007bff",
borderRadius: "2px",
textAlign:
props.pos.type !== "name" &&
props.pos.type !== "company" &&
props.pos.type !== "job title" &&
"center",
cursor: getCursor(),
zIndex:
props.pos.type === "date"
? props.pos.key === props.selectWidgetId
? 99 + 1
: 99
: props?.pos?.zIndex
? props.pos.zIndex
: "5",
background: props.data ? props.data.blockColor : "rgb(203 233 237)"
}}
onDrag={() => {
setDraggingEnabled(true);
props.handleTabDrag && props.handleTabDrag(props.pos.key);
}}
size={{
width:
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself)
}}
onResizeStart={() => {
setDraggingEnabled(true);
props.setIsResize && props.setIsResize(true);
}}
onResizeStop={(e, direction, ref) => {
props.setIsResize && props.setIsResize(false);
props.handleSignYourselfImageResize &&
props.handleSignYourselfImageResize(
ref,
props.pos.key,
props.xyPostion,
props.setXyPostion,
props.index,
containerScale,
props.scale,
props.data && props.data.Id,
props.isResize
);
}}
disableDragging={
props.isNeedSign
? true
: props.isPlaceholder && ![textWidget].includes(props.pos.type)
? false
: !isDraggingEnabled
}
onDragStop={(event, dragElement) =>
props.handleStop &&
props.handleStop(event, dragElement, props.data?.Id, props.pos?.key)
}
position={{
x: xPos(props.pos, props.isSignYourself),
y: yPos(props.pos, props.isSignYourself)
}}
onResize={(e, direction, ref) => {
setPlaceholderBorder({
w: ref.offsetWidth / (props.scale * containerScale),
h: ref.offsetHeight / (props.scale * containerScale)
});
}}
onClick={() => handleOnClickPlaceholder()}
>
{props.isShowBorder &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId ? (
<BorderResize right={-12} top={-11} />
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
props.data?.signerObjId === props.signerObjId &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" ? (
<BorderResize />
) : (
<></>
)
) : (
<>
<Rnd
id={props.pos.key}
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key}
lockAspectRatio={
![
textWidget,
"email",
"name",
"company",
"job title",
textInputWidget
].includes(props.pos.type) &&
(props.pos.Width
? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width /
defaultWidthHeight(props.pos.type).height)
}
enableResizing={{
top: false,
right: false,
bottom: false,
left: false,
topRight: false,
bottomRight:
props.data && props.isNeedSign
? props.data?.signerObjId === props.signerObjId &&
props.pos.type !== "checkbox" &&
props.pos.type !== radioButtonWidget
? true
: false
: props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId &&
true,
bottomLeft: false,
topLeft: false
}}
bounds="parent"
className="signYourselfBlock"
style={{
border: "1px solid #007bff",
borderRadius: "2px",
textAlign:
props.pos.type !== "name" &&
props.pos.type !== "company" &&
props.pos.type !== "job title" &&
"center",
cursor: getCursor(),
zIndex:
props.pos.type === "date"
? props.pos.key === props.selectWidgetId
? 99 + 1
: 99
: props?.pos?.zIndex
? props.pos.zIndex
: "5",
background: props.data ? props.data.blockColor : "rgb(203 233 237)"
}}
onDrag={() => {
setDraggingEnabled(true);
props.handleTabDrag && props.handleTabDrag(props.pos.key);
}}
size={{
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself)
}}
onResizeStart={() => {
setDraggingEnabled(true);
props.setIsResize && props.setIsResize(true);
}}
onResizeStop={(e, direction, ref) => {
props.setIsResize && props.setIsResize(false);
props.handleSignYourselfImageResize &&
props.handleSignYourselfImageResize(
ref,
props.pos.key,
props.xyPostion,
props.setXyPostion,
props.index,
containerScale,
props.scale,
props.data && props.data.Id,
props.isResize
);
}}
disableDragging={
props.isNeedSign
? true
: props.isPlaceholder && ![textWidget].includes(props.pos.type)
? false
: !isDraggingEnabled
}
onDragStop={(event, dragElement) =>
props.handleStop &&
props.handleStop(event, dragElement, props.data?.Id, props.pos?.key)
}
position={{
x: xPos(props.pos, props.isSignYourself),
y: yPos(props.pos, props.isSignYourself)
}}
onResize={(e, direction, ref) => {
setPlaceholderBorder({
w: ref.offsetWidth / (props.scale * containerScale),
h: ref.offsetHeight / (props.scale * containerScale)
});
}}
onClick={() => handleOnClickPlaceholder()}
>
{props.isShowBorder &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId && <BorderResize />
)}
props.pos.key === props.selectWidgetId ? (
<BorderResize right={-12} top={-11} />
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
props.data?.signerObjId === props.signerObjId &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" ? (
<BorderResize />
) : (
<></>
)
) : (
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId && <BorderResize />
)}
{props.isShowBorder && props.pos.key === props.selectWidgetId && (
<PlaceholderBorder
setDraggingEnabled={setDraggingEnabled}
pos={props.pos}
isPlaceholder={props.isPlaceholder}
getCheckboxRenderWidth={getCheckboxRenderWidth}
scale={props.scale}
containerScale={containerScale}
placeholderBorder={placeholderBorder}
/>
)}
{isTabAndMobile ? (
<div
className="flex items-stretch"
style={{
left: xPos(props.pos, props.isSignYourself),
top: yPos(props.pos, props.isSignYourself),
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself),
zIndex: "10"
}}
onTouchEnd={() => handleOnClickPlaceholder()}
onClick={() => handleOnClickPlaceholder()}
>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPostion={props.xyPostion}
index={props.index}
setXyPostion={props.setXyPostion}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
{props.isShowBorder && props.pos.key === props.selectWidgetId && (
<PlaceholderBorder
setDraggingEnabled={setDraggingEnabled}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
pos={props.pos}
isPlaceholder={props.isPlaceholder}
getCheckboxRenderWidth={getCheckboxRenderWidth}
scale={props.scale}
containerScale={containerScale}
placeholderBorder={placeholderBorder}
/>
)}
{isTabAndMobile ? (
<div
className="flex items-stretch"
style={{
left: xPos(props.pos, props.isSignYourself),
top: yPos(props.pos, props.isSignYourself),
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself),
zIndex: "10"
}}
onTouchEnd={() => handleOnClickPlaceholder()}
onClick={() => handleOnClickPlaceholder()}
>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPostion={props.xyPostion}
index={props.index}
setXyPostion={props.setXyPostion}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
setDraggingEnabled={setDraggingEnabled}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
/>
</div>
) : (
<>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPostion={props.xyPostion}
index={props.index}
setXyPostion={props.setXyPostion}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
setDraggingEnabled={setDraggingEnabled}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
/>
</>
)}
</Rnd>
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
<div className="h-[100%] p-[20px]">
<div className="flex flex-row items-center">
<span>{t("format")} : </span>
<div className="flex">
<select
className="ml-[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
defaultValue={""}
onChange={(e) => {
const selectedIndex = e.target.value;
e.stopPropagation();
setSelectDate(dateFormat[selectedIndex]);
}}
>
<option value="" disabled>
{t("select-date-format")}
</option>
{dateFormat.map((data, ind) => {
return (
<option className="text-[13px]" value={ind} key={ind}>
{data?.date ? data?.date : "nodata"}
</option>
);
})}
</select>
</div>
<span className="text-xs text-gray-400 ml-1">
{selectDate.format}
</span>
</div>
<div className="flex items-center mt-4 md:mt-2">
<span>{t("font-size")} :</span>
<select
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={props.fontSize || clickonWidget.options?.fontSize || "12"}
onChange={(e) => props.setFontSize(e.target.value)}
>
{fontsizeArr.map((size, ind) => {
return (
<option className="text-[13px]" value={size} key={ind}>
{size}
</option>
);
})}
</select>
<div className="flex flex-row gap-1 items-center ml-2 md:ml-4 ">
<span>{t("color")}: </span>
<select
value={
props.fontColor || clickonWidget.options?.fontColor || "black"
}
onChange={(e) => props.setFontColor(e.target.value)}
className="ml-[4px] md:ml[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
>
{fontColorArr.map((color, ind) => {
return (
<option value={color} key={ind}>
{t(`color-type.${color}`)}
</option>
);
})}
</select>
<span
style={{
background:
props.fontColor || props.pos.options?.fontColor || "black"
}}
className="w-5 h-[19px] ml-1"
></span>
</div>
</div>
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<button
type="button"
className="op-btn op-btn-primary"
onClick={() => {
setIsDateModal(false);
handleSaveDate(selectDate);
props.setFontColor("");
props.setFontSize("");
}}
>
{t("save")}
</button>
</div>
) : (
<>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPostion={props.xyPostion}
index={props.index}
setXyPostion={props.setXyPostion}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
setDraggingEnabled={setDraggingEnabled}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
/>
</>
)}
</Rnd>
</ModalUi>
</>
);
}
@@ -1,12 +1,21 @@
import React, { useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import { randomId, textWidget } from "../../constant/Utils";
import {
handleCopyNextToWidget,
randomId,
textWidget
} from "../../constant/Utils";
import { useTranslation } from "react-i18next";
function PlaceholderCopy(props) {
const { t } = useTranslation();
const copyType = ["All pages", "All pages but last", "All pages but first"];
const [selectCopyType, setSelectCopyType] = useState("");
const copyType = [
{ id: 1, type: "All pages" },
{ id: 2, type: "All pages but last" },
{ id: 3, type: "All pages but first" },
{ id: 4, type: "Next to current widget" }
];
const [selectCopyType, setSelectCopyType] = useState(1);
//function for get copy placeholder position
const getCopyPlaceholderPosition = (
type,
@@ -20,7 +29,7 @@ function PlaceholderCopy(props) {
(data) => data.xPosition !== currentPlaceholder.xPosition
);
//copy all page placeholder at requested position except first page
if (newPageNumber === 1 && type === "first") {
if (newPageNumber === 1 && type === 3) {
if (filterPosition && filterPosition.length > 0) {
return {
pageNumber: newPageNumber,
@@ -29,7 +38,7 @@ function PlaceholderCopy(props) {
}
}
//copy all page placeholder at requested position except last page
else if (newPageNumber === props.allPages && type === "last") {
else if (newPageNumber === props.allPages && type === 2) {
if (existPlaceholderPosition) {
return {
pageNumber: newPageNumber,
@@ -172,25 +181,65 @@ function PlaceholderCopy(props) {
//function for getting selected type placeholder copy
const handleApplyCopy = () => {
if (selectCopyType === "All pages") {
copyPlaceholder("all");
} else if (selectCopyType === "All pages but last") {
copyPlaceholder("last");
} else if (selectCopyType === "All pages but first") {
copyPlaceholder("first");
if (selectCopyType === 4) {
const signerPosition = props.xyPostion;
let currentXYposition;
const signerId = props.signerObjId ? props.signerObjId : props.Id;
if (signerId) {
const filterSignerPosition = signerPosition.filter(
(item) => item?.Id === signerId
);
currentXYposition = filterSignerPosition[0].placeHolder.filter(
(data) => data.pageNumber === props?.pageNumber
);
//get current placeholder position data which user want to copy
currentXYposition = currentXYposition[0].pos.find(
(position) => position.key === props.signKey
);
//function to create new widget next to just widget
handleCopyNextToWidget(
currentXYposition,
props.widgetType,
props.xyPostion,
props.pageNumber,
props.setXyPostion,
props?.Id
);
} else {
const getIndex = props?.xyPostion.findIndex(
(data) => data.pageNumber === props.pageNumber
);
const placeholderPosition = props?.xyPostion[getIndex];
//get current placeholder position data which user want to copy
currentXYposition = placeholderPosition.pos.find(
(pos) => pos.key === props.signKey
);
//function to create new widget next to just widget
handleCopyNextToWidget(
currentXYposition,
props.widgetType,
props.xyPostion,
getIndex,
props.setXyPostion
);
}
} else {
copyPlaceholder(selectCopyType);
}
};
const handleUniqueId = () => {
if (props.widgetType === textWidget) {
const signerId = props.signerObjId ? props.signerObjId : props.Id;
if (signerId && props.widgetType === textWidget) {
props.setUniqueId(props?.tempSignerId);
props.setTempSignerId("");
}
props.setIsPageCopy(false);
setSelectCopyType(1);
};
return (
<ModalUi
isOpen={props.isPageCopy}
title={t("copy-to-all-pages")}
title={t("copy-title")}
handleClose={() => handleUniqueId()}
>
<div className="h-full p-[20px] text-base-content">
@@ -201,11 +250,11 @@ function PlaceholderCopy(props) {
<input
className="mr-[8px] op-radio op-radio-xs"
type="radio"
value={data}
onChange={() => setSelectCopyType(data)}
checked={selectCopyType === data}
value={data.id}
onChange={() => setSelectCopyType(data.id)}
checked={selectCopyType === data.id}
/>
{t(`copy-type.${data}`)}
{t(`copy-type.${data.type}`)}
</label>
</div>
);
@@ -29,6 +29,8 @@ function PlaceholderType(props) {
const [textValue, setTextValue] = useState();
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const years = range(1990, getYear(new Date()) + 16, 1);
const fontSize = (props.pos.options?.fontSize || "12") + "px";
const fontColor = props.pos.options?.fontColor || "black";
const months = [
"January",
"February",
@@ -143,7 +145,12 @@ function PlaceholderType(props) {
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div
className={`${selectWidgetCls} text-[12px] overflow-hidden`}
style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif"
}}
className={`${selectWidgetCls} overflow-hidden`}
onClick={onClick}
ref={ref}
>
@@ -343,9 +350,13 @@ function PlaceholderType(props) {
return (
<div key={ind} className="flex items-center text-center gap-0.5">
<input
style={{
width: fontSize,
height: fontSize
}}
className={`${
ind === 0 ? "mt-0" : "mt-[5px]"
} flex justify-center op-checkbox op-checkbox-xs rounded-[0.350rem]`}
} flex justify-center op-checkbox rounded-[1px] `}
onBlur={handleInputBlur}
disabled={
props.isNeedSign &&
@@ -379,7 +390,15 @@ function PlaceholderType(props) {
}}
/>
{!props.pos.options?.isHideLabel && (
<label className="text-xs mb-0 text-center">{data}</label>
<label
style={{
fontSize: fontSize,
color: fontColor
}}
className="text-xs mb-0 text-center"
>
{data}
</label>
)}
</div>
);
@@ -410,20 +429,16 @@ function PlaceholderType(props) {
}}
className={textWidgetCls}
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black"
fontSize: fontSize,
color: fontColor
}}
cols="50"
/>
) : (
<div
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black",
fontSize: fontSize,
color: fontColor,
overflow: "hidden"
}}
>
@@ -433,6 +448,10 @@ function PlaceholderType(props) {
case "dropdown":
return props.data?.signerObjId === props.signerObjId ? (
<select
style={{
fontSize: fontSize,
color: fontColor
}}
className={`${selectWidgetCls} text-[12px] bg-inherit`}
id="myDropdown"
value={selectOption}
@@ -450,20 +469,41 @@ function PlaceholderType(props) {
}}
>
{/* Default/Title option */}
<option value="" disabled hidden>
<option
style={{
fontSize: fontSize,
color: fontColor
}}
value=""
disabled
hidden
>
{props?.pos?.options?.name}
</option>
{props.pos?.options?.values?.map((data, ind) => {
return (
<option key={ind} value={data}>
<option
style={{
fontSize: fontSize,
color: fontColor
}}
key={ind}
value={data}
>
{data}
</option>
);
})}
</select>
) : (
<div className="text-[12px] overflow-hidden">
<div
style={{
fontSize: fontSize,
color: fontColor
}}
className=" overflow-hidden"
>
{props.pos?.options?.name
? props.pos.options.name
: widgetTypeTraslation}
@@ -511,20 +551,16 @@ function PlaceholderType(props) {
}}
className={textWidgetCls}
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black"
fontSize: fontSize,
color: fontColor
}}
cols="50"
/>
) : (
<div
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black",
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif",
overflow: "hidden"
}}
@@ -555,20 +591,16 @@ function PlaceholderType(props) {
}}
className={textWidgetCls}
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black"
fontSize: fontSize,
color: fontColor
}}
cols="50"
/>
) : (
<div
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black",
fontSize: fontSize,
color: fontColor,
overflow: "hidden",
fontFamily: "Arial, sans-serif"
}}
@@ -599,20 +631,16 @@ function PlaceholderType(props) {
}}
className={textWidgetCls}
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black"
fontSize: fontSize,
color: fontColor
}}
cols="50"
/>
) : (
<div
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black",
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif",
overflow: "hidden"
}}
@@ -680,7 +708,14 @@ function PlaceholderType(props) {
/>
</div>
) : (
<div className="text-[12px] text-black items-center overflow-hidden">
<div
style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif"
}}
className="items-center overflow-hidden"
>
<span>
{props.selectDate
? props.selectDate?.format
@@ -732,10 +767,8 @@ function PlaceholderType(props) {
}}
className={textWidgetCls}
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black",
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif"
}}
cols="50"
@@ -743,10 +776,8 @@ function PlaceholderType(props) {
) : (
<div
style={{
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black",
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif",
overflow: "hidden"
}}
@@ -761,9 +792,12 @@ function PlaceholderType(props) {
return (
<div key={ind} className="flex items-center text-center gap-0.5">
<input
className={`${
ind === 0 ? "mt-0" : "mt-[5px]"
} ${"w-[15px] h-[15px]"} flex justify-center op-radio`}
style={{
width: fontSize,
height: fontSize,
marginTop: ind > 0 ? "10px" : "0px"
}}
className={`flex justify-center op-radio`}
type="radio"
disabled={
props.isNeedSign &&
@@ -778,7 +812,15 @@ function PlaceholderType(props) {
}}
/>
{!props.pos.options?.isHideLabel && (
<label className="text-xs mb-0">{data}</label>
<label
style={{
fontSize: fontSize,
color: fontColor
}}
className="text-xs mb-0"
>
{data}
</label>
)}
</div>
);
@@ -808,10 +850,8 @@ function PlaceholderType(props) {
className={textWidgetCls}
style={{
fontFamily: "Arial, sans-serif",
fontSize: props.pos.options?.fontSize
? props.pos.options?.fontSize + "px"
: "12px",
color: props.pos.options?.fontColor || "black"
fontSize: fontSize,
color: fontColor
}}
cols="50"
/>
@@ -16,7 +16,7 @@ const RecipientList = (props) => {
//function for onhover signer name change background color
const inputRef = useRef(null);
const isWidgetExist = (Id) => {
return props.signerPos.some((x) => x.Id === Id);
return props.signerPos.some((x) => x.Id === Id && x.placeHolder);
};
//handle drag start
@@ -63,9 +63,7 @@ const RecipientList = (props) => {
remainingItems.splice(index, 0, ...draggedItem);
//set current draggable recipient details,objectId,index,contract_className ... after replace recipient list
props?.setSignersData(remainingItems);
props?.setSignerObjId(remainingItems[index]?.objectId || "");
props?.setIsSelectId(index);
props?.setContractName(remainingItems[index]?.className || "");
props?.setUniqueId(remainingItems[index]?.Id);
props?.setRoleName(remainingItems[index]?.Role);
props?.setBlockColor(remainingItems[index]?.blockColor);
@@ -121,9 +119,7 @@ const RecipientList = (props) => {
}}
onClick={(e) => {
e.preventDefault();
props.setSignerObjId(obj?.objectId || "");
props.setIsSelectId(ind);
props.setContractName(obj?.className || "");
props.setUniqueId(obj.Id);
props.setRoleName(obj.Role);
props.setBlockColor(obj?.blockColor);
@@ -12,7 +12,8 @@ function RenderAllPdfPage({
pageNumber,
signerPos,
signerObjectId,
containerWH
containerWH,
pdfRotateBase64
}) {
const { t } = useTranslation();
const [signPageNumber, setSignPageNumber] = useState([]);
@@ -66,6 +67,7 @@ function RenderAllPdfPage({
)
);
};
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
return (
<div ref={pageContainer} className="hidden w-[20%] bg-base-100 md:block">
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
@@ -78,7 +80,7 @@ function RenderAllPdfPage({
<Document
loading={t("loading-doc")}
onLoadSuccess={onDocumentLoad}
file={signPdfUrl}
file={(pdfRotateBase64 && pdfDataBase64) || signPdfUrl}
>
{Array.from(new Array(allPages), (el, index) => (
<div
@@ -95,16 +97,16 @@ function RenderAllPdfPage({
>
{signerPos && addSignatureBookmark(index)}
<div className="relative z-[1] overflow-hidden">
<Page
key={`page_${index + 1}`}
pageNumber={index + 1}
width={pageWidth - 60}
scale={1}
renderAnnotationLayer={false}
renderTextLayer={false}
/>
</div>
{/* <div className="relative z-[1] overflow-hidden"> */}
<Page
key={`page_${index + 1}`}
pageNumber={index + 1}
width={pageWidth - 60}
scale={1}
renderAnnotationLayer={false}
renderTextLayer={false}
/>
{/* </div> */}
</div>
))}
</Document>
+254 -206
View File
@@ -41,7 +41,6 @@ function RenderPdf({
setUniqueId,
signersdata,
setIsPageCopy,
setSignerObjId,
setShowDropdown,
setIsInitial,
setIsValidate,
@@ -61,10 +60,16 @@ function RenderPdf({
scale,
setIsSelectId,
ispublicTemplate,
handleUserDetails
handleUserDetails,
pdfRotateBase64,
fontSize,
setFontSize,
fontColor,
setFontColor
}) {
const { t } = useTranslation();
const isMobile = window.innerWidth < 767;
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
const isGuestSigner = localStorage.getItem("isGuestSigner");
@@ -248,155 +253,180 @@ function RenderPdf({
);
}
};
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
return (
<>
{successEmail && (
<Alert type={"success"}>{t("success-email-alert")}</Alert>
)}
{isMobile ? (
<div
data-tut="reactourForth"
className={`${
isGuestSigner ? "30px" : ""
} border-[0.1px] border-[#ebe8e8]`}
ref={drop}
id="container"
<RSC
style={{
position: "relative",
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
height: window.innerHeight - 200,
zIndex: 0
}}
className="h-full"
noScrollY={false}
noScrollX={scale === 1 ? true : false}
>
{containerWH?.width &&
pdfOriginalWH.length > 0 &&
(pdfRequest
? signerPos.map((data, key) => {
return (
<React.Fragment key={key}>
{checkSignedSigners(data)}
</React.Fragment>
);
})
: placeholder // placeholder mobile
? signerPos.map((data, ind) => {
<div
data-tut="reactourForth"
className={`${
isGuestSigner ? "30px" : ""
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
style={{
width: containerWH?.width && containerWH?.width * scale
}}
ref={drop}
id="container"
>
{containerWH?.width &&
pdfOriginalWH.length > 0 &&
(pdfRequest
? signerPos.map((data, key) => {
return (
<React.Fragment key={ind}>
{data.placeHolder.map((placeData, index) => {
return (
<React.Fragment key={index}>
{placeData.pageNumber === pageNumber &&
placeData.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={pageNumber}
xyPostion={signerPos}
setXyPostion={setSignerPos}
setSignerObjId={setSignerObjId}
data={data}
setIsResize={setIsResize}
setShowDropdown={setShowDropdown}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={setUniqueId}
handleLinkUser={handleLinkUser}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={isDragging}
setIsValidate={setIsValidate}
setWidgetType={setWidgetType}
setIsRadio={setIsRadio}
setIsCheckbox={setIsCheckbox}
setCurrWidgetsDetails={
setCurrWidgetsDetails
}
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
handleNameModal={handleNameModal}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
handleTextSettingModal={
handleTextSettingModal
}
scale={scale}
containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
setIsSelectId={setIsSelectId}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
<React.Fragment key={key}>
{checkSignedSigners(data)}
</React.Fragment>
);
})
: xyPostion.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data.pageNumber === pageNumber &&
data.pos.map((pos) => {
return (
pos && (
<Placeholder
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
handleSignYourselfImageResize={
handleSignYourselfImageResize
}
index={index}
xyPostion={xyPostion}
setXyPostion={setXyPostion}
containerWH={containerWH}
setIsSignPad={setIsSignPad}
isShowBorder={true}
isSignYourself={true}
posWidth={posWidth}
posHeight={posHeight}
pdfDetails={pdfDetails[0]}
isDragging={isDragging}
setIsInitial={setIsInitial}
setWidgetType={setWidgetType}
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={setIsCheckbox}
setValidateAlert={setValidateAlert}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleTextSettingModal={
handleTextSettingModal
}
scale={scale}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
/>
)
);
})}
</React.Fragment>
);
}))}
: placeholder // placeholder mobile
? signerPos?.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data?.placeHolder &&
data?.placeHolder.map((placeData, index) => {
return (
<React.Fragment key={index}>
{placeData.pageNumber === pageNumber &&
placeData.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={pageNumber}
xyPostion={signerPos}
setXyPostion={setSignerPos}
data={data}
setIsResize={setIsResize}
setShowDropdown={setShowDropdown}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={setUniqueId}
handleLinkUser={handleLinkUser}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={isDragging}
setIsValidate={setIsValidate}
setWidgetType={setWidgetType}
setIsRadio={setIsRadio}
setIsCheckbox={setIsCheckbox}
setCurrWidgetsDetails={
setCurrWidgetsDetails
}
setSelectWidgetId={
setSelectWidgetId
}
selectWidgetId={selectWidgetId}
handleNameModal={handleNameModal}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
handleTextSettingModal={
handleTextSettingModal
}
scale={scale}
containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
setIsSelectId={setIsSelectId}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
</React.Fragment>
);
})
: !pdfDetails?.[0]?.IsCompleted &&
xyPostion.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data.pageNumber === pageNumber &&
data.pos.map((pos) => {
return (
pos && (
<Placeholder
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
handleSignYourselfImageResize={
handleSignYourselfImageResize
}
index={index}
xyPostion={xyPostion}
setXyPostion={setXyPostion}
containerWH={containerWH}
setIsSignPad={setIsSignPad}
isShowBorder={true}
isSignYourself={true}
posWidth={posWidth}
posHeight={posHeight}
pdfDetails={pdfDetails[0]}
isDragging={isDragging}
setIsInitial={setIsInitial}
setWidgetType={setWidgetType}
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={setIsCheckbox}
setValidateAlert={setValidateAlert}
setCurrWidgetsDetails={
setCurrWidgetsDetails
}
handleTextSettingModal={
handleTextSettingModal
}
scale={scale}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
)
);
})}
</React.Fragment>
);
}))}
{/* this component for render pdf document is in middle of the component */}
<div className="flex items-center justify-center">
<Document
onLoadError={() => {
setPdfLoad(false);
}}
onLoadError={() => setPdfLoad(false)}
loading={t("loading-doc")}
onLoadSuccess={pageDetails}
// ref={pdfRef}'
@@ -405,9 +435,14 @@ function RenderPdf({
setSelectWidgetId("");
}
}}
file={pdfDetails[0]?.SignedUrl || pdfDetails[0]?.URL}
file={
(pdfRotateBase64 && pdfDataBase64) ||
pdfDetails[0]?.SignedUrl ||
pdfDetails[0].URL
}
>
<Page
scale={scale || 1}
key={index}
pageNumber={pageNumber}
width={containerWH.width}
@@ -419,7 +454,7 @@ function RenderPdf({
/>
</Document>
</div>
</div>
</RSC>
) : (
<RSC
style={{
@@ -453,71 +488,78 @@ function RenderPdf({
? signerPos.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data.placeHolder.map((placeData, index) => {
return (
<React.Fragment key={index}>
{placeData.pageNumber === pageNumber &&
placeData.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={pageNumber}
xyPostion={signerPos}
setXyPostion={setSignerPos}
setSignerObjId={setSignerObjId}
data={data}
setIsResize={setIsResize}
setShowDropdown={setShowDropdown}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={setUniqueId}
handleLinkUser={handleLinkUser}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={isDragging}
setIsValidate={setIsValidate}
setWidgetType={setWidgetType}
setIsRadio={setIsRadio}
setIsCheckbox={setIsCheckbox}
setCurrWidgetsDetails={
setCurrWidgetsDetails
}
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
handleNameModal={handleNameModal}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
handleTextSettingModal={
handleTextSettingModal
}
scale={scale}
containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
setIsSelectId={setIsSelectId}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
{data?.placeHolder &&
data?.placeHolder.map((placeData, index) => {
return (
<React.Fragment key={index}>
{placeData.pageNumber === pageNumber &&
placeData.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={pageNumber}
xyPostion={signerPos}
setXyPostion={setSignerPos}
data={data}
setIsResize={setIsResize}
setShowDropdown={setShowDropdown}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={setUniqueId}
handleLinkUser={handleLinkUser}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={isDragging}
setIsValidate={setIsValidate}
setWidgetType={setWidgetType}
setIsRadio={setIsRadio}
setIsCheckbox={setIsCheckbox}
setCurrWidgetsDetails={
setCurrWidgetsDetails
}
setSelectWidgetId={
setSelectWidgetId
}
selectWidgetId={selectWidgetId}
handleNameModal={handleNameModal}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
handleTextSettingModal={
handleTextSettingModal
}
scale={scale}
containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
setIsSelectId={setIsSelectId}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
</React.Fragment>
);
})}
</React.Fragment>
);
})}
</React.Fragment>
);
})
: xyPostion.map((data, ind) => {
: !pdfDetails?.[0]?.IsCompleted &&
xyPostion?.map((data, ind) => {
// signyourself flow
return (
<React.Fragment key={ind}>
@@ -565,6 +607,10 @@ function RenderPdf({
containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
</React.Fragment>
);
@@ -575,9 +621,7 @@ function RenderPdf({
{/* this component for render pdf document is in middle of the component */}
<Document
onLoadError={() => {
setPdfLoad(false);
}}
onLoadError={() => setPdfLoad(false)}
loading={t("loading-doc")}
onLoadSuccess={pageDetails}
onClick={() => {
@@ -586,7 +630,11 @@ function RenderPdf({
}
}}
// ref={pdfRef}
file={pdfDetails[0]?.SignedUrl || pdfDetails[0].URL}
file={
(pdfRotateBase64 && pdfDataBase64) ||
pdfDetails[0]?.SignedUrl ||
pdfDetails[0].URL
}
>
<Page
key={index}
@@ -21,15 +21,15 @@ function SelectLanguage(props) {
return (
<div
className={`${
!props.isProfile && " mt-3 pb-2 md:pb-0 "
!props.isProfile && " mt-[9px] pb-2 md:pb-0 "
} flex justify-center items-center `}
>
<select
value={lang}
onChange={handleChangeLang}
className={`${
!props.isProfile && " md:w-[15%] w-[50%]"
} op-select op-select-bordered bg-white op-select-sm`}
!props.isProfile ? " md:w-[15%] w-[50%]" : "w-[180px]"
} op-select op-select-bordered bg-white op-select-sm `}
>
<option disabled selected>
select
@@ -56,9 +56,7 @@ function VerifyEmail(props) {
<button
className="op-btn op-btn-primary"
type="submit"
onClick={() => {
props.handleVerifyBtn();
}}
onClick={() => props.handleVerifyBtn()}
>
{t("send-otp")}
</button>
@@ -20,9 +20,7 @@ function WidgetComponent({
addPositionOfSignature,
signersdata,
isSelectListId,
setSignerObjId,
setIsSelectId,
setContractName,
isSigners,
dataTut,
isMailSend,
@@ -360,9 +358,7 @@ function WidgetComponent({
signerPos={signerPos}
signersdata={signersdata}
isSelectListId={isSelectListId}
setSignerObjId={setSignerObjId}
setIsSelectId={setIsSelectId}
setContractName={setContractName}
setUniqueId={setUniqueId}
setRoleName={setRoleName}
handleDeleteUser={handleDeleteUser}
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react";
import Select from "react-select";
import AsyncSelect from "react-select/async";
import AddSigner from "../../AddSigner";
import Parse from "parse";
import Tooltip from "../../../primitives/Tooltip";
@@ -35,7 +35,6 @@ const AddSignerModal = ({ isOpen, children }) => {
const SignersInput = (props) => {
const { t } = useTranslation();
const [state, setState] = useState(undefined);
// const [editFormData, setEditFormData] = useState([]);
const [selected, setSelected] = useState([]);
const [isModal, setIsModel] = useState(false);
const onChange = (selectedOptions) => setSelected(selectedOptions);
@@ -46,40 +45,6 @@ const SignersInput = (props) => {
setSelected(newValue);
};
const GetSelectListData = async () => {
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);
const contactRes = await contactbook.find();
if (contactRes) {
const res = JSON.parse(JSON.stringify(contactRes));
let list = [];
// let _selected = [];
res.forEach((x) => {
let obj = {
label: x.Name,
value: x.objectId,
isChecked: true
};
list.push(obj);
});
setState(list);
}
} catch (error) {
console.log("err", error);
}
};
useEffect(() => {
GetSelectListData();
}, []);
useEffect(() => {
if (props.isReset && props.isReset === true) {
setSelected([]);
@@ -118,7 +83,46 @@ const SignersInput = (props) => {
setSelected([data]);
}
};
const loadOptions = async (inputValue) => {
try {
const currentUser = Parse.User.current();
const contactbook = new Parse.Query("contracts_Contactbook");
contactbook.equalTo(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
if (inputValue.length > 1) {
contactbook.matches("Name", new RegExp(inputValue, "i"));
}
contactbook.notEqualTo("IsDeleted", true);
const contactRes = await contactbook.find();
if (contactRes) {
const res = JSON.parse(JSON.stringify(contactRes));
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
//and filter signers from total signer's list which already present in document's signers list
const compareArrays = (res, signerObj) => {
return res.filter(
(item1) =>
!signerObj.find((item2) => item2.objectId === item1.objectId)
);
};
//get update signer's List if signersdata is present
const updateSignersList =
props?.signersData && compareArrays(res, props?.signersData);
const result = updateSignersList ? updateSignersList : res;
setState(result);
return await result.map((item) => ({
label: item.Name,
value: item.objectId,
isChecked: true
}));
}
} catch (error) {
console.log("err", error);
}
};
return (
<div className="text-xs mt-2 ">
<label className="block relative">
@@ -130,16 +134,20 @@ const SignersInput = (props) => {
</label>
<div className="flex gap-x-[5px]">
<div className="w-full">
<Select
<AsyncSelect
onSortEnd={onSortEnd}
distance={4}
isMulti
cacheOptions
defaultOptions
options={state || []}
value={selected}
onChange={onChange}
closeMenuOnSelect={false}
required={props.required}
loadingMessage={() => t("loading")}
noOptionsMessage={() => t("contact-not-found")}
loadOptions={loadOptions}
unstyled
classNames={{
control: () =>
+257 -85
View File
@@ -1,7 +1,7 @@
import axios from "axios";
import moment from "moment";
import React from "react";
import { rgb } from "pdf-lib";
import { PDFDocument, rgb, degrees } from "pdf-lib";
import Parse from "parse";
import { appInfo } from "./appinfo";
import { saveAs } from "file-saver";
@@ -56,18 +56,68 @@ export async function fetchSubscription(
? { contactId: contactObjId }
: { extUserId: extUser };
const tenatRes = await axios.post(url, params, { headers: headers });
let plan, status, billingDate;
let plan, status, billingDate, adminId;
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;
const allowedUsers = tenatRes.data?.result?.result?.AllowedUsers || 0;
adminId = tenatRes?.data?.result?.result?.ExtUserPtr?.objectId;
localStorage.setItem("allowedUsers", allowedUsers);
}
return { plan, billingDate, status };
return { plan, billingDate, status, adminId };
} catch (err) {
console.log("Err in fetch subscription", err);
return { plan: "", billingDate: "" };
return { plan: "", billingDate: "", status: "", adminId: "" };
}
}
export async function fetchSubscriptionInfo() {
try {
const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class);
// console.log("extClass ", extClass);
if (extClass && extClass.length > 0) {
const extUser = extClass[0].objectId;
const baseURL = localStorage.getItem("baseUrl");
const url = `${baseURL}functions/getsubscriptions`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const params = { extUserId: extUser };
const tenatRes = await axios.post(url, params, { headers: headers });
const price =
tenatRes.data?.result?.result?.SubscriptionDetails?.data?.subscription
?.plan?.price;
const totalPrice =
tenatRes.data?.result?.result?.SubscriptionDetails?.data?.subscription
?.amount;
const planId =
tenatRes.data?.result?.result?.SubscriptionDetails?.data?.subscription
?.subscription_id;
const plan_code = tenatRes.data?.result?.result?.PlanCode;
const totalAllowedUser = tenatRes.data?.result?.result?.AllowedUsers || 0;
const adminId =
tenatRes?.data?.result?.result?.ExtUserPtr?.objectId || "";
return {
status: "success",
price: price,
totalPrice: totalPrice,
planId: planId,
plan_code: plan_code,
totalAllowedUser: totalAllowedUser,
adminId: adminId
};
}
} catch (err) {
console.log("Err in fetch subscription", err);
return { status: "error", error: err };
}
}
//function to get subcripition details from subscription class
@@ -75,41 +125,22 @@ export async function checkIsSubscribed() {
try {
const res = await fetchSubscription();
if (res.plan === "freeplan") {
return false;
} else if (res.billingDate) {
if (new Date(res.billingDate) > new Date()) {
return true;
} else {
return false;
}
} else {
return false;
}
} catch (err) {
console.log("Err in fetch subscription", err);
return false;
}
}
//function to get subcripition details from subscription class
export async function checkIsSubscribedTeam() {
try {
const res = await fetchSubscription();
if (res.plan === "freeplan") {
return false;
return { plan: res.plan, isValid: false, adminId: res?.adminId };
} else if (res.billingDate) {
const plan = validplan[res.plan] || false;
if (plan && new Date(res.billingDate) > new Date()) {
return true;
return { plan: res.plan, isValid: true, adminId: res?.adminId };
} else if (new Date(res.billingDate) > new Date()) {
return { plan: res.plan, isValid: true, adminId: res?.adminId };
} else {
return false;
return { plan: res.plan, isValid: false, adminId: res?.adminId };
}
} else {
return false;
return { plan: res.plan, isValid: false, adminId: res?.adminId };
}
} catch (err) {
console.log("Err in fetch subscription", err);
return false;
return { plan: "no-plan", isValid: false, adminId: "" };
}
}
@@ -630,7 +661,12 @@ export const randomId = () => {
return randomDigit;
};
export const createDocument = async (template, placeholders, signerData) => {
export const createDocument = async (
template,
placeholders,
signerData,
pdfUrl
) => {
if (template && template.length > 0) {
const Doc = template[0];
@@ -653,7 +689,7 @@ export const createDocument = async (template, placeholders, signerData) => {
}
const data = {
Name: Doc.Name,
URL: Doc.URL,
URL: pdfUrl,
SignedUrl: Doc.SignedUrl,
SentToOthers: Doc.SentToOthers,
Description: Doc.Description,
@@ -762,7 +798,9 @@ export const onChangeInput = (
initial,
dateFormat,
isDefaultEmpty,
isRadio
isRadio,
fontSize,
fontColor
) => {
const isSigners = xyPostion.some((data) => data.signerPtr);
let filterSignerPos;
@@ -789,6 +827,8 @@ export const onChangeInput = (
options: {
...position.options,
response: value,
fontSize: fontSize,
fontColor: fontColor,
validation: {
type: "date-format",
format: dateFormat // This indicates the required date format explicitly.
@@ -835,7 +875,6 @@ export const onChangeInput = (
}
} else {
let getXYdata = xyPostion[index].pos;
const updatePosition = getXYdata.map((positionData) => {
if (positionData.key === signKey) {
if (dateFormat) {
@@ -844,6 +883,8 @@ export const onChangeInput = (
options: {
...positionData.options,
response: value,
fontSize: fontSize,
fontColor: fontColor,
validation: {
type: "date-format",
format: dateFormat // This indicates the required date format explicitly.
@@ -1043,13 +1084,18 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
const textContent = documentId && `OpenSign™ DocumentId: ${documentId} `;
const pages = pdfDoc.getPages();
const page = pages[i];
page.drawText(textContent, {
x: 10,
y: page.getHeight() - 10,
size: fontSize,
const getObj = compensateRotation(
page.getRotation().angle,
10,
5,
1,
page.getSize(),
fontSize,
rgb(0.5, 0.5, 0.5),
font,
color: rgb(0.5, 0.5, 0.5)
});
page
);
page.drawText(textContent, getObj);
}
};
@@ -1243,6 +1289,21 @@ const calculateFontSize = (position, containerScale, signyourself) => {
return font / containerScale;
}
};
const getWidgetsFontColor = (type) => {
switch (type) {
case "red":
return rgb(1, 0, 0);
case "black":
return rgb(0, 0, 0);
case "blue":
return rgb(0, 0, 1);
case "yellow":
return rgb(0.9, 1, 0);
default:
return rgb(0, 0, 0);
}
};
//function for embed multiple signature using pdf-lib
export const multiSignEmbed = async (
widgets,
@@ -1351,6 +1412,9 @@ export const multiSignEmbed = async (
return resizePos;
}
};
const color = position?.options?.fontColor;
const updateColorInRgb = getWidgetsFontColor(color);
const fontSize = parseInt(position?.options?.fontSize || 13);
const widgetTypeExist = [
textWidget,
textInputWidget,
@@ -1361,11 +1425,11 @@ export const multiSignEmbed = async (
"email"
].includes(position.type);
if (position.type === "checkbox") {
let checkboxOptionGapFromTop, isCheck;
let checkboxGapFromTop, isCheck;
let y = yPos(position);
const optionsFontSize = 13;
const checkboxSize = 18;
const checkboxTextGapFromLeft = 22;
const optionsFontSize = fontSize || 13;
const checkboxSize = fontSize;
const checkboxTextGapFromLeft = fontSize + 5 || 22;
if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => {
const checkboxRandomId = "checkbox" + randomId();
@@ -1382,9 +1446,9 @@ export const multiSignEmbed = async (
const checkbox = form.createCheckBox(checkboxRandomId);
if (ind > 0) {
y = y + checkboxOptionGapFromTop;
y = y + checkboxGapFromTop;
} else {
checkboxOptionGapFromTop = 26;
checkboxGapFromTop = fontSize + 5 || 26;
}
if (!position?.options?.isHideLabel) {
@@ -1396,7 +1460,7 @@ export const multiSignEmbed = async (
1,
page.getSize(),
optionsFontSize,
rgb(0, 0, 0),
updateColorInRgb,
font,
page
);
@@ -1427,20 +1491,6 @@ export const multiSignEmbed = async (
signyourself
);
parseInt(fontSize);
const color = position?.options?.fontColor;
let updateColorInRgb;
if (color === "red") {
updateColorInRgb = rgb(1, 0, 0);
} else if (color === "black") {
updateColorInRgb = rgb(0, 0, 0);
} else if (color === "blue") {
updateColorInRgb = rgb(0, 0, 1);
} else if (color === "yellow") {
updateColorInRgb = rgb(0.9, 1, 0);
} else {
updateColorInRgb = rgb(0, 0, 0);
}
let textContent;
if (position?.options?.response) {
textContent = position.options?.response;
@@ -1519,6 +1569,8 @@ export const multiSignEmbed = async (
y += 18; // Adjust the line height as needed
}
} else if (position.type === "dropdown") {
const fontsize = parseInt(position?.options?.fontSize) || 12;
const dropdownRandomId = "dropdown" + randomId();
const dropdown = form.createDropdown(dropdownRandomId);
dropdown.addOptions(position?.options?.values);
@@ -1527,31 +1579,41 @@ export const multiSignEmbed = async (
} else if (position?.options?.defaultValue) {
dropdown.select(position?.options?.defaultValue);
}
// Define the default appearance string
// Example format: `/FontName FontSize Tf 0 g` where:
// - `/FontName` is the name of the font (e.g., `/Helv` for Helvetica)
// - `FontSize` is the size you want to set (e.g., 12)
// - `Tf` specifies the font and size
// - `0 g` sets the text color to black
const defaultAppearance = `/Helv ${fontsize} Tf 0 g`;
// Set the default appearance for the dropdown field
dropdown.acroField.setDefaultAppearance(defaultAppearance);
dropdown.setFontSize(fontsize);
const dropdownObj = {
x: xPos(position),
y: yPos(position),
width: widgetWidth,
height: widgetHeight
};
dropdown.defaultUpdateAppearances(font);
const dropdownOption = getWidgetPosition(page, dropdownObj, 1);
const dropdownSelected = { ...dropdownOption, font: font };
dropdown.defaultUpdateAppearances(font);
dropdown.addToPage(page, dropdownSelected);
dropdown.enableReadOnly();
} else if (position.type === radioButtonWidget) {
const radioRandomId = "radio" + randomId();
const radioGroup = form.createRadioGroup(radioRandomId);
let radioOptionGapFromTop;
const optionsFontSize = 16;
const radioTextGapFromLeft = 20;
const radioSize = 18;
const optionsFontSize = fontSize || 13;
const radioTextGapFromLeft = fontSize + 5 || 20;
const radioSize = fontSize;
let y = yPos(position);
if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => {
if (ind > 0) {
y = y + radioOptionGapFromTop;
} else {
radioOptionGapFromTop = 25;
radioOptionGapFromTop = fontSize + 10 || 25;
}
if (!position?.options?.isHideLabel) {
// below line of code is used to embed label with radio button in pdf
@@ -1563,7 +1625,7 @@ export const multiSignEmbed = async (
1,
page.getSize(),
optionsFontSize,
rgb(0, 0, 0),
updateColorInRgb,
font,
page
);
@@ -1799,29 +1861,26 @@ export const handleCopyNextToWidget = (
setXyPostion,
userId
) => {
const isSigners = xyPostion.some((data) => data.signerPtr);
let filterSignerPos;
//get position of previous widget and create new widget next to that widget on same data except
// xPosition and key
let newpos = position;
let newposition = position;
const calculateXPosition =
parseInt(position.xPosition) +
defaultWidthHeight(widgetType).width +
resizeBorderExtraWidth();
const newId = randomId();
newpos = { ...newpos, xPosition: calculateXPosition, key: newId };
newposition = { ...newposition, xPosition: calculateXPosition, key: newId };
//if condition to create widget in request-sign flow
if (isSigners) {
if (userId) {
filterSignerPos = xyPostion.filter((data) => data.Id === userId);
}
const getPlaceHolder = filterSignerPos[0]?.placeHolder;
const getPageNumer = getPlaceHolder.filter(
if (userId) {
filterSignerPos = xyPostion.filter((data) => data.Id === userId);
const getPlaceHolder = filterSignerPos && filterSignerPos[0]?.placeHolder;
const getPageNumer = getPlaceHolder?.filter(
(data) => data.pageNumber === index
);
const getXYdata = getPageNumer[0].pos;
getXYdata.push(newpos);
if (getPageNumer.length > 0) {
const getXYdata = getPageNumer && getPageNumer[0]?.pos;
getXYdata.push(newposition);
if (getPageNumer && getPageNumer.length > 0) {
const newUpdateSignPos = getPlaceHolder.map((obj) => {
if (obj.pageNumber === index) {
return { ...obj, pos: getXYdata };
@@ -1839,9 +1898,8 @@ export const handleCopyNextToWidget = (
setXyPostion(newUpdateSigner);
}
} else {
// else condition to create widget in sign-yourself flow
let getXYdata = xyPostion[index].pos;
getXYdata.push(newpos);
let getXYdata = xyPostion[index]?.pos || [];
getXYdata.push(newposition);
const updatePlaceholder = xyPostion.map((obj, ind) => {
if (ind === index) {
return { ...obj, pos: getXYdata };
@@ -2142,9 +2200,11 @@ function compensateRotation(
font,
page
) {
// Ensure pageRotation is between 0 and 360 degrees
pageRotation = ((pageRotation % 360) + 360) % 360;
let rotationRads = (pageRotation * Math.PI) / 180;
//These coords are now from bottom/left
// Coordinates are from bottom-left
let coordsFromBottomLeft = { x: x / scale };
if (pageRotation === 90 || pageRotation === 270) {
coordsFromBottomLeft.y = dimensions.width - (y + fontSize) / scale;
@@ -2154,6 +2214,7 @@ function compensateRotation(
let drawX = null;
let drawY = null;
if (pageRotation === 90) {
drawX =
coordsFromBottomLeft.x * Math.cos(rotationRads) -
@@ -2179,8 +2240,8 @@ function compensateRotation(
coordsFromBottomLeft.x * Math.sin(rotationRads) +
coordsFromBottomLeft.y * Math.cos(rotationRads) +
dimensions.height;
} else {
//no rotation
} else if (pageRotation === 0 || pageRotation === 360) {
// No rotation or full rotation
drawX = coordsFromBottomLeft.x;
drawY = coordsFromBottomLeft.y;
}
@@ -2284,3 +2345,114 @@ export const getDefaultSignature = async (objectId) => {
};
}
};
//function to rotate pdf page
export async function rotatePdfPage(
url,
rotateDegree,
pageNumber,
pdfRotateBase64
) {
let file;
//condition to handle pdf base64 in arrayBuffer format
if (pdfRotateBase64) {
file = base64ToArrayBuffer(pdfRotateBase64);
} else {
//condition to handle pdf url in arrayBuffer format
file = await convertPdfArrayBuffer(url);
}
// Load the existing PDF
const pdfDoc = await PDFDocument.load(file);
// Get the page according to page number
const page = pdfDoc.getPage(pageNumber);
//get current page rotation angle
const currentRotation = page.getRotation().angle;
// Apply the rotation in the counterclockwise direction
let newRotation = (currentRotation + rotateDegree) % 360;
// Adjust for negative angles to keep within 0-359 range
if (newRotation < 0) {
newRotation += 360;
}
page.setRotation(degrees(newRotation));
const pdfbase64 = await pdfDoc.saveAsBase64({ useObjectStreams: false });
//convert base64 to arraybuffer is used in pdf-lib
//pdfbase64 is used to show pdf rotated format
const arrayBuffer = base64ToArrayBuffer(pdfbase64);
//`base64` is used to show pdf
return { arrayBuffer: arrayBuffer, base64: pdfbase64 };
}
function base64ToArrayBuffer(base64) {
// Decode the base64 string to a binary string
const binaryString = atob(base64);
// Create a new ArrayBuffer with the same length as the binary string
const len = binaryString.length;
const bytes = new Uint8Array(len);
// Convert the binary string to a byte array
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
// Return the ArrayBuffer
return bytes.buffer;
}
export const convertBase64ToFile = async (pdfName, pdfBase64) => {
const fileName = sanitizeFileName(pdfName) + ".pdf";
const pdfFile = new Parse.File(fileName, { base64: pdfBase64 });
// Save the Parse File if needed
const pdfData = await pdfFile.save();
const pdfUrl = pdfData.url();
return pdfUrl;
};
export const onClickZoomIn = (scale, zoomPercent, setScale, setZoomPercent) => {
setScale(scale + 0.1 * scale);
setZoomPercent(zoomPercent + 10);
};
export const onClickZoomOut = (
zoomPercent,
scale,
setZoomPercent,
setScale
) => {
if (zoomPercent > 0) {
if (zoomPercent === 10) {
setScale(1);
} else {
setScale(scale - 0.1 * scale);
}
setZoomPercent(zoomPercent - 10);
}
};
//function to use remove widgets from current page when user want to rotate page
export const handleRemoveWidgets = (
setSignerPos,
signerPos,
pageNumber,
setIsRotate
) => {
const updatedSignerPos = signerPos.map((placeholderObj) => {
return {
...placeholderObj,
placeHolder: placeholderObj?.placeHolder?.filter(
(data) => data?.pageNumber !== pageNumber
)
};
});
setSignerPos(updatedSignerPos);
setIsRotate({
status: false,
degree: 0
});
};
//function to show warning when user rotate page and there are some already widgets on that page
export const handleRotateWarning = (signerPos, pageNumber) => {
const placeholderExist = signerPos?.some((placeholderObj) =>
placeholderObj?.placeHolder?.some((data) => data?.pageNumber === pageNumber)
);
if (placeholderExist) {
return true;
} else {
return false;
}
};
+1 -1
View File
@@ -3,7 +3,7 @@ import { serverUrl_fn } from "./appinfo";
const parseAppId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID
: "opensign";
const serverUrl = serverUrl_fn()
const serverUrl = serverUrl_fn();
export const SaveFileSize = async (size, imageUrl, tenantId) => {
//checking server url and save file's size
const tenantPtr = {
+32 -8
View File
@@ -12,23 +12,35 @@ body {
}
.hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
@media screen and (max-width: 766px) {
.bBofGr {
width: 14px !important;
}
}
/* below css class replace bootstrap background color with daisy */
.op-bg-primary {
@apply bg-primary;
}
.op-bg-secondary {
@apply bg-secondary;
}
.op-bg-info {
@apply bg-info;
}
.op-bg-success{
.op-bg-success {
@apply bg-success;
}
.op-bg-warning {
@apply bg-warning;
}
@@ -37,25 +49,37 @@ body {
.op-text-primary {
@apply text-primary;
}
.op-text-secondary {
@apply text-secondary;
}
.op-text-info {
@apply text-info;
}
.op-text-success{
.op-text-accent {
@apply text-accent;
}
.op-text-success {
@apply text-success;
}
.op-text-warning {
@apply text-warning;
}
.op-border-primary{
@apply border-primary
.op-border-primary {
@apply border-primary;
}
/* CSS for scrollbar customization */
* {
scrollbar-width: thin; /* For Firefox */
scrollbar-color: gray; /* For Firefox */
scrollbar-width: thin;
/* For Firefox */
scrollbar-color: gray;
/* For Firefox */
}
/* For webkit-based browsers like Chrome, Safari */
+7
View File
@@ -354,6 +354,13 @@ export default function reportJson(id) {
redirectUrl: "template",
action: "redirect"
},
{
btnId: "2434",
btnLabel: "Embed",
hoverLabel: "Embed",
btnIcon: "fa-light fa-code",
action: "Embed"
},
{
btnId: "1834",
btnLabel: "Delete",
+1 -1
View File
@@ -36,7 +36,7 @@ const dashboardJson = [
queryType: "",
class: "contracts_Document",
query:
'where={"Type":{"#*ne":"Folder"},"Signers":{"#*exists":true,"#*ne":[]},"Placeholders":{"#*ne":null},"IsCompleted":{"#*ne":true},"IsDeclined":{"#*ne":true},"IsArchive":{"#*ne":true},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"#UserId.objectId#"},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&count=1',
'where={"Type":{"#*ne":"Folder"},"Signers":{"#*exists":true,"#*ne":[]},"Placeholders":{"#*ne":null},"SignedUrl":{"#*ne":null},"IsCompleted":{"#*ne":true},"IsDeclined":{"#*ne":true},"IsArchive":{"#*ne":true},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"#UserId.objectId#"},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&count=1',
key: "count",
Redirect_type: "Report",
Redirect_id: "1MwEuxLEkF",
+87 -14
View File
@@ -3,6 +3,7 @@ import { isStaging } from "../constant/const";
const plans = [
{
planName: "OPENSIGN™ FREE",
code: { monthly: "freeplan", yearly: "freeplan" },
img: "free.png",
currency: "",
monthlyPrice: "Free",
@@ -12,7 +13,7 @@ const plans = [
url: "",
target: "_blank",
benefits: [
"Unlimited envelopes",
"Unlimited digital signatures",
"Sign documents yourself",
"Request signatures from others",
"14 field types",
@@ -26,12 +27,11 @@ const plans = [
"Decline document support",
"Email notifications",
"Recipient authentication using OTP",
"Bulk send (upto 5 docs)",
"Public profiles",
"And much more"
],
yearlyBenefits: [
"Unlimited envelopes",
"Unlimited digital signatures",
"Sign documents yourself",
"Request signatures from others",
"14 field types",
@@ -45,13 +45,15 @@ const plans = [
"Decline document support",
"Email notifications",
"Recipient authentication using OTP",
"Bulk send (upto 5 docs)",
"Public profiles",
"And much more"
]
},
{
planName: "OPENSIGN™ PROFESSIONAL",
code: isStaging
? { monthly: "pro-weekly", yearly: "pro-yearly" }
: { monthly: "professional-monthly", yearly: "professional-yearly" },
img: "professional.png",
currency: "$",
monthlyPrice: "29.99",
@@ -73,10 +75,10 @@ const plans = [
"Webhooks",
"Zapier integration",
"API Access",
"100 API signatures included",
"upto 100 API signatures",
"Custom email templates",
"Auto reminders",
"Bulk send (upto 20 docs)",
"Bulk send (upto 100 docs)",
"Premium Public profile usernames",
"Embedding (coming soon)"
],
@@ -88,16 +90,17 @@ const plans = [
"Webhooks",
"Zapier integration",
"API Access",
"240 API signatures included",
"upto 240 API signatures",
"Custom email templates",
"Auto reminders",
"Bulk send (upto 20 docs)",
"Bulk send (upto 240 docs)",
"Premium Public profile usernames",
"Embedding (coming soon)"
]
},
{
planName: "OPENSIGN™ TEAMS",
code: { monthly: "teams-monthly", yearly: "teams-yearly" },
img: "teams.png",
currency: "$",
monthlyPrice: `39.99<sup style="font-size: 17px;">/user</sup>`,
@@ -105,31 +108,31 @@ const plans = [
subtitle: "Exclusive Access to advanced features.",
btn: { text: "Subscribe", color: "op-btn-accent" },
url: isStaging
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/team-weekly"
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-monthly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901237d0702bfaa959406306635d80f138c/teams-monthly",
yearlyUrl: isStaging
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/team-weekly"
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-yearly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af9011a864994bbeeec71fcf106188630199d/teams-yearly",
target: "_self",
benefits: [
"Everything in OpenSign™ professional",
"100 API signatures included",
"upto 100 API signatures",
"Teams and Organizations",
"Share Templates with teams",
"Share Templates with individuals",
"DocumentId removal from signed docs",
"Bulk send (upto 50 docs)",
"Bulk send (upto 100 docs)",
"Request Payments (coming soon)",
"Mobile app (coming soon)"
],
yearlyBenefits: [
"Everything in OpenSign™ professional",
"500 API signatures included",
"upto 500 API signatures",
"Teams and Organizations",
"Share Templates with teams",
"Share Templates with individuals",
"DocumentId removal from signed docs",
"Bulk send (upto 50 docs)",
"Bulk send (upto 500 docs)",
"Request Payments (coming soon)",
"Mobile app (coming soon)"
]
@@ -171,4 +174,74 @@ export const validplan = {
"enterprise-monthly": true,
"enterprise-yearly": true
};
export const paidUrl = (plan) => {
const teamperiod = {
"team-weekly": "monthly",
"team-yearly": "yearly",
"teams-monthly": "monthly",
"teams-yearly": "yearly"
};
const period = teamperiod[plan] || "";
if (period) {
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
};
// console.log("userDetails ", userDetails);
const fullname = user && user.name ? user.name.split(" ") : "";
const firstname = fullname?.[0]
? "first_name=" + encodeURIComponent(fullname?.[0])
: "";
const lastname = fullname?.[1]
? "&last_name=" + encodeURIComponent(fullname?.[1])
: "";
const name = firstname ? firstname + lastname : "";
const email =
user && user.email ? "&email=" + encodeURIComponent(user.email) : "";
const company =
user && user.company
? "&company_name=" + encodeURIComponent(user.company)
: "";
const phone =
user && user.phone ? "&mobile=" + encodeURIComponent(user.phone) : "";
const allowedUsers =
localStorage.getItem("allowedUsers") &&
localStorage.getItem("allowedUsers") > 0
? localStorage.getItem("allowedUsers") - 1
: "";
const quantity = allowedUsers
? `addon_code%5B0%5D=extra-teams-users-${period}&addon_quantity%5B0%5D=${allowedUsers}&`
: "";
const details =
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
quantity +
name +
email +
company +
phone;
if (user) {
localStorage.setItem("userDetails", JSON.stringify(user));
}
const url = {
monthly: isStaging
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-monthly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901237d0702bfaa959406306635d80f138c/teams-monthly",
yearly: isStaging
? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-yearly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af9011a864994bbeeec71fcf106188630199d/teams-yearly"
};
const planurl = url[period] + details;
return planurl;
} else {
return "/subscription";
}
};
export default plans;
+7 -5
View File
@@ -95,7 +95,9 @@ const HomeLayout = () => {
domain: updateDomain
});
};
const handleNavigation = () => {
navigate("/subscription");
};
async function checkIsSubscribed() {
if (isEnableSubscription) {
const res = await fetchSubscription();
@@ -107,10 +109,10 @@ const HomeLayout = () => {
setIsUserValid(true);
setIsLoader(false);
} else {
navigate(`/subscription`);
handleNavigation(res.plan);
}
} else {
navigate(`/subscription`);
handleNavigation(res.plan);
}
} else {
setIsUserValid(true);
@@ -250,9 +252,9 @@ const HomeLayout = () => {
}
};
const handleLoginBtn = () => {
const handleLoginBtn = async () => {
try {
Parse?.User?.logOut();
await Parse?.User?.logOut();
} catch (err) {
console.log("err ", err);
} finally {
+5 -3
View File
@@ -56,8 +56,10 @@ const AddAdmin = () => {
}
};
const clearStorage = async () => {
if (Parse.User.current()) {
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
const baseUrl = localStorage.getItem("baseUrl");
const appid = localStorage.getItem("parseAppId");
@@ -459,13 +461,13 @@ const AddAdmin = () => {
<input
type="checkbox"
className="op-checkbox op-checkbox-sm"
id="termsandcondition"
id="subscribetoopensign"
checked={isSubscribeNews}
onChange={(e) => setIsSubscribeNews(e.target.checked)}
/>
<label
className="text-xs cursor-pointer ml-1 mb-0"
htmlFor="termsandcondition"
htmlFor="subscribetoopensign"
>
{t("subscribe-to-opensign")}
</label>
+1 -5
View File
@@ -215,11 +215,7 @@ const DebugPdf = () => {
};
return (
<div>
{copied && (
<Alert type="success">
<span className="ml-3">Copied</span>
</Alert>
)}
{copied && <Alert type="success">Copied</Alert>}
<Title title={"Debug Pdf"} />
{width < 800 ? (
<HandleError handleError={"Debug PDF only availble for PC"} />
+6 -5
View File
@@ -15,11 +15,7 @@ function ForgotPassword() {
const { t } = useTranslation();
const dispatch = useDispatch();
const navigate = useNavigate();
const [state, setState] = useState({
email: "",
password: "",
hideNav: ""
});
const [state, setState] = useState({ email: "", password: "", hideNav: "" });
const [sentStatus, setSentStatus] = useState("");
const [image, setImage] = useState();
@@ -65,6 +61,11 @@ function ForgotPassword() {
// eslint-disable-next-line
}, []);
const saveLogo = async () => {
try {
await Parse.User.logOut();
} catch (err) {
console.log("err while logging out ", err);
}
if (isEnableSubscription) {
const app = await getAppLogo();
if (app?.logo) {
+6 -11
View File
@@ -1,7 +1,6 @@
import React, { useEffect, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { formJson } from "../json/FormJson";
import AddUser from "../components/AddUser";
import Parse from "parse";
import DropboxChooser from "../components/shared/fields/DropboxChoose";
import Alert from "../primitives/Alert";
@@ -37,15 +36,11 @@ function generatePdfName(length) {
function Form() {
const { id } = useParams();
if (id === "lM0xRnM3iE") {
return <AddUser />;
const config = formJson[id];
if (config) {
return <Forms {...config} />;
} else {
const config = formJson[id];
if (config) {
return <Forms {...config} />;
} else {
return <PageNotFound prefix={"Form"} />;
}
return <PageNotFound prefix={"Form"} />;
}
}
@@ -95,8 +90,8 @@ const Forms = (props) => {
}, []);
const fetchSubscription = async () => {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe.isValid);
}
};
+288 -33
View File
@@ -10,17 +10,39 @@ import Loader from "../primitives/Loader";
import SubscribeCard from "../primitives/SubscribeCard";
import Tour from "reactour";
import { useTranslation } from "react-i18next";
import Parse from "parse";
function GenerateToken() {
const origin = window.location.origin;
const { t } = useTranslation();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const parseBaseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
const [apiToken, SetApiToken] = useState("");
const [isLoader, setIsLoader] = useState(true);
const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isModal, setIsModal] = useState({
generateapi: false,
buyapis: false,
unauthorized: false
});
const [isSubscribe, setIsSubscribe] = useState({
plan: "",
isValid: false,
adminId: "",
adminEmail: ""
});
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isTour, setIsTour] = useState(false);
const [amount, setAmount] = useState({
quantity: 500,
priceperapi: 0.15,
allowedcredits: 0,
addoncredits: 0,
totalcredits: 0,
price: (75.0).toFixed(2)
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [apitestToken, setApiTestToken] = useState("");
const quantityList = [500, 1000, 5000, 50000];
useEffect(() => {
fetchToken();
// eslint-disable-next-line
@@ -34,8 +56,8 @@ function GenerateToken() {
const fetchToken = async () => {
try {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe);
}
const url = parseBaseUrl + "functions/getapitoken";
const headers = {
@@ -45,22 +67,40 @@ function GenerateToken() {
};
const res = await axios.post(url, {}, { headers: headers });
if (res) {
SetApiToken(res.data?.result?.result);
const resCredits = await Parse.Cloud.run("allowedcredits");
const allowedcredits = resCredits?.allowedcredits || 0;
const addoncredits = resCredits?.addoncredits || 0;
const totalcredits = allowedcredits + addoncredits;
setAmount((obj) => ({
...obj,
allowedcredits: allowedcredits,
addoncredits: addoncredits,
totalcredits: totalcredits
}));
SetApiToken(res?.data?.result?.result);
}
try {
const body = { email: Parse?.User?.current()?.getEmail() || "" };
const testurl = "https://sandbox.opensignlabs.com/api/gettesttoken";
const testRes = await axios.post(testurl, body);
setApiTestToken(testRes.data?.token);
} catch (err) {
console.log("err in fetch test token", err);
}
setIsLoader(false);
} catch (err) {
SetApiToken();
setIsLoader(false);
console.log("Err", err);
} finally {
setIsLoader(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!isSubscribe && isEnableSubscription) {
if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
setIsTour(true);
} else {
setIsLoader(true);
setIsModal(false);
setIsModal((obj) => ({ ...obj, generateapi: false }));
try {
const url = parseBaseUrl + "functions/generateapitoken";
const headers = {
@@ -91,20 +131,114 @@ function GenerateToken() {
const copytoclipboard = (text) => {
copytoData(text);
setIsAlert({ type: "success", msg: t("copied") });
setTimeout(() => {
setIsAlert({ type: "success", msg: "" });
}, 1500); // Reset copied state after 1.5 seconds
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
};
const handleModal = () => {
if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
setIsTour(true);
} else {
setIsModal((obj) => ({ ...obj, generateapi: !obj.generateapi }));
}
};
const handleModal = () => setIsModal(!isModal);
const handleBuyAPIsModal = () => {
if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
setIsTour(true);
} else {
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (isSubscribe?.adminId && extUser?.objectId === isSubscribe?.adminId) {
setIsModal((obj) => ({ ...obj, buyapis: !obj.buyapis }));
} else {
setIsSubscribe((obj) => ({
...obj,
adminEmail: extUser?.TenantId?.EmailAddress
}));
setIsModal((obj) => ({ ...obj, unauthorized: !obj.unauthorized }));
}
}
};
const handleCloseModal = () => {
setIsModal({ generateapi: false, buyapis: false, unauthorized: false });
};
const handlePricePerAPIs = (e) => {
const quantity = e.target?.value;
const price =
quantity > 0
? (Math.round(quantity * amount.priceperapi * 100) / 100).toFixed(2)
: (Math.round(500 * amount.priceperapi * 100) / 100).toFixed(2);
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleAddOnApiSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsFormLoader(true);
try {
const resAddon = await Parse.Cloud.run("buycredits", {
credits: amount.quantity
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setAmount((obj) => ({
...obj,
quantity: 500,
priceperapi: 0.15,
price: (75.0).toFixed(2),
totalcredits: obj?.totalcredits + _resAddon.addon,
addoncredits: obj?.addoncredits + _resAddon.addon
}));
}
}
setIsModal((obj) => ({ ...obj, buyapis: false }));
} catch (err) {
console.log("Err in buy addon", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
setIsFormLoader(false);
}
};
const handleGenerateTestToken = async (e) => {
e.preventDefault();
setIsLoader(true);
// setIsModal((obj) => ({ ...obj, generateapi: false }));
try {
const url = "https://sandbox.opensignlabs.com/api/generatetesttoken";
const headers = {
"Content-Type": "application/json",
token: apitestToken || "1"
};
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const body = {
name: extUser?.Name,
email: extUser?.Email,
phone: extUser?.Phone,
company: extUser?.Company,
jobTitle: extUser?.JobTitle
};
const res = await axios.post(url, body, { headers: headers });
if (res) {
setApiTestToken(res.data?.token);
setIsAlert({ type: "success", msg: t("token-generated") });
} else {
console.error("Error while generating Token");
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
}
} catch (error) {
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
console.log("while generating Token", error);
} finally {
setIsLoader(false);
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
}
};
return (
<React.Fragment>
<Title title={"API Token"} />
{isAlert.msg && (
<Alert type={isAlert.type}>
<div className="ml-3">{isAlert.msg}</div>
</Alert>
)}
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
{isLoader ? (
<div className="flex justify-center items-center h-screen">
<Loader />
@@ -115,29 +249,66 @@ function GenerateToken() {
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
OpenSign {t("API")}{" "}
<Tooltip
url={
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
}
url="https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
isSubscribe={true}
/>
</h1>
<ul className="w-full flex flex-col p-2 text-sm">
<li className="flex flex-col md:flex-row justify-between items-center border-t-[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="ml-1">
<span className="font-medium">API Test Token : </span>
<span className="text-xs">
<Tooltip message={t("help-test-token")} />
</span>
</span>
<span id="token" className="md:text-end py-2 md:py-0">
<span
className="cursor-pointer"
onClick={() => copytoclipboard(apitestToken)}
>
{apitestToken ? apitestToken : "_____"}
</span>
<button
className="op-btn op-btn-accent op-btn-outline op-btn-sm ml-2 cursor-pointer"
onClick={() => copytoclipboard(apitestToken)}
>
<i className="fa-light fa-copy"></i>
</button>
</span>
</div>
<button
onClick={handleGenerateTestToken}
className="op-btn op-btn-primary w-[210px] md:w-auto"
>
{apitestToken
? t("regenerate-test-token")
: t("generate-test-token")}
</button>
</li>
<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="ml-1">{t("api-token")}:</span>{" "}
<span className="ml-1">
<span className="font-medium">{t("api-token")} : </span>
<span className="text-xs">
<Tooltip message={t("help-api-token", { origin })} />
</span>
</span>
<span
id="token"
className={`${
isSubscribe
? ""
: "bg-white/20 pointer-events-none select-none"
isSubscribe?.plan === "freeplan"
? "bg-white/20 pointer-events-none select-none"
: ""
} md:text-end py-2 md:py-0`}
>
<span
className="cursor-pointer"
onClick={() => copytoclipboard(apiToken)}
>
{apiToken ? apiToken : "_____"}
{isSubscribe?.plan !== "freeplan" && apiToken
? apiToken
: "_____"}
</span>
<button
className="op-btn op-btn-accent op-btn-outline op-btn-sm ml-2 cursor-pointer"
@@ -149,13 +320,30 @@ function GenerateToken() {
</div>
<button
onClick={apiToken ? handleModal : handleSubmit}
className="op-btn op-btn-primary"
className="op-btn op-btn-primary w-[210px] md:w-auto"
>
{apiToken ? t("regenerate-token") : t("generate-token")}
</button>
</li>
<div className="text-xs md:text-[15px] my-3 w-full md:w-[70%] flex-col md:flex-row flex items-center gap-x-5">
<span className="font-medium">
{t("remaining-credits")}{" "}
<span className="text-xs">
<Tooltip
message={t("remaining-credits-help", {
allowedcredits: amount?.allowedcredits || 0,
addoncredits: amount?.addoncredits || 0
})}
/>
</span>
</span>
<span className="md:text-end py-2 md:py-0">
{amount.totalcredits}
</span>
</div>
<hr />
</ul>
<div className="flex items-center justify-center">
<div className="flex flex-col md:flex-row items-center justify-center gap-0 md:gap-1">
<button
type="button"
onClick={() =>
@@ -163,13 +351,20 @@ function GenerateToken() {
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
)
}
className="op-btn op-btn-secondary mt-2 mb-3 px-8"
className="op-btn op-btn-secondary mt-2 md:mb-3 px-8 w-[210px] md:w-auto"
>
{t("view-docs")}
</button>
<button
type="button"
onClick={() => handleBuyAPIsModal()}
className="op-btn op-btn-accent mt-2 mb-3 px-8 w-[210px] md:w-auto"
>
{t("buy-credits")}
</button>
</div>
<ModalUi
isOpen={isModal}
isOpen={isModal.generateapi}
title={apiToken ? t("regenerate-token") : t("generate-token")}
handleClose={handleModal}
>
@@ -194,8 +389,68 @@ function GenerateToken() {
</div>
</div>
</ModalUi>
{/* buy APIs */}
<ModalUi
isOpen={isModal.buyapis}
title={"Buy Credits"}
handleClose={handleCloseModal}
>
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<form onSubmit={handleAddOnApiSubmit} className="p-3">
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("quantity-of-credits")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={amount.quantity}
onChange={(e) => handlePricePerAPIs(e)}
name="quantity"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs"
required
>
{quantityList.length > 0 &&
quantityList.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {amount.priceperapi})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full mt-2">
{t("Proceed")}
</button>
</form>
</ModalUi>
<ModalUi
isOpen={isModal.unauthorized}
title={"Unauthorized"}
handleClose={handleCloseModal}
>
<div className="m-8 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", {
adminEmail: isSubscribe?.adminEmail
})}
</div>
</ModalUi>
</div>
{!isSubscribe && isEnableSubscription && (
{isEnableSubscription && isSubscribe?.plan === "freeplan" && (
<div data-tut="apisubscribe">
<SubscribeCard />
</div>
+21 -19
View File
@@ -80,7 +80,9 @@ function Login() {
}
setState({ ...state, [name]: value });
};
const handlePaidRoute = () => {
navigate("/subscription");
};
const handleSubmit = async (event) => {
localStorage.removeItem("accesstoken");
event.preventDefault();
@@ -158,9 +160,9 @@ function Login() {
JSON.stringify(LocalUserDetails)
);
const res = await fetchSubscription();
const freeplan = res.plan;
const plan = res.plan;
const billingDate = res.billingDate;
if (freeplan === "freeplan") {
if (plan === "freeplan") {
setState({ ...state, loading: false });
navigate(redirectUrl);
} else if (billingDate) {
@@ -171,11 +173,11 @@ function Login() {
navigate(redirectUrl);
} else {
setState({ ...state, loading: false });
navigate(`/subscription`, { replace: true });
handlePaidRoute(plan);
}
} else {
setState({ ...state, loading: false });
navigate(`/subscription`, { replace: true });
handlePaidRoute(plan);
}
} else {
setState({ ...state, loading: false });
@@ -330,9 +332,9 @@ function Login() {
localStorage.setItem("pageType", menu.pageType);
if (isEnableSubscription) {
const res = await fetchSubscription();
const freeplan = res.plan;
const plan = res.plan;
const billingDate = res.billingDate;
if (freeplan === "freeplan") {
if (plan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
@@ -342,14 +344,14 @@ function Login() {
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, { replace: true });
handlePaidRoute(plan);
}
}
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, { replace: true });
handlePaidRoute(plan);
}
}
} else {
@@ -462,8 +464,8 @@ function Login() {
localStorage.setItem("userDetails", JSON.stringify(userInfo));
const res = await fetchSubscription();
const billingDate = res.billingDate;
const freeplan = res.plan;
if (freeplan === "freeplan") {
const plan = res.plan;
if (plan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
@@ -471,10 +473,10 @@ function Login() {
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
navigate(`/subscription`);
handlePaidRoute(plan);
}
} else {
navigate(`/subscription`);
handlePaidRoute(plan);
}
} else {
// Redirect to the appropriate URL after successful login
@@ -573,10 +575,12 @@ function Login() {
}
};
const logOutUser = () => {
const logOutUser = async () => {
setIsModal(false);
if (Parse?.User?.current()) {
Parse.User.logOut();
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
@@ -789,9 +793,7 @@ function Login() {
</div>
<SelectLanguage />
{state.alertMsg && (
<Alert type={state.alertType}>
<div className="ml-3">{state.alertMsg}</div>
</Alert>
<Alert type={state.alertType}>{state.alertMsg}</Alert>
)}
</div>
<ModalUi
+273 -276
View File
@@ -31,7 +31,10 @@ import {
handleDownloadPdf,
handleToPrint,
handleDownloadCertificate,
getDefaultSignature
getDefaultSignature,
onClickZoomIn,
onClickZoomOut,
fetchUrl
} from "../constant/Utils";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
@@ -120,7 +123,6 @@ function PdfRequestFiles(props) {
const [otp, setOtp] = useState("");
const [contractName, setContractName] = useState("");
const [zoomPercent, setZoomPercent] = useState(0);
const [totalZoomPercent, setTotalZoomPercent] = useState();
const [scale, setScale] = useState(1);
const [uniqueId, setUniqueId] = useState("");
const [isPublicTemplate, setIsPublicTemplate] = useState(false);
@@ -130,6 +132,8 @@ function PdfRequestFiles(props) {
const [res, setRes] = useState({});
const [documentId, setDocumentId] = useState("");
const [isPublicContact, setIsPublicContact] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
const [plancode, setPlanCode] = useState("");
const isHeader = useSelector((state) => state.showHeader);
const divRef = useRef(null);
@@ -138,17 +142,18 @@ function PdfRequestFiles(props) {
let isGuestSignFlow = false;
let sendmail;
let getDocId = "";
const route = !props.templateId && window.location.pathname;
const route = !props.templateId && window.location.pathname; //'/load/recipientSignPdf/TOAVuhXbfw/fPAKdK1qgX'
//window.location.search = ?sendmail=false
const getQuery =
!props.templateId &&
window.location?.search &&
window.location?.search?.split("?");
window.location?.search?.split("?"); //['','sendmail=false']
//'sendmail=false'
if (getQuery) {
sendmail = getQuery[1].split("=")[1];
sendmail = getQuery[1].split("=")[1]; //false
}
const checkSplit = route && route?.split("/");
const checkSplit = route && route?.split("/"); // ['', 'load', 'recipientSignPdf', 'TOAVuhXbfw', 'fPAKdK1qgX']
if (checkSplit && checkSplit.length > 4) {
isGuestSignFlow = true;
getDocId = checkSplit[3];
@@ -232,12 +237,17 @@ function PdfRequestFiles(props) {
const currentUser = JSON.parse(localuser);
await handleSendOTP(currentUser?.email);
};
const handleNavigation = () => {
window.location.href = "/subscription";
};
async function checkIsSubscribed(extUserId, contactId) {
const isGuestSign = isGuestSignFlow || false;
const res = await fetchSubscription(extUserId, contactId, isGuestSign);
const plan = res.plan;
const billingDate = res?.billingDate;
const status = res?.status;
setPlanCode(plan);
if (plan === "freeplan") {
return true;
} else if (billingDate) {
@@ -248,7 +258,7 @@ function PdfRequestFiles(props) {
if (isGuestSign) {
setIsSubscriptionExpired(true);
} else {
window.location.href = "/subscription";
handleNavigation(plan);
}
}
} else if (isGuestSign) {
@@ -262,7 +272,7 @@ function PdfRequestFiles(props) {
if (isGuestSign) {
setIsSubscriptionExpired(true);
} else {
window.location.href = "/subscription";
handleNavigation(res.plan);
}
}
}
@@ -285,49 +295,65 @@ function PdfRequestFiles(props) {
templateDeatils.data && templateDeatils.data.result
? [templateDeatils.data.result]
: [];
if (documentData && documentData.length > 0) {
setIsPublicTemplate(true);
const getPublicRole = documentData[0]?.PublicRole[0];
const getUniqueIdDetails = documentData[0]?.Placeholders.find(
(x) => x.Role === getPublicRole
);
if (getUniqueIdDetails) {
setUniqueId(getUniqueIdDetails.Id);
}
setSignerPos(documentData[0]?.Placeholders);
let placeholdersOrSigners = [];
// const placeholder = documentData[0]?.Placeholders;
for (const placeholder of documentData[0].Placeholders) {
//`emailExist` variable to handle condition for quick send flow and show unsigned signers list
const signerIdExist = placeholder?.signerObjId;
if (signerIdExist) {
const getSignerData = documentData[0].Signers.find(
(data) => data.objectId === placeholder?.signerObjId
);
placeholdersOrSigners.push(getSignerData);
if (documentData && documentData[0]?.error) {
props?.setTemplateStatus({
status: "Invalid"
});
} else if (documentData && documentData.length > 0) {
if (documentData[0]?.IsPublic) {
props?.setTemplateStatus({
status: "Success"
});
const url =
documentData[0] &&
(documentData[0]?.SignedUrl || documentData[0]?.URL);
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(t("something-went-wrong-mssg"));
} else {
setPdfArrayBuffer(arrayBuffer);
}
} else {
placeholdersOrSigners.push(placeholder);
setHandleError(t("something-went-wrong-mssg"));
}
setIsPublicTemplate(true);
const getPublicRole = documentData[0]?.PublicRole[0];
const getUniqueIdDetails = documentData[0]?.Placeholders.find(
(x) => x.Role === getPublicRole
);
if (getUniqueIdDetails) {
setUniqueId(getUniqueIdDetails.Id);
}
setSignerPos(documentData[0]?.Placeholders);
let placeholdersOrSigners = [];
// const placeholder = documentData[0]?.Placeholders;
for (const placeholder of documentData[0].Placeholders) {
//`emailExist` variable to handle condition for quick send flow and show unsigned signers list
const signerIdExist = placeholder?.signerObjId;
if (signerIdExist) {
const getSignerData = documentData[0].Signers.find(
(data) => data.objectId === placeholder?.signerObjId
);
placeholdersOrSigners.push(getSignerData);
} else {
placeholdersOrSigners.push(placeholder);
}
}
setUnSignedSigners(placeholdersOrSigners);
setPdfDetails(documentData);
setIsLoading({
isLoad: false
});
} else {
props?.setTemplateStatus({
status: "Private"
});
}
setUnSignedSigners(placeholdersOrSigners);
setPdfDetails(documentData);
setIsLoading({
isLoad: false
});
} else if (
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
console.log("err in get template details ");
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({
isLoad: false
});
} else {
setHandleError(t("no-data"));
setIsLoading({
isLoad: false
props?.setTemplateStatus({
status: "Invalid"
});
}
} catch (err) {
@@ -350,6 +376,20 @@ function PdfRequestFiles(props) {
//getting document details
const documentData = await contractDocument(documentId || docId);
if (documentData && documentData.length > 0) {
const url =
documentData[0] &&
(documentData[0]?.SignedUrl || documentData[0]?.URL);
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(t("something-went-wrong-mssg"));
} else {
setPdfArrayBuffer(arrayBuffer);
}
} else {
setHandleError(t("something-went-wrong-mssg"));
}
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const isCompleted =
documentData[0].IsCompleted && documentData[0].IsCompleted;
@@ -382,16 +422,11 @@ function PdfRequestFiles(props) {
}
if (isCompleted) {
setIsSigned(true);
const data = {
isCertificate: true,
isModal: true
};
const data = { isCertificate: true, isModal: true };
setAlreadySign(true);
setIsCompleted(data);
setIsCelebration(true);
setTimeout(() => {
setIsCelebration(false);
}, 5000);
setTimeout(() => setIsCelebration(false), 5000);
} else if (declined) {
const currentDecline = {
currnt: "another",
@@ -407,9 +442,7 @@ function PdfRequestFiles(props) {
} // Check if the current signer is not a last signer and handle the complete message.
else if (isNextUser) {
setIsCelebration(true);
setTimeout(() => {
setIsCelebration(false);
}, 5000);
setTimeout(() => setIsCelebration(false), 5000);
setIsCompleted({
isModal: true,
message: t("document-signed-alert-1")
@@ -436,7 +469,8 @@ function PdfRequestFiles(props) {
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
(data) =>
data.UserPtr.objectId === currUserId && data.Activity === "Signed"
data.UserPtr?.objectId === currUserId &&
data.Activity === "Signed"
);
if (
checkAlreadySign &&
@@ -516,15 +550,15 @@ function PdfRequestFiles(props) {
for (const item of placeholdersOrSigners) {
const checkEmail = item?.email;
//if email exist then compare user signed by using email else signers objectId
const emailOrId = checkEmail ? item.email : item.objectId;
const emailOrId = checkEmail ? item.email : item?.objectId;
//`isSignedSignature` variable to handle break loop whenever it get true
let isSignedSignature = false;
//checking the signer who signed the document by using audit trail details.
//and save signedSigners and unsignedSigners details
for (const doc of audittrailData) {
const signedExist = checkEmail
? doc?.UserPtr.Email
: doc?.UserPtr.objectId;
? doc?.UserPtr?.Email
: doc?.UserPtr?.objectId;
if (emailOrId === signedExist) {
signers.push({ ...item });
@@ -602,15 +636,11 @@ function PdfRequestFiles(props) {
(documentData.result && documentData.result.error)
) {
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({
isLoad: false
});
setIsLoading({ isLoad: false });
console.log("err in getDocument cloud function ");
} else {
setHandleError(t("no-data"));
setIsUiLoading({
isLoad: false
});
setIsUiLoading({ isLoad: false });
}
//function to get default signatur eof current user from `contracts_Signature` class
const defaultSignRes = await getDefaultSignature(jsonSender.objectId);
@@ -620,18 +650,13 @@ function PdfRequestFiles(props) {
} else if (defaultSignRes?.status === "error") {
setHandleError("Error: Something went wrong!");
}
setIsLoading({
isLoad: false
});
setIsLoading({ isLoad: false });
} catch (err) {
console.log("Error: error in getDocumentDetails", err);
setHandleError("Error: Something went wrong!");
setIsLoading({
isLoad: false
});
setIsLoading({ isLoad: false });
}
};
//function for embed signature or image url in pdf
async function embedWidgetsData() {
//for emailVerified data checking first in localstorage
@@ -807,30 +832,9 @@ function PdfRequestFiles(props) {
setIsUiLoading(true);
// `widgets` is Used to return widgets details with page number of current user
const widgets = checkUser?.[0]?.placeHolder;
let pdfArrBuffer;
//`contractDocument` function used to get updated SignedUrl
//resolved issue of sign document by multiple signers simultaneously
const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) {
const url = documentData[0]?.SignedUrl || 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(t("invalid-document"));
} else {
pdfArrBuffer = arrayBuffer;
}
} else if (
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
setHandleError(t("something-went-wrong-mssg"));
} else {
setHandleError(t("document-not-found"));
}
// Load a PDFDocument from the existing PDF bytes
const existingPdfBytes = pdfArrBuffer;
const existingPdfBytes = pdfArrayBuffer;
try {
const pdfDoc = await PDFDocument.load(existingPdfBytes);
const isSignYourSelfFlow = false;
@@ -984,6 +988,7 @@ function PdfRequestFiles(props) {
? replaceVar?.subject
: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign "${pdfDetails?.[0].Name}"`,
from: senderEmail,
plan: plancode,
html: requestBody
? replaceVar?.body
: "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'=> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
@@ -1008,9 +1013,7 @@ function PdfRequestFiles(props) {
openSignUrl +
" target=_blank>here</a>.</p> </div></div></body> </html>"
};
await axios.post(url, params, {
headers: headers
});
await axios.post(url, params, { headers: headers });
} catch (error) {
console.log("error", error);
}
@@ -1173,10 +1176,7 @@ function PdfRequestFiles(props) {
const img = new Image();
img.src = defaultSignImg;
if (img.complete) {
imgWH = {
width: img.width,
height: img.height
};
imgWH = { width: img.width, height: img.height };
}
}
//get current signer placeholder position data
@@ -1216,7 +1216,15 @@ function PdfRequestFiles(props) {
);
const jsonSender = JSON.parse(senderUser);
setIsDecline({ isDeclined: false });
const data = { IsDeclined: true, DeclineReason: reason };
const data = {
IsDeclined: true,
DeclineReason: reason,
DeclineBy: {
__type: "Pointer",
className: "_User",
objectId: jsonSender?.objectId
}
};
setIsUiLoading(true);
await axios
@@ -1236,10 +1244,7 @@ function PdfRequestFiles(props) {
.then(async (result) => {
const res = result.data;
if (res) {
const currentDecline = {
currnt: "YouDeclined",
isDeclined: true
};
const currentDecline = { currnt: "YouDeclined", isDeclined: true };
setIsDecline(currentDecline);
setIsUiLoading(false);
const params = {
@@ -1280,6 +1285,8 @@ function PdfRequestFiles(props) {
})
.catch((err) => {
console.log("error updating field is decline ", err);
setIsUiLoading(false);
alert(t("something-went-wrong-mssg"));
});
};
//function to add default signature for all requested placeholder of sign
@@ -1298,15 +1305,11 @@ function PdfRequestFiles(props) {
x.signerObjId === signerObjectId ? { ...x, placeHolder: updatePlace } : x
);
setSignerPos(updatesignerPos);
setDefaultSignAlert({
isShow: false,
alertMessage: ""
});
setDefaultSignAlert({ isShow: false, alertMessage: "" });
};
const handleDontShow = (isChecked) => {
setIsDontShow(isChecked);
};
// console.log("signerpos", signerPos);
//function to close tour and save tour status
const closeRequestSignTour = async () => {
setRequestSignTour(true);
@@ -1483,7 +1486,6 @@ function PdfRequestFiles(props) {
const SendOtp = async () => {
try {
const params = { email: contact.email, docId: res.docId };
const Otp = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/SendOTPMailV1`,
params,
@@ -1521,10 +1523,7 @@ function PdfRequestFiles(props) {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseId
};
let body = {
email: contact.email,
otp: otp
};
let body = { email: contact.email, otp: otp };
let user = await axios.post(url, body, { headers: headers });
if (user.data.result === "Invalid Otp") {
alert(t("invalid-otp"));
@@ -1556,9 +1555,7 @@ function PdfRequestFiles(props) {
// document.getElementById("my_modal").close();
setIsPublicContact(false);
setIsPublicTemplate(false);
setIsLoading({
isLoad: false
});
setIsLoading({ isLoad: false });
setDocumentId(res?.docId);
getDocumentDetails(res?.docId);
}
@@ -1574,13 +1571,37 @@ function PdfRequestFiles(props) {
setLoading(false);
setIsOtp(false);
setOtp();
setContact({
name: "",
email: "",
phone: ""
});
setContact({ name: "", email: "", phone: "" });
};
const clickOnZoomIn = () => {
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
};
const clickOnZoomOut = () => {
onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale);
};
const handleDownloadBtn = async () => {
const url = pdfDetails?.[0]?.SignedUrl || pdfDetails?.[0]?.URL;
const name = pdfDetails?.[0]?.Name;
await fetchUrl(url, name);
};
const handleDeclineMssg = () => {
const user = pdfDetails[0]?.DeclineBy?.email;
return (
<div>
{t("decline-alert-3")}
<div className="mt-2">
{" "}
<span className="font-medium">{t("decline-by")}</span> : {user}
</div>
<div className="mt-2">
{" "}
<span className="font-medium">{t("reason")}</span> :{" "}
{pdfDetails[0]?.DeclineReason}{" "}
</div>
</div>
);
};
return (
<DndProvider backend={HTML5Backend}>
<Title title={props.templateId ? "Public Sign" : "Request Sign"} />
@@ -1628,7 +1649,9 @@ function PdfRequestFiles(props) {
? "none"
: "auto"
}}
className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300"
className={`${
props.templateId && "m-1 border-[0.5px] border-gray-300"
} relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300`}
>
{!requestSignTour &&
signerObjectId &&
@@ -1668,13 +1691,17 @@ function PdfRequestFiles(props) {
{/* this modal is used to show decline alert */}
<PdfDeclineModal
show={isDecline.isDeclined}
headMsg={t("document-decline")}
headMsg={
pdfDetails[0]?.IsDeclined
? t("document-declined")
: t("document-decline")
}
bodyMssg={
isDecline.currnt === "Sure"
? t("decline-alert-2")
? t("decline-alert-1")
: isDecline.currnt === "YouDeclined"
? t("decline-alert-1")
: isDecline.currnt === "another" && t("decline-alert-3")
? t("decline-alert-2")
: isDecline.currnt === "another" && handleDeclineMssg()
}
footerMessage={isDecline.currnt === "Sure"}
declineDoc={declineDoc}
@@ -1685,6 +1712,8 @@ function PdfRequestFiles(props) {
show={isExpired}
headMsg={t("expired-doc-title")}
bodyMssg={t("expired-on-mssg", { expiredDate })}
isDownloadBtn={true}
handleDownloadBtn={handleDownloadBtn}
/>
{!isEmailVerified && (
<VerifyEmail
@@ -1698,145 +1727,121 @@ function PdfRequestFiles(props) {
handleResend={handleResend}
/>
)}
<ModalUi
isOpen={isPublicContact}
title={isOtp ? t("verify-email-1") : t("contact-details")}
handleClose={() => {
handleCloseOtp();
}}
handleClose={() => handleCloseOtp()}
>
<div className="h-full p-[20px]">
{isOtp ? (
<form onSubmit={VerifyOTP}>
<div className="flex flex-col gap-2">
<span>{t("get-otp-alert")}</span>
<label className="op-input op-input-bordered flex items-center gap-2 ">
<input
type="number"
name="otp"
className="grow"
placeholder={t("otp-placeholder")}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
value={otp}
onChange={(e) => setOtp(e.target.value)}
disabled={loading}
/>
<div className="mb-[0.75rem]">
<label className="block text-xs font-semibold mb-2">
{t("get-otp-alert")}
<span className="text-[13px] text-[red]"> *</span>
</label>
<input
type="number"
name="otp"
placeholder={t("otp-placeholder")}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
value={otp}
onChange={(e) => setOtp(e.target.value)}
disabled={loading}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="op-modal-action">
<div className="flex gap-2">
<button
className="op-btn op-btn-ghost"
onClick={() => {
handleCloseOtp();
}}
>
{t("cancel")}
</button>
<button
className="op-btn op-btn-primary"
disabled={loading}
>
{loading ? t("loading") : t("verify")}
</button>
</div>
<div className="mt-6 flex justify-start gap-2">
<button
className="op-btn op-btn-primary"
disabled={loading}
>
{loading ? t("loading") : t("verify")}
</button>
<button
className="op-btn op-btn-ghost"
onClick={() => handleCloseOtp()}
>
{t("cancel")}
</button>
</div>
</form>
) : (
<form onSubmit={handlePublicUser}>
<div className="flex flex-col gap-2">
<label className="op-input op-input-bordered flex items-center gap-2 ">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
className="w-4 h-4 opacity-70"
>
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z" />
</svg>
<input
type="text"
className="grow"
name="name"
value={contact.name}
onChange={handleInputChange}
placeholder={t("name")}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
disabled={loading}
/>
</label>
<label className="op-input op-input-bordered flex items-center gap-2 ">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
className="w-4 h-4 opacity-70"
>
<path d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z" />
<path d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z" />
</svg>
<input
type="email"
className="grow"
name="email"
value={contact.email}
onChange={handleInputChange}
placeholder={t("email")}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
disabled={loading}
/>
</label>
<label className="op-input op-input-bordered flex items-center gap-2 ">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
className="w-4 h-4 opacity-70"
>
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z" />
</svg>
<input
value={contact.phone}
onChange={handleInputChange}
type="text"
name="phone"
className="grow"
placeholder={t("phone")}
disabled={loading}
/>
<form
className="text-base-content p-2 mx-auto"
onSubmit={handlePublicUser}
>
<div className="mb-[0.75rem]">
<label className="block text-xs font-semibold mb-[5px]">
{t("name")}
<span className="text-[13px] text-[red]"> *</span>
</label>
<input
type="text"
id="name"
name="name"
value={contact.name}
onChange={handleInputChange}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
disabled={loading}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="op-modal-action">
<div className="flex gap-2">
<button
className="op-btn op-btn-ghost"
onClick={() => {
handleCloseOtp();
}}
>
{t("close")}
</button>
<button
className="op-btn op-btn-primary"
disabled={loading}
>
{loading ? t("loading") : t("submit")}
</button>
</div>
<div className="mb-[0.75rem]">
<label className="block text-xs font-semibold mb-[5px]">
{t("email")}
<span className="text-[13px] text-[red]"> *</span>
</label>
<input
type="email"
name="email"
value={contact.email}
onChange={handleInputChange}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
disabled={loading}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-[0.75rem]">
<label className="block text-xs font-semibold mb-[5px]">
{t("phone")}
</label>
<input
value={contact.phone}
onChange={handleInputChange}
type="text"
name="phone"
placeholder={t("phone-optional")}
disabled={loading}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mt-6 flex justify-start gap-2">
<button
className="op-btn op-btn-primary"
disabled={loading}
>
{loading ? t("loading") : t("submit")}
</button>
<button
className="op-btn op-btn-ghost"
onClick={() => handleCloseOtp()}
>
{t("close")}
</button>
</div>
</form>
)}
@@ -1904,20 +1909,18 @@ function PdfRequestFiles(props) {
{/* pdf render view */}
<div className=" w-full md:w-[57%] flex mr-4">
<PdfZoom
setScale={setScale}
scale={scale}
containerWH={containerWH}
setZoomPercent={setZoomPercent}
zoomPercent={zoomPercent}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
isDisableRotate={true}
/>
<div className=" w-full md:w-[95%] ">
{/* this modal is used show this document is already sign */}
<ModalUi
isOpen={isCompleted.isModal}
title={t("document-signed")}
handleClose={() => {
setIsCompleted((prev) => ({ ...prev, isModal: false }));
}}
handleClose={() =>
setIsCompleted((prev) => ({ ...prev, isModal: false }))
}
reduceWidth={
!isCompleted?.message &&
"md:min-w-[440px] md:max-w-[400px]"
@@ -2049,15 +2052,12 @@ function PdfRequestFiles(props) {
currentSigner={currentSigner}
pdfUrl={pdfUrl}
alreadySign={alreadySign}
totalZoomPercent={totalZoomPercent}
setTotalZoomPercent={setTotalZoomPercent}
setScale={setScale}
scale={scale}
pdfOriginalWH={pdfOriginalWH}
containerWH={containerWH}
setZoomPercent={setZoomPercent}
zoomPercent={zoomPercent}
isPublicTemplate={isPublicTemplate}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
isDisableRotate={true}
templateId={props.templateId}
/>
<div ref={divRef} data-tut="pdfArea" className="h-[95%]">
@@ -2091,7 +2091,6 @@ function PdfRequestFiles(props) {
divRef={divRef}
setIsResize={setIsResize}
isResize={isResize}
setTotalZoomPercent={setTotalZoomPercent}
setScale={setScale}
scale={scale}
uniqueId={uniqueId}
@@ -2171,9 +2170,7 @@ function PdfRequestFiles(props) {
<ModalUi
isOpen={validateAlert}
title={t("validation-alert")}
handleClose={() => {
setValidateAlert(false);
}}
handleClose={() => setValidateAlert(false)}
>
<div className="h-[100%] p-[20px]">
<p>{t("validation-alert-1")}</p>
File diff suppressed because it is too large Load Diff
+64 -8
View File
@@ -4,7 +4,7 @@ import checkmark from "../assets/images/checkmark.png";
import plansArr from "../json/plansArr";
import Title from "../components/Title";
import Parse from "parse";
import { openInNewTab } from "../constant/Utils";
import { openInNewTab, saveLanguageInLocal } from "../constant/Utils";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
const listItemStyle = {
@@ -53,12 +53,6 @@ const PlanSubscriptions = () => {
? "&mobile=" + encodeURIComponent(userDetails.phone)
: "";
const details =
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
name +
email +
company +
phone;
useEffect(() => {
setIsLoader(false);
detectLanguage();
@@ -72,6 +66,32 @@ const PlanSubscriptions = () => {
const handleFreePlan = async (item) => {
if (item.url) {
const code = yearlyVisible ? item.code.yearly : item.code.monthly;
const allowedUsers =
localStorage.getItem("allowedUsers") &&
localStorage.getItem("allowedUsers") > 0
? localStorage.getItem("allowedUsers") - 1
: "";
const teamperiod = {
"team-weekly": "monthly",
"team-yearly": "yearly",
"teams-monthly": "monthly",
"teams-yearly": "yearly"
};
const period = teamperiod[code] || "";
const quantity =
allowedUsers && period
? `addon_code%5B0%5D=extra-teams-users-${period}&addon_quantity%5B0%5D=${allowedUsers}&`
: "";
const details =
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
quantity +
name +
email +
company +
phone;
const url = yearlyVisible ? item.yearlyUrl + details : item.url + details;
if (user) {
localStorage.setItem("userDetails", JSON.stringify(user));
@@ -99,6 +119,30 @@ const PlanSubscriptions = () => {
}
}
};
const handleLogout = async () => {
try {
await Parse?.User?.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
navigate("/");
};
return (
<>
<Title title={"Subscriptions"} />
@@ -109,7 +153,13 @@ const PlanSubscriptions = () => {
) : (
<div className="overflow-y-auto max-h-full">
<div className="block my-2">
<div className="flex flex-col justify-center items-center w-full">
<div className="flex flex-col justify-center items-center w-full relative">
<button
className="md:hidden op-btn op-btn-primary op-btn-sm h-[40px] w-[250px] shadow-lg"
onClick={() => handleLogout()}
>
Log out
</button>
<div
role="tablist"
className="op-tabs op-tabs-boxed bg-base-100 my-2 shadow-lg transition-all"
@@ -130,6 +180,12 @@ const PlanSubscriptions = () => {
</a>
</div>
<ul className="op-card flex flex-col md:flex-row h-full bg-base-100 justify-center shadow-lg">
<button
className="hidden md:block -top-12 right-1 absolute w-[150px] h-[40px] op-btn op-btn-primary op-btn-sm shadow-lg"
onClick={() => handleLogout()}
>
{t("log-out")}
</button>
{plansArr.map((item) => (
<li
className="flex flex-col text-center border-collapse border-[1px] border-gray-300 max-w-[250px]"
+10 -5
View File
@@ -50,6 +50,9 @@ const SSOVerify = () => {
console.log("err", err.message);
}
};
const handlePaidRoute = () => {
navigate("/subscription");
};
const checkExtUser = async (ssosign) => {
const params = { email: ssosign?.email };
try {
@@ -196,10 +199,10 @@ const SSOVerify = () => {
localStorage.removeItem("userDetails");
navigate(redirectUrl);
} else {
navigate(`/subscription`, { replace: true });
handlePaidRoute(plan);
}
} else {
navigate(`/subscription`, { replace: true });
handlePaidRoute(plan);
}
} else {
navigate(redirectUrl);
@@ -225,10 +228,12 @@ const SSOVerify = () => {
}
};
// `handleCloseModal` is triggered when the user wants to close the new user flow modal
const handleCloseModal = () => {
const handleCloseModal = async () => {
setIsModal(false);
if (Parse?.User?.current()) {
Parse.User.logOut();
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
};
+3 -1
View File
@@ -45,8 +45,10 @@ const Signup = () => {
const togglePasswordVisibility = () => setShowPassword(!showPassword);
const clearStorage = async () => {
if (Parse.User.current()) {
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
+176 -150
View File
@@ -34,7 +34,10 @@ import {
changeImageWH,
handleSendOTP,
getContainerScale,
getDefaultSignature
getDefaultSignature,
onClickZoomIn,
onClickZoomOut,
rotatePdfPage
} from "../constant/Utils";
import { useParams } from "react-router-dom";
import Tour from "reactour";
@@ -53,6 +56,7 @@ import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
//For signYourself inProgress section signer can add sign and complete doc sign.
function SignYourSelf() {
const { t } = useTranslation();
@@ -123,52 +127,38 @@ function SignYourSelf() {
const [zoomPercent, setZoomPercent] = useState(0);
const isHeader = useSelector((state) => state.showHeader);
const [scale, setScale] = useState(1);
const [pdfRotateBase64, setPdfRotatese64] = useState("");
const [isRotate, setIsRotate] = useState({ status: false, degree: 0 });
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: true });
const divRef = useRef(null);
const nodeRef = useRef(null);
const [, drop] = useDrop({
accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor),
collect: (monitor) => ({
isOver: !!monitor.isOver()
})
collect: (monitor) => ({ isOver: !!monitor.isOver() })
});
const pdfRef = useRef();
const [{ isDragSign }, dragSignature] = useDrag({
type: "BOX",
item: {
id: 1,
text: "signature"
},
collect: (monitor) => ({
isDragSign: !!monitor.isDragging()
})
item: { id: 1, text: "signature" },
collect: (monitor) => ({ isDragSign: !!monitor.isDragging() })
});
const [{ isDragStamp }, dragStamp] = useDrag({
type: "BOX",
item: {
id: 2,
text: "stamp"
},
collect: (monitor) => ({
isDragStamp: !!monitor.isDragging()
})
item: { id: 2, text: "stamp" },
collect: (monitor) => ({ isDragStamp: !!monitor.isDragging() })
});
const index = xyPostion.findIndex((object) => {
const index = xyPostion?.findIndex((object) => {
return object.pageNumber === pageNumber;
});
// rowlevel={JSON.parse(localStorage.getItem("rowlevel"))}
const rowLevel =
localStorage.getItem("rowlevel") &&
JSON.parse(localStorage.getItem("rowlevel"));
const signObjId =
rowLevel && rowLevel?.id
? rowLevel.id
: rowLevel?.objectId && rowLevel.objectId;
const documentId = docId ? docId : signObjId && signObjId;
const senderUser =
localStorage.getItem(
@@ -202,12 +192,18 @@ function SignYourSelf() {
// Use setTimeout to wait for the transition to complete
const timer = setTimeout(updateSize, 100); // match the transition duration
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current, isHeader]);
//function for get document details for perticular signer with signer'object id
const getDocumentDetails = async (showComplete) => {
try {
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe);
} catch (err) {
console.log("err in fetch sub", err);
}
try {
let isCompleted;
//getting document details
@@ -216,6 +212,11 @@ function SignYourSelf() {
if (documentData && documentData.length > 0) {
setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const placeholders =
documentData[0]?.Placeholders?.length > 0
? documentData[0]?.Placeholders
: [];
setXyPostion(placeholders);
const url = documentData[0] && documentData[0]?.URL;
if (url) {
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
@@ -228,21 +229,17 @@ function SignYourSelf() {
} else {
setHandleError(t("something-went-wrong-mssg"));
}
isCompleted =
documentData[0].IsCompleted && documentData[0].IsCompleted;
isCompleted = documentData?.[0]?.IsCompleted;
if (isCompleted) {
setIsCelebration(true);
setTimeout(() => {
setIsCelebration(false);
}, 5000);
setTimeout(() => setIsCelebration(false), 5000);
setIsCompleted(true);
setPdfUrl(documentData[0].SignedUrl);
const alreadySign = {
status: true,
mssg: t("document-signed-alert")
};
if (showComplete) {
setShowAlreadySignDoc(alreadySign);
setShowAlreadySignDoc({
status: true,
mssg: t("document-signed-alert")
});
} else {
setIsUiLoading(false);
setIsSignPad(false);
@@ -255,17 +252,11 @@ function SignYourSelf() {
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
const loadObj = {
isLoad: false
};
setHandleError(t("something-went-wrong-mssg"));
setIsLoading(loadObj);
setIsLoading({ isLoad: false });
} else {
setHandleError(t("no-data-avaliable"));
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
setIsLoading({ isLoad: false });
}
//function to get default signatur eof current user from `contracts_Signature` class
const defaultSignRes = await getDefaultSignature(jsonSender.objectId);
@@ -274,17 +265,12 @@ function SignYourSelf() {
setMyInitial(defaultSignRes?.res?.defaultInitial);
} else if (defaultSignRes?.status === "error") {
setHandleError("Error: Something went wrong!");
setIsLoading({
isLoad: false
});
setIsLoading({ isLoad: false });
}
const contractUsersRes = await contractUsers();
if (contractUsersRes === "Error: Something went wrong!") {
const loadObj = {
isLoad: false
};
setHandleError(t("something-went-wrong-mssg"));
setIsLoading(loadObj);
setIsLoading({ isLoad: false });
} else if (contractUsersRes[0] && contractUsersRes.length > 0) {
setActiveMailAdapter(contractUsersRes[0]?.active_mail_adapter);
setContractName("_Users");
@@ -364,51 +350,32 @@ function SignYourSelf() {
const addWidgetOptions = (type) => {
switch (type) {
case "signature":
return {
name: "signature"
};
return { name: "signature" };
case "stamp":
return {
name: "stamp"
};
return { name: "stamp" };
case "checkbox":
return {
name: "checkbox"
};
return { name: "checkbox" };
case textWidget:
return {
name: "text"
};
return { name: "text" };
case "initials":
return {
name: "initials"
};
return { name: "initials" };
case "name":
return {
name: "name",
defaultValue: getWidgetValue(type),
validation: {
type: "text",
pattern: ""
}
validation: { type: "text", pattern: "" }
};
case "company":
return {
name: "company",
defaultValue: getWidgetValue(type),
validation: {
type: "text",
pattern: ""
}
validation: { type: "text", pattern: "" }
};
case "job title":
return {
name: "job title",
defaultValue: getWidgetValue(type),
validation: {
type: "text",
pattern: ""
}
validation: { type: "text", pattern: "" }
};
case "date":
return {
@@ -417,17 +384,12 @@ function SignYourSelf() {
validation: { format: "MM/dd/yyyy", type: "date-format" }
};
case "image":
return {
name: "image"
};
return { name: "image" };
case "email":
return {
name: "email",
defaultValue: getWidgetValue(type),
validation: {
type: "email",
pattern: ""
}
validation: { type: "email", pattern: "" }
};
default:
return {};
@@ -439,7 +401,7 @@ function SignYourSelf() {
const key = randomId();
let dropData = [];
let dropObj = {};
let filterDropPos = xyPostion.filter(
let filterDropPos = xyPostion?.filter(
(data) => data.pageNumber === pageNumber
);
const dragTypeValue = item?.text ? item.text : monitor.type;
@@ -454,29 +416,27 @@ function SignYourSelf() {
);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
const divHeight = divRef.current.getBoundingClientRect().height;
const getWidth = widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth
: dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).width
: "";
: defaultWidthHeight(dragTypeValue).width;
const getHeight = widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getHeight
: dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).height
: "";
: defaultWidthHeight(dragTypeValue).height;
dropObj = {
xPosition: containerWH.width / 2,
yPosition: containerWH.height / 2,
xPosition: getWidth / 2 + containerWH.width / 2,
yPosition: getHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key,
type: dragTypeValue,
scale: containerScale,
Width: getWidth / (containerScale * scale),
Height: getHeight / (containerScale * scale),
Width: getWidth,
Height: getHeight,
options: addWidgetOptions(dragTypeValue)
};
dropData.push(dropObj);
} else {
//This method returns the offset of the current pointer (mouse) position relative to the client viewport.
@@ -509,25 +469,18 @@ function SignYourSelf() {
options: addWidgetOptions(dragTypeValue),
scale: containerScale
};
dropData.push(dropObj);
}
if (filterDropPos.length > 0) {
const index = xyPostion.findIndex((object) => {
if (filterDropPos?.length > 0) {
const index = xyPostion?.findIndex((object) => {
return object.pageNumber === pageNumber;
});
const updateData = filterDropPos[0].pos;
const updateData = filterDropPos?.[0].pos;
const newSignPos = updateData.concat(dropData);
let xyPos = {
pageNumber: pageNumber,
pos: newSignPos
};
xyPostion.splice(index, 1, xyPos);
let xyPos = { pageNumber: pageNumber, pos: newSignPos };
xyPostion?.splice(index, 1, xyPos);
} else {
const xyPos = {
pageNumber: pageNumber,
pos: dropData
};
const xyPos = { pageNumber: pageNumber, pos: dropData };
setXyPostion((prev) => [...prev, xyPos]);
}
@@ -539,7 +492,6 @@ function SignYourSelf() {
) {
setIsSignPad(true);
}
if (dragTypeValue === "stamp" || dragTypeValue === "image") {
setIsStamp(true);
} else if (dragTypeValue === "initials") {
@@ -602,6 +554,29 @@ function SignYourSelf() {
setIsVerifyModal(true);
await handleSendOTP(Parse.User.current().getEmail());
};
useEffect(() => {
const timer = setTimeout(() => {
if (xyPostion?.length > 0) {
autosavedetails();
}
}, 2000);
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [xyPostion]);
// `autosavedetails` is used to save doc details after every 2 sec when changes are happern in placeholder like drag-drop widgets, remove signers
const autosavedetails = async () => {
try {
const docCls = new Parse.Object("contracts_Document");
docCls.id = documentId;
docCls.set("Placeholders", xyPostion);
docCls.set("IsSignyourself", true);
await docCls.save();
} catch (e) {
console.log("error", e);
alert(t("something-went-wrong-mssg"));
}
};
//function for send placeholder's co-ordinate(x,y) position embed signature url or stamp url
async function embedWidgetsData() {
//check current user email is verified or not
@@ -740,7 +715,7 @@ function SignYourSelf() {
//function for get digital signature
const signPdfFun = async (base64Url, documentId) => {
let isCustomCompletionMail = false;
const getIsSubscribe = await checkIsSubscribed();
const tenantDetails = await getTenantDetails(jsonSender.objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist"));
@@ -748,7 +723,7 @@ function SignYourSelf() {
if (
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject &&
getIsSubscribe
isSubscribe?.isValid
) {
isCustomCompletionMail = true;
}
@@ -781,7 +756,6 @@ function SignYourSelf() {
const getNewse64 = await changeImageWH(base64Sign);
//remove suffiix of base64
const suffixbase64 = getNewse64 && getNewse64.split(",").pop();
const params = {
pdfFile: base64Url,
docId: documentId,
@@ -794,6 +768,7 @@ function SignYourSelf() {
const signedpdf = JSON.parse(JSON.stringify(resSignPdf));
setPdfUrl(signedpdf);
getDocumentDetails(false);
setPdfRotatese64("");
}
};
@@ -814,10 +789,10 @@ function SignYourSelf() {
containerWH
);
if (dragKey >= 0) {
const filterDropPos = xyPostion.filter(
const filterDropPos = xyPostion?.filter(
(data) => data.pageNumber === pageNumber
);
if (filterDropPos.length > 0) {
if (filterDropPos?.length > 0) {
const getXYdata = xyPostion[index].pos;
const getPosData = getXYdata;
const addSign = getPosData.map((url) => {
@@ -888,15 +863,11 @@ function SignYourSelf() {
setIsSignPad(false);
setIsImageSelect(false);
setImage();
if (isDefaultSign) {
const img = new Image();
img.src = defaultSignImg;
if (img.complete) {
imgWH = {
width: img.width,
height: img.height
};
imgWH = { width: img.width, height: img.height };
}
}
const getUpdatePosition = onSaveSign(
@@ -921,19 +892,11 @@ function SignYourSelf() {
const touch = e.touches[0]; // Get the first touch point
mouseX = touch.clientX - divRect.left;
mouseY = touch.clientY - divRect.top;
setSignBtnPosition([
{
xPos: mouseX,
yPos: mouseY
}
]);
setSignBtnPosition([{ xPos: mouseX, yPos: mouseY }]);
} else {
mouseX = e.clientX - divRect.left;
mouseY = e.clientY - divRect.top;
const xyPosition = {
xPos: mouseX,
yPos: mouseY
};
const xyPosition = { xPos: mouseX, yPos: mouseY };
setXYSignature(xyPosition);
}
};
@@ -951,9 +914,7 @@ function SignYourSelf() {
const handleDeleteSign = (key) => {
setCurrWidgetsDetails({});
const updateResizeData = [];
let filterData = xyPostion[index].pos.filter((data) => data.key !== key);
//delete and update block position
if (filterData.length > 0) {
updateResizeData.push(filterData);
@@ -1093,7 +1054,11 @@ function SignYourSelf() {
name: dropdownName,
values: dropdownOptions,
isReadOnly: isReadOnly,
isHideLabel: isHideLabel || false
isHideLabel: isHideLabel || false,
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || "12",
fontColor:
fontColor || currWidgetsDetails?.options?.fontColor || "black"
}
};
}
@@ -1111,6 +1076,8 @@ function SignYourSelf() {
handleCloseModal();
}
}
setFontSize();
setFontColor();
};
const handleCloseModal = () => {
setCurrWidgetsDetails({});
@@ -1154,6 +1121,56 @@ function SignYourSelf() {
handleTextSettingModal(false);
}
};
const clickOnZoomIn = () => {
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
};
const clickOnZoomOut = () => {
onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale);
};
//`handleRotationFun` function is used to roatate pdf particular page
const handleRotationFun = async (rotateDegree) => {
const isPlaceholder = handleRotateWarning();
if (isPlaceholder) {
setIsRotate({ status: true, degree: rotateDegree });
} else {
const urlDetails = await rotatePdfPage(
pdfUrl || pdfDetails[0].URL,
rotateDegree,
pageNumber - 1,
pdfRotateBase64
);
setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer);
setPdfRotatese64(urlDetails.base64);
}
};
//function to show warning when user rotate page and there are some already widgets on that page
const handleRotateWarning = () => {
const placeholderData = xyPostion?.filter(
(data) => data.pageNumber === pageNumber
);
if (placeholderData && placeholderData.length > 0) {
return true;
} else {
return false;
}
};
//function to use remove widgets from current page when user want to rotate page
const handleRemoveWidgets = async () => {
const updatePlaceholder = xyPostion.filter(
(data) => data.pageNumber !== pageNumber
);
const urlDetails = await rotatePdfPage(
pdfUrl || pdfDetails[0].URL,
isRotate.degree,
pageNumber - 1,
pdfRotateBase64
);
setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer);
setPdfRotatese64(urlDetails.base64);
setXyPostion(updatePlaceholder);
setIsRotate({ status: false, degree: 0 });
};
return (
<DndProvider backend={HTML5Backend}>
<Title title={"Self Sign"} />
@@ -1176,7 +1193,6 @@ function SignYourSelf() {
<Confetti width={window.innerWidth} height={window.innerHeight} />
</div>
)}
<div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300">
{!isEmailVerified && (
<VerifyEmail
@@ -1213,14 +1229,13 @@ function SignYourSelf() {
setSignBtnPosition={setSignBtnPosition}
pageNumber={pageNumber}
containerWH={containerWH}
pdfRotateBase64={pdfRotateBase64}
/>
<div className=" w-full md:w-[57%] flex mr-4">
<PdfZoom
setScale={setScale}
scale={scale}
containerWH={containerWH}
setZoomPercent={setZoomPercent}
zoomPercent={zoomPercent}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
handleRotationFun={handleRotationFun}
/>
<div className="w-full md:w-[95%]">
<ModalUi
@@ -1239,9 +1254,7 @@ function SignYourSelf() {
<ModalUi
isOpen={showAlreadySignDoc.status}
title={t("document-signed")}
handleClose={() => {
setShowAlreadySignDoc({ status: false });
}}
handleClose={() => setShowAlreadySignDoc({ status: false })}
>
<div className="p-[20px] h-full">
<p>{showAlreadySignDoc.mssg}</p>
@@ -1265,6 +1278,10 @@ function SignYourSelf() {
setCurrWidgetsDetails={setCurrWidgetsDetails}
isSignYourself={true}
handleClose={handleCloseModal}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
<PlaceholderCopy
isPageCopy={isPageCopy}
@@ -1274,6 +1291,7 @@ function SignYourSelf() {
allPages={allPages}
pageNumber={pageNumber}
signKey={signKey}
widgetType={widgetType}
/>
{/* this is modal of signature pad */}
<SignPad
@@ -1324,12 +1342,12 @@ function SignYourSelf() {
isSignYourself={true}
setIsEmail={setIsEmail}
isCompleted={isCompleted}
handleRotationFun={handleRotationFun}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
widgetsDetails={xyPostion}
/>
<div
ref={divRef}
data-tut="reactourSecond"
className="h-full md:h-[95%]"
>
<div ref={divRef} data-tut="reactourSecond" className="h-full">
{containerWH?.width && containerWH?.height && (
<RenderPdf
pageNumber={pageNumber}
@@ -1368,6 +1386,11 @@ function SignYourSelf() {
handleTextSettingModal={handleTextSettingModal}
setScale={setScale}
scale={scale}
pdfRotateBase64={pdfRotateBase64}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
)}
</div>
@@ -1407,9 +1430,7 @@ function SignYourSelf() {
<ModalUi
isOpen={validateAlert}
title={t("validation-alert")}
handleClose={() => {
setValidateAlert(false);
}}
handleClose={() => setValidateAlert(false)}
>
<div className="p-[20px] h-full">
<p>{t("validate-alert-mssg")}</p>
@@ -1424,6 +1445,11 @@ function SignYourSelf() {
</button>
</div>
</ModalUi>
<RotateAlert
isRotate={isRotate.status}
setIsRotate={setIsRotate}
handleRemoveWidgets={handleRemoveWidgets}
/>
<TextFontSetting
isTextSetting={isTextSetting}
setIsTextSetting={setIsTextSetting}
+23 -15
View File
@@ -8,9 +8,10 @@ import ModalUi from "../primitives/ModalUi";
import pad from "../assets/images/pad.svg";
import AddTeam from "../components/AddTeam";
import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribedTeam } from "../constant/Utils";
import { checkIsSubscribed } from "../constant/Utils";
import SubscribeCard from "../primitives/SubscribeCard";
import Title from "../components/Title";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next";
const heading = ["Sr.No", "Name", "Parent Team", "Active"];
@@ -38,7 +39,7 @@ const TeamList = () => {
const [isActiveModal, setIsActiveModal] = useState({});
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isActLoader, setIsActLoader] = useState({});
const [isSubscribe, setIsSubscribe] = useState(false);
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: false });
const [isEditModal, setIsEditModal] = useState({});
const [isAdmin, setIsAdmin] = useState(false);
const startIndex = (currentPage - 1) * recordperPage; // user per page
@@ -103,8 +104,8 @@ const TeamList = () => {
try {
setIsLoader(true);
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribedTeam();
setIsSubscribe(getIsSubscribe);
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe);
}
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (extUser) {
@@ -134,7 +135,7 @@ const TeamList = () => {
const handleFormModal = () => {
setIsModal(!isModal);
};
// Get current list
// to slice out 10 objects from array for current page
const indexOfLastDoc = currentPage * recordperPage;
const indexOfFirstDoc = indexOfLastDoc - recordperPage;
const currentList = teamList?.slice(indexOfFirstDoc, indexOfLastDoc);
@@ -145,8 +146,19 @@ const TeamList = () => {
};
// Change page
const paginateFront = () => setCurrentPage(currentPage + 1);
const paginateBack = () => setCurrentPage(currentPage - 1);
const paginateFront = () => {
const lastValue = pageNumbers?.[pageNumbers?.length - 1];
if (currentPage < lastValue) {
setCurrentPage(currentPage + 1);
}
};
const paginateBack = () => {
if (startIndex > 0) {
setCurrentPage(currentPage - 1);
}
};
const handleActionBtn = (act, item) => {
if (act.action === "edit") {
setIsEditModal({ [item.objectId]: true });
@@ -225,15 +237,11 @@ const TeamList = () => {
<Loader />
</div>
)}
{isSubscribe && isEnableSubscription && !isLoader && (
{validplan[isSubscribe.plan] && isEnableSubscription && !isLoader && (
<>
{isAdmin ? (
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
{isAlert.msg && (
<Alert type={isAlert.type}>
<div className="ml-3">{isAlert.msg}</div>
</Alert>
)}
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<div className="font-light">
{t("report-name.Teams")}
@@ -470,9 +478,9 @@ const TeamList = () => {
)}
</>
)}
{!isSubscribe && isEnableSubscription && !isLoader && (
{isEnableSubscription && !validplan[isSubscribe.plan] && !isLoader && (
<div data-tut="apisubscribe">
<SubscribeCard plan={"TEAMS"} price={"20"} />
<SubscribeCard plan="TEAMS" />
</div>
)}
</div>
+236 -123
View File
@@ -25,7 +25,13 @@ import {
textInputWidget,
radioButtonWidget,
fetchSubscription,
getContainerScale
getContainerScale,
convertBase64ToFile,
rotatePdfPage,
onClickZoomOut,
onClickZoomIn,
handleRemoveWidgets,
handleRotateWarning
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css";
@@ -41,6 +47,7 @@ import Parse from "parse";
import { useSelector } from "react-redux";
import PdfZoom from "../components/pdf/PdfZoom";
import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
const TemplatePlaceholder = () => {
const { t } = useTranslation();
const navigate = useNavigate();
@@ -55,12 +62,15 @@ const TemplatePlaceholder = () => {
const [xySignature, setXYSignature] = useState({});
const [dragKey, setDragKey] = useState();
const [signersdata, setSignersData] = useState([]);
const [signerObjId, setSignerObjId] = useState();
const [signerPos, setSignerPos] = useState([]);
const [isSelectListId, setIsSelectId] = useState();
const [isSendAlert, setIsSendAlert] = useState(false);
const [isCreateDocModal, setIsCreateDocModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isRotate, setIsRotate] = useState({
status: false,
degree: 0
});
const [isLoading, setIsLoading] = useState({
isLoad: true,
message: t("loading-mssg")
@@ -73,7 +83,6 @@ const TemplatePlaceholder = () => {
const [tourStatus, setTourStatus] = useState([]);
const [signerUserId, setSignerUserId] = useState();
const [pdfOriginalWH, setPdfOriginalWH] = useState([]);
const [contractName, setContractName] = useState("");
const [containerWH, setContainerWH] = useState();
const signRef = useRef(null);
const dragRef = useRef(null);
@@ -91,6 +100,7 @@ const TemplatePlaceholder = () => {
const [isNameModal, setIsNameModal] = useState(false);
const [isTextSetting, setIsTextSetting] = useState(false);
const [pdfLoad, setPdfLoad] = useState(false);
const [pdfRotateBase64, setPdfRotatese64] = useState("");
const color = [
"#93a3db",
"#e6c3db",
@@ -181,21 +191,25 @@ const TemplatePlaceholder = () => {
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current, isHeader]);
const handleNavigation = () => {
navigate("/subscription");
};
async function checkIsSubscribed() {
const res = await fetchSubscription();
const freeplan = res.plan;
const plan = res.plan;
const billingDate = res.billingDate;
if (freeplan === "freeplan") {
if (plan === "freeplan") {
return true;
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
setIsSubscribe(true);
return true;
} else {
navigate(`/subscription`);
handleNavigation(plan);
}
} else {
navigate(`/subscription`);
handleNavigation(plan);
}
}
// `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
@@ -225,8 +239,6 @@ const TemplatePlaceholder = () => {
setPdfDetails(documentData);
setIsSigners(true);
if (documentData[0].Signers && documentData[0].Signers.length > 0) {
setSignerObjId(documentData[0].Signers[0].objectId);
setContractName(documentData[0].Signers[0].className);
setIsSelectId(0);
if (
documentData[0].Placeholders &&
@@ -365,21 +377,19 @@ const TemplatePlaceholder = () => {
containerWH
);
const key = randomId();
let filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
const dragTypeValue = item?.text ? item.text : monitor.type;
const widgetWidth = defaultWidthHeight(dragTypeValue).width;
const widgetHeight = defaultWidthHeight(dragTypeValue).height;
let dropData = [];
let dropData = [],
currentPagePosition;
let placeHolder;
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
const divHeight = divRef.current.getBoundingClientRect().height;
const dropObj = {
//onclick put placeholder center on pdf
xPosition:
(containerWH.width / 2 - widgetWidth / 2) /
(containerScale * scale),
yPosition:
(containerWH.height / 2 - widgetHeight / 2) /
(containerScale * scale),
xPosition: widgetWidth / 4 + containerWH.width / 2,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key,
@@ -430,65 +440,40 @@ const TemplatePlaceholder = () => {
pos: dropData
};
}
const { blockColor, Role } = signer;
//adding placholder in existing signer pos array (placaholder)
if (filterSignerPos.length > 0) {
const getPlaceHolder = filterSignerPos[0].placeHolder;
let filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
const getPlaceHolder = filterSignerPos?.placeHolder;
if (getPlaceHolder) {
//checking exist placeholder on same page
currentPagePosition = getPlaceHolder.find(
(data) => data.pageNumber === pageNumber
);
}
//checking current page has already some placeholders then update that placeholder and add upcoming placehoder position
if (getPlaceHolder && currentPagePosition) {
const updatePlace = getPlaceHolder.filter(
(data) => data.pageNumber !== pageNumber
);
const getPageNumer = getPlaceHolder.filter(
(data) => data.pageNumber === pageNumber
const getPos = currentPagePosition?.pos;
const newSignPos = getPos.concat(dropData);
let xyPos = {
pageNumber: pageNumber,
pos: newSignPos
};
updatePlace.push(xyPos);
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId ? { ...x, placeHolder: updatePlace } : x
);
//add entry of position for same signer on multiple page
if (getPageNumer.length > 0) {
const getPos = getPageNumer[0].pos;
const newSignPos = getPos.concat(dropData);
let xyPos = {
pageNumber: pageNumber,
pos: newSignPos
};
updatePlace.push(xyPos);
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId ? { ...x, placeHolder: updatePlace } : x
);
setSignerPos(updatesignerPos);
} else {
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId
? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
: x
);
setSignerPos(updatesignerPos);
}
setSignerPos(updatesignerPos);
} else {
//adding new placeholder for selected signer in pos array (placeholder)
let placeHolderPos;
if (contractName) {
placeHolderPos = {
signerPtr: {
__type: "Pointer",
className: `${contractName}`,
objectId: signerObjId
},
signerObjId: signerObjId,
blockColor: blockColor ? blockColor : color[isSelectListId],
placeHolder: [placeHolder],
Role: Role ? Role : roleName,
Id: uniqueId
};
} else {
placeHolderPos = {
signerPtr: {},
signerObjId: "",
blockColor: blockColor ? blockColor : color[isSelectListId],
placeHolder: [placeHolder],
Role: Role ? Role : roleName,
Id: uniqueId
};
}
setSignerPos((prev) => [...prev, placeHolderPos]);
//else condition to add placeholder widgets on multiple page first time
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId && x?.placeHolder
? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
: x.Id === uniqueId
? { ...x, placeHolder: [placeHolder] }
: x
);
setSignerPos(updatesignerPos);
}
if (dragTypeValue === "dropdown") {
@@ -615,10 +600,6 @@ const TemplatePlaceholder = () => {
//function for delete signature block
const handleDeleteSign = (key, Id) => {
const updateData = [];
// const filterSignerPos = signerPos.filter(
// (data) => data.signerObjId === signerId
// );
const filterSignerPos = signerPos.filter((data) => data.Id === Id);
if (filterSignerPos.length > 0) {
@@ -651,11 +632,11 @@ const TemplatePlaceholder = () => {
setSignerPos(newUpdateSigner);
} else {
const updateFilter = signerPos.filter((data) => data.Id !== Id);
const getRemainPage = filterSignerPos[0].placeHolder.filter(
(data) => data.pageNumber !== pageNumber
);
//condition to check placeholder length is greater than 1 do not need to remove whole placeholder
//array only resove particular widgets
if (getRemainPage && getRemainPage.length > 0) {
const newUpdatePos = filterSignerPos.map((obj) => {
if (obj.Id === Id) {
@@ -669,7 +650,16 @@ const TemplatePlaceholder = () => {
setSignerPos(signerupdate);
} else {
setSignerPos(updateFilter);
const updatedData = signerPos.map((item) => {
if (item.Id === Id) {
// Create a copy of the item object and delete the placeHolder field
const updatedItem = { ...item };
delete updatedItem.placeHolder;
return updatedItem;
}
return item;
});
setSignerPos(updatedData);
}
}
}
@@ -691,19 +681,11 @@ const TemplatePlaceholder = () => {
const touch = e.touches[0]; // Get the first touch point
mouseX = touch.clientX - divRect.left;
mouseY = touch.clientY - divRect.top;
setSignBtnPosition([
{
xPos: mouseX,
yPos: mouseY
}
]);
setSignBtnPosition([{ xPos: mouseX, yPos: mouseY }]);
} else {
mouseX = e.clientX - divRect.left;
mouseY = e.clientY - divRect.top;
const xyPosition = {
xPos: mouseX,
yPos: mouseY
};
const xyPosition = { xPos: mouseX, yPos: mouseY };
setXYSignature(xyPosition);
}
};
@@ -712,20 +694,54 @@ const TemplatePlaceholder = () => {
const handleMouseLeave = () => {
setSignBtnPosition([xySignature]);
};
const alertSendEmail = async () => {
if (signerPos.length !== signersdata.length) {
setIsSendAlert(true);
} else {
const isPlaceholderExist = signerPos.every((data) => data.placeHolder);
if (isPlaceholderExist) {
handleSaveTemplate();
} else {
setIsSendAlert(true);
}
};
useEffect(() => {
const timer = setTimeout(() => {
if (signerPos?.length > 0) {
autosavedetails();
}
}, 2000);
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [signerPos]);
// `autosavedetails` is used to save template details after every 2 sec when changes are happern in placeholder like drag-drop widgets, remove signers
const autosavedetails = async () => {
let signers = [];
if (signersdata?.length > 0) {
signersdata.forEach((x) => {
if (x.objectId) {
const obj = {
__type: "Pointer",
className: "contracts_Contactbook",
objectId: x.objectId
};
signers.push(obj);
}
});
}
try {
const templateCls = new Parse.Object("contracts_Template");
templateCls.id = templateId;
templateCls.set("Placeholders", signerPos);
templateCls.set("Signers", signers);
await templateCls.save();
} catch (err) {
console.log("error in autosave template", err);
}
};
const handleSaveTemplate = async () => {
if (signersdata?.length) {
const loadObj = {
isLoad: true,
message: t("loading-mssg")
};
const loadObj = { isLoad: true, message: t("loading-mssg") };
setIsLoading(loadObj);
setIsSendAlert(false);
let signers = [];
@@ -741,6 +757,17 @@ const TemplatePlaceholder = () => {
}
});
}
let pdfUrl = pdfDetails[0]?.URL;
if (pdfRotateBase64) {
try {
pdfUrl = await convertBase64ToFile(
pdfDetails[0].Name,
pdfRotateBase64
);
} catch (e) {
console.log("error to convertBase64ToFile in placeholder flow", e);
}
}
try {
const data = {
Placeholders: signerPos,
@@ -751,23 +778,18 @@ const TemplatePlaceholder = () => {
SendinOrder: pdfDetails[0]?.SendinOrder || false,
AutomaticReminders: pdfDetails[0]?.AutomaticReminders,
RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery),
NextReminderDate: pdfDetails[0]?.NextReminderDate
NextReminderDate: pdfDetails[0]?.NextReminderDate,
URL: pdfUrl
};
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 updateTemplate.save();
setIsCreateDocModal(true);
setIsMailSend(true);
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
setIsLoading({ isLoad: false });
} catch (e) {
setIsLoading(false);
alert(t("something-went-wrong-mssg"));
@@ -891,8 +913,21 @@ const TemplatePlaceholder = () => {
const handleCreateDocModal = async () => {
setIsCreateDocModal(false);
setIsCreateDoc(true);
let pdfUrl = pdfDetails[0]?.URL;
if (pdfRotateBase64) {
try {
pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfRotateBase64);
} catch (e) {
console.log("error to convertBase64ToFile in placeholder flow", e);
}
}
// handle create document
const res = await createDocument(pdfDetails, signerPos, signersdata);
const res = await createDocument(
pdfDetails,
signerPos,
signersdata,
pdfUrl
);
if (res.status === "success") {
navigate(`/placeHolderSign/${res.id}`, {
state: { title: "Use Template" }
@@ -903,7 +938,6 @@ const TemplatePlaceholder = () => {
setIsCreateDoc(false);
}
};
// `handleAddSigner` is used to open Add Role Modal
const handleAddSigner = () => {
setIsModalRole(true);
@@ -914,8 +948,6 @@ const TemplatePlaceholder = () => {
// save Role in entry in signerList and user
const handleAddRole = (e) => {
e.preventDefault();
setSignerObjId("");
setContractName("");
const count = signersdata.length > 0 ? signersdata.length + 1 : 1;
const Id = randomId();
const index = signersdata.length;
@@ -925,6 +957,15 @@ const TemplatePlaceholder = () => {
blockColor: color[index]
};
setSignersData((prevArr) => [...prevArr, obj]);
const signerPosObj = {
signerPtr: {},
signerObjId: "",
blockColor: color[index],
Role: roleName || "User " + count,
Id: Id
};
setSignerPos((prev) => [...prev, signerPosObj]);
setIsModalRole(false);
setRoleName("");
setUniqueId(Id);
@@ -956,11 +997,12 @@ const TemplatePlaceholder = () => {
setIsMailSend(false);
};
// `handleLinkUser` is used to open Add/Choose Signer Modal when user can link existing or new User with placeholder
// `handleLinkUser` is used to open Add/Choose Signer Modal when user can link existing or new User with placeholder
// and update entry in signersList
const handleLinkUser = (id) => {
setIsAddUser({ [id]: true });
};
// `handleAddUser` is used to adduser
const handleAddUser = (data) => {
const signerPtr = {
__type: "Pointer",
@@ -1089,7 +1131,13 @@ const TemplatePlaceholder = () => {
status: status,
defaultValue: defaultValue,
isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false
isHideLabel: isHideLabel || false,
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || "12",
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
}
};
}
@@ -1121,7 +1169,13 @@ const TemplatePlaceholder = () => {
},
isReadOnly: isReadOnly || false,
defaultValue: defaultValue,
isHideLabel: isHideLabel || false
isHideLabel: isHideLabel || false,
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || "12",
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
}
};
}
@@ -1133,7 +1187,13 @@ const TemplatePlaceholder = () => {
name: dropdownName,
status: status,
values: dropdownOptions,
defaultValue: defaultValue
defaultValue: defaultValue,
fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || "12",
fontColor:
fontColor ||
currWidgetsDetails?.options?.fontColor ||
"black"
}
};
}
@@ -1160,6 +1220,8 @@ const TemplatePlaceholder = () => {
}
}
}
setFontSize();
setFontColor();
};
const handleWidgetdefaultdata = (defaultdata) => {
@@ -1250,6 +1312,7 @@ const TemplatePlaceholder = () => {
setCurrWidgetsDetails({});
handleNameModal();
};
const handleNameModal = () => {
setIsNameModal(false);
setCurrWidgetsDetails({});
@@ -1258,6 +1321,38 @@ const TemplatePlaceholder = () => {
setIsCheckbox(false);
};
const clickOnZoomIn = () => {
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
};
const clickOnZoomOut = () => {
onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale);
};
//`handleRotationFun` function is used to roatate pdf particular page
const handleRotationFun = async (rotateDegree) => {
const isRotate = handleRotateWarning(signerPos, pageNumber);
if (isRotate) {
setIsRotate({ status: true, degree: rotateDegree });
} else {
const urlDetails = await rotatePdfPage(
pdfDetails[0].URL,
rotateDegree,
pageNumber - 1,
pdfRotateBase64
);
setPdfRotatese64(urlDetails.base64);
}
};
const handleRemovePlaceholder = async () => {
handleRemoveWidgets(setSignerPos, signerPos, pageNumber, setIsRotate);
const urlDetails = await rotatePdfPage(
pdfDetails[0].URL,
isRotate.degree,
pageNumber - 1,
pdfRotateBase64
);
setPdfRotatese64(urlDetails.base64);
};
return (
<>
<Title title={"Template"} />
@@ -1305,11 +1400,9 @@ const TemplatePlaceholder = () => {
{/* pdf render view */}
<div className=" w-full md:w-[57%] flex mr-4">
<PdfZoom
setScale={setScale}
scale={scale}
containerWH={containerWH}
setZoomPercent={setZoomPercent}
zoomPercent={zoomPercent}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
handleRotationFun={handleRotationFun}
/>
<div className="w-full md:w-[95%]">
{/* this modal is used show alert set placeholder for all signers before send mail */}
@@ -1391,6 +1484,10 @@ const TemplatePlaceholder = () => {
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
<DropdownWidgetOption
type="checkbox"
@@ -1402,6 +1499,10 @@ const TemplatePlaceholder = () => {
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
<DropdownWidgetOption
type="dropdown"
@@ -1413,6 +1514,10 @@ const TemplatePlaceholder = () => {
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
<PlaceholderCopy
isPageCopy={isPageCopy}
@@ -1440,6 +1545,9 @@ const TemplatePlaceholder = () => {
currentSigner={true}
setIsEditTemplate={handleEditTemplateModal}
dataTut4="reactourFour"
handleRotationFun={handleRotationFun}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
/>
<div
ref={divRef}
@@ -1471,7 +1579,6 @@ const TemplatePlaceholder = () => {
signersdata={signersdata}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
setSignerObjId={setSignerObjId}
isDragging={isDragging}
setShowDropdown={setShowDropdown}
setCurrWidgetsDetails={setCurrWidgetsDetails}
@@ -1485,6 +1592,11 @@ const TemplatePlaceholder = () => {
setScale={setScale}
scale={scale}
setIsSelectId={setIsSelectId}
pdfRotateBase64={pdfRotateBase64}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/>
)}
</div>
@@ -1509,9 +1621,7 @@ const TemplatePlaceholder = () => {
signerPos={signerPos}
signersdata={signersdata}
isSelectListId={isSelectListId}
setSignerObjId={setSignerObjId}
setIsSelectId={setIsSelectId}
setContractName={setContractName}
isSigners={isSigners}
setIsShowEmail={setIsShowEmail}
isMailSend={isMailSend}
@@ -1541,10 +1651,8 @@ const TemplatePlaceholder = () => {
setSignerPos={setSignerPos}
signersdata={signersdata}
isSelectListId={isSelectListId}
setSignerObjId={setSignerObjId}
setRoleName={setRoleName}
setIsSelectId={setIsSelectId}
setContractName={setContractName}
handleAddSigner={handleAddSigner}
setUniqueId={setUniqueId}
handleDeleteUser={handleDeleteUser}
@@ -1624,6 +1732,11 @@ const TemplatePlaceholder = () => {
fontColor={fontColor}
setFontColor={setFontColor}
/>
<RotateAlert
isRotate={isRotate.status}
setIsRotate={setIsRotate}
handleRemoveWidgets={handleRemovePlaceholder}
/>
</DndProvider>
</>
);
@@ -21,7 +21,7 @@ const UpdateExistUserAdmin = () => {
try {
const isAdminExist = await Parse.Cloud.run("checkadminexist");
if (isAdminExist !== "not_exist") {
console.log("isAdminExist ", isAdminExist);
// console.log("isAdminExist ", isAdminExist);
setErrMsg("Admin already exists.");
}
} catch (err) {
@@ -65,12 +65,8 @@ const UpdateExistUserAdmin = () => {
};
return (
<div className="h-screen flex justify-center">
<Title title="Add admin" />
{isAlert.msg && (
<Alert type={isAlert.type}>
<div className="ml-3">{isAlert.msg}</div>
</Alert>
)}
<Title title={"Add Admin"} />
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
{loader ? (
<div className="text-[grey] flex justify-center items-center text-lg md:text-2xl">
<Loader />
+246 -116
View File
@@ -9,8 +9,9 @@ import Tooltip from "../primitives/Tooltip";
import AddUser from "../components/AddUser";
import SubscribeCard from "../primitives/SubscribeCard";
import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribedTeam } from "../constant/Utils";
import { fetchSubscriptionInfo } from "../constant/Utils";
import Title from "../components/Title";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next";
const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"];
// const actions = [];
@@ -18,7 +19,11 @@ const UserList = () => {
const { t } = useTranslation();
const [userList, setUserList] = useState([]);
const [isLoader, setIsLoader] = useState(false);
const [isModal, setIsModal] = useState(false);
const [isModal, setIsModal] = useState({
form: false,
addseats: false,
options: false
});
const location = useLocation();
const isDashboard =
location?.pathname === "/dashboard/35KBoSgoAK" ? true : false;
@@ -26,8 +31,16 @@ const UserList = () => {
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isActiveModal, setIsActiveModal] = useState({});
const [isActLoader, setIsActLoader] = useState({});
const [isSubscribe, setIsSubscribe] = useState(false);
const [isSubscribe, setIsSubscribe] = useState({
plan: "",
isValid: false,
priceperUser: 0
});
const [isAdmin, setIsAdmin] = useState(false);
const [formHeader, setFormHeader] = useState(t("add-user"));
const [usersCount, setUserCounts] = useState({ allowed: 0, totalAllowed: 0 });
const [amount, setAmount] = useState({ quantity: 1, price: 0 });
const [isBuyLoader, setIsBuyLoader] = useState(false);
const recordperPage = 10;
const startIndex = (currentPage - 1) * recordperPage; // user per page
@@ -79,10 +92,13 @@ const UserList = () => {
pages.push(lastPageIndex);
}
}
return pages;
};
const pageNumbers = getPaginationRange();
// to slice out 10 objects from array for current page
const indexOfLastDoc = currentPage * recordperPage;
const indexOfFirstDoc = indexOfLastDoc - recordperPage;
const currentList = userList?.slice(indexOfFirstDoc, indexOfLastDoc);
useEffect(() => {
fetchUserList();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -90,13 +106,43 @@ const UserList = () => {
async function fetchUserList() {
try {
setIsLoader(true);
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribedTeam();
setIsSubscribe(getIsSubscribe);
const subscribe = await fetchSubscriptionInfo();
if (subscribe?.plan_code?.includes("team")) {
const isSupAdmin =
subscribe?.adminId && extUser?.objectId === subscribe?.adminId;
setIsSubscribe({
plan: subscribe.plan_code,
isValid: true,
adminId: subscribe.adminId,
priceperUser: subscribe.price,
isSuperAdmin: isSupAdmin
});
setAmount((prev) => ({ ...prev, price: subscribe.price }));
try {
const res = await Parse.Cloud.run("allowedusers");
setUserCounts((obj) => ({
...obj,
allowed: parseInt(res),
totalAllowed: parseInt(subscribe?.totalAllowedUser) || 0
}));
} catch (err) {
console.log("err while get users", err);
}
} else {
setIsSubscribe({
plan: subscribe.plan_code,
isValid: false,
adminId: subscribe.adminId
});
}
} else {
setIsSubscribe(true);
setIsSubscribe({ plan: "teams-yearly", isValid: true });
}
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (extUser) {
const admin =
extUser?.UserRole &&
@@ -115,26 +161,36 @@ const UserList = () => {
console.log("Err in fetch userlist", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => {
setIsAlert({ type: "success", msg: "" });
}, 1500);
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
setIsLoader(false);
}
}
const handleFormModal = () => {
setIsModal(!isModal);
const handleModal = (modalName) => {
setIsModal((obj) => ({ ...obj, [modalName]: !obj[modalName] }));
};
// Change page
const paginateFront = () => setCurrentPage(currentPage + 1);
const paginateBack = () => setCurrentPage(currentPage - 1);
// const handleActionBtn = (act, item) => {
// if (act.action === "delete") {
// setIsDeleteModal({ [item.objectId]: true });
// }
// };
const paginateFront = () => {
const lastValue = pageNumbers?.[pageNumbers?.length - 1];
if (currentPage < lastValue) {
setCurrentPage(currentPage + 1);
}
};
const paginateBack = () => {
if (startIndex > 0) {
setCurrentPage(currentPage - 1);
}
};
const handleUserData = (userData) => {
setUserList((prev) => [userData, ...prev]);
if (userData) {
setUserCounts((obj) => ({
...obj,
allowed: obj?.allowed > 0 ? obj.allowed - 1 : 1
}));
setUserList((prev) => [userData, ...prev]);
}
};
// `formatRow` is used to show data in poper manner like
// if data is of array type then it will join array items with ","
@@ -150,9 +206,8 @@ const UserList = () => {
return "-";
}
};
const handleClose = () => {
setIsActiveModal({});
};
const handleClose = () => setIsActiveModal({});
const handleToggleSubmit = async (user) => {
const index = userList.findIndex((obj) => obj.objectId === user.objectId);
if (index !== -1) {
@@ -184,6 +239,49 @@ const UserList = () => {
const handleToggleBtn = (user) => {
setIsActiveModal({ [user.objectId]: true });
};
const handleAddOnSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsBuyLoader(true);
try {
const resAddon = await Parse.Cloud.run("buyaddonusers", {
users: parseInt(amount.quantity)
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setUserCounts((obj) => ({
...obj,
allowed: parseInt(obj.allowed) + parseInt(amount.quantity),
totalAllowed: parseInt(_resAddon.addon)
}));
setAmount((obj) => ({ ...obj, quantity: 1 }));
}
}
} catch (err) {
console.log("Err in buy addon", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
setIsBuyLoader(false);
handleModal("addseats");
}
};
const handlePricePerUser = (e) => {
const quantity = e.target.value;
const price = e.target?.value > 0 ? isSubscribe.priceperUser * quantity : 0;
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleBuyUsers = (allowed, totalAllowed) => {
if (allowed && totalAllowed) {
setUserCounts((obj) => ({
...obj,
allowed: parseInt(obj.allowed) + parseInt(allowed),
totalAllowed: parseInt(totalAllowed)
}));
}
};
return (
<div className="relative">
<Title title={isAdmin ? "Users" : "Page not found"} />
@@ -193,34 +291,57 @@ const UserList = () => {
</div>
)}
{Object.keys(isActLoader)?.length > 0 && (
<div className="absolute w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-30 rounded-box">
<div className="absolute w-full h-full flex justify-center items-center bg-black/30 z-30 rounded-box">
<Loader />
</div>
)}
{isSubscribe && !isLoader && (
{validplan[isSubscribe.plan] && !isLoader && (
<>
{isAdmin ? (
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
{isAlert.msg && (
<Alert type={isAlert.type}>
<div className="ml-3">{isAlert.msg}</div>
</Alert>
)}
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<div className="font-light">
{t("report-name.Users")}
{t("report-name.Users")}{" "}
<span className="text-xs md:text-[13px] font-normal">
<Tooltip message={t("users-from-teams")} />
</span>
</div>
<div
className="cursor-pointer"
onClick={() => handleFormModal()}
>
<i className="fa-light fa-square-plus text-accent text-[40px]"></i>
<div className="flex flex-row gap-2 items-center">
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
<div
className="hidden md:flex op-btn op-btn-sm h-[35px] op-btn-primary op-btn-outline text-xs mb-0.5"
onClick={() => handleModal("addseats")}
>
{t("buy-users")}
</div>
)}
<div
className="cursor-pointer"
onClick={() => handleModal("form")}
>
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
</div>
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
<div
className="cursor-pointer relative md:hidden mb-0.5"
onClick={() => handleModal("options")}
>
<i className="fa-light fa-ellipsis-vertical fa-xl"></i>
{isModal?.options && (
<ul className="absolute -right-3 top-auto z-[70] w-max op-menu op-menu-xs shadow bg-base-100 text-base-content rounded-box border">
<li onClick={() => handleModal("addseats")}>
<span className="text-[13px] capitalize font-medium">
{t("buy-users")}
</span>
</li>
</ul>
)}
</div>
)}
</div>
</div>
<div className={` overflow-x-auto w-full`}>
<div className="overflow-x-auto w-full">
<table className="op-table border-collapse w-full">
<thead className="text-[14px]">
<tr className="border-y-[1px]">
@@ -234,7 +355,7 @@ const UserList = () => {
<tbody className="text-[12px]">
{userList?.length > 0 && (
<>
{userList.map((item, index) => (
{currentList.map((item, index) => (
<tr className="border-y-[1px]" key={index}>
{heading.includes("Sr.No") && (
<th className="px-4 py-2">
@@ -298,49 +419,6 @@ const UserList = () => {
)}
</td>
)}
{/* <td className="px-3 py-2 text-white grid grid-cols-2">
{actions?.length > 0 &&
actions.map((act, index) => (
<button
key={index}
onClick={() => handleActionBtn(act, item)}
title={act.hoverLabel}
className={`${
act?.btnColor ? act.btnColor : ""
} op-btn op-btn-sm`}
>
<i className={act.btnIcon}></i>
</button>
))}
{isDeleteModal[item.objectId] && (
<ModalUi
isOpen
title={t("delete-user")}
handleClose={handleClose}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t( "are-you-sure")} {t("delete")} {t("this-user")}?
</div>
<hr className="bg-[#ccc] mt-4 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() => handleDelete(item)}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{('no)}
</button>
</div>
</div>
</ModalUi>
)}
</td> */}
</tr>
))}
</>
@@ -348,34 +426,42 @@ const UserList = () => {
</tbody>
</table>
</div>
<div className="op-join flex flex-wrap items-center p-2">
{userList.length > recordperPage && (
<button
onClick={() => paginateBack()}
className="op-join-item op-btn op-btn-sm"
>
{t("prev")}
</button>
)}
{pageNumbers.map((x, i) => (
<button
key={i}
onClick={() => setCurrentPage(x)}
disabled={x === "..."}
className={`${
x === currentPage ? "op-btn-active" : ""
} op-join-item op-btn op-btn-sm`}
>
{x}
</button>
))}
{userList.length > recordperPage && (
<button
onClick={() => paginateFront()}
className="op-join-item op-btn op-btn-sm"
>
{t("next")}
</button>
<div className="flex flex-row justify-between items-center text-xs font-medium">
<div className="op-join flex flex-wrap items-center p-2">
{userList.length > recordperPage && (
<button
onClick={() => paginateBack()}
className="op-join-item op-btn op-btn-sm"
>
{t("prev")}
</button>
)}
{pageNumbers.map((x, i) => (
<button
key={i}
onClick={() => setCurrentPage(x)}
disabled={x === "..."}
className={`${
x === currentPage ? "op-btn-active" : ""
} op-join-item op-btn op-btn-sm`}
>
{x}
</button>
))}
{userList.length > recordperPage && (
<button
onClick={() => paginateFront()}
className="op-join-item op-btn op-btn-sm"
>
{t("next")}
</button>
)}
</div>
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
<div>
{t("available-seats")}: {usersCount.allowed}/
{usersCount.totalAllowed}
</div>
)}
</div>
{userList?.length <= 0 && (
@@ -397,16 +483,60 @@ const UserList = () => {
</div>
)}
<ModalUi
title={t("add-user")}
isOpen={isModal}
handleClose={handleFormModal}
isOpen={isModal.form}
title={formHeader}
handleClose={() => handleModal("form")}
>
<AddUser
setIsAlert={setIsAlert}
handleUserData={handleUserData}
closePopup={handleFormModal}
handleBuyUsers={handleBuyUsers}
closePopup={() => handleModal("form")}
setFormHeader={setFormHeader}
/>
</ModalUi>
<ModalUi
isOpen={isModal.addseats}
title="Add Seats"
handleClose={() => handleModal("addseats")}
>
{isBuyLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<form onSubmit={handleAddOnSubmit} className=" px-3 pb-6 pt-6">
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("Quantity-of-users")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="number"
name="quantity"
value={amount.quantity}
onChange={(e) => handlePricePerUser(e)}
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
required
/>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {isSubscribe.priceperUser})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full">
{t("Proceed")}
</button>
</form>
</ModalUi>
</div>
) : (
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
@@ -422,9 +552,9 @@ const UserList = () => {
)}
</>
)}
{!isSubscribe && isEnableSubscription && !isLoader && (
{isEnableSubscription && !validplan[isSubscribe.plan] && !isLoader && (
<div data-tut="apisubscribe">
<SubscribeCard plan={"TEAMS"} price={"20"} />
<SubscribeCard plan="TEAMS" />
</div>
)}
</div>
+157 -89
View File
@@ -10,7 +10,6 @@ import Tooltip from "../primitives/Tooltip";
import { isEnableSubscription, isStaging } from "../constant/const";
import {
checkIsSubscribed,
checkIsSubscribedTeam,
copytoData,
handleSendOTP,
openInNewTab
@@ -18,10 +17,13 @@ import {
import Upgrade from "../primitives/Upgrade";
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next";
import SelectLanguage from "../components/pdf/SelectLanguage";
import { RWebShare } from "react-web-share";
import Alert from "../primitives/Alert";
import Tour from "reactour";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
function UserProfile() {
const navigate = useNavigate();
@@ -39,7 +41,7 @@ function UserProfile() {
const [isDisableDocId, setIsDisableDocId] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isUpgrade, setIsUpgrade] = useState(false);
const [isAlert, setIsAlert] = useState({});
const [isAlert, setIsAlert] = useState({ type: "success", message: "" });
const [publicUserName, setPublicUserName] = useState(
extendUser && extendUser?.[0]?.UserName
);
@@ -57,7 +59,10 @@ function UserProfile() {
const [tagLine, setTagLine] = useState(
extendUser && extendUser?.[0]?.Tagline
);
const [isTeam, setIsTeam] = useState(false);
const [isPlan, setIsPlan] = useState({ plan: "", isValid: false });
const [tourStatus, setTourStatus] = useState([]);
const [isProfileTour, setIsProfileTour] = useState(false);
const [isDontShow, setIsDontShow] = useState(false);
const getPublicUrl = isStaging
? `https://staging.opensign.me/${extendUser?.[0]?.UserName}`
: `https://opensign.me/${extendUser?.[0]?.UserName}`;
@@ -65,17 +70,27 @@ function UserProfile() {
getUserDetail();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const getUserDetail = async () => {
setIsLoader(true);
const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass);
const tourstatuss = jsonSender[0]?.TourStatus && jsonSender[0].TourStatus;
if (!isEnableSubscription) {
setIsProfileTour(true);
} else if (tourstatuss && tourstatuss.length > 0) {
setTourStatus(tourstatuss);
const checkTourRecipients = tourstatuss.filter(
(data) => data.profileTour
);
if (checkTourRecipients && checkTourRecipients.length > 0) {
setIsProfileTour(checkTourRecipients[0]?.profileTour);
}
}
const HeaderDocId = jsonSender[0]?.HeaderDocId;
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
const getIsTeam = await checkIsSubscribedTeam();
setIsSubscribe(getIsSubscribe);
setIsTeam(getIsTeam);
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe.isValid);
setIsPlan(subscribe);
}
if (HeaderDocId) {
setIsDisableDocId(HeaderDocId);
@@ -109,13 +124,8 @@ function UserProfile() {
});
if (res) {
setIsLoader(false);
setIsAlert({
type: "danger",
message: t("user-name-exist")
});
setTimeout(() => {
setIsAlert({});
}, 3000);
setIsAlert({ type: "danger", message: t("user-name-exist") });
setTimeout(() => setIsAlert({}), 3000);
return res;
}
} catch (e) {
@@ -320,17 +330,80 @@ function UserProfile() {
setJobTitle(extendUser?.[0]?.JobTitle);
setIsDisableDocId(extendUser?.[0]?.HeaderDocId);
};
const copytoclipboard = () => {
copytoData(getPublicUrl);
setIsAlert({
type: "success",
message: t("copied")
});
setTimeout(() => {
setIsAlert({});
}, 3000);
const handlePaidRoute = () => {
navigate("/subscription");
};
const copytoclipboard = () => {
copytoData(getPublicUrl);
setIsAlert({ type: "success", message: t("copied") });
setTimeout(() => setIsAlert({}), 3000);
};
const tourConfig = [
{
content: () => (
<TourContentWithBtn
message={t("tour-mssg.public-template")}
isChecked={handleDontShow}
video="https://www.youtube.com/embed/_wB4UA7Jz5Q?si=I40CI3nVUWQzf42Y"
/>
),
position: "top",
style: { fontSize: "13px" }
}
];
//function for update TourStatus
const closeTour = async () => {
try {
setIsProfileTour(true);
if (isDontShow) {
let updatedTourStatus = [];
if (tourStatus.length > 0) {
updatedTourStatus = [...tourStatus];
const profileIndex = tourStatus.findIndex(
(obj) => obj["profileTour"] === false || obj["profileTour"] === true
);
if (profileIndex !== -1) {
updatedTourStatus[profileIndex] = { profileTour: true };
} else {
updatedTourStatus.push({ profileTour: true });
}
} else {
updatedTourStatus = [{ profileTour: true }];
}
await axios.put(
`${localStorage.getItem("baseUrl")}classes/contracts_Users/${
extendUser?.[0]?.objectId
}`,
{
TourStatus: updatedTourStatus
},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
}
}
);
const res = await Parse.Cloud.run("getUserDetails");
const json = JSON.parse(JSON.stringify([res]));
const extRes = JSON.stringify(json);
localStorage.setItem("Extand_Class", extRes);
}
} catch (err) {
console.log("axois err ", err);
alert(t("something-went-wrong-mssg"));
}
};
const handleDontShow = (isChecked) => {
setIsDontShow(isChecked);
};
const handleOnlickHelp = () => {
setIsProfileTour(false);
};
return (
<React.Fragment>
<Title title={"Profile"} />
@@ -340,12 +413,19 @@ function UserProfile() {
</div>
) : (
<div className="flex justify-center items-center w-full relative">
{isAlert && (
{isAlert.message && (
<Alert className="z-[1000] fixed top-[10%]" type={isAlert.type}>
{isAlert.message}
</Alert>
)}
<Tour
className="md:!min-w-[650px]"
onRequestClose={closeTour}
steps={tourConfig}
isOpen={!isProfileTour}
rounded={5}
closeWithMask={false}
/>
<div className="bg-base-100 text-base-content flex flex-col justify-center shadow-md rounded-box w-[450px]">
<div className="flex flex-col justify-center items-center my-4">
<div className="w-[200px] h-[200px] overflow-hidden rounded-full">
@@ -382,17 +462,6 @@ function UserProfile() {
</div>
</div>
<ul className="w-full flex flex-col p-2 text-sm">
<li
className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${
editmode ? "py-1.5" : "py-2"
}`}
>
<span className="font-semibold">{t("language")}:</span>{" "}
<SelectLanguage
isProfile={true}
updateExtUser={updateExtUser}
/>
</li>
<li
className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${
editmode ? "py-1.5" : "py-2"
@@ -403,7 +472,7 @@ function UserProfile() {
<input
type="text"
value={name}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-sm"
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
onChange={(e) => SetName(e.target.value)}
/>
) : (
@@ -419,7 +488,7 @@ function UserProfile() {
{editmode ? (
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-sm"
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
onChange={(e) => SetPhone(e.target.value)}
value={Phone}
/>
@@ -441,7 +510,7 @@ function UserProfile() {
<input
type="text"
value={company}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-sm"
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
onChange={(e) => setCompany(e.target.value)}
/>
) : (
@@ -458,7 +527,7 @@ function UserProfile() {
<input
type="text"
value={jobTitle}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-sm"
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
onChange={(e) => setJobTitle(e.target.value)}
/>
) : (
@@ -486,40 +555,30 @@ function UserProfile() {
</li>
{isEnableSubscription && (
<>
<li className="flex md:flex-row flex-col md:justify-between md:items-center border-t-[1px] border-gray-300 py-2 break-all">
<li className="flex flex-row justify-between md:items-center border-t-[1px] border-gray-300 py-2 break-all">
<span className="font-semibold flex gap-1">
{t("public-profile")} :{" "}
<Tooltip
maxWidth="max-w-[250px]"
message={t("public-profile-help")}
/>
<Tooltip handleOnlickHelp={handleOnlickHelp} />
</span>
<div className="flex md:flex-row flex-col md:items-center">
<div className="flex flex-row items-center gap-1">
{editmode || !extendUser?.[0]?.UserName ? (
<input
maxLength={40}
style={{
border:
!isSubscribe &&
publicUserName?.length > 0 &&
publicUserName?.length < 9 &&
"solid red"
}}
onChange={handleOnchangeUserName}
value={publicUserName}
disabled={!editmode}
placeholder="enter user name"
className="op-input op-input-bordered focus:outline-none hover:border-base-content op-input-xs"
/>
<>
<input
maxLength={40}
onChange={handleOnchangeUserName}
value={publicUserName}
disabled={!editmode}
placeholder="enter user name"
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
/>
</>
) : (
<div className="flex flex-row gap-1 items-center justify-between md:justify-start">
<span
rel="noreferrer"
target="_blank"
onClick={() => {
openInNewTab(getPublicUrl);
}}
className="cursor-pointer underline hover:text-blue-800 w-[200px] md:w-[150px] whitespace-nowrap overflow-hidden text-ellipsis"
onClick={() => openInNewTab(getPublicUrl)}
className="cursor-pointer underline hover:text-blue-800 w-[110px] md:w-[150px] whitespace-nowrap overflow-hidden text-ellipsis"
>
{isStaging
? `staging.opensign.me/${extendUser?.[0]?.UserName}`
@@ -527,10 +586,7 @@ function UserProfile() {
</span>
<div className="flex items-center gap-2">
<RWebShare
data={{
url: getPublicUrl,
title: "Sign url"
}}
data={{ url: getPublicUrl, title: "Sign url" }}
>
<button className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm ">
<i className="fa-light fa-share-from-square"></i>{" "}
@@ -547,30 +603,23 @@ function UserProfile() {
)}
</div>
</li>
<li className="flex md:flex-row flex-col md:justify-between md:items-center border-t-[1px] border-gray-300 py-2 break-all">
<li className="flex flex-row justify-between items-center border-t-[1px] border-gray-300 py-2 break-all">
<span className="font-semibold flex gap-1">
{t("tagline")} :{" "}
<Tooltip
maxWidth="max-w-[250px]"
message={t("tagline-help")}
/>
<Tooltip handleOnlickHelp={handleOnlickHelp} />
</span>
<div className="flex md:flex-row flex-col md:items-center">
{editmode ? (
<div className="flex flex-row md:items-center gap-1">
{editmode || !extendUser?.[0]?.Tagline ? (
<input
maxLength={40}
onChange={handleOnchangeTagLine}
value={tagLine}
disabled={!editmode}
placeholder="enter tagline"
className="op-input op-input-bordered focus:outline-none hover:border-base-content op-input-xs"
className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
/>
) : (
<input
value={extendUser?.[0]?.Tagline}
disabled
placeholder="enter tagline"
className="op-input op-input-bordered op-input-xs"
/>
<span>{extendUser?.[0]?.Tagline}</span>
)}
</div>
</li>
@@ -581,7 +630,9 @@ function UserProfile() {
<div className="flex justify-between items-center py-2">
<span
className={
isTeam ? "font-semibold" : "font-semibold text-gray-300"
validplan[isPlan.plan]
? "font-semibold"
: "font-semibold text-gray-300"
}
>
{t("disable-documentId")} :{" "}
@@ -590,17 +641,21 @@ function UserProfile() {
"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"
}
/>
{!isTeam && isEnableSubscription && <Upgrade />}
{!validplan[isPlan.plan] && isEnableSubscription && (
<Upgrade />
)}
</span>
<label
className={`${
isTeam
validplan[isPlan.plan]
? `${editmode ? "cursor-pointer" : ""}`
: "pointer-events-none opacity-50"
} relative block items-center mb-0`}
>
<input
disabled={isTeam ? false : true}
disabled={
validplan[isPlan.plan] && editmode ? false : true
}
type="checkbox"
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white"
checked={isDisableDocId}
@@ -610,6 +665,17 @@ function UserProfile() {
</div>
</li>
)}
<li
className={`flex justify-between items-center border-b-[1px] border-gray-300 break-all ${
editmode ? "py-1.5" : "py-2"
}`}
>
<span className="font-semibold">{t("language")}:</span>{" "}
<SelectLanguage
isProfile={true}
updateExtUser={updateExtUser}
/>
</li>
</ul>
<div
className={`${
@@ -620,6 +686,7 @@ function UserProfile() {
type="button"
onClick={(e) => {
if (
editmode &&
!isSubscribe &&
publicUserName?.length > 0 &&
publicUserName?.length < 9
@@ -689,6 +756,7 @@ function UserProfile() {
)}
</ModalUi>
)}
{isUpgrade && (
<div className="op-modal op-modal-open">
<div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative">
@@ -708,7 +776,7 @@ function UserProfile() {
<p>{t("user-name-limit-char")}</p>
<div className="op-card-actions justify-end">
<button
onClick={() => navigate("/subscription")}
onClick={() => handlePaidRoute()}
className="op-btn op-btn-accent"
>
{t("upgrade-now")}
+6 -6
View File
@@ -19,7 +19,7 @@ function Webhook() {
const [webhook, setWebhook] = useState();
const [isLoader, setIsLoader] = useState(true);
const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: true });
const [error, setError] = useState("");
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isTour, setIsTour] = useState(false);
@@ -35,8 +35,8 @@ function Webhook() {
];
const fetchWebhook = async () => {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe);
}
try {
const extRes = await Parse.Cloud.run("getUserDetails");
@@ -89,7 +89,7 @@ function Webhook() {
};
const handleModal = () => {
if (!isSubscribe && isEnableSubscription) {
if (!isSubscribe?.isValid && isEnableSubscription) {
setIsTour(true);
} else {
setIsModal(!isModal);
@@ -145,7 +145,7 @@ function Webhook() {
</div>
<ModalUi
isOpen={isModal}
title={"Regenerate Token"}
title={t("add-webhook")}
handleClose={handleModal}
>
{error && <Alert type="danger">{error}</Alert>}
@@ -177,7 +177,7 @@ function Webhook() {
</div>
</ModalUi>
</div>
{!isSubscribe && isEnableSubscription && (
{isEnableSubscription && !isSubscribe?.isValid && (
<div data-tut="webhooksubscribe">
<SubscribeCard />
</div>
+111 -95
View File
@@ -46,109 +46,125 @@ const AddContact = (props) => {
e.preventDefault();
e.stopPropagation();
setIsLoader(true);
try {
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");
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("password", email);
if (phone) {
_user.set("phone", phone);
}
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();
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");
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
props.details(parseData);
if (props.closePopup) {
props.closePopup();
if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
}
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", name);
_user.set("username", email);
_user.set("email", email);
_user.set("password", email);
if (phone) {
_user.set("phone", phone);
}
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
props.details(parseData);
if (props.closePopup) {
props.closePopup();
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: email };
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details(parseData);
}
if (props.closePopup) {
props.closePopup();
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
}
} else {
alert(t("add-signer-alert"));
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: email };
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
contactQuery.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
contactQuery.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
contactQuery.setACL(acl);
const res = await contactQuery.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.details) {
props.details(parseData);
}
if (props.closePopup) {
props.closePopup();
}
setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
}
// console.log("err", err);
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
}
} catch (err) {
// console.log("err", err);
} else {
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
}
+2 -2
View File
@@ -25,8 +25,8 @@ const Alert = ({ children, type, className }) => {
: "z-[1000] fixed top-20 left-1/2 transform -translate-x-1/2 text-sm"
} `}
>
<div className={`op-alert ${textcolor} flex items-center`}>
<span>{children}</span>
<div className={`op-alert ${textcolor}`}>
<span className="ml-3">{children}</span>
</div>
</div>
)
+334 -188
View File
@@ -11,7 +11,7 @@ import { RWebShare } from "react-web-share";
import Tour from "reactour";
import Parse from "parse";
import {
checkIsSubscribedTeam,
checkIsSubscribed,
copytoData,
fetchUrl,
replaceMailVaribles
@@ -27,6 +27,7 @@ import BulkSendUi from "../components/BulkSendUi";
import Loader from "./Loader";
import Select from "react-select";
import SubscribeCard from "./SubscribeCard";
import { validplan } from "../json/plansArr";
import { serverUrl_fn } from "../constant/appinfo";
import { useTranslation } from "react-i18next";
@@ -73,15 +74,7 @@ const ReportTable = (props) => {
const extClass = Extand_Class && JSON.parse(Extand_Class);
const startIndex = (currentPage - 1) * props.docPerPage;
const { isMoreDocs, setIsNextRecord } = props;
// For loop is used to calculate page numbers visible below table
// Initialize pageNumbers using useMemo to avoid unnecessary re-creation
// const pageNumbers = useMemo(() => {
// const calculatedPageNumbers = [];
// for (let i = 1; i <= Math.ceil(props.List.length / props.docPerPage); i++) {
// calculatedPageNumbers.push(i);
// }
// return calculatedPageNumbers;
// }, [props.List, props.docPerPage]);
const getPaginationRange = () => {
const totalPageNumbers = 7; // Adjust this value to show more/less page numbers
const pages = [];
@@ -355,10 +348,10 @@ const ReportTable = (props) => {
handleBulkSend(item);
} else if (act.action === "sharewith") {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribedTeam();
setIsSubscribe(getIsSubscribe);
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe);
} else {
setIsSubscribe(true);
setIsSubscribe({ plan: "teams-yearly", isValid: true });
}
if (item?.SharedWith && item?.SharedWith.length > 0) {
// below code is used to get existing sharewith teams and formated them as per react-select
@@ -369,6 +362,8 @@ const ReportTable = (props) => {
setSelectedTeam(formatedList);
}
setIsShareWith({ [item.objectId]: true });
} else if (act.action === "Embed") {
handleEmbedFunction(item);
}
};
// Get current list
@@ -377,8 +372,18 @@ const ReportTable = (props) => {
const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc);
// Change page
const paginateFront = () => setCurrentPage(currentPage + 1);
const paginateBack = () => setCurrentPage(currentPage - 1);
const paginateFront = () => {
const lastValue = pageNumbers?.[pageNumbers?.length - 1];
if (currentPage < lastValue) {
setCurrentPage(currentPage + 1);
}
};
const paginateBack = () => {
if (startIndex > 0) {
setCurrentPage(currentPage - 1);
}
};
const handleContactFormModal = () => {
setIsContactform(!isContactform);
@@ -396,7 +401,7 @@ const ReportTable = (props) => {
Templates: "contracts_Template"
};
try {
const serverUrl = serverUrl_fn()
const serverUrl = serverUrl_fn();
const cls = clsObj[props.ReportName] || "contracts_Document";
const url = serverUrl + `/classes/${cls}/`;
const body =
@@ -448,7 +453,6 @@ const ReportTable = (props) => {
}
setReason("");
};
const handleShare = (item) => {
setActLoader({ [item.objectId]: true });
const host = window.location.origin;
@@ -479,9 +483,21 @@ const ReportTable = (props) => {
};
//function to handle revoke/decline docment
const handleRevoke = async (item) => {
const senderUser = localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
setIsRevoke({});
setActLoader({ [`${item.objectId}`]: true });
const data = { IsDeclined: true, DeclineReason: reason };
const data = {
IsDeclined: true,
DeclineReason: reason,
DeclineBy: {
__type: "Pointer",
className: "_User",
objectId: jsonSender?.objectId
}
};
await axios
.put(
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${
@@ -790,13 +806,13 @@ const ReportTable = (props) => {
if (count > 1) {
setAlertMsg({
type: "success",
message: count + t("document-sent-alert")
message: count + " " + t("document-sent-alert")
});
setTimeout(() => setIsAlert(false), 1500);
} else {
setAlertMsg({
type: "success",
message: count + t("document-sent-alert")
message: count + " " + t("document-sent-alert")
});
setTimeout(() => setIsAlert(false), 1500);
}
@@ -838,14 +854,10 @@ const ReportTable = (props) => {
console.log("err in fetch template in bulk modal", err);
setIsBulkSend({});
setIsAlert(true);
setAlertMsg({
type: "danger",
message: t("something-went-wrong-mssg")
});
setAlertMsg({ type: "danger", message: t("something-went-wrong-mssg") });
setTimeout(() => setIsAlert(false), 1500);
}
};
//function to make template public and set public role
const handlePublicTemplate = async (item) => {
if (selectedPublicRole || !props.isPublic[item.objectId]) {
@@ -1005,9 +1017,26 @@ const ReportTable = (props) => {
type: "success",
message: t("copied")
});
setTimeout(() => setIsAlert(false), 1500);
setCopied({ ...copied, publicprofile: true });
setTimeout(() => {
setIsAlert(false);
setCopied(false);
}, 1500);
};
const copyTemplateId = (templateid) => {
copytoData(templateid);
setIsAlert(true);
setAlertMsg({
type: "success",
message: t("copied")
});
setCopied({ ...copied, templateid: true });
setTimeout(() => {
setIsAlert(false);
setCopied(false);
}, 1500);
};
const handleShowRole = (item) => {
const getRole = item.Placeholders.find((data) => !data.signerObjId);
return getRole?.Role;
@@ -1046,7 +1075,13 @@ const ReportTable = (props) => {
setTimeout(() => setIsAlert(false), 1500);
}
};
const handleEmbedFunction = async (item) => {
setIsPublicProfile({
[item.objectId]: props.isPublic[item.objectId]
});
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
setIsPublicUserName(extendUser[0]?.UserName || "");
};
return (
<div className="relative">
{Object.keys(actLoader)?.length > 0 && (
@@ -1221,7 +1256,7 @@ const ReportTable = (props) => {
{formatRow(item?.ExtUserPtr)}
</td>
<td className="px-4 py-2">
{item?.Placeholders ? (
{!item?.IsSignyourself && item?.Placeholders ? (
<button
onClick={() => handleViewSigners(item)}
className="op-link op-link-primary"
@@ -1317,24 +1352,84 @@ const ReportTable = (props) => {
<ModalUi
isOpen
title={t("public-url")}
handleClose={() => {
setIsPublicProfile({});
}}
handleClose={() => setIsPublicProfile({})}
>
<div className="m-[20px]">
{publicUserName ? (
<div className="font-normal text-black">
<p>{t("public-url-copy-mssg")}</p>
<div className=" flex items-center gap-3 mt-2 p-[2px] ">
<span className="font-bold">
{t("public-url")}:
</span>
<span
onClick={() => copytoProfileLink()}
className="underline underline-offset-2 w-[150px] md:w-[350px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer"
>
{publicUrl()}
</span>
<div className=" flex items-center justify-between gap-3 mt-2 p-[2px] ">
<div className="w-[280px] whitespace-nowrap overflow-hidden text-ellipsis">
<span className="font-bold">
{t("public-url")} :
</span>
<span
onClick={() =>
copytoProfileLink()
}
className="ml-[2px] underline underline-offset-2 cursor-pointer"
>
{publicUrl()}
</span>
</div>
<div className="flex items-center gap-2">
<RWebShare
data={{
url: publicUrl(),
title: "Public url"
}}
>
<button className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm ">
<i className="fa-light fa-share-from-square"></i>{" "}
<span className="hidden md:inline-block">
{t("btnLabel.Share")}
</span>
</button>
</RWebShare>
<button
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() =>
copytoProfileLink()
}
>
<i className="fa-light fa-link"></i>{" "}
<span className="hidden md:inline-block">
{copied["publicprofile"]
? t("copied")
: t("copy")}
</span>
</button>
</div>
</div>
<div className=" flex items-center justify-between gap-3 mt-2 p-[2px] ">
<div>
<span className="font-bold">
{t("templateid")} :
</span>
<span
onClick={() =>
copyTemplateId(item.objectId)
}
className="underline ml-[2px] underline-offset-2 w-[150px] md:w-[350px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer"
>
{item.objectId}
</span>
</div>
<div className="flex items-center gap-2">
<button
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() =>
copyTemplateId(item.objectId)
}
>
<i className="fa-light fa-link"></i>{" "}
<span className="hidden md:inline-block">
{copied["templateid"]
? t("copied")
: t("copy")}
</span>
</button>
</div>
</div>
</div>
) : (
@@ -1383,39 +1478,77 @@ const ReportTable = (props) => {
<i className={act.btnIcon}></i>
{act.btnLabel && (
<span className="uppercase font-medium">
{t(`btnLabel.${act.btnLabel}`)}
{act.btnLabel.includes(
"Quick send"
) && isEnableSubscription
? "Bulk Send"
: `${t(
`btnLabel.${act.btnLabel}`
)}`}
</span>
)}
{isOption[item.objectId] &&
act.action === "option" && (
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow bg-base-100 text-base-content rounded-box">
{act.subaction?.map((subact) => (
<li
key={subact.btnId}
onClick={() =>
handleActionBtn(
subact,
item
)
}
title={t(
`btnLabel.${subact.btnLabel}`
)}
>
<span>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
{act.subaction?.map((subact) =>
props.isPublic[
item.objectId
] ? (
<li
key={subact.btnId}
onClick={() =>
handleActionBtn(
subact,
item
)
}
title={t(
`btnLabel.${subact.btnLabel}`
)}
>
<span>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
)}
</span>
</li>
) : (
subact.action !== "Embed" && (
<li
key={subact.btnId}
onClick={() =>
handleActionBtn(
subact,
item
)
}
title={t(
`btnLabel.${subact.btnLabel}`
)}
>
<span>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
)}
</span>
)}
</span>
</li>
))}
</li>
)
)
)}
</ul>
)}
</div>
@@ -1478,114 +1611,117 @@ const ReportTable = (props) => {
{isShareWith[item.objectId] && (
<div className="op-modal op-modal-open">
<div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative">
{isSubscribe && isEnableSubscription && (
<>
{item?.Signers?.length > 0 ? (
<div className="h-[150px] flex justify-center items-center mx-2">
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
onClick={() => setIsShareWith({})}
>
{validplan[isSubscribe.plan] &&
isEnableSubscription && (
<>
{item?.Signers?.length > 0 ? (
<div className="h-[150px] flex justify-center items-center mx-2">
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
onClick={() => setIsShareWith({})}
>
</div>
<div className="text-base-content text-base text-center">
{t("share-with-alert")}
</div>
</div>
<div className="text-base-content text-base text-center">
{t("share-with-alert")}
</div>
</div>
) : (
<>
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{t("share-with")}
</h3>
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
onClick={() => setIsShareWith({})}
>
</div>
<form
className="h-full w-full z-[1300] px-2 mt-3"
onSubmit={(e) =>
handleShareWith(e, item)
}
>
<Select
// onSortEnd={onSortEnd}
distance={4}
isMulti
options={teamList}
value={selectedTeam}
onChange={onChange}
closeMenuOnSelect
required={true}
noOptionsMessage={() =>
t("team-not-found")
) : (
<>
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{t("share-with")}
</h3>
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
onClick={() => setIsShareWith({})}
>
</div>
<form
className="h-full w-full z-[1300] px-2 mt-3"
onSubmit={(e) =>
handleShareWith(e, item)
}
unstyled
classNames={{
control: () =>
"op-input op-input-bordered op-input-sm border-gray-400 focus:outline-none hover:border-base-content w-full h-full text-[11px]",
valueContainer: () =>
"flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]",
multiValue: () =>
"op-badge op-badge-primary h-full text-[11px]",
multiValueLabel: () => "mb-[2px]",
menu: () =>
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content",
menuList: () =>
"shadow-md rounded-lg overflow-hidden",
option: () =>
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
noOptionsMessage: () =>
"p-2 bg-base-200 rounded-lg m-1 p-2"
}}
/>
<button className="op-btn op-btn-primary ml-[10px] my-3">
{t("submit")}
</button>
</form>
</>
)}
</>
)}
{!isSubscribe && isEnableSubscription && (
<>
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-primary-content absolute right-2 top-2 z-40"
onClick={() => setIsShareWith({})}
>
</div>
<SubscribeCard
plan={"TEAMS"}
price={"20"}
/>
</>
)}
{isSubscribe && !isEnableSubscription && (
<>
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{t("share-with")}
</h3>
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
onClick={() => setIsShareWith({})}
>
</div>
<div className="px-2 mt-3 w-full h-full">
<div className="op-input op-input-bordered op-input-sm w-full h-full text-[13px] break-all">
{selectedTeam?.[0]?.label}
>
<Select
// onSortEnd={onSortEnd}
distance={4}
isMulti
options={teamList}
value={selectedTeam}
onChange={onChange}
closeMenuOnSelect
required={true}
noOptionsMessage={() =>
t("team-not-found")
}
unstyled
classNames={{
control: () =>
"op-input op-input-bordered op-input-sm border-gray-400 focus:outline-none hover:border-base-content w-full h-full text-[11px]",
valueContainer: () =>
"flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]",
multiValue: () =>
"op-badge op-badge-primary h-full text-[11px]",
multiValueLabel: () =>
"mb-[2px]",
menu: () =>
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content",
menuList: () =>
"shadow-md rounded-lg overflow-hidden",
option: () =>
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
noOptionsMessage: () =>
"p-2 bg-base-200 rounded-lg m-1 p-2"
}}
/>
<button className="op-btn op-btn-primary ml-[10px] my-3">
{t("submit")}
</button>
</form>
</>
)}
</>
)}
{isEnableSubscription &&
!validplan[isSubscribe.plan] && (
<>
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-primary-content absolute right-2 top-2 z-40"
onClick={() => setIsShareWith({})}
>
</div>
</div>
<button
onClick={(e) => handleShareWith(e, item)}
className="op-btn op-btn-primary ml-[10px] my-3"
>
{t("submit")}
</button>
</>
)}
<SubscribeCard plan="TEAMS" />
</>
)}
{!isEnableSubscription &&
validplan[isSubscribe.plan] && (
<>
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{t("share-with")}
</h3>
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
onClick={() => setIsShareWith({})}
>
</div>
<div className="px-2 mt-3 w-full h-full">
<div className="op-input op-input-bordered op-input-sm w-full h-full text-[13px] break-all">
{selectedTeam?.[0]?.label}
</div>
</div>
<button
onClick={(e) =>
handleShareWith(e, item)
}
className="op-btn op-btn-primary ml-[10px] my-3"
>
{t("submit")}
</button>
</>
)}
</div>
</div>
)}
@@ -1623,20 +1759,26 @@ const ReportTable = (props) => {
</tr>
</thead>
<tbody>
{item.Placeholders.map((x, i) => (
<tr key={i} className="text-sm font-medium">
{props.ReportName === "Templates" && (
<td className="text-[12px] p-2 pl-3 w-[30%]">
{x.Role && x.Role}
</td>
)}
<td className="pl-3 text-[12px] py-2 break-all">
{x.email
? x.email
: x?.signerPtr?.Email || "-"}
</td>
</tr>
))}
{item.Placeholders.map(
(x, i) =>
x.Role !== "prefill" && (
<tr
key={i}
className="text-sm font-medium"
>
{props.ReportName === "Templates" && (
<td className="text-[12px] p-2 pl-3 w-[30%]">
{x.Role && x.Role}
</td>
)}
<td className="pl-3 text-[12px] py-2 break-all">
{x.email
? x.email
: x?.signerPtr?.Email || "-"}
</td>
</tr>
)
)}
</tbody>
</table>
</ModalUi>
@@ -1672,11 +1814,15 @@ const ReportTable = (props) => {
{isBulkSend[item.objectId] && (
<ModalUi
isOpen
title={t("quick-send")}
title={
isEnableSubscription
? "Bulk send"
: t("quick-send")
}
handleClose={() => setIsBulkSend({})}
>
{isLoader[item.objectId] ? (
<div className="w-full h-[100px] flex justify-center items-center bg-opacity-30 z-30">
<div className="w-full h-[100px] flex justify-center items-center z-30">
<Loader />
</div>
) : (
+5 -3
View File
@@ -21,9 +21,11 @@ const ModalUi = ({
>
{showHeader && (
<>
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{title}
</h3>
{title && (
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{title}
</h3>
)}
{showClose && (
<button
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2"
+17 -15
View File
@@ -2,27 +2,30 @@ import React, { useState } from "react";
import "../styles/signature.css";
import { useTranslation } from "react-i18next";
function CustomModal({
show,
headMsg,
bodyMssg,
footerMessage,
declineDoc,
setIsDecline
}) {
function CustomModal(props) {
const { t } = useTranslation();
const [reason, setReason] = useState("");
return (
show && (
props.show && (
<dialog className="op-modal op-modal-open absolute z-[448]">
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box p-0 overflow-y-auto hide-scrollbar text-sm">
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{headMsg && headMsg}
{props?.headMsg && props?.headMsg}
</h3>
<div className="p-[10px] px-[20px]">
<p className="text-[15px]">{bodyMssg && bodyMssg}</p>
<p className="text-[15px]">{props.bodyMssg && props.bodyMssg}</p>
</div>
{footerMessage && (
{props.isDownloadBtn && (
<div className="flex justify-start w-full ml-[20px] mb-3 mt-1">
<button
className="op-btn op-btn-primary "
onClick={() => props.handleDownloadBtn()}
>
Download
</button>
</div>
)}
{props.footerMessage && (
<>
<div className="mx-3">
<textarea
@@ -33,14 +36,13 @@ function CustomModal({
onChange={(e) => setReason(e.target.value)}
></textarea>
</div>
{/* <div className="h-[1px] bg-[#9f9f9f] w-full"></div> */}
<div className="m-[15px]">
<button
className="op-btn op-btn-primary mr-2 px-6"
type="button"
onClick={() => {
setReason("");
declineDoc(reason);
props.declineDoc(reason);
}}
>
{t("yes")}
@@ -50,7 +52,7 @@ function CustomModal({
className="op-btn op-btn-secondary"
onClick={() => {
setReason("");
setIsDecline({ isDeclined: false });
props.setIsDecline({ isDeclined: false });
}}
>
{t("close")}
+57
View File
@@ -0,0 +1,57 @@
import React from "react";
import { Trans, useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
const QuotaCard = ({ isPaidInfo, handlClose }) => {
const { t } = useTranslation();
const navigate = useNavigate();
return isPaidInfo ? (
<>
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-primary-content absolute right-2 top-2 z-40"
onClick={() => handlClose && handlClose()}
>
</div>
<div className="op-card op-bg-primary text-primary-content w-full shadow-lg">
<div className="op-card-body">
<h2 className="op-card-title">{t("quota-mail-info-head")}</h2>
<p>{t("quota-mail-info")}</p>
<p>
<Trans i18nKey={"quota-mail-tip"}>
Tip: You can still sign <strong>unlimited documents</strong> by
manually sharing the signing request links.
</Trans>
</p>
<div className="op-card-actions justify-end">
<button
onClick={() => navigate("/subscription")}
className="op-btn op-btn-accent"
>
{t("upgrade-now")}
</button>
</div>
</div>
</div>
</>
) : (
<>
<div className="op-card op-bg-primary text-primary-content w-full shadow-lg">
<div className="op-card-body">
<p>{t("quota-mail")}</p>
<p>{t("quota-mail-tip")}</p>
<div className="op-card-actions justify-end">
<button
onClick={() => navigate("/subscription")}
className="op-btn op-btn-accent"
>
{t("upgrade-now")}
</button>
</div>
</div>
</div>
</>
);
};
export default QuotaCard;
@@ -5,18 +5,24 @@ import { useNavigate } from "react-router-dom";
const SubscribeCard = ({ plan, price }) => {
const { t } = useTranslation();
const navigate = useNavigate();
const handlePaidRoute = () => navigate("/subscription");
const premiumPrice = price ? "$" + price : "$9.99";
const addonPrice = "$0.15";
const plandName = plan ? plan.toLowerCase() : "Paid";
return (
<div className="op-card op-bg-primary text-primary-content w-full shadow-lg">
<div className="op-card-body">
<h2 className="op-card-title">
{t("upgrade-to")} {plan ? plan : "PRO"} {t("plan")}
{t("upgrade-to")} {plan ? plan : "Paid"} {t("plan")}
</h2>
<p>
${price ? price : "29.99"}/month: {t("subscribe-card-plan")}
{plandName?.includes("team")
? t("subscribe-card-teamplan")
: t("subscribe-card-plan", { premiumPrice, addonPrice })}
</p>
<div className="op-card-actions justify-end">
<button
onClick={() => navigate("/subscription")}
onClick={() => handlePaidRoute()}
className="op-btn op-btn-accent"
>
{t("upgrade-now")}
+15 -3
View File
@@ -1,9 +1,21 @@
import React from "react";
import { Tooltip as ReactTooltip } from "react-tooltip";
import { openInNewTab } from "../constant/Utils";
const Tooltip = ({ id, message, url, iconColor, maxWidth }) =>
url ? (
<button onClick={() => openInNewTab(url)} className={"text-center"}>
const Tooltip = ({
id,
message,
url,
iconColor,
maxWidth,
handleOnlickHelp
}) =>
url || handleOnlickHelp ? (
<button
onClick={() =>
handleOnlickHelp ? handleOnlickHelp() : openInNewTab(url)
}
className={"text-center cursor-pointer"}
>
<sup>
<i
className="fa-light fa-question rounded-full border-[1px] py-[1.5px] px-[4px] text-[13px]"
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
export default function TourContentWithBtn({ message, isChecked }) {
export default function TourContentWithBtn({ message, isChecked, video }) {
const { t } = useTranslation();
const [isCheck, setIsCheck] = useState(false);
@@ -13,6 +13,17 @@ export default function TourContentWithBtn({ message, isChecked }) {
return (
<div>
<p>{message}</p>
{video && (
<div className="flex items-center justify-center w-full md:h-[300px] my-[10px] border-[1.3px] border-gray-400 rounded-sm">
<iframe
className="w-full h-full"
src={video}
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowFullScreen
></iframe>
</div>
)}
<label className="flex items-center justify-center mb-0">
<input
type="checkbox"
+2 -2
View File
@@ -37,9 +37,9 @@ const Validate = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleLoginBtn = () => {
const handleLoginBtn = async () => {
try {
Parse.User.logOut();
await Parse.User.logOut();
} catch (err) {
console.log("err ", err);
} finally {
-12
View File
@@ -1,12 +0,0 @@
import { combineReducers } from "redux";
import infoReducer from "./infoReducer";
import ShowTenant from "./ShowTenant";
import TourStepsReducer from "./TourStepsReducer";
import showHeader from "./showHeader";
export default combineReducers({
appInfo: infoReducer,
TourSteps: TourStepsReducer,
ShowTenant,
showHeader
});
+1 -1
View File
@@ -56,7 +56,7 @@ document.head.appendChild(tailwindCssLink);
// Create a root and render the wrapper component with initial props
const root = ReactDOM.createRoot(document.getElementById("script-component"));
root.render(
<div>
<div data-theme="opensigncss">
<Provider store={store}>
<PublicScriptFileWrapper initialProps={{ text: "templateId" }} />
</Provider>
@@ -1,9 +1,12 @@
{
"header-news": "New feature: Public profiles are now live! Easily create and share public templates",
"header-news-btn": "Setup Now",
"create-account": "Create Account",
"login": "Login",
"language": "Language",
"name": "Name",
"phone": "Phone",
"phone-optional": "optional",
"email": "Email",
"company": "Company",
"job-title": "Job title",
@@ -38,7 +41,8 @@
"upgrade-now": "Upgrade now",
"upgrade-to": "Upgrade to",
"plan": "Plan",
"subscribe-card-plan": "Sign 100 docs using API. Only $0.15/doc after that.",
"subscribe-card-teamplan":"Unlock the full power of collaboration! Create unlimited organizations, teams, and hierarchies. Share templates seamlessly across teams and assign custom user roles. Elevate your workflow today!",
"subscribe-card-plan": "Unlock premium features starting at just {{premiumPrice}}/month. Enjoy enhanced performance and only {{addonPrice}} per additional credit after your included premium credits.",
"user-name-limit-char": "To have a username less than 8 character please subscribe",
"tour-content": "Don't show this again",
"pro": "PRO",
@@ -91,6 +95,7 @@
"API Token": "API Token",
"Webhook": "Webhook",
"Teams": "Teams",
"Team-update-successfully": "Team Updated successfully.",
"Users": "Users"
}
},
@@ -125,7 +130,8 @@
"Share with team": "Share with team",
"Share": "Share",
"View": "View",
"option": "Option"
"option": "Option",
"Embed": "Embed"
},
"report-heading": {
"Sr.No": "Sr.No",
@@ -212,8 +218,8 @@
"initials": "Initials",
"API": "API",
"api-token": "API Token",
"regenerate-token": "Regenerate Token",
"generate-token": "Generate Token",
"regenerate-token": "Regenerate Live Token",
"generate-token": "Generate Live Token",
"view-docs": "View Docs",
"generate-token-alert": "Are you sure you want to regenerate token it will expire old token?",
"yes": "Yes",
@@ -308,7 +314,7 @@
"contact-details": "Contact Details",
"verify-email": "Please verify your email !",
"send-otp": " Send OTP",
"otp-placeholder": "Enter OTP received over email",
"otp-placeholder": "Enter verification code received over email",
"loading-doc": "Loading Document..",
"widgets-name": {
"signature": "signature",
@@ -358,7 +364,7 @@
"initial-teb": "Initials",
"signature-tab": "Signature",
"your-signature": "Your signature",
"copy-to-all-pages": "Copy to all pages",
"copy-title": "Copy widget to",
"contact-delete-alert": "Are you sure you want to delete this contact?",
"reset-password-alert-1": "Reset password link has been sent to your email id",
"reset-password-alert-2": "Please setup email adapter ",
@@ -368,7 +374,8 @@
"copy-type": {
"All pages": "All pages",
"All pages but last": "All pages but last",
"All pages but first": "All pages but first"
"All pages but first": "All pages but first",
"Next to current widget": "Next to current widget"
},
"options": "Options",
"minimun-check": "Minimun check",
@@ -445,7 +452,7 @@
"decline-alert-3": "You can not sign this document as it has been declined/revoked.",
"sign here": "Sign here",
"guest-email-alert": "verification code is sent to your email",
"get-otp-alert": "you will get a OTP via Email",
"get-otp-alert": "you will get a verification code via Email",
"user-already-exist": "User already exist.",
"server-error": "Internal server error !",
"fill-required-details!": "Please fill required details!",
@@ -468,7 +475,7 @@
"role-not-found": "Role not found.",
"do-not-access": "You don't have access, please contact the admin.",
"add-admin": "Add admin",
"opensign-setup": "Opensign Setup",
"opensign-setup": "OpenSign Setup",
"join-discord": "Join our discord server",
"admin-already-exist": "Admin already exist.",
"password-update-alert-1": "Password updated successfully.",
@@ -524,7 +531,7 @@
"filed-required-correctly": "Please fill required details correctly.",
"admin-created": "Admin created",
"invalid-masterkey": "Invalid masterkey",
"opensign-Setup": "Opensign Setup",
"opensign-Setup": "OpenSign Setup",
"master-key": " Master key",
"profile-update-alert": "Profile updated successfully.",
"date": "Date",
@@ -550,7 +557,7 @@
"home-layout-1": "You have logged in successfully! Let's take a look.",
"home-layout-2": "To upload documents for self-signing or to request others signatures, simply select the respective buttons.",
"home-layout-3": "You are ready to start using OpenSign! If you need support feel free to contact us.",
"generate-token": "Upgrade now to generate API token",
"generate-token": "Upgrade now to generate production API token.",
"opensign-drive-1": "Click on the breadcrumb links to easily navigate through the folder hierarchy and view the documents within each folder.",
"opensign-drive-2": "Click the add button to create a new folder or document.",
"opensign-drive-3": "Sort your documents by Date or Name using this menu.",
@@ -584,7 +591,8 @@
"Out for signatures": "Clicking on this card will take you to a list of documents awaiting signature.",
"Recent signature requests": "This is a list of documents that are waiting for your signature.",
"Recently sent for signatures": "This is a list of documents you've sent to other parties for signature.",
"Drafts": "This are documents you have started but have not finalized for sending."
"Drafts": "This are documents you have started but have not finalized for sending.",
"public-template": "This video demonstrates how to set up your personalized public profile, such as https://opensign.me/your-username. Youll also learn how to customize your tagline and make your templates available for public signing."
},
"enter-email-plaholder": "Add an email address and hit enter",
"success-email-alert": "Email sent successfully!",
@@ -592,5 +600,41 @@
"expired-on-mssg": "This document expired on {{expiredDate}} and is no longer available to sign.",
"signature-validate-alert": "Please confirm that you have selected at least {{minRequiredCount}} checkboxes.",
"signature-validate-alert-2": "Ensure this field is accurately filled and meets all requirements.",
"user-name-exist": "user name already exist"
"remaing-users": "Remaining users",
"Role": "Role",
"additional-users": "Please purchase add-on users.",
"Quantity-of-users": "Quantity of users",
"Price": "Price",
"Total-price-for-next-time": "Total price for next time",
"Proceed": "Proceed",
"Select": "Select",
"Add-seats": "Add Seats",
"user-name-exist": "user name already exist",
"format": "format",
"select-date-format": "Select a date format",
"quantity-of-credits": "Quantity of premium credits",
"remaining-credits": "Premium credits available:",
"remaining-credits-help":"Use premium credits for API document signing, bulk sending, or embedding OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
"additional-credits": "Please purchase premium credits",
"quota-err-quicksend": "Quota Reached, You don't have enough credits.",
"buy-credits": "Buy Premium Credits",
"rotate-right":"Rotate right",
"rotate-left":"Rotate left",
"rotate-alert-mssg":"All widgets on this page will be lost. Are you sure you want to proceed?",
"templateid":"Template-Id",
"bulk-send-subcription-alert":"Please upgrade to Professional or Team plan to use bulk send.",
"generate-test-token":"Generate Test Token",
"regenerate-test-token":"Regenerate Test Token",
"help-test-token":"This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once youve completed your testing, you can upgrade to one of our paid plans to generate a production token.",
"help-api-token":"This token can be used to access the production APIs at the {{origin}}/api/v1 endpoint. It can only be generated on one of our paid plans.",
"quota-mail-info-head":"Monthly request signatures email limit",
"quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.",
"quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.",
"quota-mail-tip":"Tip: You can still sign <1>unlimited documents</1> by manually sharing the signing request links.",
"quota-mail-head":"Quota Reached",
"unauthorized-modal":"You don't have permission to perform this action, please contact {{adminEmail}}.",
"sent-this-month":"Sent this month",
"available-seats":"Available seats",
"buy-users":"Buy more users"
}
@@ -1,9 +1,12 @@
{
"header-news": "Nouvelle fonctionnalité : les profils publics sont désormais en ligne ! Créez et partagez facilement des modèles publics",
"header-news-btn": "Configurer maintenant",
"create-account": "Créer un compte",
"login": "Se Connecter",
"language": "Langue",
"name": "Nom et Prénom",
"phone": "Téléphone",
"phone-optional": "(facultatif)",
"email": "E-mail",
"company": "Organisation",
"job-title": "Votre Fonction",
@@ -39,10 +42,11 @@
"upgrade-to": "Mettre à niveau vers",
"pro": "PRO",
"plan": "Offre",
"subscribe-card-plan": "Signez 100 documents à l'aide de l'API. Seulement 0,15 $/doc par la suite.",
"subscribe-card-teamplan":"Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !",
"subscribe-card-plan": "Débloquez des fonctionnalités premium à partir de seulement {{premiumPrice}}/mois. Bénéficiez de performances améliorées et de seulement {{addonPrice}} par crédit supplémentaire après vos crédits premium inclus.",
"user-name-limit-char": "Pour avoir un nom d'utilisateur de moins de 8 caractères s'il vous plaît s'abonner",
"tour-content": "Ne plus afficher",
"team": QUIPE",
"team": quipe",
"docs": "Documents",
"session-expired": "Votre session a expiré.",
"user": "Utilisateur",
@@ -91,6 +95,7 @@
"API Token": "Jeton API",
"Webhook": "Webhook",
"Teams": "Équipe",
"Team-update-successfully": "Équipe mise à jour avec succès.",
"Users": "Utilisateurs"
}
},
@@ -144,7 +149,8 @@
"Share with team": "Partager avec l'équipe",
"Share": "Partager",
"View": "Voir",
"option": "Option"
"option": "Option",
"Embed": "Intégrer"
},
"report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -197,7 +203,7 @@
"parent-folder": "Dossier Parent",
"create": "Créer",
"signers": "Signataires",
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
"add-signer": "Ajouter un signataire",
"contact-not-found": "Contact introuvable",
"add-yourself": "Ajoutez-vous",
@@ -211,8 +217,8 @@
"initials": "Initiales",
"API": "API",
"api-token": "Jeton API",
"regenerate-token": "Régénérer le jeton",
"generate-token": "Générer un jeton",
"regenerate-token": "Régénérer en direct jeton",
"generate-token": "Générer en direct jeton",
"view-docs": "Afficher les documents",
"generate-token-alert": "Êtes-vous sûr de vouloir régénérer le jeton, votre ancien jeton sera supprimer?",
"yes": "Oui",
@@ -252,11 +258,11 @@
"view": "voir",
"make-template-public": "Rendre le modèle public",
"make-template-private": "Rendre le modèle privé",
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
"public-role": "Rôle public",
"public-url": "URL publique",
"public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
"public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
"add-public-url-alert": "Veuillez ajouter votre URL publique et vous pourra rendre public modèle.",
"share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
"share-with": "Partager avec",
@@ -274,14 +280,14 @@
"copy-link": "Copier le lien",
"copy": "Copier",
"revoke-document": "Révoquer le document",
"revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?",
"revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?",
"resend-mail": "Renvoyer le courrier",
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"subject": "Sujet",
"body": "Corps",
"add-contact": "Ajouter le contact",
"add-signer-alert": "Le contact existe déjà !",
"record-delete-alert": "Enregistrement supprimé avec succès !",
"record-delete-alert": "Enregistrement supprimé avec succès !",
"record-revoke-alert": " Enregistrement révoqué avec succès ! ",
"mail-sent-alert": "Courriel envoyé avec succès.",
"document-sent-alert": "Document envoyé avec succès !",
@@ -307,7 +313,7 @@
"contact-details": "Coordonnées",
"verify-email": "Veuillez vérifier votre email!",
"send-otp": "envoyer un code à usage unique",
"otp-placeholder": "Saisissez l'OTP reçu par e-mail",
"otp-placeholder": "Entrez le code de vérification reçu par e-mail",
"loading-doc": "Chargement du document..",
"widgets-name": {
"signature": "signature",
@@ -337,7 +343,7 @@
"finish": "terminé",
"mail": "Mail",
"sign-now": "Signez maintenant",
"successfully-signed": "Signé avec succès !",
"successfully-signed": "Signé avec succès !",
"email-mssg": "Les destinataires ajoutés ici recevront une copie du document signé.",
"email-error-1": "veuillez fournir une adresse e-mail correcte",
"email-error-2": "Vous ne pouvez envoyer qu'à dix destinataires à la fois.",
@@ -357,8 +363,8 @@
"initial-teb": "Initiales",
"signature-tab": "Signature",
"your-signature": "Votre signature",
"copy-to-all-pages": "Copier sur toutes les pages",
"contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?",
"copy-title": "Copier le widget vers",
"contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?",
"reset-password": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie",
"reset-password-alert-2": "Veuillez configurer l'adaptateur de messagerie",
"reset-password-alert-3": "Réinitialisez votre mot de passe",
@@ -367,7 +373,8 @@
"copy-type": {
"All pages": "Toutes les pages",
"All pages but last": "Toutes les pages sauf la dernière",
"All pages but first": "Toutes les pages sauf la première"
"All pages but first": "Toutes les pages sauf la première",
"Next to current widget": "À côté du widget actuel"
},
"options": "Possibilités",
"minimun-check": "Vérification minimale",
@@ -393,7 +400,7 @@
"create-document": "Créer un document",
"template-created-alert": "Voulez-vous créer un document en utilisant le modèle que vous vient de créer ?",
"signers-alert": "alerte des signataires",
"template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
"template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
"ok": "D'accord",
"radio-group": "Groupe de radio",
"dropdown-options": "Options déroulantes",
@@ -409,9 +416,9 @@
"hint": "Indice",
"color": "couleur",
"role-help": {
"p1": "Que sont les rôles modèles ?",
"p1": "Que sont les rôles modèles ?",
"p2": "Commencez par spécifier chaque rôle nécessaire à la finalisation du document. Pensez aux parties impliquées dans le processus de signature et à leurs responsabilités. Les rôles courants incluent les RH pour les documents internes, le Client pour les accords ou le Fournisseur pour les accords commerciaux.",
"p3": "Pourquoi pré-attacher les utilisateurs à certains rôles ?",
"p3": "Pourquoi pré-attacher les utilisateurs à certains rôles ?",
"p4": "Pour les rôles qui impliquent systématiquement la même personne (par exemple, la signature du PDG sur les lettres d'offre des employés), vous pouvez pré-attacher un utilisateur à un rôle dans le modèle. Cette étape est facultative mais recommandée pour des raisons d'efficacité et de cohérence entre les documents.",
"p5": "Quand dois-je spécifier l'utilisateur attaché à chaque rôle ?",
"p6": "Lorsque vous créez un document à partir de votre modèle, vous serez invité à attacher des utilisateurs à chaque rôle défini. Si un rôle est déjà associé à un utilisateur, celui-ci sera pré-rempli, mais vous pourrez le modifier si nécessaire avant d'envoyer le document."
@@ -424,8 +431,8 @@
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
"email-subject": "Sujet du courriel",
"email-body": "corps de l'e-mail",
"email-placeholder": "Ajouter le corps de l'e-mail",
@@ -444,14 +451,14 @@
"decline-alert-3": "Vous ne pouvez pas signer ce document car il a été refusé/révoqué.",
"sign here": "Signer ici",
"guest-email-alert": "le code de vérification a été envoyé sur votre e-mail",
"get-otp-alert": "vous recevrez un mot de passe à usage unique par e-mail",
"get-otp-alert": "vous recevrez un code de vérification par e-mail",
"user-already-exist": "L'utilisateur existe déjà.",
"server-error": "Erreur interne du serveur !",
"fill-required-details!": "Veuillez remplir les informations requises !",
"fill-required-details!": "Veuillez remplir les informations requises !",
"sign-up-form": "Formulaire d'inscription",
"sign-up": "S'inscrire",
"folder-already-exist!": "le dossier existe déjà !",
"please-select-position!": "Veuillez sélectionner un poste !",
"please-select-position!": "Veuillez sélectionner un poste !",
"default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les emplacements demandés ? ",
"Your-Signature": "Votre signature",
"auto-sign-all": "Tout signer automatiquement",
@@ -459,15 +466,15 @@
"pdf-download": "Téléchargement PDF",
"generate-certificate-alert": " Votre certificat d'achèvement est en cours de génération. Veuillez patienter quelques instants. Si le téléchargement ne démarre pas sous peu, cliquez à nouveau sur le bouton. ",
"folder-already-exist": "Le dossier existe déjà !",
"folder-created-successfully": "Dossier créé avec succès !",
"folder-created-successfully": "Dossier créé avec succès !",
"fill-folder-name": "Veuillez indiquer le nom du dossier",
"already-exists-this-username": "Un utilisateur existe déjà avec ce nom d'utilisateur !",
"verification-code-sent": "Un e-mail de vérification a été envoyé à votre adresse e-mail !",
"already-exists-this-username": "Un utilisateur existe déjà avec ce nom d'utilisateur !",
"verification-code-sent": "Un e-mail de vérification a été envoyé à votre adresse e-mail !",
"registered-user-successfully": "Utilisateur enregistré avec succès.",
"role-not-found": "Rôle introuvable.",
"do-not-access": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
"add-admin": "Ajouter un administrateur",
"opensign-setup": "Configuration d'Opensign",
"opensign-setup": "Configuration d'OpenSign",
"join-discord": "Rejoignez notre serveur Discord",
"admin-already-exist": "L'administrateur existe déjà.",
"password-update-alert-1": "Mot de passe mis à jour avec succès.",
@@ -511,19 +518,19 @@
"choose-password": "Choisissez un mot de passe",
"enter-password": "Entrer le mot de passe",
"sure": "bien sûr",
"subscribed-alert": "Vous êtes déjà abonné au forfait !",
"subscribed-alert": "Vous êtes déjà abonné au forfait !",
"monthly": "Mensuel",
"yearly-upto": "Annuel (jusqu'à 66 % de réduction)",
"yearly-upto": "Annuel (jusqu'à 66 % de réduction)",
"billed-yearly": "Facturé annuellement",
"billed-monthly": "Facturé mensuellement",
"plansubscription-1": "Hébergez-le vous-même gratuitement",
"visit-github": "Visitez Github",
"user-already-exist-name": "Un utilisateur existe déjà avec ce nom d'utilisateur !",
"user-already-exist-name": "Un utilisateur existe déjà avec ce nom d'utilisateur !",
"do-not-access-contact-admin": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
"filed-required-correctly": "Veuillez remplir correctement les informations requises.",
"admin-created": "Administrateur créé",
"invalid-masterkey": "Clé principale invalide",
"opensign-Setup": "Configuration d'Opensign",
"opensign-Setup": "Configuration d'OpenSign",
"master-key": "La clef maitresse",
"profile-update-alert": "Mise à jour du profil réussie.",
"date": "Date",
@@ -546,10 +553,10 @@
"discord": "Discorde"
},
"tour-mssg": {
"home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.",
"home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.",
"home-layout-2": "Pour télécharger des documents à auto-signature ou pour demander la signature d'autres personnes, sélectionnez simplement les boutons correspondants.",
"home-layout-3": "Vous êtes prêt à commencer à utiliser OpenSign ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
"generate-token": "Mettez à niveau maintenant pour générer un jeton API",
"home-layout-3": "Vous êtes prêt à commencer à utiliser OpenSign ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
"generate-token": "Effectuez la mise à niveau maintenant pour générer un jeton API de production.",
"opensign-drive-1": "Cliquez sur les liens du fil d'Ariane pour naviguer facilement dans la hiérarchie des dossiers et afficher les documents dans chaque dossier.",
"opensign-drive-2": "Cliquez sur le bouton Ajouter pour créer un nouveau dossier ou document.",
"opensign-drive-3": "Triez vos documents par date ou par nom à l'aide de ce menu.",
@@ -583,7 +590,8 @@
"Out for signatures": "En cliquant sur cette carte, vous accéderez à une liste de documents en attente de signature.",
"Recent signature requests": "Voici une liste de documents qui attendent votre signature.",
"Recently sent for signatures": "Il s'agit d'une liste de documents que vous avez envoyés à d'autres parties pour signature.",
"Drafts": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi."
"Drafts": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
"public-template": "Cette vidéo montre comment configurer votre profil public personnalisé, tel que 'https://opensign.me/your-username'. Vous apprendrez également comment personnaliser votre slogan et rendre vos modèles disponibles pour la signature publique."
},
"enter-email-plaholder": "Ajoutez une adresse email et appuyez sur Entrée",
"success-email-alert": "E-mail envoyé avec succès!",
@@ -591,5 +599,41 @@
"expired-on-mssg": "Ce document a expiré le {{expiredDate}} et n'est plus disponible pour signature.",
"signature-validate-alert": "Veuillez confirmer que vous avez coché au moins {{minRequiredCount}} cases.",
"signature-validate-alert-2": "Assurez-vous que ce champ est rempli avec précision et répond à toutes les exigences.",
"user-name-exist": "le nom d'utilisateur existe déjà"
"user-name-exist": "le nom d'utilisateur existe déjà",
"remaing-users": "Utilisateurs restants",
"Role": "Rôle",
"additional-users": "Veuillez acheter des utilisateurs supplémentaires.",
"Quantity-of-users": "Quantité d'utilisateurs",
"Price": "Prix",
"Total-price-for-next-time": "Prix total pour la prochaine fois",
"Proceed": "Procéder",
"Select": "sélectionner",
"Add-seats": "Ajouter des sièges",
"format": "format",
"select-date-format": "Sélectionnez un format de date",
"quantity-of-credits":"Quantité de crédits de prime",
"remaining-credits":"Crédits premium disponibles :",
"additional-credits":"Veuillez acheter des crédits premium",
"remaining-credits-help":"Utilisez des crédits premium pour la signature de documents API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits achetés supplémentaires.",
"quota-err-quicksend": "Quota atteint, vous n'avez pas assez de crédits.",
"buy-credits": "Acheter des crédits premium",
"rotate-right" :"Faire pivoter à droite",
"rotate-left" :"Faire pivoter à gauche",
"rotate-alert-mssg" :"Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?",
"templateid":"ID de modèle",
"bulk-send-subcription-alert":"Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
"generate-test-token": "Générer jeton de test",
"regenerate-test-token":"Régénérer le jeton de test",
"help-test-token":"Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à lun de nos forfaits payants pour générer un jeton de production.",
"help-api-token":"Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
"quota-mail-info-head":"Limite mensuelle d'e-mails de signatures de demandes",
"quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.",
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
"quota-mail-tip":"Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
"quota-mail-head":"Quota atteint",
"unauthorized-modal":"Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
"sent-this-month":"envoyé ce mois-ci",
"available-seats":"Disponible sièges",
"buy-users":"Acheter plus d'utilisateurs"
}
+58 -35
View File
@@ -42,36 +42,38 @@ export function replaceMailVaribles(subject, body, variables) {
export const saveFileUsage = async (size, fileUrl, userId) => {
//checking server url and save file's size
try {
const tenantQuery = new Parse.Query('partners_Tenant');
tenantQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
const tenant = await tenantQuery.first();
if (tenant) {
const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id };
try {
const tenantCredits = new Parse.Query('partners_TenantCredits');
tenantCredits.equalTo('PartnersTenant', tenantPtr);
const res = await tenantCredits.first({ useMasterKey: true });
if (res) {
const response = JSON.parse(JSON.stringify(res));
const usedStorage = response?.usedStorage ? response.usedStorage + size : size;
const updateCredit = new Parse.Object('partners_TenantCredits');
updateCredit.id = res.id;
updateCredit.set('usedStorage', usedStorage);
await updateCredit.save(null, { useMasterKey: true });
} else {
const newCredit = new Parse.Object('partners_TenantCredits');
newCredit.set('usedStorage', size);
newCredit.set('PartnersTenant', tenantPtr);
await newCredit.save(null, { useMasterKey: true });
if (userId) {
const tenantQuery = new Parse.Query('partners_Tenant');
tenantQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
const tenant = await tenantQuery.first();
if (tenant) {
const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id };
try {
const tenantCredits = new Parse.Query('partners_TenantCredits');
tenantCredits.equalTo('PartnersTenant', tenantPtr);
const res = await tenantCredits.first({ useMasterKey: true });
if (res) {
const response = JSON.parse(JSON.stringify(res));
const usedStorage = response?.usedStorage ? response.usedStorage + size : size;
const updateCredit = new Parse.Object('partners_TenantCredits');
updateCredit.id = res.id;
updateCredit.set('usedStorage', usedStorage);
await updateCredit.save(null, { useMasterKey: true });
} else {
const newCredit = new Parse.Object('partners_TenantCredits');
newCredit.set('usedStorage', size);
newCredit.set('PartnersTenant', tenantPtr);
await newCredit.save(null, { useMasterKey: true });
}
} catch (err) {
console.log('err in save usage', err);
}
} catch (err) {
console.log('err in save usage', err);
saveDataFile(size, fileUrl, tenantPtr);
}
saveDataFile(size, fileUrl, tenantPtr);
}
} catch (err) {
console.log('err in fetch tenant Id', err);
@@ -91,7 +93,7 @@ const saveDataFile = async (size, fileUrl, tenantPtr) => {
}
};
export const updateMailCount = async extUserId => {
export const updateMailCount = async (extUserId, plan, monthchange) => {
// Update count in contracts_Users class
const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', extUserId);
@@ -100,13 +102,23 @@ export const updateMailCount = async extUserId => {
const contractUser = await query.first({ useMasterKey: true });
if (contractUser) {
contractUser.increment('EmailCount', 1);
if (plan === 'freeplan') {
if (monthchange) {
contractUser.set('LastEmailCountReset', new Date());
contractUser.set('MonthlyFreeEmails', 1);
} else {
if (contractUser?.get('MonthlyFreeEmails')) {
contractUser.increment('MonthlyFreeEmails', 1);
if (contractUser?.get('LastEmailCountReset')) {
contractUser.set('LastEmailCountReset', new Date());
}
} else {
contractUser.set('MonthlyFreeEmails', 1);
contractUser.set('LastEmailCountReset', new Date());
}
}
}
await contractUser.save(null, { useMasterKey: true });
} else {
// Create new entry if not found
const ContractsUsers = Parse.Object.extend('contracts_Users');
const newContractUser = new ContractsUsers();
newContractUser.set('EmailCount', 1);
await newContractUser.save(null, { useMasterKey: true });
}
} catch (error) {
console.log('Error updating EmailCount in contracts_Users: ' + error.message);
@@ -210,3 +222,14 @@ export const useLocal = process.env.USE_LOCAL ? process.env.USE_LOCAL.toLowerCas
export const smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '465' ? false : true;
export const smtpenable =
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false;
export const planCredits = {
'pro-weekly': 100,
'pro-yearly': 240,
'professional-monthly': 100,
'professional-yearly': 240,
'team-weekly': 100,
'team-yearly': 500,
'teams-monthly': 100,
'teams-yearly': 500,
};
@@ -1,10 +1,36 @@
import { planCredits } from '../../Utils.js';
export default async function saveSubscription(request, response) {
const SubscriptionId = request.body?.data?.subscription?.subscription_id;
const body = request.body;
const Email = request.body.data?.subscription?.customer?.email;
const Next_billing_date = request.body?.data?.subscription?.next_billing_at;
const planCode = request.body?.data?.subscription?.plan?.plan_code;
const addons = request.body?.data?.subscription?.addons || [];
const event = request.body?.data?.event_type || '';
const credits = planCredits?.[planCode] || 0;
const isTeamPlan = planCode?.includes('team');
let newAddons = 0;
if (addons?.length > 0) {
let allowedUsersMonthly = 0;
let allowedUsersYearly = 0;
addons?.forEach(item => {
if (item.addon_code === 'extra-teams-users-monthly') {
allowedUsersMonthly += item.quantity;
} else if (item.addon_code === 'extra-teams-users-yearly') {
allowedUsersYearly += item.quantity;
} else if (item.addon_code === 'extra-users') {
allowedUsersMonthly += item.quantity;
}
});
if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) {
newAddons = allowedUsersMonthly + allowedUsersYearly + 1;
}
} else {
if (planCode === 'teams-yearly' || planCode === 'teams-monthly' || planCode === 'team-weekly') {
newAddons = 1;
}
}
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('Email', Email);
@@ -18,6 +44,7 @@ export default async function saveSubscription(request, response) {
});
const subscription = await subcriptionCls.first({ useMasterKey: true });
if (subscription) {
const _resSub = JSON.parse(JSON.stringify(subscription));
const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subscription.id;
updateSubscription.set('SubscriptionId', SubscriptionId);
@@ -28,6 +55,81 @@ export default async function saveSubscription(request, response) {
updateSubscription.unset('Next_billing_date');
}
updateSubscription.set('PlanCode', planCode);
if (newAddons > 0) {
updateSubscription.set('AllowedUsers', parseInt(newAddons));
}
let existAddon = 0;
let allowedUsersMonthly = 0;
let allowedUsersYearly = 0;
_resSub.SubscriptionDetails?.data?.subscription?.addons?.forEach(item => {
if (item.addon_code === 'extra-teams-users-monthly') {
allowedUsersMonthly += item.quantity;
} else if (item.addon_code === 'extra-teams-users-yearly') {
allowedUsersYearly += item.quantity;
} else if (item.addon_code === 'extra-users') {
allowedUsersMonthly += item.quantity;
}
});
if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) {
existAddon = allowedUsersMonthly + allowedUsersYearly + 1; // + 1 is Admin user
} else {
if (
planCode === 'teams-yearly' ||
planCode === 'teams-monthly' ||
planCode === 'team-weekly'
) {
existAddon = 1; // 1 is Admin user
}
}
const isSameAsPrevPlan = subscription?.get('PlanCode') === planCode;
if (isSameAsPrevPlan) {
const planCredits = subscription?.get('PlanCredits');
const existAllowedCredits = subscription?.get('AllowedCredits') || 0;
if (planCredits) {
const oldAddons = existAddon;
const substractedAddon = newAddons - oldAddons;
if (isTeamPlan && substractedAddon > 0) {
const newCredits = existAllowedCredits + substractedAddon * planCredits;
updateSubscription.set('AllowedCredits', newCredits);
if (event === 'subscription_created') {
updateSubscription.set('PlanCredits', credits);
}
} else if (isTeamPlan) {
const existCredits = existAddon * planCredits;
updateSubscription.set('AllowedCredits', existCredits);
if (event === 'subscription_created') {
updateSubscription.set('PlanCredits', credits);
}
} else {
updateSubscription.set('AllowedCredits', planCredits);
if (event === 'subscription_created') {
updateSubscription.set('PlanCredits', credits);
}
}
} else {
if (credits > 0) {
if (isTeamPlan) {
const newCredits = newAddons * credits;
updateSubscription.set('AllowedCredits', newCredits);
updateSubscription.set('PlanCredits', credits);
} else {
updateSubscription.set('AllowedCredits', credits);
updateSubscription.set('PlanCredits', credits);
}
}
}
} else {
if (credits > 0) {
if (isTeamPlan) {
const newCredits = newAddons * credits;
updateSubscription.set('AllowedCredits', newCredits);
updateSubscription.set('PlanCredits', credits);
} else {
updateSubscription.set('AllowedCredits', credits);
updateSubscription.set('PlanCredits', credits);
}
}
}
await updateSubscription.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'update subscription!' });
} else {
@@ -55,6 +157,19 @@ export default async function saveSubscription(request, response) {
createSubscription.unset('Next_billing_date');
}
createSubscription.set('PlanCode', planCode);
if (newAddons > 0) {
createSubscription.set('AllowedUsers', parseInt(newAddons));
}
if (credits > 0) {
if (isTeamPlan) {
const totalCredits = parseInt(newAddons) * credits;
createSubscription.set('AllowedCredits', totalCredits);
createSubscription.set('PlanCredits', credits);
} else {
createSubscription.set('AllowedCredits', credits);
createSubscription.set('PlanCredits', credits);
}
}
await createSubscription.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'create subscription!' });
}
@@ -4,14 +4,9 @@ import { cloudServerUrl, customAPIurl, replaceMailVaribles } from '../../../../U
// `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) {
if (WebhookUrl) {
const params = {
event: 'created',
...doc,
};
const params = { event: 'created', ...doc };
await axios
.post(WebhookUrl, params, {
headers: { 'Content-Type': 'application/json' },
})
.post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } })
.then(res => {
try {
// console.log('res ', res);
@@ -62,6 +57,7 @@ export default async function createDocumentWithTemplate(request, response) {
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');
@@ -74,304 +70,344 @@ export default async function createDocumentWithTemplate(request, response) {
className: '_User',
objectId: parseUser.userId.objectId,
};
const templateQuery = new Parse.Query('contracts_Template');
templateQuery.include('ExtUserPtr');
const templateRes = await templateQuery.get(templateId, { useMasterKey: true });
if (templateRes) {
const template = JSON.parse(JSON.stringify(templateRes));
if (template?.Placeholders?.length > 0) {
const emptyplaceholder = template?.Placeholders.filter(x => !x.signerObjId);
const isValid =
signers.length >= emptyplaceholder.length &&
signers.length <= template?.Placeholders?.length;
const placeholder =
signers.length > emptyplaceholder.length ? template.Placeholders : emptyplaceholder;
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 object = new Parse.Object('contracts_Document');
object.set('Name', template.Name);
if (template?.Note) {
object.set('Note', template.Note);
}
if (template?.Description) {
object.set('Description', template.Description);
}
object.set('IsSendMail', send_email);
if (sendInOrder) {
object.set('SendinOrder', sendInOrder);
} else if (template?.SendinOrder && template?.SendinOrder) {
object.set('SendinOrder', template?.SendinOrder);
}
let templateSigner = template?.Signers ? template?.Signers : [];
let contact = [];
if (signers && signers.length > 0) {
let parseSigners = [...signers];
let createContactUrl = protocol + '/v1/createcontact';
const extUsers = new Parse.Query('contracts_Users');
extUsers.equalTo('UserId', userPtr);
const extUser = await extUsers.first({ useMasterKey: true });
for (const obj of parseSigners) {
const body = {
name: obj?.name || '',
email: obj?.email || '',
phone: obj?.phone || '',
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
subscription.include('ExtUserPtr');
subscription.greaterThanOrEqualTo('Next_billing_date', new Date());
const resSub = await subscription.first({ useMasterKey: true });
if (resSub) {
const _resSub = JSON.parse(JSON.stringify(resSub));
const allowedCredits = _resSub?.AllowedCredits || 0;
const addonCredits = _resSub?.AddonCredits || 0;
const totalcredits = allowedCredits + addonCredits;
if (totalcredits > 0) {
const templateQuery = new Parse.Query('contracts_Template');
templateQuery.include('ExtUserPtr');
const templateRes = await templateQuery.get(templateId, { useMasterKey: true });
if (templateRes) {
const template = JSON.parse(JSON.stringify(templateRes));
if (template?.Placeholders?.length > 0) {
const emptyplaceholder = template?.Placeholders.filter(x => !x.signerObjId);
const isValid =
signers.length >= emptyplaceholder.length &&
signers.length <= template?.Placeholders?.length;
const placeholder =
signers.length > emptyplaceholder.length ? template.Placeholders : emptyplaceholder;
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,
};
try {
const res = await axios.post(createContactUrl, body, {
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
const object = new Parse.Object('contracts_Document');
object.set('Name', template.Name);
if (template?.Note) {
object.set('Note', template.Note);
}
if (template?.Description) {
object.set('Description', template.Description);
}
object.set('IsSendMail', send_email);
if (sendInOrder) {
object.set('SendinOrder', sendInOrder);
} else if (template?.SendinOrder && template?.SendinOrder) {
object.set('SendinOrder', template?.SendinOrder);
}
let templateSigner = template?.Signers ? template?.Signers : [];
let contact = [];
if (signers && signers.length > 0) {
let parseSigners = [...signers];
let createContactUrl = protocol + '/v1/createcontact';
for (const obj of parseSigners) {
const body = {
name: obj?.name || '',
email: obj?.email || '',
phone: obj?.phone || '',
};
try {
const res = await axios.post(createContactUrl, body, {
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
});
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: res.data?.objectId,
};
const newObj = { ...obj, contactPtr: contactPtr };
contact.push(newObj);
} catch (err) {
// console.log('err ', err);
if (err?.response?.data?.objectId) {
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: err.response.data?.objectId,
};
const newObj = { ...obj, contactPtr: contactPtr };
contact.push(newObj);
} else {
console.log('err ', err);
}
}
}
const contactPtrs = contact.map(x => x.contactPtr);
object.set('Signers', [...templateSigner, ...contactPtrs]);
let updatedPlaceholder = template?.Placeholders?.map(x => {
let matchingSigner = contact.find(y => x.Role && x.Role === y.role);
if (matchingSigner) {
return {
...x,
signerObjId: matchingSigner?.contactPtr?.objectId,
signerPtr: matchingSigner?.contactPtr,
};
} else {
return { ...x };
}
});
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: res.data?.objectId,
};
const newObj = { ...obj, contactPtr: contactPtr };
contact.push(newObj);
} catch (err) {
// console.log('err ', err);
if (err?.response?.data?.objectId) {
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: err.response.data?.objectId,
};
const newObj = { ...obj, contactPtr: contactPtr };
contact.push(newObj);
} else {
console.log('err ', err);
}
}
}
const contactPtrs = contact.map(x => x.contactPtr);
object.set('Signers', [...templateSigner, ...contactPtrs]);
let updatedPlaceholder = template?.Placeholders?.map(x => {
let matchingSigner = contact.find(y => x.Role && x.Role === y.role);
if (matchingSigner) {
return {
...x,
signerObjId: matchingSigner?.contactPtr?.objectId,
signerPtr: matchingSigner?.contactPtr,
};
object.set('Placeholders', updatedPlaceholder);
} else {
return {
...x,
};
object.set('Signers', templateSigner);
}
});
object.set('Placeholders', updatedPlaceholder);
} else {
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',
className: 'contracts_Users',
objectId: template.ExtUserPtr.objectId,
});
if (folderId) {
object.set('Folder', folderPtr);
}
const newACL = new Parse.ACL();
newACL.setPublicReadAccess(false);
newACL.setPublicWriteAccess(false);
newACL.setReadAccess(userPtr.objectId, true);
newACL.setWriteAccess(userPtr.objectId, true);
object.setACL(newACL);
const res = await object.save(null, { useMasterKey: true });
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',
className: 'contracts_Users',
objectId: template.ExtUserPtr.objectId,
});
if (folderId) {
object.set('Folder', folderPtr);
}
const newACL = new Parse.ACL();
newACL.setPublicReadAccess(false);
newACL.setPublicWriteAccess(false);
newACL.setReadAccess(userPtr.objectId, true);
newACL.setWriteAccess(userPtr.objectId, true);
object.setACL(newACL);
const res = await object.save(null, { useMasterKey: true });
const newDate = new Date();
newDate.setDate(newDate.getDate() + 15);
const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
let sender = template.ExtUserPtr.Email;
let sendMail;
if (send_email === false) {
console.log("don't send mail");
} else {
let contactMail = contact;
if (sendInOrder) {
contactMail = contact.slice();
contactMail.splice(1);
}
for (let i = 0; i < contactMail.length; i++) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
let url = `${cloudServerUrl}/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 = contactMail[i].contactPtr.objectId;
const hostUrl = baseUrl.origin;
//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';
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;'> " +
template.ExtUserPtr.Name +
' has requested you to review and sign <strong> ' +
template.Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
sender +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender +
' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' +
openSignUrl +
' target=_blank>here</a>.</p> </div></div></body> </html>';
let replaceVar;
const variables = {
document_title: template.Name,
sender_name: template.ExtUserPtr.Name,
sender_mail: template.ExtUserPtr.Email,
sender_phone: template.ExtUserPtr?.Phone || '',
receiver_name: contactMail[i].name,
receiver_email: contactMail[i].email,
receiver_phone: contactMail[i]?.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(
`${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}"`,
body: email_html,
};
const newDate = new Date();
newDate.setDate(newDate.getDate() + 15);
const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
let sender = template.ExtUserPtr.Email;
let sendMail;
if (send_email === false) {
console.log("don't send mail");
} else {
let contactMail = contact;
if (sendInOrder) {
contactMail = contact.slice();
contactMail.splice(1);
}
for (let i = 0; i < contactMail.length; i++) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
let url = `${cloudServerUrl}/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 subject = replaceVar.subject;
const html = replaceVar.body;
let params = {
recipient: contactMail[i].email,
subject: subject,
from: sender,
html: html,
extUserId: template.ExtUserPtr.objectId,
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
console.log('error', error);
const objectId = contactMail[i].contactPtr.objectId;
const hostUrl = baseUrl.origin;
//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';
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;'> " +
template.ExtUserPtr.Name +
' has requested you to review and sign <strong> ' +
template.Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
sender +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender +
' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' +
openSignUrl +
' target=_blank>here</a>.</p> </div></div></body> </html>';
let replaceVar;
const variables = {
document_title: template.Name,
sender_name: template.ExtUserPtr.Name,
sender_mail: template.ExtUserPtr.Email,
sender_phone: template.ExtUserPtr?.Phone || '',
receiver_name: contactMail[i].name,
receiver_email: contactMail[i].email,
receiver_phone: contactMail[i]?.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(
`${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}"`,
body: email_html,
};
}
const subject = replaceVar.subject;
const html = replaceVar.body;
let params = {
recipient: contactMail[i].email,
subject: subject,
from: sender,
html: html,
extUserId: template.ExtUserPtr.objectId,
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
console.log('error', error);
}
}
}
// if (sendMail.data.result.status === 'success') {
try {
const doc = {
objectId: res.id,
file: template?.URL,
name: template?.Name,
note: template?.Note || '',
description: template?.Description || '',
signers: contact?.map(x => ({
name: x.name,
email: x.email,
phone: x?.phone || '',
})),
createdAt: res.createdAt,
};
if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) {
sendDoctoWebhook(doc, template.ExtUserPtr?.Webhook, userPtr?.objectId);
}
} catch (err) {
console.log('Err', err);
}
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document_with_templateid',
properties: { response_code: 200 },
});
}
const subscriptionCls = new Parse.Object('contracts_Subscriptions');
subscriptionCls.id = resSub.id;
if (allowedCredits > 0) {
const updateAllowedcredits = allowedCredits - 1 || 0;
subscriptionCls.set('AllowedCredits', updateAllowedcredits);
} else {
const updateAddonCredits = addonCredits > 0 ? addonCredits - 1 : 0;
subscriptionCls.set('AddonCredits', updateAddonCredits);
}
const resSubcription = await subscriptionCls.save(null, { useMasterKey: true });
// console.log("resSubcription ", resSubcription)
return response.json({
objectId: res.id,
signurl: contact.map(x => ({
email: x.email,
url: `${baseUrl.origin}/login/${btoa(
`${res.id}/${x.email}/${x.contactPtr.objectId}`
)}`,
})),
message: 'Document sent successfully!',
});
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document_with_templateid',
properties: { response_code: 400 },
});
}
return response.status(400).json({ error: 'Please provide signers properly!' });
}
}
// if (sendMail.data.result.status === 'success') {
try {
const doc = {
objectId: res.id,
file: template?.URL,
name: template?.Name,
note: template?.Note || '',
description: template?.Description || '',
signers: contact?.map(x => ({
name: x.name,
email: x.email,
phone: x?.phone || '',
})),
createdAt: res.createdAt,
};
if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) {
sendDoctoWebhook(doc, template.ExtUserPtr?.Webhook, userPtr?.objectId);
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document_with_templateid',
properties: { response_code: 400 },
});
}
} catch (err) {
console.log('Err', err);
return response.status(400).json({ error: 'Please setup template properly!' });
}
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document_with_templateid',
properties: { response_code: 200 },
});
}
return response.json({
objectId: res.id,
signurl: contact.map(x => ({
email: x.email,
url: `${baseUrl.origin}/login/${btoa(
`${res.id}/${x.email}/${x.contactPtr.objectId}`
)}`,
})),
message: 'Document sent successfully!',
});
// }
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document_with_templateid',
properties: { response_code: 400 },
properties: { response_code: 404 },
});
}
return response.status(400).json({ error: 'Please provide signers properly!' });
return response.status(404).json({ error: 'Invalid template id!' });
}
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document_with_templateid',
properties: { response_code: 400 },
});
}
return response.status(400).json({ error: 'Please setup template properly!' });
return response
.status(429)
.json({ error: 'Quota reached, Please buy credits and try again later.' });
}
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document_with_templateid',
properties: { response_code: 404 },
});
}
return response.status(404).json({ error: 'Invalid template id!' });
return response.status(400).json({
error: 'Please purchase or renew your subscription.',
});
}
} else {
return response.status(405).json({ error: 'Invalid API Token!' });
@@ -12,15 +12,10 @@ import {
// `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) {
if (WebhookUrl) {
const params = {
event: 'created',
...doc,
};
const params = { event: 'created', ...doc };
await axios
.post(WebhookUrl, params, {
headers: { 'Content-Type': 'application/json' },
})
.post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } })
.then(res => {
try {
// console.log('res ', res);
@@ -89,309 +84,348 @@ export default async function createDocumentwithCoordinate(request, response) {
className: '_User',
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');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const filename = sanitizeFileName(`${name}.pdf`);
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
const extUser = await contractsUser.first({ useMasterKey: true });
const parseExtUser = JSON.parse(JSON.stringify(extUser));
const extUsers = new Parse.Query('contracts_Users');
extUsers.equalTo('UserId', userPtr);
const extUser = await extUsers.first({ useMasterKey: true });
const extUserPtr = {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
};
const object = new Parse.Object('contracts_Document');
object.set('Name', name);
if (note) {
object.set('Note', note);
}
if (description) {
object.set('Description', description);
}
if (sendInOrder) {
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) {
let parseSigners;
if (base64File) {
parseSigners = signers;
} else {
parseSigners = JSON.parse(signers);
}
let createContactUrl = protocol + '/v1/createcontact';
for (const [index, element] of parseSigners.entries()) {
const body = {
name: element?.name || '',
email: element?.email || '',
phone: element?.phone || '',
};
try {
const res = await axios.post(createContactUrl, body, {
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
subscription.include('ExtUserPtr');
subscription.greaterThanOrEqualTo('Next_billing_date', new Date());
const resSub = await subscription.first({ useMasterKey: true });
if (resSub) {
const _resSub = JSON.parse(JSON.stringify(resSub));
const allowedCredits = _resSub?.AllowedCredits || 0;
const addonCredits = _resSub?.AddonCredits || 0;
const totalcredits = allowedCredits + addonCredits;
if (totalcredits > 0) {
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');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: base64,
});
// console.log('res ', res.data);
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: res.data?.objectId,
};
const newObj = { ...element, contactPtr: contactPtr, index: index };
contact.push(newObj);
} catch (err) {
// console.log('err ', err.response);
if (err?.response?.data?.objectId) {
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: err.response.data?.objectId,
};
const newObj = { ...element, contactPtr: contactPtr, index: index };
contact.push(newObj);
}
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const filename = sanitizeFileName(`${name}.pdf`);
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
}
object.set(
'Signers',
contact?.map(x => x.contactPtr)
);
let updatePlaceholders = contact.map(signer => {
const placeHolder = [];
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
const extUser = await contractsUser.first({ useMasterKey: true });
const parseExtUser = JSON.parse(JSON.stringify(extUser));
for (const widget of signer.widgets) {
const pageNumber = widget.page;
const options = formatWidgetOptions(widget.type, widget.options);
const page = placeHolder.find(page => page.pageNumber === pageNumber);
const widgetData = {
isStamp: widget.type === 'stamp' || widget.type === 'image',
key: randomId(),
isDrag: false,
scale: 1,
isMobile: false,
zIndex: 1,
type: widget.type === 'textbox' ? 'text input' : widget.type,
options: options,
Width: widget.w,
Height: widget.h,
xPosition: widget.x,
yPosition: widget.y,
};
if (page) {
page.pos.push(widgetData);
} else {
placeHolder.push({
pageNumber,
pos: [widgetData],
});
}
}
return {
signerObjId: signer?.contactPtr?.objectId,
signerPtr: signer?.contactPtr,
Role: signer.role,
Id: randomId(),
blockColor: color[signer?.index],
placeHolder,
const extUserPtr = {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
};
});
object.set('Placeholders', updatePlaceholders);
}
if (folderId) {
object.set('Folder', {
__type: 'Pointer',
className: 'contracts_Document',
objectId: folderId,
});
}
const newACL = new Parse.ACL();
newACL.setPublicReadAccess(false);
newACL.setPublicWriteAccess(false);
newACL.setReadAccess(userPtr.objectId, true);
newACL.setWriteAccess(userPtr.objectId, true);
object.setACL(newACL);
const res = await object.save(null, { useMasterKey: true });
const doc = {
objectId: res.id,
file: fileUrl,
name: name,
note: note || '',
description: description || '',
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })),
createdAt: res.createdAt,
};
if (parseExtUser && parseExtUser.Webhook) {
sendDoctoWebhook(doc, parseExtUser?.Webhook, userPtr?.objectId);
}
const newDate = new Date();
newDate.setDate(newDate.getDate() + 15);
const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
let sender = parseExtUser.Email;
if (send_email === false) {
console.log("don't send mail");
} else {
let contactMail = contact;
if (sendInOrder) {
contactMail = contact.slice();
contactMail.splice(1);
}
for (let i = 0; i < contactMail.length; i++) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
let url = `${cloudServerUrl}/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 = contactMail[i].contactPtr.objectId;
const hostUrl = baseUrl.origin;
//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';
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;'> " +
parseExtUser.Name +
' has requested you to review and sign <strong> ' +
name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
sender +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender +
' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' +
openSignUrl +
' target=_blank>here</a>.</p> </div></div></body> </html>';
let replaceVar;
const variables = {
document_title: name,
sender_name: parseExtUser.Name,
sender_mail: parseExtUser.Email,
sender_phone: parseExtUser?.Phone || '',
receiver_name: contactMail[i].name,
receiver_email: contactMail[i].email,
receiver_phone: contactMail[i]?.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(
`${parseExtUser.Name} has requested you to sign "${name}"`,
email_body,
variables
);
} else {
replaceVar = {
subject: `${parseExtUser.Name} has requested you to sign "${name}"`,
body: email_html,
};
}
const subject = replaceVar.subject;
const html = replaceVar.body;
const object = new Parse.Object('contracts_Document');
object.set('Name', name);
let params = {
recipient: contactMail[i].email,
subject: subject,
from: sender,
html: html,
extUserId: extUser.id,
};
await axios.post(url, params, { headers: headers });
} catch (error) {
console.log('error', error);
if (note) {
object.set('Note', note);
}
if (description) {
object.set('Description', description);
}
if (sendInOrder) {
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) {
let parseSigners;
if (base64File) {
parseSigners = signers;
} else {
parseSigners = JSON.parse(signers);
}
let createContactUrl = protocol + '/v1/createcontact';
for (const [index, element] of parseSigners.entries()) {
const body = {
name: element?.name || '',
email: element?.email || '',
phone: element?.phone || '',
};
try {
const res = await axios.post(createContactUrl, body, {
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
});
// console.log('res ', res.data);
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: res.data?.objectId,
};
const newObj = { ...element, contactPtr: contactPtr, index: index };
contact.push(newObj);
} catch (err) {
// console.log('err ', err.response);
if (err?.response?.data?.objectId) {
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: err.response.data?.objectId,
};
const newObj = { ...element, contactPtr: contactPtr, index: index };
contact.push(newObj);
}
}
}
object.set(
'Signers',
contact?.map(x => x.contactPtr)
);
let updatePlaceholders = contact.map(signer => {
const placeHolder = [];
for (const widget of signer.widgets) {
const pageNumber = widget.page;
const options = formatWidgetOptions(widget.type, widget.options);
const page = placeHolder.find(page => page.pageNumber === pageNumber);
const widgetData = {
isStamp: widget.type === 'stamp' || widget.type === 'image',
key: randomId(),
isDrag: false,
scale: 1,
isMobile: false,
zIndex: 1,
type: widget.type === 'textbox' ? 'text input' : widget.type,
options: options,
Width: widget.w,
Height: widget.h,
xPosition: widget.x,
yPosition: widget.y,
};
if (page) {
page.pos.push(widgetData);
} else {
placeHolder.push({ pageNumber, pos: [widgetData] });
}
}
return {
signerObjId: signer?.contactPtr?.objectId,
signerPtr: signer?.contactPtr,
Role: signer.role,
Id: randomId(),
blockColor: color[signer?.index],
placeHolder,
};
});
object.set('Placeholders', updatePlaceholders);
}
if (folderId) {
object.set('Folder', {
__type: 'Pointer',
className: 'contracts_Document',
objectId: folderId,
});
}
const newACL = new Parse.ACL();
newACL.setPublicReadAccess(false);
newACL.setPublicWriteAccess(false);
newACL.setReadAccess(userPtr.objectId, true);
newACL.setWriteAccess(userPtr.objectId, true);
object.setACL(newACL);
const res = await object.save(null, { useMasterKey: true });
const doc = {
objectId: res.id,
file: fileUrl,
name: name,
note: note || '',
description: description || '',
signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })),
createdAt: res.createdAt,
};
if (parseExtUser && parseExtUser.Webhook) {
sendDoctoWebhook(doc, parseExtUser?.Webhook, userPtr?.objectId);
}
const newDate = new Date();
newDate.setDate(newDate.getDate() + 15);
const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
});
let sender = parseExtUser.Email;
if (send_email === false) {
console.log("don't send mail");
} else {
let contactMail = contact;
if (sendInOrder) {
contactMail = contact.slice();
contactMail.splice(1);
}
for (let i = 0; i < contactMail.length; i++) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
let url = `${cloudServerUrl}/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 = contactMail[i].contactPtr.objectId;
const hostUrl = baseUrl.origin;
//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';
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;'> " +
parseExtUser.Name +
' has requested you to review and sign <strong> ' +
name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
sender +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender +
' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' +
openSignUrl +
' target=_blank>here</a>.</p> </div></div></body> </html>';
let replaceVar;
const variables = {
document_title: name,
sender_name: parseExtUser.Name,
sender_mail: parseExtUser.Email,
sender_phone: parseExtUser?.Phone || '',
receiver_name: contactMail[i].name,
receiver_email: contactMail[i].email,
receiver_phone: contactMail[i]?.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(
`${parseExtUser.Name} has requested you to sign "${name}"`,
email_body,
variables
);
} else {
replaceVar = {
subject: `${parseExtUser.Name} has requested you to sign "${name}"`,
body: email_html,
};
}
const subject = replaceVar.subject;
const html = replaceVar.body;
let params = {
recipient: contactMail[i].email,
subject: subject,
from: sender,
html: html,
extUserId: extUser.id,
};
await axios.post(url, params, { headers: headers });
} catch (error) {
console.log('error', error);
}
}
}
// if (sendMail.data.result.status === 'success') {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document',
properties: { response_code: 200 },
});
}
const subscriptionCls = new Parse.Object('contracts_Subscriptions');
subscriptionCls.id = resSub.id;
if (allowedCredits > 0) {
const updateAllowedcredits = allowedCredits - 1 || 0;
subscriptionCls.set('AllowedCredits', updateAllowedcredits);
} else {
const updateAddonCredits = addonCredits > 0 ? addonCredits - 1 : 0;
subscriptionCls.set('AddonCredits', updateAddonCredits);
}
const resSubcription = await subscriptionCls.save(null, { useMasterKey: true });
// console.log('resSubcription ', resSubcription);
return response.json({
objectId: res.id,
signurl: contact.map(x => ({
email: x.email,
url: `${baseUrl.origin}/login/${btoa(
`${res.id}/${x.email}/${x.contactPtr.objectId}`
)}`,
})),
message: 'Document sent successfully!',
});
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document',
properties: { response_code: 400 },
});
}
return response.status(400).json({ error: 'Please provide signers!' });
}
} else {
return response
.status(429)
.json({ error: 'Quota reached, Please buy credits and try again later.' });
}
// if (sendMail.data.result.status === 'success') {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document',
properties: { response_code: 200 },
});
}
return response.json({
objectId: res.id,
signurl: contact.map(x => ({
email: x.email,
url: `${baseUrl.origin}/login/${btoa(`${res.id}/${x.email}/${x.contactPtr.objectId}`)}`,
})),
message: 'Document sent successfully!',
});
// }
} else {
if (request.posthog) {
request.posthog?.capture({
distinctId: parseUser.userId.email,
event: 'api_create_document',
properties: { response_code: 400 },
});
}
return response.status(400).json({ error: 'Please provide signers!' });
return response.status(400).json({
error: 'Please purchase or renew your subscription.',
});
}
} else {
return response.status(405).json({ error: 'Invalid API Token!' });
+15 -2
View File
@@ -6,7 +6,6 @@ import usersignup from './parsefunction/usersignup.js';
import FacebookSign from './parsefunction/FacebookSign.js';
import DocumentAftersave from './parsefunction/DocumentAftersave.js';
import ContactbookAftersave from './parsefunction/ContactBookAftersave.js';
// import ContractUsersAftersave from './parsefunction/ContractUsersAftersave.js';
import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js';
import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js';
import getUserId from './parsefunction/getUserId.js';
@@ -60,14 +59,21 @@ import addTeam from './parsefunction/addTeam.js';
import updateTeam from './parsefunction/updateTeam.js';
import getOrgAdmins from './parsefunction/getOrgAdmins.js';
import getAllUserTeamByOrg from './parsefunction/getAllUserTeamByOrg.js';
import AllowedUsers from './parsefunction/AlllowedUsers.js';
import BuyAddonUsers from './parsefunction/BuyAddonUsers.js';
import ExtUserAftersave from './parsefunction/ExtUserAftersave.js';
import ExtUserAfterdelete from './parsefunction/ExtUserAfterdelete.js';
import AllowedCredits from './parsefunction/AllowedCredits.js';
import BuyCredits from './parsefunction/BuyCredits.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);
Parse.Cloud.afterSave('contracts_Teams', TeamsAftersave);
Parse.Cloud.afterSave('contracts_Subscriptions', SubscriptionAftersave);
Parse.Cloud.afterSave('contracts_Users', ExtUserAftersave);
// 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);
@@ -79,6 +85,9 @@ Parse.Cloud.afterFind('contracts_Template', TemplateAfterFind);
Parse.Cloud.afterFind('contracts_Signature', SignatureAfterFind);
Parse.Cloud.afterFind('partners_Tenant', TenantAterFind);
// This afterDelete function triggers after an object get deleted.
Parse.Cloud.afterDelete('contracts_Users', ExtUserAfterdelete);
// 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('signPdf', PDF);
Parse.Cloud.define('sendmailv3', sendmailv3);
@@ -129,3 +138,7 @@ Parse.Cloud.define('addteam', addTeam);
Parse.Cloud.define('updateteam', updateTeam);
Parse.Cloud.define('getorgadmins', getOrgAdmins);
Parse.Cloud.define('getalluserteambyorg', getAllUserTeamByOrg);
Parse.Cloud.define('allowedusers', AllowedUsers);
Parse.Cloud.define('buyaddonusers', BuyAddonUsers);
Parse.Cloud.define('allowedcredits', AllowedCredits);
Parse.Cloud.define('buycredits', BuyCredits);
@@ -189,34 +189,3 @@ export default async function AddAdmin(request) {
console.log('Err ', err);
}
}
// async function saveSubscription(extUserId, UserId, tenantId, subscription) {
// const SubscriptionId = subscription?.data?.subscription?.subscription_id || '';
// const Next_billing_date = subscription?.data?.subscription?.next_billing_at || '';
// const planCode = subscription?.data?.subscription?.plan?.plan_code || '';
// try {
// const createSubscription = new Parse.Object('contracts_Subscriptions');
// createSubscription.set('SubscriptionId', SubscriptionId);
// createSubscription.set('SubscriptionDetails', subscription);
// createSubscription.set('ExtUserPtr', {
// __type: 'Pointer',
// className: 'contracts_Users',
// objectId: extUserId,
// });
// createSubscription.set('CreatedBy', {
// __type: 'Pointer',
// className: '_User',
// objectId: UserId,
// });
// createSubscription.set('TenantId', {
// __type: 'Pointer',
// className: 'partners_Tenant',
// objectId: tenantId,
// });
// createSubscription.set('Next_billing_date', new Date(Next_billing_date));
// createSubscription.set('PlanCode', planCode);
// await createSubscription.save(null, { useMasterKey: true });
// } catch (err) {
// console.log('err in save subscription pgsignup', err);
// }
// }
@@ -0,0 +1,48 @@
export default async function AllowedUsers(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
try {
const extUser = new Parse.Query('contracts_Users');
extUser.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: request.user.id,
});
const resExtUser = await extUser.first({ useMasterKey: true });
if (resExtUser) {
const _resExtUser = JSON.parse(JSON.stringify(resExtUser));
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: _resExtUser.TenantId.objectId,
});
subscription.include('ExtUserPtr');
const resSub = await subscription.first({ useMasterKey: true });
if (resSub) {
const _resSub = JSON.parse(JSON.stringify(resSub));
const count = _resSub?.UsersCount || 0;
if (count > 0) {
const allowedUser = resSub.get('AllowedUsers') || 0;
const remainUsers = allowedUser - count;
if (remainUsers > 0) {
return remainUsers;
} else {
return 0;
}
} else {
const alloweduser = resSub.get('AllowedUsers') || 0;
return alloweduser;
}
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
console.log('err in allowedusers', err);
const code = err?.code || 400;
const msg = err?.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
}
@@ -0,0 +1,38 @@
export default async function AllowedCredits(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
try {
const extUser = new Parse.Query('contracts_Users');
extUser.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: request.user.id,
});
const resExtUser = await extUser.first({ useMasterKey: true });
if (resExtUser) {
const _resExtUser = JSON.parse(JSON.stringify(resExtUser));
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: _resExtUser.TenantId.objectId,
});
subscription.include('ExtUserPtr');
const resSub = await subscription.first({ useMasterKey: true });
if (resSub) {
const _resSub = JSON.parse(JSON.stringify(resSub));
const AllowedCredits = _resSub?.AllowedCredits || 0;
const AddonCredits = _resSub?.AddonCredits || 0;
return { allowedcredits: AllowedCredits, addoncredits: AddonCredits };
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
console.log('err in allowedCredits', err);
const code = err?.code || 400;
const msg = err?.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
}
@@ -0,0 +1,121 @@
import axios from 'axios';
import { planCredits } from '../../Utils.js';
export default async function BuyAddonUsers(request) {
const users = request.params.users;
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
if (users) {
try {
const extUser = new Parse.Query('contracts_Users');
extUser.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: request.user.id,
});
const resExtUser = await extUser.first({ useMasterKey: true });
if (resExtUser) {
const _resExtUser = JSON.parse(JSON.stringify(resExtUser));
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: _resExtUser.TenantId.objectId,
});
subscription.include('ExtUserPtr');
const resSub = await subscription.first({ useMasterKey: true });
if (resSub) {
const _resSub = JSON.parse(JSON.stringify(resSub));
// Define the URL
const url = 'https://accounts.zoho.in/oauth/v2/token';
// Convert the data to x-www-form-urlencoded format
const formData = new URLSearchParams();
formData.append('refresh_token', process.env.ZOHO_REFRESH_TOKEN);
formData.append('client_id', process.env.ZOHO_CLIENT_ID);
formData.append('client_secret', process.env.ZOHO_CLIENT_SECRET);
formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI);
formData.append('grant_type', 'refresh_token');
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
// Make the POST request using Axios
const res = await axios.post(url, formData, { headers });
if (res.data.access_token) {
const subscriptionId = _resSub.SubscriptionId;
const price = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.price;
const plan_code = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.plan_code;
const addonsArr = _resSub?.SubscriptionDetails?.data?.subscription?.addons || [];
let addon = 0;
if (addonsArr?.length > 0) {
let allowedUsersMonthly = 0;
let allowedUsersYearly = 0;
addonsArr?.forEach(item => {
if (item.addon_code === 'extra-teams-users-monthly') {
allowedUsersMonthly += item.quantity;
} else if (item.addon_code === 'extra-teams-users-yearly') {
allowedUsersYearly += item.quantity;
} else if (item.addon_code === 'extra-users') {
allowedUsersMonthly += item.quantity;
}
});
if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) {
addon = allowedUsersMonthly + allowedUsersYearly;
}
}
const quantity = parseInt(users) + parseInt(addon);
const addoncode = plan_code.includes('yearly')
? 'extra-teams-users-yearly'
: 'extra-teams-users-monthly';
const data = JSON.stringify({
plan: { plan_code: plan_code },
addons: [
{
addon_code: addoncode,
addon_description: 'Extra users',
price: price,
quantity: quantity,
},
],
});
const updatedSubscription = await axios.put(
'https://www.zohoapis.in/billing/v1/subscriptions/' + subscriptionId,
data,
{
headers: {
Authorization: 'Zoho-oauthtoken ' + res.data.access_token,
'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID,
},
}
);
const subscriptionInfo = { data: updatedSubscription.data };
const allowedUsers = quantity + 1;
const existAllowedCredits = _resSub?.AllowedCredits || 0;
const credits = resSub?.PlanCredits || planCredits[plan_code];
const newAllowedCredits = users * credits;
const totalCredits = existAllowedCredits + newAllowedCredits;
const updateSub = new Parse.Object('contracts_Subscriptions');
updateSub.id = resSub.id;
updateSub.set('SubscriptionDetails', subscriptionInfo);
updateSub.set('AllowedUsers', allowedUsers);
updateSub.set('AllowedCredits', totalCredits);
const resupdateSub = await updateSub.save(null, { useMasterKey: true });
return { status: 'success', addon: allowedUsers };
} else {
throw new Parse.Error('400', 'Invalid access token.');
}
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
const code = err?.response?.data?.code || err?.response?.status || err?.code || 400;
const msg =
err?.response?.data?.error ||
err?.response?.data ||
err?.message ||
'Something went wrong.';
console.log('err in buyaddon', code, msg);
throw new Parse.Error(code, msg);
}
}
}
@@ -0,0 +1,92 @@
import axios from 'axios';
export default async function BuyCredits(request) {
const credits = request.params.credits;
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
if (credits) {
try {
const extUser = new Parse.Query('contracts_Users');
extUser.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: request.user.id,
});
const resExtUser = await extUser.first({ useMasterKey: true });
if (resExtUser) {
const _resExtUser = JSON.parse(JSON.stringify(resExtUser));
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: _resExtUser.TenantId.objectId,
});
subscription.include('ExtUserPtr');
const resSub = await subscription.first({ useMasterKey: true });
if (resSub) {
const _resSub = JSON.parse(JSON.stringify(resSub));
// Define the URL
const url = 'https://accounts.zoho.in/oauth/v2/token';
// Convert the data to x-www-form-urlencoded format
const formData = new URLSearchParams();
formData.append('refresh_token', process.env.ZOHO_REFRESH_TOKEN);
formData.append('client_id', process.env.ZOHO_CLIENT_ID);
formData.append('client_secret', process.env.ZOHO_CLIENT_SECRET);
formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI);
formData.append('grant_type', 'refresh_token');
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
// Make the POST request using Axios
const res = await axios.post(url, formData, { headers });
if (res.data.access_token) {
const subscriptionId = _resSub.SubscriptionId;
const plan_code = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.plan_code;
const quantity = parseInt(credits);
const addoncode = 'addon-credits';
const data = JSON.stringify({
plan: { plan_code: plan_code },
addons: [
{ addon_code: addoncode, addon_description: 'addon credits', quantity: quantity },
],
});
const creditsUrl = `https://www.zohoapis.in/billing/v1/subscriptions/${subscriptionId}/buyonetimeaddon`;
const resCredits = await axios.post(creditsUrl, data, {
headers: {
Authorization: 'Zoho-oauthtoken ' + res.data.access_token,
'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID,
},
});
// console.log('resCredits ', resCredits.data);
if (resCredits.data) {
const existAddonCredits = _resSub?.AddonCredits ? _resSub.AddonCredits : 0;
const addonCredits = existAddonCredits + quantity;
const updateSub = new Parse.Object('contracts_Subscriptions');
updateSub.id = resSub.id;
updateSub.set('AddonCredits', addonCredits);
const resupdateSub = await updateSub.save(null, { useMasterKey: true });
// console.log('resupdateSub ', resupdateSub);
return { status: 'success', addon: quantity };
}
} else {
throw new Parse.Error('400', 'Invalid access token.');
}
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
const code = err?.response?.data?.code || err?.response?.status || err?.code || 400;
const msg =
err?.response?.data?.error ||
err?.response?.data ||
err?.message ||
'Something went wrong.';
console.log('err in Buyaddonusers', code, msg);
throw new Parse.Error(code, msg);
}
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide parameters.');
}
}
@@ -1,40 +0,0 @@
async function ContractUsersAftersave(request) {
console.log('In contracts_Users aftersave');
if (!request.original) {
const shareWithTeam = request.object.get('ShareWithTeam');
const newACL = new Parse.ACL();
const contactbook = new Parse.Object('contracts_Contactbook');
contactbook.set('Name', request.object.get('Name'));
contactbook.set('Email', request.object.get('Email'));
contactbook.set('ExtUserPtr', request.object.get('objectId'));
if (request.object?.get('Phone')) {
contactbook.set('Phone', request.object.get('Phone'));
}
contactbook.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: request.object.id,
});
contactbook.set('UserId', request.object.get('UserId'));
// newACL.setPublicReadAccess(false);
// newACL.setPublicWriteAccess(false);
const tenant_Id = request.object.get('TenantId');
if (shareWithTeam) {
if (tenant_Id) {
newACL.setReadAccess('tenant_' + tenant_Id.id, true);
newACL.setWriteAccess('tenant_' + tenant_Id.id, true);
}
newACL.setReadAccess(request.user, true);
newACL.setWriteAccess(request.user, true);
} else {
newACL.setReadAccess(request.user, true);
newACL.setWriteAccess(request.user, true);
}
contactbook.setACL(newACL);
contactbook.save(null, { useMasterkey: true });
}
}
export default ContractUsersAftersave;
@@ -5,7 +5,7 @@ async function DocumentBeforesave(request) {
const oldDocument = request.original;
// Check if SignedUrl field has been added (transition from undefined to defined)
if (!oldDocument?.get('SignedUrl') && document?.get('SignedUrl')) {
if (oldDocument && !oldDocument?.get('SignedUrl') && document?.get('SignedUrl')) {
// Update count in contracts_Users class
const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', oldDocument.get('ExtUserPtr').id);
@@ -0,0 +1,31 @@
async function ExtUserAfterdelete(request) {
try {
const extUser = request.object;
const tenantId = extUser.get('TenantId')?.id;
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: tenantId,
});
const resSub = await subscription.first({ useMasterKey: true });
if (resSub) {
const updateSub = new Parse.Object('contracts_Subscriptions');
updateSub.id = resSub.id;
if (resSub?.get('UsersCount') > 0) {
updateSub.decrement('UsersCount', 1);
} else {
updateSub.decrement('UsersCount', 0);
}
updateSub.save(null, { useMasterKey: true });
} else {
const addSub = new Parse.Object('contracts_Subscriptions');
addSub.set('UsersCount', 0);
addSub.save(null, { useMasterKey: true });
}
} catch (err) {
console.log('err in extuser afterdelete', err);
}
}
export default ExtUserAfterdelete;
@@ -0,0 +1,44 @@
async function ExtUserAftersave(request) {
try {
if (!request.original) {
const extUser = request.object;
const tenantId = extUser.get('TenantId')?.id;
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: tenantId,
});
const resSub = await subscription.first({ useMasterKey: true });
if (resSub) {
const updateSub = new Parse.Object('contracts_Subscriptions');
updateSub.id = resSub.id;
updateSub.increment('UsersCount', 1);
await updateSub.save(null, { useMasterKey: true });
} else {
const addSub = new Parse.Object('contracts_Subscriptions');
addSub.set('UsersCount', 1);
addSub.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
});
addSub.set('CreatedBy', {
__type: 'Pointer',
className: '_User',
objectId: extUser.get('UserId').id,
});
addSub.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
await addSub.save(null, { useMasterKey: true });
}
}
} catch (err) {
console.log('err in extuser aftersave', err);
}
}
export default ExtUserAftersave;
@@ -14,21 +14,38 @@ export default async function SubscribeFree(request) {
});
subscriptionCls.descending('createdAt');
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
if (subcripitions?.get('PlanCode') === 'freeplan') {
return { status: 'success', result: 'already subscribed!' };
} else if (subcripitions?.get('Next_billing_date') < new Date()) {
try {
const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subcripitions.id;
updateSubscription.set('PlanCode', 'freeplan');
await updateSubscription.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
if (subcripitions) {
if (subcripitions?.get('PlanCode') === 'freeplan') {
return { status: 'success', result: 'already subscribed!' };
} else if (subcripitions?.get('Next_billing_date') < new Date()) {
try {
const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subcripitions.id;
updateSubscription.set('PlanCode', 'freeplan');
updateSubscription.set('AllowedCredits', 0);
updateSubscription.set('PlanCredits', 0);
await updateSubscription.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
}
} else if (subcripitions?.get('Next_billing_date') > new Date()) {
return { status: 'success', result: 'already subscribed!' };
} else {
try {
const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subcripitions.id;
updateSubscription.set('PlanCode', 'freeplan');
updateSubscription.set('AllowedCredits', 0);
updateSubscription.set('PlanCredits', 0);
await updateSubscription.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
}
}
} else if (subcripitions?.get('Next_billing_date') > new Date()) {
return { status: 'success', result: 'already subscribed!' };
} else {
try {
const createSubscription = new Parse.Object('contracts_Subscriptions');
@@ -50,6 +67,8 @@ export default async function SubscribeFree(request) {
objectId: extUser.get('TenantId').id,
});
}
createSubscription.set('AllowedCredits', 0);
createSubscription.set('PlanCredits', 0);
await createSubscription.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
@@ -18,9 +18,7 @@ export default async function ZohoDetails(request) {
formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI);
formData.append('grant_type', 'refresh_token');
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
};
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
// Make the POST request using Axios
const res = await axios.post(url, formData, { headers });
// console.log("Access Token:", res.data);
@@ -3,7 +3,33 @@ import { cloudServerUrl } from '../../Utils.js';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID;
const licenseKey = process.env.LICENSE_KEY;
async function deductcount(docsCount, extUserId, subscription) {
try {
if (licenseKey) {
const allowedCredits = subscription?.AllowedCredits || 0;
const addonCredits = subscription?.AddonCredits || 0;
const subscriptionCls = new Parse.Object('contracts_Subscriptions');
subscriptionCls.id = subscription.objectId;
if (docsCount <= allowedCredits) {
const updateAllowedcredits = allowedCredits - docsCount;
subscriptionCls.set('AllowedCredits', updateAllowedcredits);
} else {
const remaingCount = docsCount - allowedCredits;
const updateAddonCredits = addonCredits - remaingCount;
subscriptionCls.set('AllowedCredits', 0);
subscriptionCls.set('AddonCredits', updateAddonCredits);
}
await subscriptionCls.save(null, { useMasterKey: true });
}
const extCls = new Parse.Object('contracts_Users');
extCls.id = extUserId;
extCls.increment('DocumentCount', docsCount);
const resExt = await extCls.save(null, { useMasterKey: true });
} catch (err) {
console.log('Err in deduct in quick send', err);
}
}
async function sendMail(document, sessionToken) {
const baseUrl = new URL(process.env.PUBLIC_URL);
@@ -90,7 +116,6 @@ export default async function createBatchDocs(request) {
const sessionToken = request.headers['sessiontoken'];
const Documents = JSON.parse(strDocuments);
const Ip = request?.headers?.['x-real-ip'] || '';
// console.log('Documents ', Documents);
const parseConfig = {
baseURL: serverUrl, //localStorage.getItem('baseUrl'),
headers: {
@@ -100,84 +125,164 @@ export default async function createBatchDocs(request) {
},
};
try {
const requests = Documents.map(x => {
const Signers = x.Signers;
const allSigner = x?.Placeholders?.map(
item => Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr
).filter(signer => Object.keys(signer).length > 0);
const date = new Date();
const isoDate = date.toISOString();
let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } };
if (allSigner && allSigner.length > 0) {
allSigner.forEach(x => {
const obj = { [x.CreatedBy.objectId]: { read: true, write: true } };
Acl = { ...Acl, ...obj };
});
}
return {
method: 'POST',
path: '/app/classes/contracts_Document',
body: {
Name: x.Name,
URL: x.URL,
Note: x.Note,
Description: x.Description,
CreatedBy: x.CreatedBy,
SendinOrder: x.SendinOrder || true,
ExtUserPtr: {
__type: 'Pointer',
className: x.ExtUserPtr.className,
objectId: x.ExtUserPtr.objectId,
},
Placeholders: x.Placeholders.map(y =>
y?.signerPtr?.objectId
? {
...y,
signerPtr: {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.signerPtr.objectId,
},
signerObjId: y.signerObjId,
}
: { ...y, signerPtr: {}, signerObjId: '' }
),
SignedUrl: x.URL || x.SignedUrl,
SentToOthers: true,
Signers: allSigner?.map(y => ({
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.objectId,
})),
ACL: Acl,
SentToOthers: true,
RemindOnceInEvery: x.RemindOnceInEvery || 5,
AutomaticReminders: x.AutomaticReminders || false,
TimeToCompleteDays: x.TimeToCompleteDays || 15,
OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate },
},
};
const user = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
// console.log('requests ', requests);
const response = await axios.post('batch', { requests: requests }, parseConfig);
// // Handle the batch query response
// console.log('Batch query response:', response.data);
if (response.data && response.data.length > 0) {
const updateDocuments = Documents.map((x, i) => ({
...x,
objectId: response.data[i]?.success?.objectId,
createdAt: response.data[i]?.success?.createdAt,
}));
for (let i = 0; i < updateDocuments.length; i++) {
sendMail(updateDocuments[i], sessionToken);
if (user.data) {
const extCls = new Parse.Query('contracts_Users');
extCls.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: user.data.objectId,
});
const resExt = await extCls.first({ useMasterKey: true });
if (resExt) {
const _resExt = JSON.parse(JSON.stringify(resExt));
try {
const requests = Documents.map(x => {
const Signers = x.Signers;
const allSigner = x?.Placeholders?.map(
item =>
Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr
).filter(signer => Object.keys(signer).length > 0);
const date = new Date();
const isoDate = date.toISOString();
let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } };
if (allSigner && allSigner.length > 0) {
allSigner.forEach(x => {
const obj = { [x.CreatedBy.objectId]: { read: true, write: true } };
Acl = { ...Acl, ...obj };
});
}
return {
method: 'POST',
path: '/app/classes/contracts_Document',
body: {
Name: x.Name,
URL: x.URL,
Note: x.Note,
Description: x.Description,
CreatedBy: x.CreatedBy,
SendinOrder: x.SendinOrder || true,
ExtUserPtr: {
__type: 'Pointer',
className: x.ExtUserPtr.className,
objectId: x.ExtUserPtr.objectId,
},
Placeholders: x.Placeholders.map(y =>
y?.signerPtr?.objectId
? {
...y,
signerPtr: {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.signerPtr.objectId,
},
signerObjId: y.signerObjId,
}
: { ...y, signerPtr: {}, signerObjId: '' }
),
SignedUrl: x.URL || x.SignedUrl,
SentToOthers: true,
Signers: allSigner?.map(y => ({
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.objectId,
})),
ACL: Acl,
SentToOthers: true,
RemindOnceInEvery: x.RemindOnceInEvery || 5,
AutomaticReminders: x.AutomaticReminders || false,
TimeToCompleteDays: x.TimeToCompleteDays || 15,
OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate },
},
};
});
// console.log('requests ', requests);
if (licenseKey) {
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: _resExt.TenantId.objectId,
});
subscription.include('ExtUserPtr');
subscription.greaterThanOrEqualTo('Next_billing_date', new Date());
const resSub = await subscription.first({ useMasterKey: true });
if (resSub) {
const _resSub = JSON.parse(JSON.stringify(resSub));
const allowedCredits = _resSub?.AllowedCredits || 0;
const addonCredits = _resSub?.AddonCredits || 0;
const totalcredits = allowedCredits + addonCredits;
if (requests?.length <= totalcredits) {
const response = await axios.post('batch', { requests: requests }, parseConfig);
// // Handle the batch query response
// console.log('Batch query response:', response.data);
if (response.data && response.data.length > 0) {
const updateDocuments = Documents.map((x, i) => ({
...x,
objectId: response.data[i]?.success?.objectId,
createdAt: response.data[i]?.success?.createdAt,
}));
deductcount(response.data.length, resExt.id, _resSub);
for (let i = 0; i < updateDocuments.length; i++) {
sendMail(updateDocuments[i], sessionToken);
}
return 'success';
}
} else {
throw new Parse.Error(
429,
'Quota reached, Please buy credits and try again later.'
);
}
} else {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'Please purchase or renew your subscription.'
);
}
} else {
if (requests?.length > 0) {
const newrequests = [requests?.[0]];
const response = await axios.post('batch', { requests: newrequests }, parseConfig);
// Handle the batch query response
// console.log('Batch query response:', response.data);
if (response.data && response.data.length > 0) {
const document = Documents?.[0];
const updateDocuments = {
...document,
objectId: response.data[0]?.success?.objectId,
createdAt: response.data[0]?.success?.createdAt,
};
deductcount(response.data.length, resExt.id);
sendMail(updateDocuments, sessionToken);
return 'success';
}
}
}
} catch (error) {
const code = error?.response?.data?.code || error?.response?.status || error?.code || 400;
const msg =
error?.response?.data?.error ||
error?.response?.data ||
error?.message ||
'Something went wrong.';
console.log('Error performing batch query:', code, msg);
throw new Parse.Error(code, msg);
}
}
return 'success';
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
// Handle individual responses within response.data.results
} catch (error) {
console.error('Error performing batch query:', error);
} catch (err) {
console.log('err in createbatchdoc', err);
const code = err?.code || 400;
const msg = err?.message || 'Something went wrong.';
new Parse.Error(code, msg);
}
}
@@ -22,6 +22,7 @@ export default async function getDocument(request) {
query.include('Signers');
query.include('AuditTrail.UserPtr');
query.include('Placeholders');
query.include('DeclineBy');
query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true });
if (res) {
@@ -12,6 +12,8 @@ async function getUserDetails(request) {
}
userQuery.include('TenantId');
userQuery.include('UserId');
userQuery.include('CreatedBy');
userQuery.exclude('CreatedBy.authData');
userQuery.exclude('google_refresh_token');
if (userId) {
userQuery.equalTo('CreatedBy', { __type: 'Pointer', className: '_User', objectId: userId });
@@ -1,8 +1,9 @@
import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js';
export default async function getapitoken(request) {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
try {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': process.env.APP_ID,
@@ -16,14 +17,18 @@ export default async function getapitoken(request) {
const res = await tokenQuery.first({ useMasterKey: true });
if (res) {
return { status: 'success', result: res.get('token') };
} else {
return { error: 'api token found.' };
}
} else {
return { error: 'Invalid session token.' };
}
} catch (err) {
console.log('Err in getapitoken', err);
if (err.code == 209) {
return { error: 'Invalid session token' };
return { error: 'Invalid session token.' };
} else {
return { error: "You don't have access!" };
return { error: "You don't have access." };
}
}
}
@@ -29,11 +29,7 @@ async function uploadFile(pdfName, filepath) {
// `updateDoc` is used to update signedUrl, AuditTrail, Iscompleted in document
async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
try {
const UserPtr = {
__type: 'Pointer',
className: className,
objectId: userId,
};
const UserPtr = { __type: 'Pointer', className: className, objectId: userId };
const obj = {
UserPtr: UserPtr,
SignedUrl: url,
@@ -66,11 +62,7 @@ async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
} else {
isCompleted = true;
}
const body = {
SignedUrl: url,
AuditTrail: updateAuditTrail,
IsCompleted: isCompleted,
};
const body = { SignedUrl: url, AuditTrail: updateAuditTrail, IsCompleted: isCompleted };
const signedRes = await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, {
headers: {
'Content-Type': 'application/json',
@@ -164,29 +156,16 @@ async function sendCompletedMail(obj) {
'X-Parse-Master-Key': masterKEY,
},
});
// console.log('Res ', res);
}
// `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) {
let signers = [];
if (signUser) {
signers = {
name: signUser?.Name,
email: signUser?.Email,
phone: signUser?.Phone,
};
signers = { name: signUser?.Name, email: signUser?.Email, phone: signUser?.Phone };
} else {
signers = doc?.Signers?.map(x => ({
name: x.Name,
email: x.Email,
phone: x.Phone,
})) || [
{
name: doc?.ExtUserPtr?.Name,
email: doc?.ExtUserPtr?.Email,
phone: doc?.ExtUserPtr?.Phone,
},
signers = doc?.Signers?.map(x => ({ name: x.Name, email: x.Email, phone: x.Phone })) || [
{ name: doc?.ExtUserPtr?.Name, email: doc?.ExtUserPtr?.Email, phone: doc?.ExtUserPtr?.Phone },
];
}
@@ -213,7 +192,6 @@ async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) {
})
.then(res => {
try {
// console.log('res ', res);
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', res?.status);
webhook.set('UserId', {
@@ -241,7 +219,6 @@ async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) {
console.log('err save in contracts_Webhook', err.message);
}
});
// console.log('res ', res.data);
}
}
@@ -281,15 +258,9 @@ const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailPro
if (doc.IsSendMail === false) {
console.log("don't send mail");
} else {
const mailObj = {
url: url,
isCustomMail: isCustomMail,
doc: doc,
mailProvider: mailProvider,
};
const mailObj = { url: url, isCustomMail: isCustomMail, doc: doc, mailProvider: mailProvider };
sendCompletedMail(mailObj);
}
saveFileUsage(CertificateBuffer.length, file.imageUrl, userId);
sendDoctoWebhook(doc, url, 'completed', '', file.imageUrl);
};
@@ -304,8 +275,7 @@ async function PDF(req) {
if (!req?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
} else {
const password = '';
const authUser = req?.user?.toJSON();
const userIP = req.headers['x-real-ip']; // client IPaddress
const docId = req.params.docId;
const reqUserId = req.params.userId;
const isCustomMail = req.params.isCustomCompletionMail || false;
@@ -320,7 +290,6 @@ async function PDF(req) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
const _resDoc = resDoc?.toJSON();
let signUser;
let className;
// `reqUserId` is send throught pdfrequest signing flow
@@ -346,18 +315,8 @@ async function PDF(req) {
// const P12Buffer = fs.readFileSync();
const P12Buffer = Buffer.from(pfxFile, 'base64');
const p12Cert = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
const UserPtr = {
__type: 'Pointer',
className: className,
objectId: signUser.objectId,
};
const obj = {
UserPtr: UserPtr,
SignedUrl: '',
Activity: 'Signed',
ipAddress: req.headers['x-real-ip'],
};
const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId };
const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP };
let updateAuditTrail;
if (_resDoc.AuditTrail && _resDoc.AuditTrail.length > 0) {
updateAuditTrail = [..._resDoc.AuditTrail, obj];
@@ -430,13 +389,13 @@ async function PDF(req) {
req.params.docId, //docId
data.imageUrl, // url
signUser.objectId, // userID
req.headers['x-real-ip'], // client ipAddress,
userIP, // client ipAddress,
_resDoc, // auditTrail, signers, etc data
className,
sign
className, // className based on flow
sign // sign base64
);
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
saveFileUsage(pdfSize, data.imageUrl, authUser.objectId);
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
if (updatedDoc && updatedDoc.isCompleted) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail };
sendMailsaveCertifcate(
@@ -445,7 +404,7 @@ async function PDF(req) {
data.imageUrl,
isCustomMail,
mailProvider,
authUser.objectId
_resDoc?.CreatedBy?.objectId
);
}
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
@@ -28,6 +28,7 @@ export default function reportJson(id, userId) {
'Signers.Email',
'Signers.Phone',
'Placeholders',
'IsSignyourself',
],
};
@@ -137,6 +138,7 @@ export default function reportJson(id, userId) {
'Signers.Phone',
'TimeToCompleteDays',
'Placeholders',
'IsSignyourself',
],
};
// declined documents report
@@ -241,6 +243,7 @@ export default function reportJson(id, userId) {
reportName: 'Recent signature requests',
params: {
Type: { $ne: 'Folder' },
SignedUrl: { $ne: null },
IsCompleted: { $ne: true },
IsDeclined: { $ne: true },
IsArchive: { $ne: true },
@@ -1,5 +1,5 @@
import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js';
import { cloudServerUrl, planCredits } from '../../Utils.js';
export default async function saveSubscription(request) {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID;
@@ -8,7 +8,10 @@ export default async function saveSubscription(request) {
const body = subscription;
const Next_billing_date = subscription.data.subscription.next_billing_at;
const planCode = subscription.data.subscription.plan.plan_code;
const event = subscription?.data?.event_type || '';
const credits = planCredits?.[planCode] || 0;
const isTeamPlan = planCode?.includes('team');
let newAddons = 0;
try {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
@@ -31,14 +34,113 @@ export default async function saveSubscription(request) {
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
const subscription = await subcriptionCls.first({ useMasterKey: true });
if (subscription) {
const resSubscription = await subcriptionCls.first({ useMasterKey: true });
const addons = subscription?.data?.subscription?.addons || [];
if (addons?.length > 0) {
let allowedUsersMonthly = 0;
let allowedUsersYearly = 0;
addons?.forEach(item => {
if (item.addon_code === 'extra-teams-users-monthly') {
allowedUsersMonthly += item.quantity;
} else if (item.addon_code === 'extra-teams-users-yearly') {
allowedUsersYearly += item.quantity;
} else if (item.addon_code === 'extra-users') {
allowedUsersMonthly += item.quantity;
}
});
if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) {
newAddons = allowedUsersMonthly + allowedUsersYearly + 1; // + 1 is Admin user
}
} else {
if (
planCode === 'teams-yearly' ||
planCode === 'teams-monthly' ||
planCode === 'team-weekly'
) {
newAddons = 1; // 1 is Admin user
}
}
if (resSubscription) {
const _resSub = JSON.parse(JSON.stringify(resSubscription));
const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subscription.id;
updateSubscription.id = resSubscription.id;
updateSubscription.set('SubscriptionId', SubscriptionId);
updateSubscription.set('SubscriptionDetails', body);
updateSubscription.set('Next_billing_date', new Date(Next_billing_date));
updateSubscription.set('PlanCode', planCode);
if (newAddons > 0) {
updateSubscription.set('AllowedUsers', parseInt(newAddons));
}
let existAddon = 0;
let allowedUsersMonthly = 0;
let allowedUsersYearly = 0;
_resSub.SubscriptionDetails?.data?.subscription?.addons?.forEach(item => {
if (item.addon_code === 'extra-teams-users-monthly') {
allowedUsersMonthly += item.quantity;
} else if (item.addon_code === 'extra-teams-users-yearly') {
allowedUsersYearly += item.quantity;
} else if (item.addon_code === 'extra-users') {
allowedUsersMonthly += item.quantity;
}
});
if (allowedUsersMonthly > 0 || allowedUsersYearly > 0) {
existAddon = allowedUsersMonthly + allowedUsersYearly + 1; // + 1 is Admin user
} else {
if (
planCode === 'teams-yearly' ||
planCode === 'teams-monthly' ||
planCode === 'team-weekly'
) {
existAddon = 1; // 1 is Admin user
}
}
const isSameAsPrevPlan = resSubscription?.get('PlanCode') === planCode;
if (isSameAsPrevPlan) {
const planCredits = resSubscription?.get('PlanCredits');
const existAllowedCredits = resSubscription?.get('AllowedCredits') || 0;
if (planCredits) {
const oldAddons = existAddon;
const substractedAddon = newAddons - oldAddons;
if (isTeamPlan && substractedAddon > 0) {
const newCredits = existAllowedCredits + substractedAddon * planCredits;
updateSubscription.set('AllowedCredits', newCredits);
if (event === 'subscription_created') {
updateSubscription.set('PlanCredits', credits);
}
} else if (isTeamPlan) {
const existCredits = existAddon * planCredits;
updateSubscription.set('AllowedCredits', existCredits);
if (event === 'subscription_created') {
updateSubscription.set('PlanCredits', credits);
}
} else {
updateSubscription.set('AllowedCredits', planCredits);
if (event === 'subscription_created') {
updateSubscription.set('PlanCredits', credits);
}
}
} else {
if (isTeamPlan) {
const newCredits = newAddons * credits;
updateSubscription.set('AllowedCredits', newCredits);
updateSubscription.set('PlanCredits', credits);
} else {
updateSubscription.set('AllowedCredits', credits);
updateSubscription.set('PlanCredits', credits);
}
}
} else {
if (credits > 0) {
if (isTeamPlan) {
const newCredits = newAddons * credits;
updateSubscription.set('AllowedCredits', newCredits);
updateSubscription.set('PlanCredits', credits);
} else {
updateSubscription.set('AllowedCredits', credits);
updateSubscription.set('PlanCredits', credits);
}
}
}
await updateSubscription.save(null, { useMasterKey: true });
return { status: 'update subscription!' };
} else {
@@ -62,6 +164,19 @@ export default async function saveSubscription(request) {
});
createSubscription.set('Next_billing_date', new Date(Next_billing_date));
createSubscription.set('PlanCode', planCode);
if (newAddons > 0) {
createSubscription.set('AllowedUsers', parseInt(newAddons));
}
if (credits > 0) {
if (isTeamPlan) {
const totalCredits = parseInt(newAddons) * credits;
createSubscription.set('AllowedCredits', totalCredits);
createSubscription.set('PlanCredits', credits);
} else {
createSubscription.set('AllowedCredits', credits);
createSubscription.set('PlanCredits', credits);
}
}
await createSubscription.save(null, { useMasterKey: true });
return { status: 'create subscription!' };
}
@@ -142,8 +142,7 @@ export default async function sendMailGmailProvider(_extRes, template) {
raw: email,
},
});
// console.log('response ', response);
console.log('gmail provider res: ', response?.status);
return { code: 200, message: 'Email sent successfully' };
} catch (error) {
console.error('Error sending email:', error);
@@ -6,7 +6,7 @@ import Mailgun from 'mailgun.js';
import { smtpenable, smtpsecure, updateMailCount, useLocal } from '../../Utils.js';
import sendMailGmailProvider from './sendMailGmailProvider.js';
import { createTransport } from 'nodemailer';
async function sendMailProvider(req) {
async function sendMailProvider(req, plan, monthchange) {
const mailgunApiKey = process.env.MAILGUN_API_KEY;
try {
let transporterSMTP;
@@ -69,18 +69,23 @@ async function sendMailProvider(req) {
};
let attachment;
// `certificateBuffer` used to create buffer from pdf file
try {
const certificateBuffer = fs.readFileSync('./exports/certificate.pdf');
const certificate = {
filename: 'certificate.pdf',
content: smtpenable ? certificateBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
data: smtpenable ? undefined : certificateBuffer,
};
attachment = [file, certificate];
} catch (err) {
const certificatePath = './exports/certificate.pdf';
if (fs.existsSync(certificatePath)) {
try {
// `certificateBuffer` used to create buffer from pdf file
const certificateBuffer = fs.readFileSync(certificatePath);
const certificate = {
filename: 'certificate.pdf',
content: smtpenable ? certificateBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
data: smtpenable ? undefined : certificateBuffer,
};
attachment = [file, certificate];
} catch (err) {
attachment = [file];
console.log('Err in read certificate sendmailv3', err);
}
} else {
attachment = [file];
console.log('Err in read certificate sendmailv3', err);
}
const from = req.params.from || '';
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
@@ -96,38 +101,44 @@ async function sendMailProvider(req) {
};
if (transporterSMTP) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res);
console.log('smtp transporter res: ', res?.response);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
await updateMailCount(req.params.extUserId, plan, monthchange);
}
try {
fs.unlinkSync('./exports/certificate.pdf');
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
if (fs.existsSync(certificatePath)) {
try {
fs.unlinkSync(certificatePath);
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
}
return { status: 'success' };
}
} else {
if (mailgunApiKey) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res);
console.log('mailgun res: ', res?.status);
if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
await updateMailCount(req.params.extUserId, plan, monthchange);
}
try {
fs.unlinkSync('./exports/certificate.pdf');
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
if (fs.existsSync(certificatePath)) {
try {
fs.unlinkSync(certificatePath);
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
}
return { status: 'success' };
}
} else {
try {
fs.unlinkSync('./exports/certificate.pdf');
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
if (fs.existsSync(certificatePath)) {
try {
fs.unlinkSync(certificatePath);
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
}
return { status: 'error' };
}
@@ -147,20 +158,20 @@ async function sendMailProvider(req) {
if (transporterSMTP) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res);
console.log('smtp transporter res: ', res?.response);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
await updateMailCount(req.params.extUserId, plan, monthchange);
}
return { status: 'success' };
}
} else {
if (mailgunApiKey) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res);
console.log('mailgun res: ', res?.status);
if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
await updateMailCount(req.params.extUserId, plan, monthchange);
}
return { status: 'success' };
}
@@ -180,6 +191,7 @@ async function sendmailv3(req) {
const mailProvider = req.params.mailProvider || 'default';
if (mailProvider) {
try {
const Plan = req.params.plan;
const extUserId = req.params.extUserId || '';
const pdfName = req.params.pdfName || '';
const template = {
@@ -203,8 +215,31 @@ async function sendmailv3(req) {
return { status: 'error' };
}
} else {
const nonCustomMail = await sendMailProvider(req);
return nonCustomMail;
if (Plan && Plan === 'freeplan') {
let MonthlyFreeEmails = _extRes?.MonthlyFreeEmails || 0;
if (_extRes?.LastEmailCountReset?.iso) {
const lastDate = new Date(_extRes?.LastEmailCountReset?.iso);
const newDate = new Date();
const isMonthChange = newDate.getMonth() > lastDate.getMonth();
if (isMonthChange) {
const nonCustomMail = await sendMailProvider(req, Plan, true);
return nonCustomMail;
} else {
if (MonthlyFreeEmails >= 15) {
return { status: 'quota-reached' };
} else {
const nonCustomMail = await sendMailProvider(req, Plan);
return nonCustomMail;
}
}
} else {
const nonCustomMail = await sendMailProvider(req, Plan);
return nonCustomMail;
}
} else {
const nonCustomMail = await sendMailProvider(req, '');
return nonCustomMail;
}
}
}
} catch (err) {
@@ -1,5 +1,5 @@
import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js';
import { cloudServerUrl, planCredits } from '../../Utils.js';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY;
@@ -135,6 +135,8 @@ async function saveSubscription(extUserId, UserId, tenantId, subscription) {
const SubscriptionId = subscription?.data?.subscription?.subscription_id || '';
const Next_billing_date = subscription?.data?.subscription?.next_billing_at || '';
const planCode = subscription?.data?.subscription?.plan?.plan_code || '';
const credits = planCredits?.[planCode] || 0;
try {
const createSubscription = new Parse.Object('contracts_Subscriptions');
createSubscription.set('SubscriptionId', SubscriptionId);
@@ -156,6 +158,10 @@ async function saveSubscription(extUserId, UserId, tenantId, subscription) {
});
createSubscription.set('Next_billing_date', new Date(Next_billing_date));
createSubscription.set('PlanCode', planCode);
if (credits > 0) {
createSubscription.set('AllowedCredits', credits);
createSubscription.set('PlanCredits', credits);
}
await createSubscription.save(null, { useMasterKey: true });
} catch (err) {
console.log('err in save subscription pgsignup', err);
@@ -0,0 +1,21 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.addBoolean('IsSignyourself');
return schema.update();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.deleteField('IsSignyourself');
return schema.update();
};
@@ -0,0 +1,31 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
// TODO: set className here
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.addPointer('DeclineBy', '_User');
// TODO: Set the schema here
// Example:
// schema.addString('name').addNumber('cash');
return schema.update();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
// TODO: set className here
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.deleteField('DeclineBy');
// TODO: Set the schema here
// Example:
// schema.deleteField('name').deleteField('cash');
return schema.update();
};
+490 -305
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -24,35 +24,35 @@
"@signpdf/placeholder-pdf-lib": "^3.2.4",
"@signpdf/signer-p12": "^3.2.4",
"@signpdf/signpdf": "^3.2.4",
"aws-sdk": "^2.1667.0",
"axios": "^1.7.3",
"aws-sdk": "^2.1687.0",
"axios": "^1.7.7",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express": "^4.21.0",
"form-data": "^4.0.0",
"generate-api-key": "^1.0.2",
"googleapis": "^140.0.1",
"jsonschema": "^1.4.1",
"mailgun.js": "^10.2.3",
"mongoose": "^8.5.2",
"mongoose": "^8.6.1",
"multer": "^1.4.5-lts.1",
"multer-s3": "^3.0.1",
"node-forge": "^1.3.1",
"nodemailer": "^6.9.14",
"nodemailer": "^6.9.15",
"parse": "^5.3.0",
"parse-dbtool": "^1.2.0",
"parse-server": "^7.2.0",
"parse-server-api-mail-adapter": "^4.0.0",
"pdf-lib": "^1.17.1",
"posthog-node": "^4.0.1",
"posthog-node": "^4.2.0",
"ws": "^8.18.0"
},
"type": "module",
"devDependencies": {
"@babel/eslint-parser": "^7.25.1",
"eslint": "^9.8.0",
"jasmine": "^5.2.0",
"mongodb-runner": "^5.6.4",
"eslint": "^9.9.1",
"jasmine": "^5.3.0",
"mongodb-runner": "^5.6.6",
"nodemon": "^3.1.4",
"nyc": "^17.0.0",
"prettier": "^3.3.3"
@@ -62,6 +62,6 @@
"parse": "$parse"
},
"engines": {
"node": ">=12.22.10 <21"
"node": "18 || 20 || 22"
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ services:
env_file: .env.prod
environment:
- NODE_ENV=production
- SERVER_URL=${HOST_URL:-https://localhost:3001/app}
- SERVER_URL=${HOST_URL:-https://localhost:3001}/app
networks:
- app-network
mongo:
+167 -127
View File
@@ -15,15 +15,15 @@
}
},
"node_modules/ansi-escapes": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz",
"integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
"integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
"dev": true,
"dependencies": {
"type-fest": "^1.0.2"
"environment": "^1.0.0"
},
"engines": {
"node": ">=12"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -78,31 +78,31 @@
}
},
"node_modules/cli-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
"integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"dev": true,
"dependencies": {
"restore-cursor": "^4.0.0"
"restore-cursor": "^5.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-truncate": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
"integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
"integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
"dev": true,
"dependencies": {
"slice-ansi": "^5.0.0",
"string-width": "^5.0.0"
"string-width": "^7.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -115,12 +115,12 @@
"dev": true
},
"node_modules/commander": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true,
"engines": {
"node": ">=16"
"node": ">=18"
}
},
"node_modules/cross-spawn": {
@@ -138,9 +138,9 @@
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
@@ -154,17 +154,23 @@
}
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"node_modules/emoji-regex": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
@@ -207,6 +213,18 @@
"node": ">=8"
}
},
"node_modules/get-east-asian-width": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
"integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-stream": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
@@ -283,30 +301,33 @@
"dev": true
},
"node_modules/lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
"dev": true,
"engines": {
"node": ">=10"
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/lint-staged": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.1.0.tgz",
"integrity": "sha512-ZPKXWHVlL7uwVpy8OZ7YQjYDAuO5X4kMh0XgZvPNxLcCCngd0PO5jKQyy3+s4TL2EnHoIXIzP1422f/l3nZKMw==",
"version": "15.2.9",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.9.tgz",
"integrity": "sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==",
"dev": true,
"dependencies": {
"chalk": "5.3.0",
"commander": "11.1.0",
"debug": "4.3.4",
"execa": "8.0.1",
"lilconfig": "2.1.0",
"listr2": "7.0.2",
"micromatch": "4.0.5",
"pidtree": "0.6.0",
"string-argv": "0.3.2",
"yaml": "2.3.4"
"chalk": "~5.3.0",
"commander": "~12.1.0",
"debug": "~4.3.6",
"execa": "~8.0.1",
"lilconfig": "~3.1.2",
"listr2": "~8.2.4",
"micromatch": "~4.0.7",
"pidtree": "~0.6.0",
"string-argv": "~0.3.2",
"yaml": "~2.5.0"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
@@ -319,41 +340,72 @@
}
},
"node_modules/listr2": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-7.0.2.tgz",
"integrity": "sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g==",
"version": "8.2.4",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz",
"integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==",
"dev": true,
"dependencies": {
"cli-truncate": "^3.1.0",
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
"log-update": "^5.0.1",
"rfdc": "^1.3.0",
"wrap-ansi": "^8.1.0"
"log-update": "^6.1.0",
"rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": ">=16.0.0"
"node": ">=18.0.0"
}
},
"node_modules/log-update": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz",
"integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
"dev": true,
"dependencies": {
"ansi-escapes": "^5.0.0",
"cli-cursor": "^4.0.0",
"slice-ansi": "^5.0.0",
"strip-ansi": "^7.0.1",
"wrap-ansi": "^8.0.1"
"ansi-escapes": "^7.0.0",
"cli-cursor": "^5.0.0",
"slice-ansi": "^7.1.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/is-fullwidth-code-point": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
"integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
"dev": true,
"dependencies": {
"get-east-asian-width": "^1.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update/node_modules/slice-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
"integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.2.1",
"is-fullwidth-code-point": "^5.0.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -361,12 +413,12 @@
"dev": true
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": {
"braces": "^3.0.2",
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
@@ -385,6 +437,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -482,55 +546,40 @@
}
},
"node_modules/restore-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"dev": true,
"dependencies": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor/node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/restore-cursor/node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true,
"dependencies": {
"mimic-fn": "^2.1.0"
"mimic-function": "^5.0.0"
},
"engines": {
"node": ">=6"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/restore-cursor/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
"node_modules/rfdc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true
},
"node_modules/shebang-command": {
@@ -592,17 +641,17 @@
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
"emoji-regex": "^10.3.0",
"get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=12"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -647,18 +696,6 @@
"node": ">=8.0"
}
},
"node_modules/type-fest": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
"dev": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -675,27 +712,30 @@
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
"ansi-styles": "^6.2.1",
"string-width": "^7.0.0",
"strip-ansi": "^7.1.0"
},
"engines": {
"node": ">=12"
"node": ">=18"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/yaml": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
"dev": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}