Compare commits

..
208 Commits
Author SHA1 Message Date
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 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
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
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
97 changed files with 10434 additions and 7296 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 # 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 # 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 # Set it to true if you want to generate the Sourcemap for debugging
GENERATE_SOURCEMAP=false 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) # 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)
+396 -574
View File
File diff suppressed because it is too large Load Diff
+14 -14
View File
@@ -4,16 +4,16 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.8.2", "@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.8.7", "@lottiefiles/dotlottie-react": "^0.8.9",
"@pdf-lib/fontkit": "^1.1.1", "@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.1.1", "@radix-ui/themes": "^3.1.3",
"@react-pdf/renderer": "^3.4.4", "@react-pdf/renderer": "^3.4.4",
"@reduxjs/toolkit": "^2.2.7", "@reduxjs/toolkit": "^2.2.7",
"axios": "^1.7.3", "axios": "^1.7.5",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18next": "^23.12.2", "i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.2", "i18next-http-backend": "^2.6.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"nth-check": "^2.1.1", "nth-check": "^2.1.1",
@@ -36,10 +36,10 @@
"react-i18next": "^15.0.1", "react-i18next": "^15.0.1",
"react-konva": "^18.2.10", "react-konva": "^18.2.10",
"react-pdf": "^9.1.0", "react-pdf": "^9.1.0",
"react-quill-new": "^3.2.2", "react-quill-new": "^3.3.0",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"react-rnd": "^10.4.11", "react-rnd": "^10.4.11",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.1",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1", "react-scrollbars-custom": "^4.1.1",
"react-select": "^5.8.0", "react-select": "^5.8.0",
@@ -52,7 +52,7 @@
"regex-parser": "^2.3.0", "regex-parser": "^2.3.0",
"serve": "^14.2.3", "serve": "^14.2.3",
"styled-components": "^5.3.0", "styled-components": "^5.3.0",
"web-vitals": "^4.2.2", "web-vitals": "^4.2.3",
"ws": "^8.18.0" "ws": "^8.18.0"
}, },
"scripts": { "scripts": {
@@ -94,10 +94,10 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.25.2", "@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3", "@babel/preset-env": "^7.25.4",
"@babel/preset-react": "^7.24.7", "@babel/preset-react": "^7.24.7",
"@babel/runtime-corejs2": "^7.25.0", "@babel/runtime-corejs2": "^7.25.4",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.20",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"commitizen": "^4.3.0", "commitizen": "^4.3.0",
@@ -110,11 +110,11 @@
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.34.3", "eslint-plugin-react": "^7.34.3",
"lint-staged": "^15.2.8", "lint-staged": "^15.2.8",
"mini-css-extract-plugin": "^2.9.0", "mini-css-extract-plugin": "^2.9.1",
"postcss": "^8.4.41", "postcss": "^8.4.45",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"pretty-quick": "^4.0.0", "pretty-quick": "^4.0.0",
"tailwindcss": "^3.4.9", "tailwindcss": "^3.4.10",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"webpack-cli": "^5.1.4" "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
+317 -185
View File
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import Parse from "parse"; import Parse from "parse";
import Title from "./Title"; import Title from "./Title";
import Loader from "../primitives/Loader"; import Loader from "../primitives/Loader";
import { copytoData } from "../constant/Utils"; import { copytoData, fetchSubscriptionInfo } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const"; import { isEnableSubscription } from "../constant/const";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
function generatePassword(length) { function generatePassword(length) {
@@ -27,15 +27,64 @@ const AddUser = (props) => {
password: "", password: "",
role: "" 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 [isLoader, setIsLoader] = useState(false);
const [teamList, setTeamList] = useState([]); const [teamList, setTeamList] = useState([]);
const [allowedUser, setAllowedUser] = useState(0);
const [err, setErr] = useState("");
const role = ["OrgAdmin", "Editor", "User"]; const role = ["OrgAdmin", "Editor", "User"];
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
useEffect(() => { useEffect(() => {
getTeamList(); getTeamList();
// eslint-disable-next-line
}, []); }, []);
const getTeamList = async () => { 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) })); setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
const teamRes = await Parse.Cloud.run("getteams", { active: true }); const teamRes = await Parse.Cloud.run("getteams", { active: true });
if (teamRes.length > 0) { if (teamRes.length > 0) {
@@ -67,14 +116,12 @@ const AddUser = (props) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
setIsLoader(true); setIsFormLoader(true);
const res = await checkUserExist(); const res = await checkUserExist();
if (res) { if (res) {
props.setIsAlert({ type: "danger", msg: t("user-already-exist") }); props.setIsAlert({ type: "danger", msg: t("user-already-exist") });
setIsLoader(false); setIsFormLoader(false);
setTimeout(() => { setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1000);
props.setIsAlert({ type: "success", msg: "" });
}, 1000);
} else { } else {
try { try {
const extUser = new Parse.Object("contracts_Users"); const extUser = new Parse.Object("contracts_Users");
@@ -157,14 +204,8 @@ const AddUser = (props) => {
props.handleUserData(parseData); props.handleUserData(parseData);
} }
setIsLoader(false); setIsFormLoader(false);
setFormdata({ setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
name: "",
email: "",
phone: "",
team: "",
role: ""
});
} }
} catch (err) { } catch (err) {
console.log("err ", err); console.log("err ", err);
@@ -204,19 +245,13 @@ const AddUser = (props) => {
} }
props.handleUserData(parseData); props.handleUserData(parseData);
} }
setIsLoader(false); setIsFormLoader(false);
setFormdata({ setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
name: "",
email: "",
phone: "",
team: "",
role: ""
});
} }
} }
} catch (err) { } catch (err) {
console.log("err", err); console.log("err", err);
setIsLoader(false); setIsFormLoader(false);
props.setIsAlert({ props.setIsAlert({
type: "danger", type: "danger",
msg: t("something-went-wrong-mssg") msg: t("something-went-wrong-mssg")
@@ -229,13 +264,7 @@ const AddUser = (props) => {
// Define a function to handle the "add yourself" checkbox // Define a function to handle the "add yourself" checkbox
const handleReset = () => { const handleReset = () => {
setFormdata({ setFormdata({ name: "", email: "", phone: "", team: "", role: "" });
name: "",
email: "",
phone: "",
team: "",
role: ""
});
if (props.closePopup) { if (props.closePopup) {
props.closePopup(); props.closePopup();
} }
@@ -245,10 +274,7 @@ const AddUser = (props) => {
if (name === "email") { if (name === "email") {
value = value?.toLowerCase()?.replace(/\s/g, ""); value = value?.toLowerCase()?.replace(/\s/g, "");
} }
setFormdata((prev) => ({ setFormdata((prev) => ({ ...prev, [name]: value }));
...prev,
[name]: value
}));
}; };
const copytoclipboard = (text) => { const copytoclipboard = (text) => {
@@ -256,163 +282,269 @@ const AddUser = (props) => {
props.setIsAlert({ type: "success", msg: t("copied") }); props.setIsAlert({ type: "success", msg: t("copied") });
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds 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 }));
}
}
} 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 ( return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-[#ffffff]"> <div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title={"Add User"} /> <Title title={t("add-user")} />
{isLoader && ( {isFormLoader && (
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50 rounded-box"> <div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader /> <Loader />
</div> </div>
)} )}
<div className="w-full mx-auto"> {!isLoader ? (
<form onSubmit={handleSubmit}> <>
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */} {err ? (
<div className="mb-3"> <div className="flex justify-center items-center h-[70px] text-[18px]">
<label {err}
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>
</div> </div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none"> ) : (
{t("password-generateed")} <div className="w-full mx-auto">
</div> {!isEnableSubscription || allowedUser > 0 ? (
</div> <form onSubmit={handleSubmit}>
<div className="mb-3"> {isEnableSubscription && (
<label <p className="flex justify-center mb-1">
htmlFor="phone" {t("remaing-users")}{" "}
className="block text-xs text-gray-700 font-semibold" <span
> className={`${
{t("phone")} allowedUser < 2 ? "op-text-accent" : "op-text-primary"
{/* <span className="text-[red] text-[13px]"> *</span> */} } font-medium ml-1`}
</label> >
<input {allowedUser} {t("of")} {planInfo.totalAllowedUser}
type="text" </span>
name="phone" </p>
placeholder="optional" )}
value={formdata.phone} <div className="mb-3">
onChange={(e) => handleChange(e)} <label
// required htmlFor="name"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" className="block text-xs text-gray-700 font-semibold"
/> >
</div> {t("name")}
{isEnableSubscription && ( <span className="text-[red] text-[13px]"> *</span>
<div className="mb-3"> </label>
<label <input
htmlFor="phone" type="text"
className="block text-xs text-gray-700 font-semibold" name="name"
> value={formdata.name}
Team<span className="text-[red] text-[13px]"> *</span> onChange={(e) => handleChange(e)}
</label> onInvalid={(e) =>
<select e.target.setCustomValidity(t("input-required"))
value={formdata.team} }
onChange={(e) => handleChange(e)} onInput={(e) => e.target.setCustomValidity("")}
name="team" required
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs" className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) => />
e.target.setCustomValidity(t("input-required")) </div>
} <div className="mb-3">
onInput={(e) => e.target.setCustomValidity("")} <label
required htmlFor="email"
> className="block text-xs text-gray-700 font-semibold"
<option defaultValue={""} value={""}> >
select {t("email")}
</option> <span className="text-[red] text-[13px]"> *</span>
{teamList.length > 0 && </label>
teamList.map((x) => ( <input
<option key={x.objectId} value={x.objectId}> type="email"
{x.Name} name="email"
</option> value={formdata.email}
))} onChange={(e) => handleChange(e)}
</select> 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", {
adminName: extUser?.CreatedBy?.name,
adminEmail: extUser?.CreatedBy?.email
})}
</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>
)} )}
<div className="mb-3"> </>
<label ) : (
htmlFor="phone" <div className="w-full h-[200px] inset-0 flex justify-center items-center z-50">
className="block text-xs text-gray-700 font-semibold" <Loader />
> </div>
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> </div>
); );
}; };
+340 -111
View File
@@ -3,9 +3,14 @@ import axios from "axios";
import SuggestionInput from "./shared/fields/SuggestionInput"; import SuggestionInput from "./shared/fields/SuggestionInput";
import Loader from "../primitives/Loader"; import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next"; 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 BulkSendUi = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate();
const [forms, setForms] = useState([]); const [forms, setForms] = useState([]);
const [formId, setFormId] = useState(2); const [formId, setFormId] = useState(2);
const formRef = useRef(null); const formRef = useRef(null);
@@ -13,6 +18,23 @@ const BulkSendUi = (props) => {
const [isSubmit, setIsSubmit] = useState(false); const [isSubmit, setIsSubmit] = useState(false);
const [allowedForm, setAllowedForm] = useState(0); const [allowedForm, setAllowedForm] = useState(0);
const [isSignatureExist, setIsSignatureExist] = useState(); 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: "",
name: "",
email: "",
isAdmin: true
});
const allowedSigners = 50; const allowedSigners = 50;
useEffect(() => { useEffect(() => {
signatureExist(); signatureExist();
@@ -20,14 +42,72 @@ const BulkSendUi = (props) => {
}, []); }, []);
//function to check atleast one signature field exist //function to check atleast one signature field exist
const signatureExist = () => { const signatureExist = async () => {
const getPlaceholder = props.item?.Placeholders; if (isEnableSubscription) {
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) => setIsLoader(true);
placeholderObj?.placeHolder?.some((holder) => try {
holder?.pos?.some((posItem) => posItem?.type === "signature") const subscription = await fetchSubscription();
) const extUser =
); localStorage.getItem("Extand_Class") &&
setIsSignatureExist(checkIsSignatureExistt); 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,
name: extUser?.CreatedBy?.name,
email: extUser?.CreatedBy?.email
}));
}
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(() => { useEffect(() => {
if (scrollOnNextUpdate && formRef.current) { if (scrollOnNextUpdate && formRef.current) {
@@ -76,29 +156,33 @@ const BulkSendUi = (props) => {
const handleAddForm = (e) => { const handleAddForm = (e) => {
e.preventDefault(); e.preventDefault();
// Check if the quick send limit has been reached // Check if the quick send limit has been reached
if (forms?.length < allowedForm) { if (isEnableSubscription && forms.length >= amount.totalcredits) {
if (props?.Placeholders.length > 0) { setIsQuotaReached(true);
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 { } else {
// If the limit has been reached, throw an error with the appropriate message if (forms?.length < allowedForm) {
alert(t("quick-send-alert-4")); 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 +246,9 @@ const BulkSendUi = (props) => {
: [...existSigner] : [...existSigner]
}); });
} else { } else {
Documents.push({ Documents.push({ ...props.item, Placeholders: updatedPlaceholders });
...props.item,
Placeholders: updatedPlaceholders
});
} }
}); });
//console.log("Documents ", Documents);
await batchQuery(Documents); await batchQuery(Documents);
}; };
@@ -180,9 +260,7 @@ const BulkSendUi = (props) => {
"X-Parse-Application-Id": localStorage.getItem("parseAppId"), "X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken") sessionToken: localStorage.getItem("accesstoken")
}; };
const params = { const params = { Documents: JSON.stringify(Documents) };
Documents: JSON.stringify(Documents)
};
try { try {
const res = await axios.post(functionsUrl, params, { headers: headers }); const res = await axios.post(functionsUrl, params, { headers: headers });
// console.log("res ", res); // console.log("res ", res);
@@ -196,83 +274,234 @@ const BulkSendUi = (props) => {
setIsSubmit(false); 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 ( return (
<> <>
{isSubmit && ( {isLoader ? (
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30"> <div className="w-full h-[100px] flex justify-center items-center z-[999]">
<Loader /> <Loader />
</div> </div>
)}
{props.Placeholders?.length > 0 ? (
isSignatureExist ? (
<>
{props.Placeholders?.some((x) => !x.signerObjId) ? (
<form onSubmit={handleSubmit}>
<div className="min-h-max max-h-[250px] overflow-y-auto">
{forms?.map((form, index) => (
<div
key={form.Id}
className="p-3 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")} {isSubmit && (
</div> <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", {
adminName: admin?.name,
adminEmail: admin?.email
})}
</div>
)}
</>
)}
</>
)}
</>
)} )}
</> </>
); );
+162 -128
View File
@@ -6,27 +6,30 @@ import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize"; import { useWindowSize } from "../hook/useWindowSize";
import { import {
checkIsSubscribed, checkIsSubscribed,
checkIsSubscribedTeam,
getAppLogo, getAppLogo,
openInNewTab, openInNewTab,
saveLanguageInLocal saveLanguageInLocal
} from "../constant/Utils"; } from "../constant/Utils";
import { isEnableSubscription, isStaging } from "../constant/const"; import { isEnableSubscription, isStaging } from "../constant/const";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ModalUi from "../primitives/ModalUi";
import QuotaCard from "../primitives/QuotaCard";
const Header = ({ showSidebar }) => { const Header = ({ showSidebar }) => {
const { t, i18n } = useTranslation(); const { t, i18n } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const { width } = useWindowSize(); const { width } = useWindowSize();
let username = localStorage.getItem("username"); const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp; const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true); const [isSubscribe, setIsSubscribe] = useState(true);
const [isPro, setIsPro] = useState(false); const [isTeam, setIsTeam] = useState({ plan: "", isValid: false });
const [isTeam, setIsTeam] = useState(false);
const [applogo, setAppLogo] = useState( const [applogo, setAppLogo] = useState(
localStorage.getItem("appLogo") || " " localStorage.getItem("appLogo") || " "
); );
const [emailUsed, setEmailUsed] = useState(0);
const [isModal, setIsModal] = useState(false);
const toggleDropdown = () => { const toggleDropdown = () => {
setIsOpen(!isOpen); setIsOpen(!isOpen);
@@ -37,9 +40,8 @@ const Header = ({ showSidebar }) => {
}, []); }, []);
async function checkSubscription() { async function checkSubscription() {
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed(); const subscribe = await checkIsSubscribed();
const getIsTeam = await checkIsSubscribedTeam(); if (subscribe.isValid) {
if (getIsSubscribe) {
const applogo = await getAppLogo(); const applogo = await getAppLogo();
if (applogo?.logo) { if (applogo?.logo) {
setAppLogo(applogo?.logo); setAppLogo(applogo?.logo);
@@ -47,20 +49,24 @@ const Header = ({ showSidebar }) => {
setAppLogo(localStorage.getItem("appLogo") || ""); setAppLogo(localStorage.getItem("appLogo") || "");
} }
} }
setIsPro(getIsSubscribe); setIsTeam(subscribe);
setIsTeam(getIsTeam); setIsSubscribe(subscribe.isValid);
setIsSubscribe(getIsSubscribe); 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); setIsOpen(false);
if (Parse?.User?.current()) { try {
try { await Parse.User.logOut();
Parse.User.logOut(); } catch (err) {
} catch (err) { console.log("Err while logging out", err);
console.log("Err", err);
}
} }
let appdata = localStorage.getItem("userSettings"); let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo"); let applogo = localStorage.getItem("appLogo");
@@ -103,129 +109,157 @@ const Header = ({ showSidebar }) => {
window.open("https://console.opensignlabs.com/"); window.open("https://console.opensignlabs.com/");
} }
}; };
const handleNavigation = () => {
navigate("/subscription");
};
const handleMailUsed = () => {
setIsModal(!isModal);
};
return ( return (
<div className="op-navbar bg-base-100 shadow"> <div>
<div className="flex-none"> {isEnableSubscription && (
<button <div className="shadow py-1 text-center bg-[#CAE4FA] text-[14px] p-1">
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation" {t("header-news")}
onClick={showSidebar} <span
> className="cursor-pointer font-medium underline text-blue-800"
<i className="fa-light fa-bars text-xl text-base-content"></i> onClick={() => navigate("/profile")}
</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"
> >
{" " + 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 <img
className="w-[35px] h-[35px] object-contain" className="object-contain h-full w-auto"
src={image} src={applogo}
alt="img" alt="img"
/> />
</div> </div>
)} </div>
{width >= 768 && ( <div id="profile-menu" className="flex-none gap-2">
<div {!isSubscribe && (
onClick={toggleDropdown} <div>
className="cursor-pointer text-base-content text-sm" <button
> className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
{username && username} 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>
)} {width >= 768 && (
<div className="op-dropdown op-dropdown-end" id="profile-menu"> <div
<div onClick={toggleDropdown}
tabIndex={0} className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden"
role="button" >
className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent" <img
> className="w-[35px] h-[35px] object-contain"
<i 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} tabIndex={0}
role="button" role="button"
onClick={toggleDropdown} className="op-btn op-btn-ghost op-btn-xs w-[10px] h-[20px] hover:bg-transparent"
className="fa-light fa-angle-down text-base-content" >
></i> <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> </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>
</div> </div>
<ModalUi isOpen={isModal}>
<QuotaCard isPaidInfo={true} handlClose={handleMailUsed} />
</ModalUi>
</div> </div>
); );
}; };
+9 -4
View File
@@ -47,8 +47,10 @@ const GoogleSignInBtn = ({
}); });
}); });
const clearStorage = async () => { const clearStorage = async () => {
if (Parse.User.current()) { try {
await Parse.User.logOut(); await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
} }
let baseUrl = localStorage.getItem("baseUrl"); let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId"); let appid = localStorage.getItem("parseAppId");
@@ -160,10 +162,13 @@ const GoogleSignInBtn = ({
alert(t("fill-required-details!")); alert(t("fill-required-details!"));
} }
}; };
const handleCloseModal = () => { const handleCloseModal = async () => {
setIsModal(false); 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 appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo"); let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid"); 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(); e.stopPropagation();
handleMenuItemClick("Download", data); 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" aria-hidden="true"
></i> ></i>
</td> </td>
@@ -370,7 +370,11 @@ function DriveBody(props) {
{/* folder */} {/* folder */}
<div <div
data-tut={props.dataTutSeventh} data-tut={props.dataTutSeventh}
onClick={() => handleOnclikFolder(data)} onClick={() => {
if (!rename) {
handleOnclikFolder(data);
}
}}
className="cursor-pointer" className="cursor-pointer"
> >
<svg <svg
@@ -383,7 +387,6 @@ function DriveBody(props) {
{rename === data.objectId ? ( {rename === data.objectId ? (
<input <input
onFocus={() => { onFocus={() => {
inputRef.current.setSelectionRange(0, 0);
const input = inputRef.current; const input = inputRef.current;
if (input) { if (input) {
input.select(); input.select();
@@ -395,9 +398,8 @@ function DriveBody(props) {
onKeyDown={(e) => handleEnterPress(e, data)} onKeyDown={(e) => handleEnterPress(e, data)}
ref={inputRef} ref={inputRef}
defaultValue={renameValue} defaultValue={renameValue}
// value={renameValue}
onChange={(e) => setRenameValue(e.target.value)} 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> <span className="fileName">{data.Name}</span>
@@ -423,7 +425,11 @@ function DriveBody(props) {
</ContextMenu.Root> </ContextMenu.Root>
</div> </div>
) : ( ) : (
<HoverCard.Root openDelay={0} closeDelay={100}> <HoverCard.Root
open={rename ? false : undefined}
openDelay={0}
closeDelay={100}
>
<HoverCard.Trigger asChild> <HoverCard.Trigger asChild>
<div> <div>
<ContextMenu.Root> <ContextMenu.Root>
@@ -432,7 +438,11 @@ function DriveBody(props) {
{/* pdf */} {/* pdf */}
<div <div
data-tut={props.dataTutSixth} data-tut={props.dataTutSixth}
onClick={() => checkPdfStatus(data)} onClick={() => {
if (!rename) {
checkPdfStatus(data);
}
}}
className="cursor-pointer" className="cursor-pointer"
> >
<svg <svg
@@ -447,7 +457,6 @@ function DriveBody(props) {
autoFocus={true} autoFocus={true}
type="text" type="text"
onFocus={() => { onFocus={() => {
inputRef.current.setSelectionRange(0, 0);
const input = inputRef.current; const input = inputRef.current;
if (input) { if (input) {
input.select(); input.select();
@@ -457,9 +466,8 @@ function DriveBody(props) {
onKeyDown={(e) => handleEnterPress(e, data)} onKeyDown={(e) => handleEnterPress(e, data)}
ref={inputRef} ref={inputRef}
defaultValue={renameValue} defaultValue={renameValue}
// value={renameValue}
onChange={(e) => setRenameValue(e.target.value)} 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> <span className="fileName">{data.Name}</span>
@@ -54,7 +54,7 @@ function DraftDocument() {
navigate(`/recipientSignPdf/${data.objectId}`); navigate(`/recipientSignPdf/${data.objectId}`);
} }
//checking if document has completed and signyour-self flow //checking if document has completed and signyour-self flow
else if (!signerExist && !isPlaceholder) { else if ((!signerExist && !isPlaceholder) || data?.IsSignyourself) {
navigate(`/signaturePdf/${data.objectId}`); navigate(`/signaturePdf/${data.objectId}`);
} }
//checking if document has declined by someone //checking if document has declined by someone
@@ -4,6 +4,8 @@ import ModalUi from "../../primitives/ModalUi";
import { radioButtonWidget } from "../../constant/Utils"; import { radioButtonWidget } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade"; import Upgrade from "../../primitives/Upgrade";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
function DropdownWidgetOption(props) { function DropdownWidgetOption(props) {
const { t } = useTranslation(); const { t } = useTranslation();
const [dropdownOptionList, setDropdownOptionList] = useState([ const [dropdownOptionList, setDropdownOptionList] = useState([
@@ -330,6 +332,55 @@ function DropdownWidgetOption(props) {
</div> </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) && ( {["checkbox", radioButtonWidget].includes(props.type) && (
<div className="flex flex-row gap-5 my-2 items-center text-center"> <div className="flex flex-row gap-5 my-2 items-center text-center">
{!props.isSignYourself && ( {!props.isSignYourself && (
@@ -362,6 +413,7 @@ function DropdownWidgetOption(props) {
</div> </div>
)} )}
</div> </div>
<div <div
className={`${ className={`${
props.type === "checkbox" && !props.isSignYourself props.type === "checkbox" && !props.isSignYourself
@@ -23,8 +23,8 @@ const EditTemplate = ({ template, onSuccess }) => {
}, []); }, []);
const fetchSubscription = async () => { const fetchSubscription = async () => {
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed(); const subscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe); setIsSubscribe(subscribe.isValid);
} }
}; };
const handleStrInput = (e) => { const handleStrInput = (e) => {
@@ -34,7 +34,7 @@ function EmailComponent({
const imgPng = const imgPng =
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png"; "https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3/`; let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"), "X-Parse-Application-Id": localStorage.getItem("parseAppId"),
@@ -74,26 +74,15 @@ function EmailComponent({
}); });
} }
} }
if (sendMail?.data?.result?.status === "success") {
if (sendMail && sendMail.data.result.status === "success") {
setSuccessEmail(true); setSuccessEmail(true);
setIsEmail(false);
setTimeout(() => { setTimeout(() => {
setSuccessEmail(false); setSuccessEmail(false);
setIsEmail(false);
setEmailValue(""); setEmailValue("");
setEmailList([]); setEmailList([]);
}, 1500); }, 1500);
setIsLoading(false); 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 { } else {
setIsLoading(false); setIsLoading(false);
setIsEmail(false); setIsEmail(false);
+98 -37
View File
@@ -34,7 +34,12 @@ function Header({
setIsEmail, setIsEmail,
completeBtnTitle, completeBtnTitle,
setIsEditTemplate, setIsEditTemplate,
isPublicTemplate isPublicTemplate,
clickOnZoomIn,
clickOnZoomOut,
handleRotationFun,
isDisableRotate,
templateId
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const filterPrefill = const filterPrefill =
@@ -48,7 +53,6 @@ function Header({
const currentDecline = { currnt: "Sure", isDeclined: true }; const currentDecline = { currnt: "Sure", isDeclined: true };
setIsDecline(currentDecline); setIsDecline(currentDecline);
}; };
return ( return (
<div className="flex py-[5px]"> <div className="flex py-[5px]">
{isMobile && isShowHeader ? ( {isMobile && isShowHeader ? (
@@ -73,7 +77,7 @@ function Header({
allPages={allPages} allPages={allPages}
changePage={changePage} changePage={changePage}
/> />
{pdfUrl && alreadySign ? ( {isCompleted || alreadySign ? (
<DropdownMenu.Root> <DropdownMenu.Root>
<DropdownMenu.Trigger asChild> <DropdownMenu.Trigger asChild>
<div className="op-link op-link-primary no-underline text-[16px] font-semibold pr-[3px] pl-[5px]"> <div className="op-link op-link-primary no-underline text-[16px] font-semibold pr-[3px] pl-[5px]">
@@ -184,13 +188,7 @@ function Header({
) : ( ) : (
<div <div
data-tut="reactourThird" data-tut="reactourThird"
onClick={() => { onClick={() => embedWidgetsData()}
if (!pdfUrl) {
embedWidgetsData();
} else if (isPdfRequestFiles) {
embedWidgetsData();
}
}}
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline" className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
> >
{t("finish")} {t("finish")}
@@ -207,7 +205,7 @@ function Header({
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Portal> <DropdownMenu.Portal>
<DropdownMenu.Content <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} sideOffset={5}
> >
<DropdownMenu.Item <DropdownMenu.Item
@@ -215,7 +213,7 @@ function Header({
onClick={() => onClick={() =>
handleDownloadPdf( handleDownloadPdf(
pdfDetails, pdfDetails,
pdfUrl, pdfDetails[0]?.URL,
setIsDownloading setIsDownloading
) )
} }
@@ -226,11 +224,57 @@ function Header({
></i> ></i>
<span className="font-[500]">{t("download")}</span> <span className="font-[500]">{t("download")}</span>
</DropdownMenu.Item> </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.Content>
</DropdownMenu.Portal> </DropdownMenu.Portal>
</DropdownMenu.Root> </DropdownMenu.Root>
</div> </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>
)} )}
</div> </div>
@@ -339,15 +383,32 @@ function Header({
</div> </div>
) : ( ) : (
<div className="flex" data-tut="reactourFifth"> <div className="flex" data-tut="reactourFifth">
<button {!templateId && (
onClick={() => window.history.go(-2)} <button
type="button" onClick={() => window.history.go(-2)}
className="op-btn op-btn-ghost op-btn-sm mr-[3px]" type="button"
> className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
{t("back")} >
</button> {t("back")}
</button>
)}
{currentSigner && ( {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 <button
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow" className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
onClick={() => handleDeclinePdfAlert()} onClick={() => handleDeclinePdfAlert()}
@@ -361,16 +422,22 @@ function Header({
> >
{t("finish")} {t("finish")}
</button> </button>
<button {!templateId && (
type="button" <button
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow" type="button"
onClick={() => className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading) onClick={() =>
} handleDownloadPdf(
> pdfDetails,
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i> pdfUrl,
<span className="hidden lg:block">{t("download")}</span> setIsDownloading
</button> )
}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
)}
</> </>
)} )}
</div> </div>
@@ -423,9 +490,7 @@ function Header({
<button <button
type="button" type="button"
className="op-btn op-btn-primary op-btn-sm shadow" className="op-btn op-btn-primary op-btn-sm shadow"
onClick={() => { onClick={() => embedWidgetsData()}
embedWidgetsData();
}}
> >
{t("sign-now")} {t("sign-now")}
</button> </button>
@@ -442,11 +507,7 @@ function Header({
<button <button
type="button" type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px]" className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => { onClick={() => embedWidgetsData()}
if (!pdfUrl) {
embedWidgetsData();
}
}}
> >
{t("finish")} {t("finish")}
</button> </button>
+22 -29
View File
@@ -3,44 +3,37 @@ import { useTranslation } from "react-i18next";
function PdfZoom(props) { function PdfZoom(props) {
const { t } = useTranslation(); 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 ( return (
// md:mt-[41px] xl:mt-[63px]mt=[] <span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[43px] 2xl:mt-[63px]">
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[50px] 2xl:mt-[63px]">
<span <span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer" className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={onClickZoomIn} onClick={() => props.clickOnZoomIn()}
title={t("zoom-in")} title={t("zoom-in")}
> >
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i> <i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
</span> </span>
<span {!props?.isDisableRotate && (
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer" <>
onClick={handleReset} <span
title={t("reset")} className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
> onClick={() => props.handleRotationFun(90)}
<i className="fa-light fa-magnifying-glass-arrows-rotate text-gray-500 2xl:text-[30px]"></i> title={t("rotate-right")}
</span> >
<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 <span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]" className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
onClick={onClickZoomOut} onClick={() => props.clickOnZoomOut()}
style={{ style={{
cursor: props.zoomPercent > 0 ? "pointer" : "default" cursor: props.zoomPercent > 0 ? "pointer" : "default"
}} }}
+334 -282
View File
@@ -4,6 +4,8 @@ import PlaceholderBorder from "./PlaceholderBorder";
import { Rnd } from "react-rnd"; import { Rnd } from "react-rnd";
import { import {
defaultWidthHeight, defaultWidthHeight,
fontColorArr,
fontsizeArr,
getContainerScale, getContainerScale,
handleCopyNextToWidget, handleCopyNextToWidget,
isTabAndMobile, isTabAndMobile,
@@ -15,6 +17,8 @@ import {
import PlaceholderType from "./PlaceholderType"; import PlaceholderType from "./PlaceholderType";
import moment from "moment"; import moment from "moment";
import "../../styles/opensigndrive.css"; import "../../styles/opensigndrive.css";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
const selectFormat = (data) => { const selectFormat = (data) => {
switch (data) { switch (data) {
@@ -89,9 +93,10 @@ const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy"; const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
function Placeholder(props) { function Placeholder(props) {
const { t } = useTranslation();
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 }); const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
const [isDraggingEnabled, setDraggingEnabled] = useState(true); const [isDraggingEnabled, setDraggingEnabled] = useState(true);
const [isShowDateFormat, setIsShowDateFormat] = useState(false); const [isDateModal, setIsDateModal] = useState(false);
const [containerScale, setContainerScale] = useState(); const [containerScale, setContainerScale] = useState();
const [selectDate, setSelectDate] = useState({ const [selectDate, setSelectDate] = useState({
date: date:
@@ -111,6 +116,7 @@ function Placeholder(props) {
: "" : ""
}); });
const [dateFormat, setDateFormat] = useState([]); const [dateFormat, setDateFormat] = useState([]);
const [clickonWidget, setClickonWidget] = useState({});
const [startDate, setStartDate] = useState( const [startDate, setStartDate] = useState(
props.pos.type === "date" && props.pos.type === "date" &&
getDefaultdate( getDefaultdate(
@@ -200,20 +206,6 @@ function Placeholder(props) {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectDate]); }, [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 //`handleWidgetIdandPopup` is used to set current widget id and open relative popup
const handleWidgetIdandPopup = () => { const handleWidgetIdandPopup = () => {
if (props.setSelectWidgetId) { if (props.setSelectWidgetId) {
@@ -338,7 +330,6 @@ function Placeholder(props) {
//condition for only placeholder and template flow //condition for only placeholder and template flow
if (props.data && props?.pos?.type !== textWidget) { if (props.data && props?.pos?.type !== textWidget) {
props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id); props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex( const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id (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 //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) { if (props.data && props?.pos?.type !== textWidget) {
props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id); props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex( const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id (data) => data.Id === props.data.Id
@@ -378,7 +368,6 @@ function Placeholder(props) {
props.setIsSelectId(checkIndex || 0); props.setIsSelectId(checkIndex || 0);
} else if (props.data && props.pos.type === textWidget) { } else if (props.data && props.pos.type === textWidget) {
props.setTempSignerId(props.uniqueId); props.setTempSignerId(props.uniqueId);
props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id); props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion.findIndex( const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id (data) => data.Id === props.data.Id
@@ -430,7 +419,11 @@ function Placeholder(props) {
props.setXyPostion, props.setXyPostion,
props.data && props.data.Id, props.data && props.data.Id,
false, 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 }); setSelectDate({ date: date, format: data?.format });
}; };
@@ -522,17 +515,12 @@ function Placeholder(props) {
)} )}
{/* setting icon only for date widgets */} {/* setting icon only for date widgets */}
{props.pos.type === "date" && selectDate && ( {props.pos.type === "date" && selectDate && (
<div <i
id="menu-container"
style={{
zIndex: "99",
top: "-19px",
right: props.isPlaceholder ? "60px" : "44px"
}}
className={`${isShowDateFormat ? "show" : ""} dropdown icon`}
onClick={(e) => { onClick={(e) => {
setIsShowDateFormat(!isShowDateFormat); props.setCurrWidgetsDetails(props.pos);
setIsDateModal(!isDateModal);
e.stopPropagation(); e.stopPropagation();
setClickonWidget(props.pos);
if (props.data) { if (props.data) {
props.setSignKey(props.pos.key); props.setSignKey(props.pos.key);
props.setUniqueId(props.data.Id); props.setUniqueId(props.data.Id);
@@ -543,8 +531,9 @@ function Placeholder(props) {
} }
}} }}
onTouchEnd={(e) => { onTouchEnd={(e) => {
props.setCurrWidgetsDetails(props.pos);
e.stopPropagation(); e.stopPropagation();
setIsShowDateFormat(!isShowDateFormat); setIsDateModal(!isDateModal);
if (props.data) { if (props.data) {
props.setSignKey(props.pos.key); props.setSignKey(props.pos.key);
props.setUniqueId(props.data.Id); props.setUniqueId(props.data.Id);
@@ -554,44 +543,15 @@ function Placeholder(props) {
props.setIsSelectId(checkIndex || 0); props.setIsSelectId(checkIndex || 0);
} }
}} }}
> style={{
<i zIndex: "99",
className="fa-light fa-gear icon" top: "-18px",
style={{ right: props.isPlaceholder ? "50px" : "30px",
color: "#188ae2", color: "#188ae2",
fontSize: "14px" fontSize: "14px"
}} }}
></i> className="fa-light fa-gear icon"
<div ></i>
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>
)} )}
{/* copy icon for all widgets */} {/* copy icon for all widgets */}
<i <i
@@ -705,228 +665,320 @@ function Placeholder(props) {
return "all-scroll"; return "all-scroll";
} }
}; };
return ( return (
<Rnd <>
id={props.pos.key} <Rnd
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""} id={props.pos.key}
key={props.pos.key} data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
lockAspectRatio={ key={props.pos.key}
![ lockAspectRatio={
textWidget, ![
"email", textWidget,
"name", "email",
"company", "name",
"job title", "company",
textInputWidget "job title",
].includes(props.pos.type) && textInputWidget
(props.pos.Width ].includes(props.pos.type) &&
? props.pos.Width / props.pos.Height (props.pos.Width
: defaultWidthHeight(props.pos.type).width / ? props.pos.Width / props.pos.Height
defaultWidthHeight(props.pos.type).height) : defaultWidthHeight(props.pos.type).width /
} defaultWidthHeight(props.pos.type).height)
enableResizing={{ }
top: false, enableResizing={{
right: false, top: false,
bottom: false, right: false,
left: false, bottom: false,
topRight: false, left: false,
bottomRight: topRight: false,
props.data && props.isNeedSign bottomRight:
? props.data?.signerObjId === props.signerObjId && props.data && props.isNeedSign
props.pos.type !== "checkbox" && ? props.data?.signerObjId === props.signerObjId &&
props.pos.type !== radioButtonWidget props.pos.type !== "checkbox" &&
? true props.pos.type !== radioButtonWidget
: false ? true
: props.pos.type !== radioButtonWidget && : false
props.pos.type !== "checkbox" && : props.pos.type !== radioButtonWidget &&
props.pos.key === props.selectWidgetId && props.pos.type !== "checkbox" &&
true, props.pos.key === props.selectWidgetId &&
bottomLeft: false, true,
topLeft: false bottomLeft: false,
}} topLeft: false
bounds="parent" }}
className="signYourselfBlock" bounds="parent"
style={{ className="signYourselfBlock"
border: "1px solid #007bff", style={{
borderRadius: "2px", border: "1px solid #007bff",
textAlign: borderRadius: "2px",
props.pos.type !== "name" && textAlign:
props.pos.type !== "company" && props.pos.type !== "name" &&
props.pos.type !== "job title" && props.pos.type !== "company" &&
"center", props.pos.type !== "job title" &&
cursor: getCursor(), "center",
zIndex: cursor: getCursor(),
props.pos.type === "date" zIndex:
? props.pos.key === props.selectWidgetId props.pos.type === "date"
? 99 + 1 ? props.pos.key === props.selectWidgetId
: 99 ? 99 + 1
: props?.pos?.zIndex : 99
? props.pos.zIndex : props?.pos?.zIndex
: "5", ? props.pos.zIndex
background: props.data ? props.data.blockColor : "rgb(203 233 237)" : "5",
}} background: props.data ? props.data.blockColor : "rgb(203 233 237)"
onDrag={() => { }}
setDraggingEnabled(true); onDrag={() => {
props.handleTabDrag && props.handleTabDrag(props.pos.key); setDraggingEnabled(true);
}} props.handleTabDrag && props.handleTabDrag(props.pos.key);
size={{ }}
width: size={{
props.pos.type === radioButtonWidget || props.pos.type === "checkbox" width:
? "auto" props.pos.type === radioButtonWidget ||
: props.posWidth(props.pos, props.isSignYourself), props.pos.type === "checkbox"
height: ? "auto"
props.pos.type === radioButtonWidget || props.pos.type === "checkbox" : props.posWidth(props.pos, props.isSignYourself),
? "auto" height:
: props.posHeight(props.pos, props.isSignYourself) props.pos.type === radioButtonWidget ||
}} props.pos.type === "checkbox"
onResizeStart={() => { ? "auto"
setDraggingEnabled(true); : props.posHeight(props.pos, props.isSignYourself)
props.setIsResize && props.setIsResize(true); }}
}} onResizeStart={() => {
onResizeStop={(e, direction, ref) => { setDraggingEnabled(true);
props.setIsResize && props.setIsResize(false); props.setIsResize && props.setIsResize(true);
props.handleSignYourselfImageResize && }}
props.handleSignYourselfImageResize( onResizeStop={(e, direction, ref) => {
ref, props.setIsResize && props.setIsResize(false);
props.pos.key, props.handleSignYourselfImageResize &&
props.xyPostion, props.handleSignYourselfImageResize(
props.setXyPostion, ref,
props.index, props.pos.key,
containerScale, props.xyPostion,
props.scale, props.setXyPostion,
props.data && props.data.Id, props.index,
props.isResize containerScale,
); props.scale,
}} props.data && props.data.Id,
disableDragging={ props.isResize
props.isNeedSign );
? true }}
: props.isPlaceholder && ![textWidget].includes(props.pos.type) disableDragging={
? false props.isNeedSign
: !isDraggingEnabled ? true
} : props.isPlaceholder && ![textWidget].includes(props.pos.type)
onDragStop={(event, dragElement) => ? false
props.handleStop && : !isDraggingEnabled
props.handleStop(event, dragElement, props.data?.Id, props.pos?.key) }
} onDragStop={(event, dragElement) =>
position={{ props.handleStop &&
x: xPos(props.pos, props.isSignYourself), props.handleStop(event, dragElement, props.data?.Id, props.pos?.key)
y: yPos(props.pos, props.isSignYourself) }
}} position={{
onResize={(e, direction, ref) => { x: xPos(props.pos, props.isSignYourself),
setPlaceholderBorder({ y: yPos(props.pos, props.isSignYourself)
w: ref.offsetWidth / (props.scale * containerScale), }}
h: ref.offsetHeight / (props.scale * containerScale) onResize={(e, direction, ref) => {
}); setPlaceholderBorder({
}} w: ref.offsetWidth / (props.scale * containerScale),
onClick={() => handleOnClickPlaceholder()} h: ref.offsetHeight / (props.scale * containerScale)
> });
{props.isShowBorder && }}
props.pos.type !== radioButtonWidget && onClick={() => handleOnClickPlaceholder()}
props.pos.type !== "checkbox" && >
props.pos.key === props.selectWidgetId ? ( {props.isShowBorder &&
<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 !== radioButtonWidget &&
props.pos.type !== "checkbox" && 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 && ( {props.isShowBorder && props.pos.key === props.selectWidgetId && (
<PlaceholderBorder <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}
setDraggingEnabled={setDraggingEnabled} setDraggingEnabled={setDraggingEnabled}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]} pos={props.pos}
isNeedSign={props.isNeedSign} isPlaceholder={props.isPlaceholder}
setSelectDate={setSelectDate} getCheckboxRenderWidth={getCheckboxRenderWidth}
selectDate={selectDate} scale={props.scale}
setValidateAlert={props.setValidateAlert} containerScale={containerScale}
setStartDate={setStartDate} placeholderBorder={placeholderBorder}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
/> />
)}
{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> </div>
) : ( </ModalUi>
<> </>
{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>
); );
} }
@@ -1,12 +1,21 @@
import React, { useState } from "react"; import React, { useState } from "react";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
import { randomId, textWidget } from "../../constant/Utils"; import {
handleCopyNextToWidget,
randomId,
textWidget
} from "../../constant/Utils";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
function PlaceholderCopy(props) { function PlaceholderCopy(props) {
const { t } = useTranslation(); const { t } = useTranslation();
const copyType = ["All pages", "All pages but last", "All pages but first"]; const copyType = [
const [selectCopyType, setSelectCopyType] = useState(""); { 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 //function for get copy placeholder position
const getCopyPlaceholderPosition = ( const getCopyPlaceholderPosition = (
type, type,
@@ -20,7 +29,7 @@ function PlaceholderCopy(props) {
(data) => data.xPosition !== currentPlaceholder.xPosition (data) => data.xPosition !== currentPlaceholder.xPosition
); );
//copy all page placeholder at requested position except first page //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) { if (filterPosition && filterPosition.length > 0) {
return { return {
pageNumber: newPageNumber, pageNumber: newPageNumber,
@@ -29,7 +38,7 @@ function PlaceholderCopy(props) {
} }
} }
//copy all page placeholder at requested position except last page //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) { if (existPlaceholderPosition) {
return { return {
pageNumber: newPageNumber, pageNumber: newPageNumber,
@@ -172,25 +181,65 @@ function PlaceholderCopy(props) {
//function for getting selected type placeholder copy //function for getting selected type placeholder copy
const handleApplyCopy = () => { const handleApplyCopy = () => {
if (selectCopyType === "All pages") { if (selectCopyType === 4) {
copyPlaceholder("all"); const signerPosition = props.xyPostion;
} else if (selectCopyType === "All pages but last") { let currentXYposition;
copyPlaceholder("last"); const signerId = props.signerObjId ? props.signerObjId : props.Id;
} else if (selectCopyType === "All pages but first") { if (signerId) {
copyPlaceholder("first"); 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 = () => { const handleUniqueId = () => {
if (props.widgetType === textWidget) { const signerId = props.signerObjId ? props.signerObjId : props.Id;
if (signerId && props.widgetType === textWidget) {
props.setUniqueId(props?.tempSignerId); props.setUniqueId(props?.tempSignerId);
props.setTempSignerId(""); props.setTempSignerId("");
} }
props.setIsPageCopy(false); props.setIsPageCopy(false);
setSelectCopyType(1);
}; };
return ( return (
<ModalUi <ModalUi
isOpen={props.isPageCopy} isOpen={props.isPageCopy}
title={t("copy-to-all-pages")} title={t("copy-title")}
handleClose={() => handleUniqueId()} handleClose={() => handleUniqueId()}
> >
<div className="h-full p-[20px] text-base-content"> <div className="h-full p-[20px] text-base-content">
@@ -201,11 +250,11 @@ function PlaceholderCopy(props) {
<input <input
className="mr-[8px] op-radio op-radio-xs" className="mr-[8px] op-radio op-radio-xs"
type="radio" type="radio"
value={data} value={data.id}
onChange={() => setSelectCopyType(data)} onChange={() => setSelectCopyType(data.id)}
checked={selectCopyType === data} checked={selectCopyType === data.id}
/> />
{t(`copy-type.${data}`)} {t(`copy-type.${data.type}`)}
</label> </label>
</div> </div>
); );
@@ -29,6 +29,8 @@ function PlaceholderType(props) {
const [textValue, setTextValue] = useState(); const [textValue, setTextValue] = useState();
const [selectedCheckbox, setSelectedCheckbox] = useState([]); const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const years = range(1990, getYear(new Date()) + 16, 1); const years = range(1990, getYear(new Date()) + 16, 1);
const fontSize = (props.pos.options?.fontSize || "12") + "px";
const fontColor = props.pos.options?.fontColor || "black";
const months = [ const months = [
"January", "January",
"February", "February",
@@ -143,7 +145,12 @@ function PlaceholderType(props) {
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => ( const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div <div
className={`${selectWidgetCls} text-[12px] overflow-hidden`} style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif"
}}
className={`${selectWidgetCls} overflow-hidden`}
onClick={onClick} onClick={onClick}
ref={ref} ref={ref}
> >
@@ -343,9 +350,13 @@ function PlaceholderType(props) {
return ( return (
<div key={ind} className="flex items-center text-center gap-0.5"> <div key={ind} className="flex items-center text-center gap-0.5">
<input <input
style={{
width: fontSize,
height: fontSize
}}
className={`${ className={`${
ind === 0 ? "mt-0" : "mt-[5px]" 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} onBlur={handleInputBlur}
disabled={ disabled={
props.isNeedSign && props.isNeedSign &&
@@ -379,7 +390,15 @@ function PlaceholderType(props) {
}} }}
/> />
{!props.pos.options?.isHideLabel && ( {!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> </div>
); );
@@ -410,20 +429,16 @@ function PlaceholderType(props) {
}} }}
className={textWidgetCls} className={textWidgetCls}
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor
: "12px",
color: props.pos.options?.fontColor || "black"
}} }}
cols="50" cols="50"
/> />
) : ( ) : (
<div <div
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor,
: "12px",
color: props.pos.options?.fontColor || "black",
overflow: "hidden" overflow: "hidden"
}} }}
> >
@@ -433,6 +448,10 @@ function PlaceholderType(props) {
case "dropdown": case "dropdown":
return props.data?.signerObjId === props.signerObjId ? ( return props.data?.signerObjId === props.signerObjId ? (
<select <select
style={{
fontSize: fontSize,
color: fontColor
}}
className={`${selectWidgetCls} text-[12px] bg-inherit`} className={`${selectWidgetCls} text-[12px] bg-inherit`}
id="myDropdown" id="myDropdown"
value={selectOption} value={selectOption}
@@ -450,20 +469,41 @@ function PlaceholderType(props) {
}} }}
> >
{/* Default/Title option */} {/* Default/Title option */}
<option value="" disabled hidden> <option
style={{
fontSize: fontSize,
color: fontColor
}}
value=""
disabled
hidden
>
{props?.pos?.options?.name} {props?.pos?.options?.name}
</option> </option>
{props.pos?.options?.values?.map((data, ind) => { {props.pos?.options?.values?.map((data, ind) => {
return ( return (
<option key={ind} value={data}> <option
style={{
fontSize: fontSize,
color: fontColor
}}
key={ind}
value={data}
>
{data} {data}
</option> </option>
); );
})} })}
</select> </select>
) : ( ) : (
<div className="text-[12px] overflow-hidden"> <div
style={{
fontSize: fontSize,
color: fontColor
}}
className=" overflow-hidden"
>
{props.pos?.options?.name {props.pos?.options?.name
? props.pos.options.name ? props.pos.options.name
: widgetTypeTraslation} : widgetTypeTraslation}
@@ -511,20 +551,16 @@ function PlaceholderType(props) {
}} }}
className={textWidgetCls} className={textWidgetCls}
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor
: "12px",
color: props.pos.options?.fontColor || "black"
}} }}
cols="50" cols="50"
/> />
) : ( ) : (
<div <div
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor,
: "12px",
color: props.pos.options?.fontColor || "black",
fontFamily: "Arial, sans-serif", fontFamily: "Arial, sans-serif",
overflow: "hidden" overflow: "hidden"
}} }}
@@ -555,20 +591,16 @@ function PlaceholderType(props) {
}} }}
className={textWidgetCls} className={textWidgetCls}
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor
: "12px",
color: props.pos.options?.fontColor || "black"
}} }}
cols="50" cols="50"
/> />
) : ( ) : (
<div <div
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor,
: "12px",
color: props.pos.options?.fontColor || "black",
overflow: "hidden", overflow: "hidden",
fontFamily: "Arial, sans-serif" fontFamily: "Arial, sans-serif"
}} }}
@@ -599,20 +631,16 @@ function PlaceholderType(props) {
}} }}
className={textWidgetCls} className={textWidgetCls}
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor
: "12px",
color: props.pos.options?.fontColor || "black"
}} }}
cols="50" cols="50"
/> />
) : ( ) : (
<div <div
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor,
: "12px",
color: props.pos.options?.fontColor || "black",
fontFamily: "Arial, sans-serif", fontFamily: "Arial, sans-serif",
overflow: "hidden" overflow: "hidden"
}} }}
@@ -680,7 +708,14 @@ function PlaceholderType(props) {
/> />
</div> </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> <span>
{props.selectDate {props.selectDate
? props.selectDate?.format ? props.selectDate?.format
@@ -732,10 +767,8 @@ function PlaceholderType(props) {
}} }}
className={textWidgetCls} className={textWidgetCls}
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor,
: "12px",
color: props.pos.options?.fontColor || "black",
fontFamily: "Arial, sans-serif" fontFamily: "Arial, sans-serif"
}} }}
cols="50" cols="50"
@@ -743,10 +776,8 @@ function PlaceholderType(props) {
) : ( ) : (
<div <div
style={{ style={{
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor,
: "12px",
color: props.pos.options?.fontColor || "black",
fontFamily: "Arial, sans-serif", fontFamily: "Arial, sans-serif",
overflow: "hidden" overflow: "hidden"
}} }}
@@ -761,9 +792,12 @@ function PlaceholderType(props) {
return ( return (
<div key={ind} className="flex items-center text-center gap-0.5"> <div key={ind} className="flex items-center text-center gap-0.5">
<input <input
className={`${ style={{
ind === 0 ? "mt-0" : "mt-[5px]" width: fontSize,
} ${"w-[15px] h-[15px]"} flex justify-center op-radio`} height: fontSize,
marginTop: ind > 0 ? "10px" : "0px"
}}
className={`flex justify-center op-radio`}
type="radio" type="radio"
disabled={ disabled={
props.isNeedSign && props.isNeedSign &&
@@ -778,7 +812,15 @@ function PlaceholderType(props) {
}} }}
/> />
{!props.pos.options?.isHideLabel && ( {!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> </div>
); );
@@ -808,10 +850,8 @@ function PlaceholderType(props) {
className={textWidgetCls} className={textWidgetCls}
style={{ style={{
fontFamily: "Arial, sans-serif", fontFamily: "Arial, sans-serif",
fontSize: props.pos.options?.fontSize fontSize: fontSize,
? props.pos.options?.fontSize + "px" color: fontColor
: "12px",
color: props.pos.options?.fontColor || "black"
}} }}
cols="50" cols="50"
/> />
@@ -16,7 +16,7 @@ const RecipientList = (props) => {
//function for onhover signer name change background color //function for onhover signer name change background color
const inputRef = useRef(null); const inputRef = useRef(null);
const isWidgetExist = (Id) => { const isWidgetExist = (Id) => {
return props.signerPos.some((x) => x.Id === Id); return props.signerPos.some((x) => x.Id === Id && x.placeHolder);
}; };
//handle drag start //handle drag start
@@ -63,9 +63,7 @@ const RecipientList = (props) => {
remainingItems.splice(index, 0, ...draggedItem); remainingItems.splice(index, 0, ...draggedItem);
//set current draggable recipient details,objectId,index,contract_className ... after replace recipient list //set current draggable recipient details,objectId,index,contract_className ... after replace recipient list
props?.setSignersData(remainingItems); props?.setSignersData(remainingItems);
props?.setSignerObjId(remainingItems[index]?.objectId || "");
props?.setIsSelectId(index); props?.setIsSelectId(index);
props?.setContractName(remainingItems[index]?.className || "");
props?.setUniqueId(remainingItems[index]?.Id); props?.setUniqueId(remainingItems[index]?.Id);
props?.setRoleName(remainingItems[index]?.Role); props?.setRoleName(remainingItems[index]?.Role);
props?.setBlockColor(remainingItems[index]?.blockColor); props?.setBlockColor(remainingItems[index]?.blockColor);
@@ -121,9 +119,7 @@ const RecipientList = (props) => {
}} }}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
props.setSignerObjId(obj?.objectId || "");
props.setIsSelectId(ind); props.setIsSelectId(ind);
props.setContractName(obj?.className || "");
props.setUniqueId(obj.Id); props.setUniqueId(obj.Id);
props.setRoleName(obj.Role); props.setRoleName(obj.Role);
props.setBlockColor(obj?.blockColor); props.setBlockColor(obj?.blockColor);
@@ -12,7 +12,8 @@ function RenderAllPdfPage({
pageNumber, pageNumber,
signerPos, signerPos,
signerObjectId, signerObjectId,
containerWH containerWH,
pdfRotateBase64
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [signPageNumber, setSignPageNumber] = useState([]); const [signPageNumber, setSignPageNumber] = useState([]);
@@ -66,6 +67,7 @@ function RenderAllPdfPage({
) )
); );
}; };
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
return ( return (
<div ref={pageContainer} className="hidden w-[20%] bg-base-100 md:block"> <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"> <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 <Document
loading={t("loading-doc")} loading={t("loading-doc")}
onLoadSuccess={onDocumentLoad} onLoadSuccess={onDocumentLoad}
file={signPdfUrl} file={(pdfRotateBase64 && pdfDataBase64) || signPdfUrl}
> >
{Array.from(new Array(allPages), (el, index) => ( {Array.from(new Array(allPages), (el, index) => (
<div <div
@@ -95,16 +97,16 @@ function RenderAllPdfPage({
> >
{signerPos && addSignatureBookmark(index)} {signerPos && addSignatureBookmark(index)}
<div className="relative z-[1] overflow-hidden"> {/* <div className="relative z-[1] overflow-hidden"> */}
<Page <Page
key={`page_${index + 1}`} key={`page_${index + 1}`}
pageNumber={index + 1} pageNumber={index + 1}
width={pageWidth - 60} width={pageWidth - 60}
scale={1} scale={1}
renderAnnotationLayer={false} renderAnnotationLayer={false}
renderTextLayer={false} renderTextLayer={false}
/> />
</div> {/* </div> */}
</div> </div>
))} ))}
</Document> </Document>
+254 -206
View File
@@ -41,7 +41,6 @@ function RenderPdf({
setUniqueId, setUniqueId,
signersdata, signersdata,
setIsPageCopy, setIsPageCopy,
setSignerObjId,
setShowDropdown, setShowDropdown,
setIsInitial, setIsInitial,
setIsValidate, setIsValidate,
@@ -61,10 +60,16 @@ function RenderPdf({
scale, scale,
setIsSelectId, setIsSelectId,
ispublicTemplate, ispublicTemplate,
handleUserDetails handleUserDetails,
pdfRotateBase64,
fontSize,
setFontSize,
fontColor,
setFontColor
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const isMobile = window.innerWidth < 767; const isMobile = window.innerWidth < 767;
//check isGuestSigner is present in local if yes than handle login flow header in mobile view //check isGuestSigner is present in local if yes than handle login flow header in mobile view
const isGuestSigner = localStorage.getItem("isGuestSigner"); const isGuestSigner = localStorage.getItem("isGuestSigner");
@@ -248,155 +253,180 @@ function RenderPdf({
); );
} }
}; };
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
return ( return (
<> <>
{successEmail && ( {successEmail && (
<Alert type={"success"}>{t("success-email-alert")}</Alert> <Alert type={"success"}>{t("success-email-alert")}</Alert>
)} )}
{isMobile ? ( {isMobile ? (
<div <RSC
data-tut="reactourForth" style={{
className={`${ position: "relative",
isGuestSigner ? "30px" : "" boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
} border-[0.1px] border-[#ebe8e8]`} height: window.innerHeight - 200,
ref={drop} zIndex: 0
id="container" }}
className="h-full"
noScrollY={false}
noScrollX={scale === 1 ? true : false}
> >
{containerWH?.width && <div
pdfOriginalWH.length > 0 && data-tut="reactourForth"
(pdfRequest className={`${
? signerPos.map((data, key) => { isGuestSigner ? "30px" : ""
return ( } border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
<React.Fragment key={key}> style={{
{checkSignedSigners(data)} width: containerWH?.width && containerWH?.width * scale
</React.Fragment> }}
); ref={drop}
}) id="container"
: placeholder // placeholder mobile >
? signerPos.map((data, ind) => { {containerWH?.width &&
pdfOriginalWH.length > 0 &&
(pdfRequest
? signerPos.map((data, key) => {
return ( return (
<React.Fragment key={ind}> <React.Fragment key={key}>
{data.placeHolder.map((placeData, index) => { {checkSignedSigners(data)}
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> </React.Fragment>
); );
}) })
: xyPostion.map((data, ind) => { : placeholder // placeholder mobile
return ( ? signerPos?.map((data, ind) => {
<React.Fragment key={ind}> return (
{data.pageNumber === pageNumber && <React.Fragment key={ind}>
data.pos.map((pos) => { {data?.placeHolder &&
return ( data?.placeHolder.map((placeData, index) => {
pos && ( return (
<Placeholder <React.Fragment key={index}>
pos={pos} {placeData.pageNumber === pageNumber &&
setIsPageCopy={setIsPageCopy} placeData.pos.map((pos) => {
setSignKey={setSignKey} return (
handleDeleteSign={handleDeleteSign} <React.Fragment key={pos.key}>
setIsStamp={setIsStamp} <Placeholder
handleTabDrag={handleTabDrag} pos={pos}
handleStop={handleStop} setIsPageCopy={setIsPageCopy}
handleSignYourselfImageResize={ setSignKey={setSignKey}
handleSignYourselfImageResize handleDeleteSign={handleDeleteSign}
} setIsStamp={setIsStamp}
index={index} handleTabDrag={handleTabDrag}
xyPostion={xyPostion} handleStop={handleStop}
setXyPostion={setXyPostion} handleSignYourselfImageResize={
containerWH={containerWH} handleImageResize
setIsSignPad={setIsSignPad} }
isShowBorder={true} index={pageNumber}
isSignYourself={true} xyPostion={signerPos}
posWidth={posWidth} setXyPostion={setSignerPos}
posHeight={posHeight} data={data}
pdfDetails={pdfDetails[0]} setIsResize={setIsResize}
isDragging={isDragging} setShowDropdown={setShowDropdown}
setIsInitial={setIsInitial} isShowBorder={true}
setWidgetType={setWidgetType} isPlaceholder={true}
setSelectWidgetId={setSelectWidgetId} setUniqueId={setUniqueId}
selectWidgetId={selectWidgetId} handleLinkUser={handleLinkUser}
handleUserName={handleUserName} handleUserName={handleUserName}
setIsCheckbox={setIsCheckbox} isSignYourself={false}
setValidateAlert={setValidateAlert} posWidth={posWidth}
setCurrWidgetsDetails={setCurrWidgetsDetails} posHeight={posHeight}
handleTextSettingModal={ isDragging={isDragging}
handleTextSettingModal setIsValidate={setIsValidate}
} setWidgetType={setWidgetType}
scale={scale} setIsRadio={setIsRadio}
pdfOriginalWH={pdfOriginalWH} setIsCheckbox={setIsCheckbox}
pageNumber={pageNumber} setCurrWidgetsDetails={
/> setCurrWidgetsDetails
) }
); setSelectWidgetId={
})} setSelectWidgetId
</React.Fragment> }
); 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 <Document
onLoadError={() => { onLoadError={() => setPdfLoad(false)}
setPdfLoad(false);
}}
loading={t("loading-doc")} loading={t("loading-doc")}
onLoadSuccess={pageDetails} onLoadSuccess={pageDetails}
// ref={pdfRef}' // ref={pdfRef}'
@@ -405,9 +435,14 @@ function RenderPdf({
setSelectWidgetId(""); setSelectWidgetId("");
} }
}} }}
file={pdfDetails[0]?.SignedUrl || pdfDetails[0]?.URL} file={
(pdfRotateBase64 && pdfDataBase64) ||
pdfDetails[0]?.SignedUrl ||
pdfDetails[0].URL
}
> >
<Page <Page
scale={scale || 1}
key={index} key={index}
pageNumber={pageNumber} pageNumber={pageNumber}
width={containerWH.width} width={containerWH.width}
@@ -419,7 +454,7 @@ function RenderPdf({
/> />
</Document> </Document>
</div> </div>
</div> </RSC>
) : ( ) : (
<RSC <RSC
style={{ style={{
@@ -453,71 +488,78 @@ function RenderPdf({
? signerPos.map((data, ind) => { ? signerPos.map((data, ind) => {
return ( return (
<React.Fragment key={ind}> <React.Fragment key={ind}>
{data.placeHolder.map((placeData, index) => { {data?.placeHolder &&
return ( data?.placeHolder.map((placeData, index) => {
<React.Fragment key={index}> return (
{placeData.pageNumber === pageNumber && <React.Fragment key={index}>
placeData.pos.map((pos) => { {placeData.pageNumber === pageNumber &&
return ( placeData.pos.map((pos) => {
<React.Fragment key={pos.key}> return (
<Placeholder <React.Fragment key={pos.key}>
pos={pos} <Placeholder
setIsPageCopy={setIsPageCopy} pos={pos}
setSignKey={setSignKey} setIsPageCopy={setIsPageCopy}
handleDeleteSign={handleDeleteSign} setSignKey={setSignKey}
setIsStamp={setIsStamp} handleDeleteSign={handleDeleteSign}
handleTabDrag={handleTabDrag} setIsStamp={setIsStamp}
handleStop={handleStop} handleTabDrag={handleTabDrag}
handleSignYourselfImageResize={ handleStop={handleStop}
handleImageResize handleSignYourselfImageResize={
} handleImageResize
index={pageNumber} }
xyPostion={signerPos} index={pageNumber}
setXyPostion={setSignerPos} xyPostion={signerPos}
setSignerObjId={setSignerObjId} setXyPostion={setSignerPos}
data={data} data={data}
setIsResize={setIsResize} setIsResize={setIsResize}
setShowDropdown={setShowDropdown} setShowDropdown={setShowDropdown}
isShowBorder={true} isShowBorder={true}
isPlaceholder={true} isPlaceholder={true}
setUniqueId={setUniqueId} setUniqueId={setUniqueId}
handleLinkUser={handleLinkUser} handleLinkUser={handleLinkUser}
handleUserName={handleUserName} handleUserName={handleUserName}
isSignYourself={false} isSignYourself={false}
posWidth={posWidth} posWidth={posWidth}
posHeight={posHeight} posHeight={posHeight}
isDragging={isDragging} isDragging={isDragging}
setIsValidate={setIsValidate} setIsValidate={setIsValidate}
setWidgetType={setWidgetType} setWidgetType={setWidgetType}
setIsRadio={setIsRadio} setIsRadio={setIsRadio}
setIsCheckbox={setIsCheckbox} setIsCheckbox={setIsCheckbox}
setCurrWidgetsDetails={ setCurrWidgetsDetails={
setCurrWidgetsDetails setCurrWidgetsDetails
} }
setSelectWidgetId={setSelectWidgetId} setSelectWidgetId={
selectWidgetId={selectWidgetId} setSelectWidgetId
handleNameModal={handleNameModal} }
setTempSignerId={setTempSignerId} selectWidgetId={selectWidgetId}
uniqueId={uniqueId} handleNameModal={handleNameModal}
handleTextSettingModal={ setTempSignerId={setTempSignerId}
handleTextSettingModal uniqueId={uniqueId}
} handleTextSettingModal={
scale={scale} handleTextSettingModal
containerWH={containerWH} }
pdfOriginalWH={pdfOriginalWH} scale={scale}
pageNumber={pageNumber} containerWH={containerWH}
setIsSelectId={setIsSelectId} pdfOriginalWH={pdfOriginalWH}
/> pageNumber={pageNumber}
</React.Fragment> setIsSelectId={setIsSelectId}
); fontSize={fontSize}
})} setFontSize={setFontSize}
</React.Fragment> fontColor={fontColor}
); setFontColor={setFontColor}
})} />
</React.Fragment>
);
})}
</React.Fragment>
);
})}
</React.Fragment> </React.Fragment>
); );
}) })
: xyPostion.map((data, ind) => { : !pdfDetails?.[0]?.IsCompleted &&
xyPostion?.map((data, ind) => {
// signyourself flow // signyourself flow
return ( return (
<React.Fragment key={ind}> <React.Fragment key={ind}>
@@ -565,6 +607,10 @@ function RenderPdf({
containerWH={containerWH} containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH} pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber} pageNumber={pageNumber}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/> />
</React.Fragment> </React.Fragment>
); );
@@ -575,9 +621,7 @@ function RenderPdf({
{/* this component for render pdf document is in middle of the component */} {/* this component for render pdf document is in middle of the component */}
<Document <Document
onLoadError={() => { onLoadError={() => setPdfLoad(false)}
setPdfLoad(false);
}}
loading={t("loading-doc")} loading={t("loading-doc")}
onLoadSuccess={pageDetails} onLoadSuccess={pageDetails}
onClick={() => { onClick={() => {
@@ -586,7 +630,11 @@ function RenderPdf({
} }
}} }}
// ref={pdfRef} // ref={pdfRef}
file={pdfDetails[0]?.SignedUrl || pdfDetails[0].URL} file={
(pdfRotateBase64 && pdfDataBase64) ||
pdfDetails[0]?.SignedUrl ||
pdfDetails[0].URL
}
> >
<Page <Page
key={index} key={index}
@@ -21,15 +21,15 @@ function SelectLanguage(props) {
return ( return (
<div <div
className={`${ className={`${
!props.isProfile && " mt-3 pb-2 md:pb-0 " !props.isProfile && " mt-[9px] pb-2 md:pb-0 "
} flex justify-center items-center `} } flex justify-center items-center `}
> >
<select <select
value={lang} value={lang}
onChange={handleChangeLang} onChange={handleChangeLang}
className={`${ className={`${
!props.isProfile && " md:w-[15%] w-[50%]" !props.isProfile ? " md:w-[15%] w-[50%]" : "w-[180px]"
} op-select op-select-bordered bg-white op-select-sm`} } op-select op-select-bordered bg-white op-select-sm `}
> >
<option disabled selected> <option disabled selected>
select select
@@ -56,9 +56,7 @@ function VerifyEmail(props) {
<button <button
className="op-btn op-btn-primary" className="op-btn op-btn-primary"
type="submit" type="submit"
onClick={() => { onClick={() => props.handleVerifyBtn()}
props.handleVerifyBtn();
}}
> >
{t("send-otp")} {t("send-otp")}
</button> </button>
@@ -20,9 +20,7 @@ function WidgetComponent({
addPositionOfSignature, addPositionOfSignature,
signersdata, signersdata,
isSelectListId, isSelectListId,
setSignerObjId,
setIsSelectId, setIsSelectId,
setContractName,
isSigners, isSigners,
dataTut, dataTut,
isMailSend, isMailSend,
@@ -360,9 +358,7 @@ function WidgetComponent({
signerPos={signerPos} signerPos={signerPos}
signersdata={signersdata} signersdata={signersdata}
isSelectListId={isSelectListId} isSelectListId={isSelectListId}
setSignerObjId={setSignerObjId}
setIsSelectId={setIsSelectId} setIsSelectId={setIsSelectId}
setContractName={setContractName}
setUniqueId={setUniqueId} setUniqueId={setUniqueId}
setRoleName={setRoleName} setRoleName={setRoleName}
handleDeleteUser={handleDeleteUser} handleDeleteUser={handleDeleteUser}
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import Select from "react-select"; import AsyncSelect from "react-select/async";
import AddSigner from "../../AddSigner"; import AddSigner from "../../AddSigner";
import Parse from "parse"; import Parse from "parse";
import Tooltip from "../../../primitives/Tooltip"; import Tooltip from "../../../primitives/Tooltip";
@@ -35,7 +35,6 @@ const AddSignerModal = ({ isOpen, children }) => {
const SignersInput = (props) => { const SignersInput = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [state, setState] = useState(undefined); const [state, setState] = useState(undefined);
// const [editFormData, setEditFormData] = useState([]);
const [selected, setSelected] = useState([]); const [selected, setSelected] = useState([]);
const [isModal, setIsModel] = useState(false); const [isModal, setIsModel] = useState(false);
const onChange = (selectedOptions) => setSelected(selectedOptions); const onChange = (selectedOptions) => setSelected(selectedOptions);
@@ -46,40 +45,6 @@ const SignersInput = (props) => {
setSelected(newValue); 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(() => { useEffect(() => {
if (props.isReset && props.isReset === true) { if (props.isReset && props.isReset === true) {
setSelected([]); setSelected([]);
@@ -118,7 +83,46 @@ const SignersInput = (props) => {
setSelected([data]); 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 ( return (
<div className="text-xs mt-2 "> <div className="text-xs mt-2 ">
<label className="block relative"> <label className="block relative">
@@ -130,16 +134,20 @@ const SignersInput = (props) => {
</label> </label>
<div className="flex gap-x-[5px]"> <div className="flex gap-x-[5px]">
<div className="w-full"> <div className="w-full">
<Select <AsyncSelect
onSortEnd={onSortEnd} onSortEnd={onSortEnd}
distance={4} distance={4}
isMulti isMulti
cacheOptions
defaultOptions
options={state || []} options={state || []}
value={selected} value={selected}
onChange={onChange} onChange={onChange}
closeMenuOnSelect={false} closeMenuOnSelect={false}
required={props.required} required={props.required}
loadingMessage={() => t("loading")}
noOptionsMessage={() => t("contact-not-found")} noOptionsMessage={() => t("contact-not-found")}
loadOptions={loadOptions}
unstyled unstyled
classNames={{ classNames={{
control: () => control: () =>
+257 -85
View File
@@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
import moment from "moment"; import moment from "moment";
import React from "react"; import React from "react";
import { rgb } from "pdf-lib"; import { PDFDocument, rgb, degrees } from "pdf-lib";
import Parse from "parse"; import Parse from "parse";
import { appInfo } from "./appinfo"; import { appInfo } from "./appinfo";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
@@ -56,18 +56,68 @@ export async function fetchSubscription(
? { contactId: contactObjId } ? { contactId: contactObjId }
: { extUserId: extUser }; : { extUserId: extUser };
const tenatRes = await axios.post(url, params, { headers: headers }); const tenatRes = await axios.post(url, params, { headers: headers });
let plan, status, billingDate; let plan, status, billingDate, adminId;
if (isGuestSign) { if (isGuestSign) {
plan = tenatRes.data?.result?.result?.plan; plan = tenatRes.data?.result?.result?.plan;
status = tenatRes.data?.result?.result?.isSubscribed; status = tenatRes.data?.result?.result?.isSubscribed;
} else { } else {
plan = tenatRes.data?.result?.result?.PlanCode; plan = tenatRes.data?.result?.result?.PlanCode;
billingDate = tenatRes.data?.result?.result?.Next_billing_date?.iso; 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) { } catch (err) {
console.log("Err in fetch subscription", 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 //function to get subcripition details from subscription class
@@ -75,41 +125,22 @@ export async function checkIsSubscribed() {
try { try {
const res = await fetchSubscription(); const res = await fetchSubscription();
if (res.plan === "freeplan") { if (res.plan === "freeplan") {
return false; return { plan: res.plan, isValid: false, adminId: res?.adminId };
} 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;
} else if (res.billingDate) { } else if (res.billingDate) {
const plan = validplan[res.plan] || false; const plan = validplan[res.plan] || false;
if (plan && new Date(res.billingDate) > new Date()) { 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 { } else {
return false; return { plan: res.plan, isValid: false, adminId: res?.adminId };
} }
} else { } else {
return false; return { plan: res.plan, isValid: false, adminId: res?.adminId };
} }
} catch (err) { } catch (err) {
console.log("Err in fetch subscription", 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; return randomDigit;
}; };
export const createDocument = async (template, placeholders, signerData) => { export const createDocument = async (
template,
placeholders,
signerData,
pdfUrl
) => {
if (template && template.length > 0) { if (template && template.length > 0) {
const Doc = template[0]; const Doc = template[0];
@@ -653,7 +689,7 @@ export const createDocument = async (template, placeholders, signerData) => {
} }
const data = { const data = {
Name: Doc.Name, Name: Doc.Name,
URL: Doc.URL, URL: pdfUrl,
SignedUrl: Doc.SignedUrl, SignedUrl: Doc.SignedUrl,
SentToOthers: Doc.SentToOthers, SentToOthers: Doc.SentToOthers,
Description: Doc.Description, Description: Doc.Description,
@@ -762,7 +798,9 @@ export const onChangeInput = (
initial, initial,
dateFormat, dateFormat,
isDefaultEmpty, isDefaultEmpty,
isRadio isRadio,
fontSize,
fontColor
) => { ) => {
const isSigners = xyPostion.some((data) => data.signerPtr); const isSigners = xyPostion.some((data) => data.signerPtr);
let filterSignerPos; let filterSignerPos;
@@ -789,6 +827,8 @@ export const onChangeInput = (
options: { options: {
...position.options, ...position.options,
response: value, response: value,
fontSize: fontSize,
fontColor: fontColor,
validation: { validation: {
type: "date-format", type: "date-format",
format: dateFormat // This indicates the required date format explicitly. format: dateFormat // This indicates the required date format explicitly.
@@ -835,7 +875,6 @@ export const onChangeInput = (
} }
} else { } else {
let getXYdata = xyPostion[index].pos; let getXYdata = xyPostion[index].pos;
const updatePosition = getXYdata.map((positionData) => { const updatePosition = getXYdata.map((positionData) => {
if (positionData.key === signKey) { if (positionData.key === signKey) {
if (dateFormat) { if (dateFormat) {
@@ -844,6 +883,8 @@ export const onChangeInput = (
options: { options: {
...positionData.options, ...positionData.options,
response: value, response: value,
fontSize: fontSize,
fontColor: fontColor,
validation: { validation: {
type: "date-format", type: "date-format",
format: dateFormat // This indicates the required date format explicitly. 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 textContent = documentId && `OpenSign™ DocumentId: ${documentId} `;
const pages = pdfDoc.getPages(); const pages = pdfDoc.getPages();
const page = pages[i]; const page = pages[i];
page.drawText(textContent, { const getObj = compensateRotation(
x: 10, page.getRotation().angle,
y: page.getHeight() - 10, 10,
size: fontSize, 5,
1,
page.getSize(),
fontSize,
rgb(0.5, 0.5, 0.5),
font, 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; 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 //function for embed multiple signature using pdf-lib
export const multiSignEmbed = async ( export const multiSignEmbed = async (
widgets, widgets,
@@ -1351,6 +1412,9 @@ export const multiSignEmbed = async (
return resizePos; return resizePos;
} }
}; };
const color = position?.options?.fontColor;
const updateColorInRgb = getWidgetsFontColor(color);
const fontSize = parseInt(position?.options?.fontSize || 13);
const widgetTypeExist = [ const widgetTypeExist = [
textWidget, textWidget,
textInputWidget, textInputWidget,
@@ -1361,11 +1425,11 @@ export const multiSignEmbed = async (
"email" "email"
].includes(position.type); ].includes(position.type);
if (position.type === "checkbox") { if (position.type === "checkbox") {
let checkboxOptionGapFromTop, isCheck; let checkboxGapFromTop, isCheck;
let y = yPos(position); let y = yPos(position);
const optionsFontSize = 13; const optionsFontSize = fontSize || 13;
const checkboxSize = 18; const checkboxSize = fontSize;
const checkboxTextGapFromLeft = 22; const checkboxTextGapFromLeft = fontSize + 5 || 22;
if (position?.options?.values.length > 0) { if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => { position?.options?.values.forEach((item, ind) => {
const checkboxRandomId = "checkbox" + randomId(); const checkboxRandomId = "checkbox" + randomId();
@@ -1382,9 +1446,9 @@ export const multiSignEmbed = async (
const checkbox = form.createCheckBox(checkboxRandomId); const checkbox = form.createCheckBox(checkboxRandomId);
if (ind > 0) { if (ind > 0) {
y = y + checkboxOptionGapFromTop; y = y + checkboxGapFromTop;
} else { } else {
checkboxOptionGapFromTop = 26; checkboxGapFromTop = fontSize + 5 || 26;
} }
if (!position?.options?.isHideLabel) { if (!position?.options?.isHideLabel) {
@@ -1396,7 +1460,7 @@ export const multiSignEmbed = async (
1, 1,
page.getSize(), page.getSize(),
optionsFontSize, optionsFontSize,
rgb(0, 0, 0), updateColorInRgb,
font, font,
page page
); );
@@ -1427,20 +1491,6 @@ export const multiSignEmbed = async (
signyourself signyourself
); );
parseInt(fontSize); 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; let textContent;
if (position?.options?.response) { if (position?.options?.response) {
textContent = position.options?.response; textContent = position.options?.response;
@@ -1519,6 +1569,8 @@ export const multiSignEmbed = async (
y += 18; // Adjust the line height as needed y += 18; // Adjust the line height as needed
} }
} else if (position.type === "dropdown") { } else if (position.type === "dropdown") {
const fontsize = parseInt(position?.options?.fontSize) || 12;
const dropdownRandomId = "dropdown" + randomId(); const dropdownRandomId = "dropdown" + randomId();
const dropdown = form.createDropdown(dropdownRandomId); const dropdown = form.createDropdown(dropdownRandomId);
dropdown.addOptions(position?.options?.values); dropdown.addOptions(position?.options?.values);
@@ -1527,31 +1579,41 @@ export const multiSignEmbed = async (
} else if (position?.options?.defaultValue) { } else if (position?.options?.defaultValue) {
dropdown.select(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 = { const dropdownObj = {
x: xPos(position), x: xPos(position),
y: yPos(position), y: yPos(position),
width: widgetWidth, width: widgetWidth,
height: widgetHeight height: widgetHeight
}; };
dropdown.defaultUpdateAppearances(font);
const dropdownOption = getWidgetPosition(page, dropdownObj, 1); const dropdownOption = getWidgetPosition(page, dropdownObj, 1);
const dropdownSelected = { ...dropdownOption, font: font }; const dropdownSelected = { ...dropdownOption, font: font };
dropdown.defaultUpdateAppearances(font);
dropdown.addToPage(page, dropdownSelected); dropdown.addToPage(page, dropdownSelected);
dropdown.enableReadOnly(); dropdown.enableReadOnly();
} else if (position.type === radioButtonWidget) { } else if (position.type === radioButtonWidget) {
const radioRandomId = "radio" + randomId(); const radioRandomId = "radio" + randomId();
const radioGroup = form.createRadioGroup(radioRandomId); const radioGroup = form.createRadioGroup(radioRandomId);
let radioOptionGapFromTop; let radioOptionGapFromTop;
const optionsFontSize = 16; const optionsFontSize = fontSize || 13;
const radioTextGapFromLeft = 20; const radioTextGapFromLeft = fontSize + 5 || 20;
const radioSize = 18; const radioSize = fontSize;
let y = yPos(position); let y = yPos(position);
if (position?.options?.values.length > 0) { if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => { position?.options?.values.forEach((item, ind) => {
if (ind > 0) { if (ind > 0) {
y = y + radioOptionGapFromTop; y = y + radioOptionGapFromTop;
} else { } else {
radioOptionGapFromTop = 25; radioOptionGapFromTop = fontSize + 10 || 25;
} }
if (!position?.options?.isHideLabel) { if (!position?.options?.isHideLabel) {
// below line of code is used to embed label with radio button in pdf // below line of code is used to embed label with radio button in pdf
@@ -1563,7 +1625,7 @@ export const multiSignEmbed = async (
1, 1,
page.getSize(), page.getSize(),
optionsFontSize, optionsFontSize,
rgb(0, 0, 0), updateColorInRgb,
font, font,
page page
); );
@@ -1799,29 +1861,26 @@ export const handleCopyNextToWidget = (
setXyPostion, setXyPostion,
userId userId
) => { ) => {
const isSigners = xyPostion.some((data) => data.signerPtr);
let filterSignerPos; let filterSignerPos;
//get position of previous widget and create new widget next to that widget on same data except //get position of previous widget and create new widget next to that widget on same data except
// xPosition and key // xPosition and key
let newpos = position; let newposition = position;
const calculateXPosition = const calculateXPosition =
parseInt(position.xPosition) + parseInt(position.xPosition) +
defaultWidthHeight(widgetType).width + defaultWidthHeight(widgetType).width +
resizeBorderExtraWidth(); resizeBorderExtraWidth();
const newId = randomId(); 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 condition to create widget in request-sign flow
if (isSigners) { if (userId) {
if (userId) { filterSignerPos = xyPostion.filter((data) => data.Id === userId);
filterSignerPos = xyPostion.filter((data) => data.Id === userId); const getPlaceHolder = filterSignerPos && filterSignerPos[0]?.placeHolder;
} const getPageNumer = getPlaceHolder?.filter(
const getPlaceHolder = filterSignerPos[0]?.placeHolder;
const getPageNumer = getPlaceHolder.filter(
(data) => data.pageNumber === index (data) => data.pageNumber === index
); );
const getXYdata = getPageNumer[0].pos; const getXYdata = getPageNumer && getPageNumer[0]?.pos;
getXYdata.push(newpos); getXYdata.push(newposition);
if (getPageNumer.length > 0) { if (getPageNumer && getPageNumer.length > 0) {
const newUpdateSignPos = getPlaceHolder.map((obj) => { const newUpdateSignPos = getPlaceHolder.map((obj) => {
if (obj.pageNumber === index) { if (obj.pageNumber === index) {
return { ...obj, pos: getXYdata }; return { ...obj, pos: getXYdata };
@@ -1839,9 +1898,8 @@ export const handleCopyNextToWidget = (
setXyPostion(newUpdateSigner); setXyPostion(newUpdateSigner);
} }
} else { } else {
// else condition to create widget in sign-yourself flow let getXYdata = xyPostion[index]?.pos || [];
let getXYdata = xyPostion[index].pos; getXYdata.push(newposition);
getXYdata.push(newpos);
const updatePlaceholder = xyPostion.map((obj, ind) => { const updatePlaceholder = xyPostion.map((obj, ind) => {
if (ind === index) { if (ind === index) {
return { ...obj, pos: getXYdata }; return { ...obj, pos: getXYdata };
@@ -2142,9 +2200,11 @@ function compensateRotation(
font, font,
page page
) { ) {
// Ensure pageRotation is between 0 and 360 degrees
pageRotation = ((pageRotation % 360) + 360) % 360;
let rotationRads = (pageRotation * Math.PI) / 180; let rotationRads = (pageRotation * Math.PI) / 180;
//These coords are now from bottom/left // Coordinates are from bottom-left
let coordsFromBottomLeft = { x: x / scale }; let coordsFromBottomLeft = { x: x / scale };
if (pageRotation === 90 || pageRotation === 270) { if (pageRotation === 90 || pageRotation === 270) {
coordsFromBottomLeft.y = dimensions.width - (y + fontSize) / scale; coordsFromBottomLeft.y = dimensions.width - (y + fontSize) / scale;
@@ -2154,6 +2214,7 @@ function compensateRotation(
let drawX = null; let drawX = null;
let drawY = null; let drawY = null;
if (pageRotation === 90) { if (pageRotation === 90) {
drawX = drawX =
coordsFromBottomLeft.x * Math.cos(rotationRads) - coordsFromBottomLeft.x * Math.cos(rotationRads) -
@@ -2179,8 +2240,8 @@ function compensateRotation(
coordsFromBottomLeft.x * Math.sin(rotationRads) + coordsFromBottomLeft.x * Math.sin(rotationRads) +
coordsFromBottomLeft.y * Math.cos(rotationRads) + coordsFromBottomLeft.y * Math.cos(rotationRads) +
dimensions.height; dimensions.height;
} else { } else if (pageRotation === 0 || pageRotation === 360) {
//no rotation // No rotation or full rotation
drawX = coordsFromBottomLeft.x; drawX = coordsFromBottomLeft.x;
drawY = coordsFromBottomLeft.y; 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 const parseAppId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID ? process.env.REACT_APP_APPID
: "opensign"; : "opensign";
const serverUrl = serverUrl_fn() const serverUrl = serverUrl_fn();
export const SaveFileSize = async (size, imageUrl, tenantId) => { export const SaveFileSize = async (size, imageUrl, tenantId) => {
//checking server url and save file's size //checking server url and save file's size
const tenantPtr = { const tenantPtr = {
+32 -8
View File
@@ -12,23 +12,35 @@ body {
} }
.hide-scrollbar { .hide-scrollbar {
-ms-overflow-style: none; /* IE and Edge */ -ms-overflow-style: none;
scrollbar-width: none; /* Firefox */ /* 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 */ /* below css class replace bootstrap background color with daisy */
.op-bg-primary { .op-bg-primary {
@apply bg-primary; @apply bg-primary;
} }
.op-bg-secondary { .op-bg-secondary {
@apply bg-secondary; @apply bg-secondary;
} }
.op-bg-info { .op-bg-info {
@apply bg-info; @apply bg-info;
} }
.op-bg-success{
.op-bg-success {
@apply bg-success; @apply bg-success;
} }
.op-bg-warning { .op-bg-warning {
@apply bg-warning; @apply bg-warning;
} }
@@ -37,25 +49,37 @@ body {
.op-text-primary { .op-text-primary {
@apply text-primary; @apply text-primary;
} }
.op-text-secondary { .op-text-secondary {
@apply text-secondary; @apply text-secondary;
} }
.op-text-info { .op-text-info {
@apply text-info; @apply text-info;
} }
.op-text-success{
.op-text-accent {
@apply text-accent;
}
.op-text-success {
@apply text-success; @apply text-success;
} }
.op-text-warning { .op-text-warning {
@apply text-warning; @apply text-warning;
} }
.op-border-primary{
@apply border-primary .op-border-primary {
@apply border-primary;
} }
/* CSS for scrollbar customization */ /* CSS for scrollbar customization */
* { * {
scrollbar-width: thin; /* For Firefox */ scrollbar-width: thin;
scrollbar-color: gray; /* For Firefox */ /* For Firefox */
scrollbar-color: gray;
/* For Firefox */
} }
/* For webkit-based browsers like Chrome, Safari */ /* For webkit-based browsers like Chrome, Safari */
+7
View File
@@ -354,6 +354,13 @@ export default function reportJson(id) {
redirectUrl: "template", redirectUrl: "template",
action: "redirect" action: "redirect"
}, },
{
btnId: "2434",
btnLabel: "Embed",
hoverLabel: "Embed",
btnIcon: "fa-light fa-code",
action: "Embed"
},
{ {
btnId: "1834", btnId: "1834",
btnLabel: "Delete", btnLabel: "Delete",
+1 -1
View File
@@ -36,7 +36,7 @@ const dashboardJson = [
queryType: "", queryType: "",
class: "contracts_Document", class: "contracts_Document",
query: 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", key: "count",
Redirect_type: "Report", Redirect_type: "Report",
Redirect_id: "1MwEuxLEkF", Redirect_id: "1MwEuxLEkF",
+87 -14
View File
@@ -3,6 +3,7 @@ import { isStaging } from "../constant/const";
const plans = [ const plans = [
{ {
planName: "OPENSIGN™ FREE", planName: "OPENSIGN™ FREE",
code: { monthly: "freeplan", yearly: "freeplan" },
img: "free.png", img: "free.png",
currency: "", currency: "",
monthlyPrice: "Free", monthlyPrice: "Free",
@@ -12,7 +13,7 @@ const plans = [
url: "", url: "",
target: "_blank", target: "_blank",
benefits: [ benefits: [
"Unlimited envelopes", "Unlimited digital signatures",
"Sign documents yourself", "Sign documents yourself",
"Request signatures from others", "Request signatures from others",
"14 field types", "14 field types",
@@ -26,12 +27,11 @@ const plans = [
"Decline document support", "Decline document support",
"Email notifications", "Email notifications",
"Recipient authentication using OTP", "Recipient authentication using OTP",
"Bulk send (upto 5 docs)",
"Public profiles", "Public profiles",
"And much more" "And much more"
], ],
yearlyBenefits: [ yearlyBenefits: [
"Unlimited envelopes", "Unlimited digital signatures",
"Sign documents yourself", "Sign documents yourself",
"Request signatures from others", "Request signatures from others",
"14 field types", "14 field types",
@@ -45,13 +45,15 @@ const plans = [
"Decline document support", "Decline document support",
"Email notifications", "Email notifications",
"Recipient authentication using OTP", "Recipient authentication using OTP",
"Bulk send (upto 5 docs)",
"Public profiles", "Public profiles",
"And much more" "And much more"
] ]
}, },
{ {
planName: "OPENSIGN™ PROFESSIONAL", planName: "OPENSIGN™ PROFESSIONAL",
code: isStaging
? { monthly: "pro-weekly", yearly: "pro-yearly" }
: { monthly: "professional-monthly", yearly: "professional-yearly" },
img: "professional.png", img: "professional.png",
currency: "$", currency: "$",
monthlyPrice: "29.99", monthlyPrice: "29.99",
@@ -73,10 +75,10 @@ const plans = [
"Webhooks", "Webhooks",
"Zapier integration", "Zapier integration",
"API Access", "API Access",
"100 API signatures included", "upto 100 API signatures",
"Custom email templates", "Custom email templates",
"Auto reminders", "Auto reminders",
"Bulk send (upto 20 docs)", "Bulk send (upto 100 docs)",
"Premium Public profile usernames", "Premium Public profile usernames",
"Embedding (coming soon)" "Embedding (coming soon)"
], ],
@@ -88,16 +90,17 @@ const plans = [
"Webhooks", "Webhooks",
"Zapier integration", "Zapier integration",
"API Access", "API Access",
"240 API signatures included", "upto 240 API signatures",
"Custom email templates", "Custom email templates",
"Auto reminders", "Auto reminders",
"Bulk send (upto 20 docs)", "Bulk send (upto 240 docs)",
"Premium Public profile usernames", "Premium Public profile usernames",
"Embedding (coming soon)" "Embedding (coming soon)"
] ]
}, },
{ {
planName: "OPENSIGN™ TEAMS", planName: "OPENSIGN™ TEAMS",
code: { monthly: "teams-monthly", yearly: "teams-yearly" },
img: "teams.png", img: "teams.png",
currency: "$", currency: "$",
monthlyPrice: `39.99<sup style="font-size: 17px;">/user</sup>`, monthlyPrice: `39.99<sup style="font-size: 17px;">/user</sup>`,
@@ -105,31 +108,31 @@ const plans = [
subtitle: "Exclusive Access to advanced features.", subtitle: "Exclusive Access to advanced features.",
btn: { text: "Subscribe", color: "op-btn-accent" }, btn: { text: "Subscribe", color: "op-btn-accent" },
url: isStaging 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", : "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901237d0702bfaa959406306635d80f138c/teams-monthly",
yearlyUrl: isStaging 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", : "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af9011a864994bbeeec71fcf106188630199d/teams-yearly",
target: "_self", target: "_self",
benefits: [ benefits: [
"Everything in OpenSign™ professional", "Everything in OpenSign™ professional",
"100 API signatures included", "upto 100 API signatures",
"Teams and Organizations", "Teams and Organizations",
"Share Templates with teams", "Share Templates with teams",
"Share Templates with individuals", "Share Templates with individuals",
"DocumentId removal from signed docs", "DocumentId removal from signed docs",
"Bulk send (upto 50 docs)", "Bulk send (upto 100 docs)",
"Request Payments (coming soon)", "Request Payments (coming soon)",
"Mobile app (coming soon)" "Mobile app (coming soon)"
], ],
yearlyBenefits: [ yearlyBenefits: [
"Everything in OpenSign™ professional", "Everything in OpenSign™ professional",
"500 API signatures included", "upto 500 API signatures",
"Teams and Organizations", "Teams and Organizations",
"Share Templates with teams", "Share Templates with teams",
"Share Templates with individuals", "Share Templates with individuals",
"DocumentId removal from signed docs", "DocumentId removal from signed docs",
"Bulk send (upto 50 docs)", "Bulk send (upto 500 docs)",
"Request Payments (coming soon)", "Request Payments (coming soon)",
"Mobile app (coming soon)" "Mobile app (coming soon)"
] ]
@@ -171,4 +174,74 @@ export const validplan = {
"enterprise-monthly": true, "enterprise-monthly": true,
"enterprise-yearly": 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; export default plans;
+5 -3
View File
@@ -95,7 +95,9 @@ const HomeLayout = () => {
domain: updateDomain domain: updateDomain
}); });
}; };
const handleNavigation = () => {
navigate("/subscription");
};
async function checkIsSubscribed() { async function checkIsSubscribed() {
if (isEnableSubscription) { if (isEnableSubscription) {
const res = await fetchSubscription(); const res = await fetchSubscription();
@@ -107,10 +109,10 @@ const HomeLayout = () => {
setIsUserValid(true); setIsUserValid(true);
setIsLoader(false); setIsLoader(false);
} else { } else {
navigate(`/subscription`); handleNavigation(res.plan);
} }
} else { } else {
navigate(`/subscription`); handleNavigation(res.plan);
} }
} else { } else {
setIsUserValid(true); setIsUserValid(true);
+5 -3
View File
@@ -56,8 +56,10 @@ const AddAdmin = () => {
} }
}; };
const clearStorage = async () => { const clearStorage = async () => {
if (Parse.User.current()) { try {
await Parse.User.logOut(); await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
} }
const baseUrl = localStorage.getItem("baseUrl"); const baseUrl = localStorage.getItem("baseUrl");
const appid = localStorage.getItem("parseAppId"); const appid = localStorage.getItem("parseAppId");
@@ -459,13 +461,13 @@ const AddAdmin = () => {
<input <input
type="checkbox" type="checkbox"
className="op-checkbox op-checkbox-sm" className="op-checkbox op-checkbox-sm"
id="termsandcondition" id="subscribetoopensign"
checked={isSubscribeNews} checked={isSubscribeNews}
onChange={(e) => setIsSubscribeNews(e.target.checked)} onChange={(e) => setIsSubscribeNews(e.target.checked)}
/> />
<label <label
className="text-xs cursor-pointer ml-1 mb-0" className="text-xs cursor-pointer ml-1 mb-0"
htmlFor="termsandcondition" htmlFor="subscribetoopensign"
> >
{t("subscribe-to-opensign")} {t("subscribe-to-opensign")}
</label> </label>
+1 -5
View File
@@ -215,11 +215,7 @@ const DebugPdf = () => {
}; };
return ( return (
<div> <div>
{copied && ( {copied && <Alert type="success">Copied</Alert>}
<Alert type="success">
<span className="ml-3">Copied</span>
</Alert>
)}
<Title title={"Debug Pdf"} /> <Title title={"Debug Pdf"} />
{width < 800 ? ( {width < 800 ? (
<HandleError handleError={"Debug PDF only availble for PC"} /> <HandleError handleError={"Debug PDF only availble for PC"} />
+6 -5
View File
@@ -15,11 +15,7 @@ function ForgotPassword() {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const [state, setState] = useState({ const [state, setState] = useState({ email: "", password: "", hideNav: "" });
email: "",
password: "",
hideNav: ""
});
const [sentStatus, setSentStatus] = useState(""); const [sentStatus, setSentStatus] = useState("");
const [image, setImage] = useState(); const [image, setImage] = useState();
@@ -65,6 +61,11 @@ function ForgotPassword() {
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
const saveLogo = async () => { const saveLogo = async () => {
try {
await Parse.User.logOut();
} catch (err) {
console.log("err while logging out ", err);
}
if (isEnableSubscription) { if (isEnableSubscription) {
const app = await getAppLogo(); const app = await getAppLogo();
if (app?.logo) { if (app?.logo) {
+6 -11
View File
@@ -1,7 +1,6 @@
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { formJson } from "../json/FormJson"; import { formJson } from "../json/FormJson";
import AddUser from "../components/AddUser";
import Parse from "parse"; import Parse from "parse";
import DropboxChooser from "../components/shared/fields/DropboxChoose"; import DropboxChooser from "../components/shared/fields/DropboxChoose";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
@@ -37,15 +36,11 @@ function generatePdfName(length) {
function Form() { function Form() {
const { id } = useParams(); const { id } = useParams();
if (id === "lM0xRnM3iE") { const config = formJson[id];
return <AddUser />; if (config) {
return <Forms {...config} />;
} else { } else {
const config = formJson[id]; return <PageNotFound prefix={"Form"} />;
if (config) {
return <Forms {...config} />;
} else {
return <PageNotFound prefix={"Form"} />;
}
} }
} }
@@ -95,8 +90,8 @@ const Forms = (props) => {
}, []); }, []);
const fetchSubscription = async () => { const fetchSubscription = async () => {
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed(); const subscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe); setIsSubscribe(subscribe.isValid);
} }
}; };
+291 -33
View File
@@ -10,17 +10,40 @@ import Loader from "../primitives/Loader";
import SubscribeCard from "../primitives/SubscribeCard"; import SubscribeCard from "../primitives/SubscribeCard";
import Tour from "reactour"; import Tour from "reactour";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Parse from "parse";
function GenerateToken() { function GenerateToken() {
const origin = window.location.origin;
const { t } = useTranslation(); const { t } = useTranslation();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl")); const parseBaseUrl = localStorage.getItem("baseUrl");
const [parseAppId] = useState(localStorage.getItem("parseAppId")); const parseAppId = localStorage.getItem("parseAppId");
const [apiToken, SetApiToken] = useState(""); const [apiToken, SetApiToken] = useState("");
const [isLoader, setIsLoader] = useState(true); const [isLoader, setIsLoader] = useState(true);
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState({
const [isSubscribe, setIsSubscribe] = useState(false); generateapi: false,
buyapis: false,
unauthorized: false
});
const [isSubscribe, setIsSubscribe] = useState({
plan: "",
isValid: false,
adminId: "",
adminName: "",
adminEmail: ""
});
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isTour, setIsTour] = useState(false); 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(() => { useEffect(() => {
fetchToken(); fetchToken();
// eslint-disable-next-line // eslint-disable-next-line
@@ -34,8 +57,8 @@ function GenerateToken() {
const fetchToken = async () => { const fetchToken = async () => {
try { try {
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed(); const subscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe); setIsSubscribe(subscribe);
} }
const url = parseBaseUrl + "functions/getapitoken"; const url = parseBaseUrl + "functions/getapitoken";
const headers = { const headers = {
@@ -45,22 +68,40 @@ function GenerateToken() {
}; };
const res = await axios.post(url, {}, { headers: headers }); const res = await axios.post(url, {}, { headers: headers });
if (res) { 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) { } catch (err) {
SetApiToken(); SetApiToken();
setIsLoader(false);
console.log("Err", err); console.log("Err", err);
} finally {
setIsLoader(false);
} }
}; };
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
if (!isSubscribe && isEnableSubscription) { if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
setIsTour(true); setIsTour(true);
} else { } else {
setIsLoader(true); setIsLoader(true);
setIsModal(false); setIsModal((obj) => ({ ...obj, generateapi: false }));
try { try {
const url = parseBaseUrl + "functions/generateapitoken"; const url = parseBaseUrl + "functions/generateapitoken";
const headers = { const headers = {
@@ -91,20 +132,115 @@ function GenerateToken() {
const copytoclipboard = (text) => { const copytoclipboard = (text) => {
copytoData(text); copytoData(text);
setIsAlert({ type: "success", msg: t("copied") }); setIsAlert({ type: "success", msg: t("copied") });
setTimeout(() => { setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
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,
adminName: extUser?.CreatedBy?.name,
adminEmail: extUser?.CreatedBy?.email
}));
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 ( return (
<React.Fragment> <React.Fragment>
<Title title={"API Token"} /> <Title title={"API Token"} />
{isAlert.msg && ( {isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
<Alert type={isAlert.type}>
<div className="ml-3">{isAlert.msg}</div>
</Alert>
)}
{isLoader ? ( {isLoader ? (
<div className="flex justify-center items-center h-screen"> <div className="flex justify-center items-center h-screen">
<Loader /> <Loader />
@@ -115,29 +251,66 @@ function GenerateToken() {
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}> <h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
OpenSign {t("API")}{" "} OpenSign {t("API")}{" "}
<Tooltip <Tooltip
url={ url="https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
}
isSubscribe={true} isSubscribe={true}
/> />
</h1> </h1>
<ul className="w-full flex flex-col p-2 text-sm"> <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"> <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"> <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 <span
id="token" id="token"
className={`${ className={`${
isSubscribe isSubscribe?.plan === "freeplan"
? "" ? "bg-white/20 pointer-events-none select-none"
: "bg-white/20 pointer-events-none select-none" : ""
} md:text-end py-2 md:py-0`} } md:text-end py-2 md:py-0`}
> >
<span <span
className="cursor-pointer" className="cursor-pointer"
onClick={() => copytoclipboard(apiToken)} onClick={() => copytoclipboard(apiToken)}
> >
{apiToken ? apiToken : "_____"} {isSubscribe?.plan !== "freeplan" && apiToken
? apiToken
: "_____"}
</span> </span>
<button <button
className="op-btn op-btn-accent op-btn-outline op-btn-sm ml-2 cursor-pointer" className="op-btn op-btn-accent op-btn-outline op-btn-sm ml-2 cursor-pointer"
@@ -149,13 +322,30 @@ function GenerateToken() {
</div> </div>
<button <button
onClick={apiToken ? handleModal : handleSubmit} 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")} {apiToken ? t("regenerate-token") : t("generate-token")}
</button> </button>
</li> </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> </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 <button
type="button" type="button"
onClick={() => onClick={() =>
@@ -163,13 +353,20 @@ function GenerateToken() {
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1" "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")} {t("view-docs")}
</button> </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> </div>
<ModalUi <ModalUi
isOpen={isModal} isOpen={isModal.generateapi}
title={apiToken ? t("regenerate-token") : t("generate-token")} title={apiToken ? t("regenerate-token") : t("generate-token")}
handleClose={handleModal} handleClose={handleModal}
> >
@@ -194,8 +391,69 @@ function GenerateToken() {
</div> </div>
</div> </div>
</ModalUi> </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", {
adminName: isSubscribe?.adminName,
adminEmail: isSubscribe?.adminEmail
})}
</div>
</ModalUi>
</div> </div>
{!isSubscribe && isEnableSubscription && ( {isEnableSubscription && isSubscribe?.plan === "freeplan" && (
<div data-tut="apisubscribe"> <div data-tut="apisubscribe">
<SubscribeCard /> <SubscribeCard />
</div> </div>
+21 -19
View File
@@ -80,7 +80,9 @@ function Login() {
} }
setState({ ...state, [name]: value }); setState({ ...state, [name]: value });
}; };
const handlePaidRoute = () => {
navigate("/subscription");
};
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
localStorage.removeItem("accesstoken"); localStorage.removeItem("accesstoken");
event.preventDefault(); event.preventDefault();
@@ -158,9 +160,9 @@ function Login() {
JSON.stringify(LocalUserDetails) JSON.stringify(LocalUserDetails)
); );
const res = await fetchSubscription(); const res = await fetchSubscription();
const freeplan = res.plan; const plan = res.plan;
const billingDate = res.billingDate; const billingDate = res.billingDate;
if (freeplan === "freeplan") { if (plan === "freeplan") {
setState({ ...state, loading: false }); setState({ ...state, loading: false });
navigate(redirectUrl); navigate(redirectUrl);
} else if (billingDate) { } else if (billingDate) {
@@ -171,11 +173,11 @@ function Login() {
navigate(redirectUrl); navigate(redirectUrl);
} else { } else {
setState({ ...state, loading: false }); setState({ ...state, loading: false });
navigate(`/subscription`, { replace: true }); handlePaidRoute(plan);
} }
} else { } else {
setState({ ...state, loading: false }); setState({ ...state, loading: false });
navigate(`/subscription`, { replace: true }); handlePaidRoute(plan);
} }
} else { } else {
setState({ ...state, loading: false }); setState({ ...state, loading: false });
@@ -330,9 +332,9 @@ function Login() {
localStorage.setItem("pageType", menu.pageType); localStorage.setItem("pageType", menu.pageType);
if (isEnableSubscription) { if (isEnableSubscription) {
const res = await fetchSubscription(); const res = await fetchSubscription();
const freeplan = res.plan; const plan = res.plan;
const billingDate = res.billingDate; const billingDate = res.billingDate;
if (freeplan === "freeplan") { if (plan === "freeplan") {
navigate(redirectUrl); navigate(redirectUrl);
} else if (billingDate) { } else if (billingDate) {
if (new Date(billingDate) > new Date()) { if (new Date(billingDate) > new Date()) {
@@ -342,14 +344,14 @@ function Login() {
if (isFreeplan) { if (isFreeplan) {
navigate(redirectUrl); navigate(redirectUrl);
} else { } else {
navigate(`/subscription`, { replace: true }); handlePaidRoute(plan);
} }
} }
} else { } else {
if (isFreeplan) { if (isFreeplan) {
navigate(redirectUrl); navigate(redirectUrl);
} else { } else {
navigate(`/subscription`, { replace: true }); handlePaidRoute(plan);
} }
} }
} else { } else {
@@ -462,8 +464,8 @@ function Login() {
localStorage.setItem("userDetails", JSON.stringify(userInfo)); localStorage.setItem("userDetails", JSON.stringify(userInfo));
const res = await fetchSubscription(); const res = await fetchSubscription();
const billingDate = res.billingDate; const billingDate = res.billingDate;
const freeplan = res.plan; const plan = res.plan;
if (freeplan === "freeplan") { if (plan === "freeplan") {
navigate(redirectUrl); navigate(redirectUrl);
} else if (billingDate) { } else if (billingDate) {
if (new Date(billingDate) > new Date()) { if (new Date(billingDate) > new Date()) {
@@ -471,10 +473,10 @@ function Login() {
// Redirect to the appropriate URL after successful login // Redirect to the appropriate URL after successful login
navigate(redirectUrl); navigate(redirectUrl);
} else { } else {
navigate(`/subscription`); handlePaidRoute(plan);
} }
} else { } else {
navigate(`/subscription`); handlePaidRoute(plan);
} }
} else { } else {
// Redirect to the appropriate URL after successful login // Redirect to the appropriate URL after successful login
@@ -573,10 +575,12 @@ function Login() {
} }
}; };
const logOutUser = () => { const logOutUser = async () => {
setIsModal(false); setIsModal(false);
if (Parse?.User?.current()) { try {
Parse.User.logOut(); await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
} }
let appdata = localStorage.getItem("userSettings"); let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo"); let applogo = localStorage.getItem("appLogo");
@@ -789,9 +793,7 @@ function Login() {
</div> </div>
<SelectLanguage /> <SelectLanguage />
{state.alertMsg && ( {state.alertMsg && (
<Alert type={state.alertType}> <Alert type={state.alertType}>{state.alertMsg}</Alert>
<div className="ml-3">{state.alertMsg}</div>
</Alert>
)} )}
</div> </div>
<ModalUi <ModalUi
+273 -276
View File
@@ -31,7 +31,10 @@ import {
handleDownloadPdf, handleDownloadPdf,
handleToPrint, handleToPrint,
handleDownloadCertificate, handleDownloadCertificate,
getDefaultSignature getDefaultSignature,
onClickZoomIn,
onClickZoomOut,
fetchUrl
} from "../constant/Utils"; } from "../constant/Utils";
import LoaderWithMsg from "../primitives/LoaderWithMsg"; import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError"; import HandleError from "../primitives/HandleError";
@@ -120,7 +123,6 @@ function PdfRequestFiles(props) {
const [otp, setOtp] = useState(""); const [otp, setOtp] = useState("");
const [contractName, setContractName] = useState(""); const [contractName, setContractName] = useState("");
const [zoomPercent, setZoomPercent] = useState(0); const [zoomPercent, setZoomPercent] = useState(0);
const [totalZoomPercent, setTotalZoomPercent] = useState();
const [scale, setScale] = useState(1); const [scale, setScale] = useState(1);
const [uniqueId, setUniqueId] = useState(""); const [uniqueId, setUniqueId] = useState("");
const [isPublicTemplate, setIsPublicTemplate] = useState(false); const [isPublicTemplate, setIsPublicTemplate] = useState(false);
@@ -130,6 +132,8 @@ function PdfRequestFiles(props) {
const [res, setRes] = useState({}); const [res, setRes] = useState({});
const [documentId, setDocumentId] = useState(""); const [documentId, setDocumentId] = useState("");
const [isPublicContact, setIsPublicContact] = useState(false); const [isPublicContact, setIsPublicContact] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
const [plancode, setPlanCode] = useState("");
const isHeader = useSelector((state) => state.showHeader); const isHeader = useSelector((state) => state.showHeader);
const divRef = useRef(null); const divRef = useRef(null);
@@ -138,17 +142,18 @@ function PdfRequestFiles(props) {
let isGuestSignFlow = false; let isGuestSignFlow = false;
let sendmail; let sendmail;
let getDocId = ""; 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 = const getQuery =
!props.templateId && !props.templateId &&
window.location?.search && window.location?.search &&
window.location?.search?.split("?"); window.location?.search?.split("?"); //['','sendmail=false']
//'sendmail=false'
if (getQuery) { 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) { if (checkSplit && checkSplit.length > 4) {
isGuestSignFlow = true; isGuestSignFlow = true;
getDocId = checkSplit[3]; getDocId = checkSplit[3];
@@ -232,12 +237,17 @@ function PdfRequestFiles(props) {
const currentUser = JSON.parse(localuser); const currentUser = JSON.parse(localuser);
await handleSendOTP(currentUser?.email); await handleSendOTP(currentUser?.email);
}; };
const handleNavigation = () => {
window.location.href = "/subscription";
};
async function checkIsSubscribed(extUserId, contactId) { async function checkIsSubscribed(extUserId, contactId) {
const isGuestSign = isGuestSignFlow || false; const isGuestSign = isGuestSignFlow || false;
const res = await fetchSubscription(extUserId, contactId, isGuestSign); const res = await fetchSubscription(extUserId, contactId, isGuestSign);
const plan = res.plan; const plan = res.plan;
const billingDate = res?.billingDate; const billingDate = res?.billingDate;
const status = res?.status; const status = res?.status;
setPlanCode(plan);
if (plan === "freeplan") { if (plan === "freeplan") {
return true; return true;
} else if (billingDate) { } else if (billingDate) {
@@ -248,7 +258,7 @@ function PdfRequestFiles(props) {
if (isGuestSign) { if (isGuestSign) {
setIsSubscriptionExpired(true); setIsSubscriptionExpired(true);
} else { } else {
window.location.href = "/subscription"; handleNavigation(plan);
} }
} }
} else if (isGuestSign) { } else if (isGuestSign) {
@@ -262,7 +272,7 @@ function PdfRequestFiles(props) {
if (isGuestSign) { if (isGuestSign) {
setIsSubscriptionExpired(true); setIsSubscriptionExpired(true);
} else { } else {
window.location.href = "/subscription"; handleNavigation(res.plan);
} }
} }
} }
@@ -285,49 +295,65 @@ function PdfRequestFiles(props) {
templateDeatils.data && templateDeatils.data.result templateDeatils.data && templateDeatils.data.result
? [templateDeatils.data.result] ? [templateDeatils.data.result]
: []; : [];
if (documentData && documentData[0]?.error) {
if (documentData && documentData.length > 0) { props?.setTemplateStatus({
setIsPublicTemplate(true); status: "Invalid"
const getPublicRole = documentData[0]?.PublicRole[0]; });
const getUniqueIdDetails = documentData[0]?.Placeholders.find( } else if (documentData && documentData.length > 0) {
(x) => x.Role === getPublicRole if (documentData[0]?.IsPublic) {
); props?.setTemplateStatus({
if (getUniqueIdDetails) { status: "Success"
setUniqueId(getUniqueIdDetails.Id); });
} const url =
setSignerPos(documentData[0]?.Placeholders); documentData[0] &&
let placeholdersOrSigners = []; (documentData[0]?.SignedUrl || documentData[0]?.URL);
// const placeholder = documentData[0]?.Placeholders; if (url) {
for (const placeholder of documentData[0].Placeholders) { //convert document url in array buffer format to use embed widgets in pdf using pdf-lib
//`emailExist` variable to handle condition for quick send flow and show unsigned signers list const arrayBuffer = await convertPdfArrayBuffer(url);
const signerIdExist = placeholder?.signerObjId; if (arrayBuffer === "Error") {
if (signerIdExist) { setHandleError(t("something-went-wrong-mssg"));
const getSignerData = documentData[0].Signers.find( } else {
(data) => data.objectId === placeholder?.signerObjId setPdfArrayBuffer(arrayBuffer);
); }
placeholdersOrSigners.push(getSignerData);
} else { } 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 { } else {
setHandleError(t("no-data")); props?.setTemplateStatus({
setIsLoading({ status: "Invalid"
isLoad: false
}); });
} }
} catch (err) { } catch (err) {
@@ -350,6 +376,20 @@ function PdfRequestFiles(props) {
//getting document details //getting document details
const documentData = await contractDocument(documentId || docId); const documentData = await contractDocument(documentId || docId);
if (documentData && documentData.length > 0) { 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); setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const isCompleted = const isCompleted =
documentData[0].IsCompleted && documentData[0].IsCompleted; documentData[0].IsCompleted && documentData[0].IsCompleted;
@@ -382,16 +422,11 @@ function PdfRequestFiles(props) {
} }
if (isCompleted) { if (isCompleted) {
setIsSigned(true); setIsSigned(true);
const data = { const data = { isCertificate: true, isModal: true };
isCertificate: true,
isModal: true
};
setAlreadySign(true); setAlreadySign(true);
setIsCompleted(data); setIsCompleted(data);
setIsCelebration(true); setIsCelebration(true);
setTimeout(() => { setTimeout(() => setIsCelebration(false), 5000);
setIsCelebration(false);
}, 5000);
} else if (declined) { } else if (declined) {
const currentDecline = { const currentDecline = {
currnt: "another", currnt: "another",
@@ -407,9 +442,7 @@ function PdfRequestFiles(props) {
} // Check if the current signer is not a last signer and handle the complete message. } // Check if the current signer is not a last signer and handle the complete message.
else if (isNextUser) { else if (isNextUser) {
setIsCelebration(true); setIsCelebration(true);
setTimeout(() => { setTimeout(() => setIsCelebration(false), 5000);
setIsCelebration(false);
}, 5000);
setIsCompleted({ setIsCompleted({
isModal: true, isModal: true,
message: t("document-signed-alert-1") message: t("document-signed-alert-1")
@@ -436,7 +469,8 @@ function PdfRequestFiles(props) {
documentData[0].AuditTrail.length > 0 && documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter( documentData[0].AuditTrail.filter(
(data) => (data) =>
data.UserPtr.objectId === currUserId && data.Activity === "Signed" data.UserPtr?.objectId === currUserId &&
data.Activity === "Signed"
); );
if ( if (
checkAlreadySign && checkAlreadySign &&
@@ -516,15 +550,15 @@ function PdfRequestFiles(props) {
for (const item of placeholdersOrSigners) { for (const item of placeholdersOrSigners) {
const checkEmail = item?.email; const checkEmail = item?.email;
//if email exist then compare user signed by using email else signers objectId //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 //`isSignedSignature` variable to handle break loop whenever it get true
let isSignedSignature = false; let isSignedSignature = false;
//checking the signer who signed the document by using audit trail details. //checking the signer who signed the document by using audit trail details.
//and save signedSigners and unsignedSigners details //and save signedSigners and unsignedSigners details
for (const doc of audittrailData) { for (const doc of audittrailData) {
const signedExist = checkEmail const signedExist = checkEmail
? doc?.UserPtr.Email ? doc?.UserPtr?.Email
: doc?.UserPtr.objectId; : doc?.UserPtr?.objectId;
if (emailOrId === signedExist) { if (emailOrId === signedExist) {
signers.push({ ...item }); signers.push({ ...item });
@@ -602,15 +636,11 @@ function PdfRequestFiles(props) {
(documentData.result && documentData.result.error) (documentData.result && documentData.result.error)
) { ) {
setHandleError(t("something-went-wrong-mssg")); setHandleError(t("something-went-wrong-mssg"));
setIsLoading({ setIsLoading({ isLoad: false });
isLoad: false
});
console.log("err in getDocument cloud function "); console.log("err in getDocument cloud function ");
} else { } else {
setHandleError(t("no-data")); setHandleError(t("no-data"));
setIsUiLoading({ setIsUiLoading({ isLoad: false });
isLoad: false
});
} }
//function to get default signatur eof current user from `contracts_Signature` class //function to get default signatur eof current user from `contracts_Signature` class
const defaultSignRes = await getDefaultSignature(jsonSender.objectId); const defaultSignRes = await getDefaultSignature(jsonSender.objectId);
@@ -620,18 +650,13 @@ function PdfRequestFiles(props) {
} else if (defaultSignRes?.status === "error") { } else if (defaultSignRes?.status === "error") {
setHandleError("Error: Something went wrong!"); setHandleError("Error: Something went wrong!");
} }
setIsLoading({ setIsLoading({ isLoad: false });
isLoad: false
});
} catch (err) { } catch (err) {
console.log("Error: error in getDocumentDetails", err); console.log("Error: error in getDocumentDetails", err);
setHandleError("Error: Something went wrong!"); setHandleError("Error: Something went wrong!");
setIsLoading({ setIsLoading({ isLoad: false });
isLoad: false
});
} }
}; };
//function for embed signature or image url in pdf //function for embed signature or image url in pdf
async function embedWidgetsData() { async function embedWidgetsData() {
//for emailVerified data checking first in localstorage //for emailVerified data checking first in localstorage
@@ -807,30 +832,9 @@ function PdfRequestFiles(props) {
setIsUiLoading(true); setIsUiLoading(true);
// `widgets` is Used to return widgets details with page number of current user // `widgets` is Used to return widgets details with page number of current user
const widgets = checkUser?.[0]?.placeHolder; 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 // Load a PDFDocument from the existing PDF bytes
const existingPdfBytes = pdfArrBuffer; const existingPdfBytes = pdfArrayBuffer;
try { try {
const pdfDoc = await PDFDocument.load(existingPdfBytes); const pdfDoc = await PDFDocument.load(existingPdfBytes);
const isSignYourSelfFlow = false; const isSignYourSelfFlow = false;
@@ -984,6 +988,7 @@ function PdfRequestFiles(props) {
? replaceVar?.subject ? replaceVar?.subject
: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign "${pdfDetails?.[0].Name}"`, : `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign "${pdfDetails?.[0].Name}"`,
from: senderEmail, from: senderEmail,
plan: plancode,
html: requestBody html: requestBody
? replaceVar?.body ? 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=" + : "<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 + openSignUrl +
" target=_blank>here</a>.</p> </div></div></body> </html>" " target=_blank>here</a>.</p> </div></div></body> </html>"
}; };
await axios.post(url, params, { await axios.post(url, params, { headers: headers });
headers: headers
});
} catch (error) { } catch (error) {
console.log("error", error); console.log("error", error);
} }
@@ -1173,10 +1176,7 @@ function PdfRequestFiles(props) {
const img = new Image(); const img = new Image();
img.src = defaultSignImg; img.src = defaultSignImg;
if (img.complete) { if (img.complete) {
imgWH = { imgWH = { width: img.width, height: img.height };
width: img.width,
height: img.height
};
} }
} }
//get current signer placeholder position data //get current signer placeholder position data
@@ -1216,7 +1216,15 @@ function PdfRequestFiles(props) {
); );
const jsonSender = JSON.parse(senderUser); const jsonSender = JSON.parse(senderUser);
setIsDecline({ isDeclined: false }); 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); setIsUiLoading(true);
await axios await axios
@@ -1236,10 +1244,7 @@ function PdfRequestFiles(props) {
.then(async (result) => { .then(async (result) => {
const res = result.data; const res = result.data;
if (res) { if (res) {
const currentDecline = { const currentDecline = { currnt: "YouDeclined", isDeclined: true };
currnt: "YouDeclined",
isDeclined: true
};
setIsDecline(currentDecline); setIsDecline(currentDecline);
setIsUiLoading(false); setIsUiLoading(false);
const params = { const params = {
@@ -1280,6 +1285,8 @@ function PdfRequestFiles(props) {
}) })
.catch((err) => { .catch((err) => {
console.log("error updating field is decline ", err); console.log("error updating field is decline ", err);
setIsUiLoading(false);
alert(t("something-went-wrong-mssg"));
}); });
}; };
//function to add default signature for all requested placeholder of sign //function to add default signature for all requested placeholder of sign
@@ -1298,15 +1305,11 @@ function PdfRequestFiles(props) {
x.signerObjId === signerObjectId ? { ...x, placeHolder: updatePlace } : x x.signerObjId === signerObjectId ? { ...x, placeHolder: updatePlace } : x
); );
setSignerPos(updatesignerPos); setSignerPos(updatesignerPos);
setDefaultSignAlert({ setDefaultSignAlert({ isShow: false, alertMessage: "" });
isShow: false,
alertMessage: ""
});
}; };
const handleDontShow = (isChecked) => { const handleDontShow = (isChecked) => {
setIsDontShow(isChecked); setIsDontShow(isChecked);
}; };
// console.log("signerpos", signerPos);
//function to close tour and save tour status //function to close tour and save tour status
const closeRequestSignTour = async () => { const closeRequestSignTour = async () => {
setRequestSignTour(true); setRequestSignTour(true);
@@ -1483,7 +1486,6 @@ function PdfRequestFiles(props) {
const SendOtp = async () => { const SendOtp = async () => {
try { try {
const params = { email: contact.email, docId: res.docId }; const params = { email: contact.email, docId: res.docId };
const Otp = await axios.post( const Otp = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/SendOTPMailV1`, `${localStorage.getItem("baseUrl")}/functions/SendOTPMailV1`,
params, params,
@@ -1521,10 +1523,7 @@ function PdfRequestFiles(props) {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": parseId "X-Parse-Application-Id": parseId
}; };
let body = { let body = { email: contact.email, otp: otp };
email: contact.email,
otp: otp
};
let user = await axios.post(url, body, { headers: headers }); let user = await axios.post(url, body, { headers: headers });
if (user.data.result === "Invalid Otp") { if (user.data.result === "Invalid Otp") {
alert(t("invalid-otp")); alert(t("invalid-otp"));
@@ -1556,9 +1555,7 @@ function PdfRequestFiles(props) {
// document.getElementById("my_modal").close(); // document.getElementById("my_modal").close();
setIsPublicContact(false); setIsPublicContact(false);
setIsPublicTemplate(false); setIsPublicTemplate(false);
setIsLoading({ setIsLoading({ isLoad: false });
isLoad: false
});
setDocumentId(res?.docId); setDocumentId(res?.docId);
getDocumentDetails(res?.docId); getDocumentDetails(res?.docId);
} }
@@ -1574,13 +1571,37 @@ function PdfRequestFiles(props) {
setLoading(false); setLoading(false);
setIsOtp(false); setIsOtp(false);
setOtp(); setOtp();
setContact({ setContact({ name: "", email: "", phone: "" });
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 ( return (
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<Title title={props.templateId ? "Public Sign" : "Request Sign"} /> <Title title={props.templateId ? "Public Sign" : "Request Sign"} />
@@ -1628,7 +1649,9 @@ function PdfRequestFiles(props) {
? "none" ? "none"
: "auto" : "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 && {!requestSignTour &&
signerObjectId && signerObjectId &&
@@ -1668,13 +1691,17 @@ function PdfRequestFiles(props) {
{/* this modal is used to show decline alert */} {/* this modal is used to show decline alert */}
<PdfDeclineModal <PdfDeclineModal
show={isDecline.isDeclined} show={isDecline.isDeclined}
headMsg={t("document-decline")} headMsg={
pdfDetails[0]?.IsDeclined
? t("document-declined")
: t("document-decline")
}
bodyMssg={ bodyMssg={
isDecline.currnt === "Sure" isDecline.currnt === "Sure"
? t("decline-alert-2") ? t("decline-alert-1")
: isDecline.currnt === "YouDeclined" : isDecline.currnt === "YouDeclined"
? t("decline-alert-1") ? t("decline-alert-2")
: isDecline.currnt === "another" && t("decline-alert-3") : isDecline.currnt === "another" && handleDeclineMssg()
} }
footerMessage={isDecline.currnt === "Sure"} footerMessage={isDecline.currnt === "Sure"}
declineDoc={declineDoc} declineDoc={declineDoc}
@@ -1685,6 +1712,8 @@ function PdfRequestFiles(props) {
show={isExpired} show={isExpired}
headMsg={t("expired-doc-title")} headMsg={t("expired-doc-title")}
bodyMssg={t("expired-on-mssg", { expiredDate })} bodyMssg={t("expired-on-mssg", { expiredDate })}
isDownloadBtn={true}
handleDownloadBtn={handleDownloadBtn}
/> />
{!isEmailVerified && ( {!isEmailVerified && (
<VerifyEmail <VerifyEmail
@@ -1698,145 +1727,121 @@ function PdfRequestFiles(props) {
handleResend={handleResend} handleResend={handleResend}
/> />
)} )}
<ModalUi <ModalUi
isOpen={isPublicContact} isOpen={isPublicContact}
title={isOtp ? t("verify-email-1") : t("contact-details")} title={isOtp ? t("verify-email-1") : t("contact-details")}
handleClose={() => { handleClose={() => handleCloseOtp()}
handleCloseOtp();
}}
> >
<div className="h-full p-[20px]"> <div className="h-full p-[20px]">
{isOtp ? ( {isOtp ? (
<form onSubmit={VerifyOTP}> <form onSubmit={VerifyOTP}>
<div className="flex flex-col gap-2"> <div className="mb-[0.75rem]">
<span>{t("get-otp-alert")}</span> <label className="block text-xs font-semibold mb-2">
<label className="op-input op-input-bordered flex items-center gap-2 "> {t("get-otp-alert")}
<input <span className="text-[13px] text-[red]"> *</span>
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}
/>
</label> </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>
<div className="op-modal-action"> <div className="mt-6 flex justify-start gap-2">
<div className="flex gap-2"> <button
<button className="op-btn op-btn-primary"
className="op-btn op-btn-ghost" disabled={loading}
onClick={() => { >
handleCloseOtp(); {loading ? t("loading") : t("verify")}
}} </button>
> <button
{t("cancel")} className="op-btn op-btn-ghost"
</button> onClick={() => handleCloseOtp()}
<button >
className="op-btn op-btn-primary" {t("cancel")}
disabled={loading} </button>
>
{loading ? t("loading") : t("verify")}
</button>
</div>
</div> </div>
</form> </form>
) : ( ) : (
<form onSubmit={handlePublicUser}> <form
<div className="flex flex-col gap-2"> className="text-base-content p-2 mx-auto"
<label className="op-input op-input-bordered flex items-center gap-2 "> onSubmit={handlePublicUser}
<svg >
xmlns="http://www.w3.org/2000/svg" <div className="mb-[0.75rem]">
viewBox="0 0 16 16" <label className="block text-xs font-semibold mb-[5px]">
fill="currentColor" {t("name")}
className="w-4 h-4 opacity-70" <span className="text-[13px] text-[red]"> *</span>
>
<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}
/>
</label> </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>
<div className="op-modal-action"> <div className="mb-[0.75rem]">
<div className="flex gap-2"> <label className="block text-xs font-semibold mb-[5px]">
<button {t("email")}
className="op-btn op-btn-ghost" <span className="text-[13px] text-[red]"> *</span>
onClick={() => { </label>
handleCloseOtp(); <input
}} type="email"
> name="email"
{t("close")} value={contact.email}
</button> onChange={handleInputChange}
<button onInvalid={(e) =>
className="op-btn op-btn-primary" e.target.setCustomValidity(t("input-required"))
disabled={loading} }
> onInput={(e) => e.target.setCustomValidity("")}
{loading ? t("loading") : t("submit")} required
</button> disabled={loading}
</div> 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> </div>
</form> </form>
)} )}
@@ -1904,20 +1909,18 @@ function PdfRequestFiles(props) {
{/* pdf render view */} {/* pdf render view */}
<div className=" w-full md:w-[57%] flex mr-4"> <div className=" w-full md:w-[57%] flex mr-4">
<PdfZoom <PdfZoom
setScale={setScale} clickOnZoomIn={clickOnZoomIn}
scale={scale} clickOnZoomOut={clickOnZoomOut}
containerWH={containerWH} isDisableRotate={true}
setZoomPercent={setZoomPercent}
zoomPercent={zoomPercent}
/> />
<div className=" w-full md:w-[95%] "> <div className=" w-full md:w-[95%] ">
{/* this modal is used show this document is already sign */} {/* this modal is used show this document is already sign */}
<ModalUi <ModalUi
isOpen={isCompleted.isModal} isOpen={isCompleted.isModal}
title={t("document-signed")} title={t("document-signed")}
handleClose={() => { handleClose={() =>
setIsCompleted((prev) => ({ ...prev, isModal: false })); setIsCompleted((prev) => ({ ...prev, isModal: false }))
}} }
reduceWidth={ reduceWidth={
!isCompleted?.message && !isCompleted?.message &&
"md:min-w-[440px] md:max-w-[400px]" "md:min-w-[440px] md:max-w-[400px]"
@@ -2049,15 +2052,12 @@ function PdfRequestFiles(props) {
currentSigner={currentSigner} currentSigner={currentSigner}
pdfUrl={pdfUrl} pdfUrl={pdfUrl}
alreadySign={alreadySign} alreadySign={alreadySign}
totalZoomPercent={totalZoomPercent}
setTotalZoomPercent={setTotalZoomPercent}
setScale={setScale}
scale={scale}
pdfOriginalWH={pdfOriginalWH}
containerWH={containerWH} containerWH={containerWH}
setZoomPercent={setZoomPercent}
zoomPercent={zoomPercent}
isPublicTemplate={isPublicTemplate} isPublicTemplate={isPublicTemplate}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
isDisableRotate={true}
templateId={props.templateId}
/> />
<div ref={divRef} data-tut="pdfArea" className="h-[95%]"> <div ref={divRef} data-tut="pdfArea" className="h-[95%]">
@@ -2091,7 +2091,6 @@ function PdfRequestFiles(props) {
divRef={divRef} divRef={divRef}
setIsResize={setIsResize} setIsResize={setIsResize}
isResize={isResize} isResize={isResize}
setTotalZoomPercent={setTotalZoomPercent}
setScale={setScale} setScale={setScale}
scale={scale} scale={scale}
uniqueId={uniqueId} uniqueId={uniqueId}
@@ -2171,9 +2170,7 @@ function PdfRequestFiles(props) {
<ModalUi <ModalUi
isOpen={validateAlert} isOpen={validateAlert}
title={t("validation-alert")} title={t("validation-alert")}
handleClose={() => { handleClose={() => setValidateAlert(false)}
setValidateAlert(false);
}}
> >
<div className="h-[100%] p-[20px]"> <div className="h-[100%] p-[20px]">
<p>{t("validation-alert-1")}</p> <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 plansArr from "../json/plansArr";
import Title from "../components/Title"; import Title from "../components/Title";
import Parse from "parse"; import Parse from "parse";
import { openInNewTab } from "../constant/Utils"; import { openInNewTab, saveLanguageInLocal } from "../constant/Utils";
import Loader from "../primitives/Loader"; import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const listItemStyle = { const listItemStyle = {
@@ -53,12 +53,6 @@ const PlanSubscriptions = () => {
? "&mobile=" + encodeURIComponent(userDetails.phone) ? "&mobile=" + encodeURIComponent(userDetails.phone)
: ""; : "";
const details =
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
name +
email +
company +
phone;
useEffect(() => { useEffect(() => {
setIsLoader(false); setIsLoader(false);
detectLanguage(); detectLanguage();
@@ -72,6 +66,32 @@ const PlanSubscriptions = () => {
const handleFreePlan = async (item) => { const handleFreePlan = async (item) => {
if (item.url) { 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; const url = yearlyVisible ? item.yearlyUrl + details : item.url + details;
if (user) { if (user) {
localStorage.setItem("userDetails", JSON.stringify(user)); localStorage.setItem("userDetails", JSON.stringify(user));
@@ -99,6 +119,30 @@ const PlanSubscriptions = () => {
} }
} }
}; };
const handleLogout = async () => {
try {
Parse?.User?.logOut();
} catch (err) {
console.log("Err", 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 ( return (
<> <>
<Title title={"Subscriptions"} /> <Title title={"Subscriptions"} />
@@ -109,7 +153,13 @@ const PlanSubscriptions = () => {
) : ( ) : (
<div className="overflow-y-auto max-h-full"> <div className="overflow-y-auto max-h-full">
<div className="block my-2"> <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 <div
role="tablist" role="tablist"
className="op-tabs op-tabs-boxed bg-base-100 my-2 shadow-lg transition-all" className="op-tabs op-tabs-boxed bg-base-100 my-2 shadow-lg transition-all"
@@ -130,6 +180,12 @@ const PlanSubscriptions = () => {
</a> </a>
</div> </div>
<ul className="op-card flex flex-col md:flex-row h-full bg-base-100 justify-center shadow-lg"> <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) => ( {plansArr.map((item) => (
<li <li
className="flex flex-col text-center border-collapse border-[1px] border-gray-300 max-w-[250px]" 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); console.log("err", err.message);
} }
}; };
const handlePaidRoute = () => {
navigate("/subscription");
};
const checkExtUser = async (ssosign) => { const checkExtUser = async (ssosign) => {
const params = { email: ssosign?.email }; const params = { email: ssosign?.email };
try { try {
@@ -196,10 +199,10 @@ const SSOVerify = () => {
localStorage.removeItem("userDetails"); localStorage.removeItem("userDetails");
navigate(redirectUrl); navigate(redirectUrl);
} else { } else {
navigate(`/subscription`, { replace: true }); handlePaidRoute(plan);
} }
} else { } else {
navigate(`/subscription`, { replace: true }); handlePaidRoute(plan);
} }
} else { } else {
navigate(redirectUrl); navigate(redirectUrl);
@@ -225,10 +228,12 @@ const SSOVerify = () => {
} }
}; };
// `handleCloseModal` is triggered when the user wants to close the new user flow modal // `handleCloseModal` is triggered when the user wants to close the new user flow modal
const handleCloseModal = () => { const handleCloseModal = async () => {
setIsModal(false); setIsModal(false);
if (Parse?.User?.current()) { try {
Parse.User.logOut(); 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 togglePasswordVisibility = () => setShowPassword(!showPassword);
const clearStorage = async () => { const clearStorage = async () => {
if (Parse.User.current()) { try {
await Parse.User.logOut(); await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
} }
let baseUrl = localStorage.getItem("baseUrl"); let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId"); let appid = localStorage.getItem("parseAppId");
+176 -150
View File
@@ -34,7 +34,10 @@ import {
changeImageWH, changeImageWH,
handleSendOTP, handleSendOTP,
getContainerScale, getContainerScale,
getDefaultSignature getDefaultSignature,
onClickZoomIn,
onClickZoomOut,
rotatePdfPage
} from "../constant/Utils"; } from "../constant/Utils";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Tour from "reactour"; import Tour from "reactour";
@@ -53,6 +56,7 @@ import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom"; import PdfZoom from "../components/pdf/PdfZoom";
import Loader from "../primitives/Loader"; import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
//For signYourself inProgress section signer can add sign and complete doc sign. //For signYourself inProgress section signer can add sign and complete doc sign.
function SignYourSelf() { function SignYourSelf() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -123,52 +127,38 @@ function SignYourSelf() {
const [zoomPercent, setZoomPercent] = useState(0); const [zoomPercent, setZoomPercent] = useState(0);
const isHeader = useSelector((state) => state.showHeader); const isHeader = useSelector((state) => state.showHeader);
const [scale, setScale] = useState(1); 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 divRef = useRef(null);
const nodeRef = useRef(null); const nodeRef = useRef(null);
const [, drop] = useDrop({ const [, drop] = useDrop({
accept: "BOX", accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor), drop: (item, monitor) => addPositionOfSignature(item, monitor),
collect: (monitor) => ({ collect: (monitor) => ({ isOver: !!monitor.isOver() })
isOver: !!monitor.isOver()
})
}); });
const pdfRef = useRef(); const pdfRef = useRef();
const [{ isDragSign }, dragSignature] = useDrag({ const [{ isDragSign }, dragSignature] = useDrag({
type: "BOX", type: "BOX",
item: { item: { id: 1, text: "signature" },
id: 1, collect: (monitor) => ({ isDragSign: !!monitor.isDragging() })
text: "signature"
},
collect: (monitor) => ({
isDragSign: !!monitor.isDragging()
})
}); });
const [{ isDragStamp }, dragStamp] = useDrag({ const [{ isDragStamp }, dragStamp] = useDrag({
type: "BOX", type: "BOX",
item: { item: { id: 2, text: "stamp" },
id: 2, collect: (monitor) => ({ isDragStamp: !!monitor.isDragging() })
text: "stamp"
},
collect: (monitor) => ({
isDragStamp: !!monitor.isDragging()
})
}); });
const index = xyPostion.findIndex((object) => { const index = xyPostion?.findIndex((object) => {
return object.pageNumber === pageNumber; return object.pageNumber === pageNumber;
}); });
// rowlevel={JSON.parse(localStorage.getItem("rowlevel"))}
const rowLevel = const rowLevel =
localStorage.getItem("rowlevel") && localStorage.getItem("rowlevel") &&
JSON.parse(localStorage.getItem("rowlevel")); JSON.parse(localStorage.getItem("rowlevel"));
const signObjId = const signObjId =
rowLevel && rowLevel?.id rowLevel && rowLevel?.id
? rowLevel.id ? rowLevel.id
: rowLevel?.objectId && rowLevel.objectId; : rowLevel?.objectId && rowLevel.objectId;
const documentId = docId ? docId : signObjId && signObjId; const documentId = docId ? docId : signObjId && signObjId;
const senderUser = const senderUser =
localStorage.getItem( localStorage.getItem(
@@ -202,12 +192,18 @@ function SignYourSelf() {
// Use setTimeout to wait for the transition to complete // Use setTimeout to wait for the transition to complete
const timer = setTimeout(updateSize, 100); // match the transition duration const timer = setTimeout(updateSize, 100); // match the transition duration
return () => clearTimeout(timer); return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current, isHeader]); }, [divRef.current, isHeader]);
//function for get document details for perticular signer with signer'object id //function for get document details for perticular signer with signer'object id
const getDocumentDetails = async (showComplete) => { const getDocumentDetails = async (showComplete) => {
try {
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe);
} catch (err) {
console.log("err in fetch sub", err);
}
try { try {
let isCompleted; let isCompleted;
//getting document details //getting document details
@@ -216,6 +212,11 @@ function SignYourSelf() {
if (documentData && documentData.length > 0) { if (documentData && documentData.length > 0) {
setPdfDetails(documentData); setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId); setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const placeholders =
documentData[0]?.Placeholders?.length > 0
? documentData[0]?.Placeholders
: [];
setXyPostion(placeholders);
const url = documentData[0] && documentData[0]?.URL; const url = documentData[0] && documentData[0]?.URL;
if (url) { if (url) {
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib //convert document url in array buffer format to use embed widgets in pdf using pdf-lib
@@ -228,21 +229,17 @@ function SignYourSelf() {
} else { } else {
setHandleError(t("something-went-wrong-mssg")); setHandleError(t("something-went-wrong-mssg"));
} }
isCompleted = isCompleted = documentData?.[0]?.IsCompleted;
documentData[0].IsCompleted && documentData[0].IsCompleted;
if (isCompleted) { if (isCompleted) {
setIsCelebration(true); setIsCelebration(true);
setTimeout(() => { setTimeout(() => setIsCelebration(false), 5000);
setIsCelebration(false);
}, 5000);
setIsCompleted(true); setIsCompleted(true);
setPdfUrl(documentData[0].SignedUrl); setPdfUrl(documentData[0].SignedUrl);
const alreadySign = {
status: true,
mssg: t("document-signed-alert")
};
if (showComplete) { if (showComplete) {
setShowAlreadySignDoc(alreadySign); setShowAlreadySignDoc({
status: true,
mssg: t("document-signed-alert")
});
} else { } else {
setIsUiLoading(false); setIsUiLoading(false);
setIsSignPad(false); setIsSignPad(false);
@@ -255,17 +252,11 @@ function SignYourSelf() {
documentData === "Error: Something went wrong!" || documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error) (documentData.result && documentData.result.error)
) { ) {
const loadObj = {
isLoad: false
};
setHandleError(t("something-went-wrong-mssg")); setHandleError(t("something-went-wrong-mssg"));
setIsLoading(loadObj); setIsLoading({ isLoad: false });
} else { } else {
setHandleError(t("no-data-avaliable")); setHandleError(t("no-data-avaliable"));
const loadObj = { setIsLoading({ isLoad: false });
isLoad: false
};
setIsLoading(loadObj);
} }
//function to get default signatur eof current user from `contracts_Signature` class //function to get default signatur eof current user from `contracts_Signature` class
const defaultSignRes = await getDefaultSignature(jsonSender.objectId); const defaultSignRes = await getDefaultSignature(jsonSender.objectId);
@@ -274,17 +265,12 @@ function SignYourSelf() {
setMyInitial(defaultSignRes?.res?.defaultInitial); setMyInitial(defaultSignRes?.res?.defaultInitial);
} else if (defaultSignRes?.status === "error") { } else if (defaultSignRes?.status === "error") {
setHandleError("Error: Something went wrong!"); setHandleError("Error: Something went wrong!");
setIsLoading({ setIsLoading({ isLoad: false });
isLoad: false
});
} }
const contractUsersRes = await contractUsers(); const contractUsersRes = await contractUsers();
if (contractUsersRes === "Error: Something went wrong!") { if (contractUsersRes === "Error: Something went wrong!") {
const loadObj = {
isLoad: false
};
setHandleError(t("something-went-wrong-mssg")); setHandleError(t("something-went-wrong-mssg"));
setIsLoading(loadObj); setIsLoading({ isLoad: false });
} else if (contractUsersRes[0] && contractUsersRes.length > 0) { } else if (contractUsersRes[0] && contractUsersRes.length > 0) {
setActiveMailAdapter(contractUsersRes[0]?.active_mail_adapter); setActiveMailAdapter(contractUsersRes[0]?.active_mail_adapter);
setContractName("_Users"); setContractName("_Users");
@@ -364,51 +350,32 @@ function SignYourSelf() {
const addWidgetOptions = (type) => { const addWidgetOptions = (type) => {
switch (type) { switch (type) {
case "signature": case "signature":
return { return { name: "signature" };
name: "signature"
};
case "stamp": case "stamp":
return { return { name: "stamp" };
name: "stamp"
};
case "checkbox": case "checkbox":
return { return { name: "checkbox" };
name: "checkbox"
};
case textWidget: case textWidget:
return { return { name: "text" };
name: "text"
};
case "initials": case "initials":
return { return { name: "initials" };
name: "initials"
};
case "name": case "name":
return { return {
name: "name", name: "name",
defaultValue: getWidgetValue(type), defaultValue: getWidgetValue(type),
validation: { validation: { type: "text", pattern: "" }
type: "text",
pattern: ""
}
}; };
case "company": case "company":
return { return {
name: "company", name: "company",
defaultValue: getWidgetValue(type), defaultValue: getWidgetValue(type),
validation: { validation: { type: "text", pattern: "" }
type: "text",
pattern: ""
}
}; };
case "job title": case "job title":
return { return {
name: "job title", name: "job title",
defaultValue: getWidgetValue(type), defaultValue: getWidgetValue(type),
validation: { validation: { type: "text", pattern: "" }
type: "text",
pattern: ""
}
}; };
case "date": case "date":
return { return {
@@ -417,17 +384,12 @@ function SignYourSelf() {
validation: { format: "MM/dd/yyyy", type: "date-format" } validation: { format: "MM/dd/yyyy", type: "date-format" }
}; };
case "image": case "image":
return { return { name: "image" };
name: "image"
};
case "email": case "email":
return { return {
name: "email", name: "email",
defaultValue: getWidgetValue(type), defaultValue: getWidgetValue(type),
validation: { validation: { type: "email", pattern: "" }
type: "email",
pattern: ""
}
}; };
default: default:
return {}; return {};
@@ -439,7 +401,7 @@ function SignYourSelf() {
const key = randomId(); const key = randomId();
let dropData = []; let dropData = [];
let dropObj = {}; let dropObj = {};
let filterDropPos = xyPostion.filter( let filterDropPos = xyPostion?.filter(
(data) => data.pageNumber === pageNumber (data) => data.pageNumber === pageNumber
); );
const dragTypeValue = item?.text ? item.text : monitor.type; 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 //adding and updating drop position in array when user drop signature button in div
if (item === "onclick") { if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
const divHeight = divRef.current.getBoundingClientRect().height;
const getWidth = widgetTypeExist const getWidth = widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth ? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth
: dragTypeValue === "initials" : defaultWidthHeight(dragTypeValue).width;
? defaultWidthHeight(dragTypeValue).width
: "";
const getHeight = widgetTypeExist const getHeight = widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getHeight ? calculateInitialWidthHeight(dragTypeValue, widgetValue).getHeight
: dragTypeValue === "initials" : defaultWidthHeight(dragTypeValue).height;
? defaultWidthHeight(dragTypeValue).height
: "";
dropObj = { dropObj = {
xPosition: containerWH.width / 2, xPosition: getWidth / 2 + containerWH.width / 2,
yPosition: containerWH.height / 2, yPosition: getHeight + divHeight / 2,
isStamp: isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true, (dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key, key: key,
type: dragTypeValue, type: dragTypeValue,
scale: containerScale, scale: containerScale,
Width: getWidth / (containerScale * scale), Width: getWidth,
Height: getHeight / (containerScale * scale), Height: getHeight,
options: addWidgetOptions(dragTypeValue) options: addWidgetOptions(dragTypeValue)
}; };
dropData.push(dropObj); dropData.push(dropObj);
} else { } else {
//This method returns the offset of the current pointer (mouse) position relative to the client viewport. //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), options: addWidgetOptions(dragTypeValue),
scale: containerScale scale: containerScale
}; };
dropData.push(dropObj); dropData.push(dropObj);
} }
if (filterDropPos.length > 0) { if (filterDropPos?.length > 0) {
const index = xyPostion.findIndex((object) => { const index = xyPostion?.findIndex((object) => {
return object.pageNumber === pageNumber; return object.pageNumber === pageNumber;
}); });
const updateData = filterDropPos[0].pos; const updateData = filterDropPos?.[0].pos;
const newSignPos = updateData.concat(dropData); const newSignPos = updateData.concat(dropData);
let xyPos = { let xyPos = { pageNumber: pageNumber, pos: newSignPos };
pageNumber: pageNumber, xyPostion?.splice(index, 1, xyPos);
pos: newSignPos
};
xyPostion.splice(index, 1, xyPos);
} else { } else {
const xyPos = { const xyPos = { pageNumber: pageNumber, pos: dropData };
pageNumber: pageNumber,
pos: dropData
};
setXyPostion((prev) => [...prev, xyPos]); setXyPostion((prev) => [...prev, xyPos]);
} }
@@ -539,7 +492,6 @@ function SignYourSelf() {
) { ) {
setIsSignPad(true); setIsSignPad(true);
} }
if (dragTypeValue === "stamp" || dragTypeValue === "image") { if (dragTypeValue === "stamp" || dragTypeValue === "image") {
setIsStamp(true); setIsStamp(true);
} else if (dragTypeValue === "initials") { } else if (dragTypeValue === "initials") {
@@ -602,6 +554,29 @@ function SignYourSelf() {
setIsVerifyModal(true); setIsVerifyModal(true);
await handleSendOTP(Parse.User.current().getEmail()); 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 //function for send placeholder's co-ordinate(x,y) position embed signature url or stamp url
async function embedWidgetsData() { async function embedWidgetsData() {
//check current user email is verified or not //check current user email is verified or not
@@ -740,7 +715,7 @@ function SignYourSelf() {
//function for get digital signature //function for get digital signature
const signPdfFun = async (base64Url, documentId) => { const signPdfFun = async (base64Url, documentId) => {
let isCustomCompletionMail = false; let isCustomCompletionMail = false;
const getIsSubscribe = await checkIsSubscribed();
const tenantDetails = await getTenantDetails(jsonSender.objectId); const tenantDetails = await getTenantDetails(jsonSender.objectId);
if (tenantDetails && tenantDetails === "user does not exist!") { if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist")); alert(t("user-not-exist"));
@@ -748,7 +723,7 @@ function SignYourSelf() {
if ( if (
tenantDetails?.CompletionBody && tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject && tenantDetails?.CompletionSubject &&
getIsSubscribe isSubscribe?.isValid
) { ) {
isCustomCompletionMail = true; isCustomCompletionMail = true;
} }
@@ -781,7 +756,6 @@ function SignYourSelf() {
const getNewse64 = await changeImageWH(base64Sign); const getNewse64 = await changeImageWH(base64Sign);
//remove suffiix of base64 //remove suffiix of base64
const suffixbase64 = getNewse64 && getNewse64.split(",").pop(); const suffixbase64 = getNewse64 && getNewse64.split(",").pop();
const params = { const params = {
pdfFile: base64Url, pdfFile: base64Url,
docId: documentId, docId: documentId,
@@ -794,6 +768,7 @@ function SignYourSelf() {
const signedpdf = JSON.parse(JSON.stringify(resSignPdf)); const signedpdf = JSON.parse(JSON.stringify(resSignPdf));
setPdfUrl(signedpdf); setPdfUrl(signedpdf);
getDocumentDetails(false); getDocumentDetails(false);
setPdfRotatese64("");
} }
}; };
@@ -814,10 +789,10 @@ function SignYourSelf() {
containerWH containerWH
); );
if (dragKey >= 0) { if (dragKey >= 0) {
const filterDropPos = xyPostion.filter( const filterDropPos = xyPostion?.filter(
(data) => data.pageNumber === pageNumber (data) => data.pageNumber === pageNumber
); );
if (filterDropPos.length > 0) { if (filterDropPos?.length > 0) {
const getXYdata = xyPostion[index].pos; const getXYdata = xyPostion[index].pos;
const getPosData = getXYdata; const getPosData = getXYdata;
const addSign = getPosData.map((url) => { const addSign = getPosData.map((url) => {
@@ -888,15 +863,11 @@ function SignYourSelf() {
setIsSignPad(false); setIsSignPad(false);
setIsImageSelect(false); setIsImageSelect(false);
setImage(); setImage();
if (isDefaultSign) { if (isDefaultSign) {
const img = new Image(); const img = new Image();
img.src = defaultSignImg; img.src = defaultSignImg;
if (img.complete) { if (img.complete) {
imgWH = { imgWH = { width: img.width, height: img.height };
width: img.width,
height: img.height
};
} }
} }
const getUpdatePosition = onSaveSign( const getUpdatePosition = onSaveSign(
@@ -921,19 +892,11 @@ function SignYourSelf() {
const touch = e.touches[0]; // Get the first touch point const touch = e.touches[0]; // Get the first touch point
mouseX = touch.clientX - divRect.left; mouseX = touch.clientX - divRect.left;
mouseY = touch.clientY - divRect.top; mouseY = touch.clientY - divRect.top;
setSignBtnPosition([ setSignBtnPosition([{ xPos: mouseX, yPos: mouseY }]);
{
xPos: mouseX,
yPos: mouseY
}
]);
} else { } else {
mouseX = e.clientX - divRect.left; mouseX = e.clientX - divRect.left;
mouseY = e.clientY - divRect.top; mouseY = e.clientY - divRect.top;
const xyPosition = { const xyPosition = { xPos: mouseX, yPos: mouseY };
xPos: mouseX,
yPos: mouseY
};
setXYSignature(xyPosition); setXYSignature(xyPosition);
} }
}; };
@@ -951,9 +914,7 @@ function SignYourSelf() {
const handleDeleteSign = (key) => { const handleDeleteSign = (key) => {
setCurrWidgetsDetails({}); setCurrWidgetsDetails({});
const updateResizeData = []; const updateResizeData = [];
let filterData = xyPostion[index].pos.filter((data) => data.key !== key); let filterData = xyPostion[index].pos.filter((data) => data.key !== key);
//delete and update block position //delete and update block position
if (filterData.length > 0) { if (filterData.length > 0) {
updateResizeData.push(filterData); updateResizeData.push(filterData);
@@ -1093,7 +1054,11 @@ function SignYourSelf() {
name: dropdownName, name: dropdownName,
values: dropdownOptions, values: dropdownOptions,
isReadOnly: isReadOnly, 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(); handleCloseModal();
} }
} }
setFontSize();
setFontColor();
}; };
const handleCloseModal = () => { const handleCloseModal = () => {
setCurrWidgetsDetails({}); setCurrWidgetsDetails({});
@@ -1154,6 +1121,56 @@ function SignYourSelf() {
handleTextSettingModal(false); 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 ( return (
<DndProvider backend={HTML5Backend}> <DndProvider backend={HTML5Backend}>
<Title title={"Self Sign"} /> <Title title={"Self Sign"} />
@@ -1176,7 +1193,6 @@ function SignYourSelf() {
<Confetti width={window.innerWidth} height={window.innerHeight} /> <Confetti width={window.innerWidth} height={window.innerHeight} />
</div> </div>
)} )}
<div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300"> <div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300">
{!isEmailVerified && ( {!isEmailVerified && (
<VerifyEmail <VerifyEmail
@@ -1213,14 +1229,13 @@ function SignYourSelf() {
setSignBtnPosition={setSignBtnPosition} setSignBtnPosition={setSignBtnPosition}
pageNumber={pageNumber} pageNumber={pageNumber}
containerWH={containerWH} containerWH={containerWH}
pdfRotateBase64={pdfRotateBase64}
/> />
<div className=" w-full md:w-[57%] flex mr-4"> <div className=" w-full md:w-[57%] flex mr-4">
<PdfZoom <PdfZoom
setScale={setScale} clickOnZoomIn={clickOnZoomIn}
scale={scale} clickOnZoomOut={clickOnZoomOut}
containerWH={containerWH} handleRotationFun={handleRotationFun}
setZoomPercent={setZoomPercent}
zoomPercent={zoomPercent}
/> />
<div className="w-full md:w-[95%]"> <div className="w-full md:w-[95%]">
<ModalUi <ModalUi
@@ -1239,9 +1254,7 @@ function SignYourSelf() {
<ModalUi <ModalUi
isOpen={showAlreadySignDoc.status} isOpen={showAlreadySignDoc.status}
title={t("document-signed")} title={t("document-signed")}
handleClose={() => { handleClose={() => setShowAlreadySignDoc({ status: false })}
setShowAlreadySignDoc({ status: false });
}}
> >
<div className="p-[20px] h-full"> <div className="p-[20px] h-full">
<p>{showAlreadySignDoc.mssg}</p> <p>{showAlreadySignDoc.mssg}</p>
@@ -1265,6 +1278,10 @@ function SignYourSelf() {
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
isSignYourself={true} isSignYourself={true}
handleClose={handleCloseModal} handleClose={handleCloseModal}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/> />
<PlaceholderCopy <PlaceholderCopy
isPageCopy={isPageCopy} isPageCopy={isPageCopy}
@@ -1274,6 +1291,7 @@ function SignYourSelf() {
allPages={allPages} allPages={allPages}
pageNumber={pageNumber} pageNumber={pageNumber}
signKey={signKey} signKey={signKey}
widgetType={widgetType}
/> />
{/* this is modal of signature pad */} {/* this is modal of signature pad */}
<SignPad <SignPad
@@ -1324,12 +1342,12 @@ function SignYourSelf() {
isSignYourself={true} isSignYourself={true}
setIsEmail={setIsEmail} setIsEmail={setIsEmail}
isCompleted={isCompleted} isCompleted={isCompleted}
handleRotationFun={handleRotationFun}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
widgetsDetails={xyPostion}
/> />
<div <div ref={divRef} data-tut="reactourSecond" className="h-full">
ref={divRef}
data-tut="reactourSecond"
className="h-full md:h-[95%]"
>
{containerWH?.width && containerWH?.height && ( {containerWH?.width && containerWH?.height && (
<RenderPdf <RenderPdf
pageNumber={pageNumber} pageNumber={pageNumber}
@@ -1368,6 +1386,11 @@ function SignYourSelf() {
handleTextSettingModal={handleTextSettingModal} handleTextSettingModal={handleTextSettingModal}
setScale={setScale} setScale={setScale}
scale={scale} scale={scale}
pdfRotateBase64={pdfRotateBase64}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/> />
)} )}
</div> </div>
@@ -1407,9 +1430,7 @@ function SignYourSelf() {
<ModalUi <ModalUi
isOpen={validateAlert} isOpen={validateAlert}
title={t("validation-alert")} title={t("validation-alert")}
handleClose={() => { handleClose={() => setValidateAlert(false)}
setValidateAlert(false);
}}
> >
<div className="p-[20px] h-full"> <div className="p-[20px] h-full">
<p>{t("validate-alert-mssg")}</p> <p>{t("validate-alert-mssg")}</p>
@@ -1424,6 +1445,11 @@ function SignYourSelf() {
</button> </button>
</div> </div>
</ModalUi> </ModalUi>
<RotateAlert
isRotate={isRotate.status}
setIsRotate={setIsRotate}
handleRemoveWidgets={handleRemoveWidgets}
/>
<TextFontSetting <TextFontSetting
isTextSetting={isTextSetting} isTextSetting={isTextSetting}
setIsTextSetting={setIsTextSetting} setIsTextSetting={setIsTextSetting}
+23 -15
View File
@@ -8,9 +8,10 @@ import ModalUi from "../primitives/ModalUi";
import pad from "../assets/images/pad.svg"; import pad from "../assets/images/pad.svg";
import AddTeam from "../components/AddTeam"; import AddTeam from "../components/AddTeam";
import { isEnableSubscription } from "../constant/const"; import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribedTeam } from "../constant/Utils"; import { checkIsSubscribed } from "../constant/Utils";
import SubscribeCard from "../primitives/SubscribeCard"; import SubscribeCard from "../primitives/SubscribeCard";
import Title from "../components/Title"; import Title from "../components/Title";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const heading = ["Sr.No", "Name", "Parent Team", "Active"]; const heading = ["Sr.No", "Name", "Parent Team", "Active"];
@@ -38,7 +39,7 @@ const TeamList = () => {
const [isActiveModal, setIsActiveModal] = useState({}); const [isActiveModal, setIsActiveModal] = useState({});
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isActLoader, setIsActLoader] = useState({}); const [isActLoader, setIsActLoader] = useState({});
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: false });
const [isEditModal, setIsEditModal] = useState({}); const [isEditModal, setIsEditModal] = useState({});
const [isAdmin, setIsAdmin] = useState(false); const [isAdmin, setIsAdmin] = useState(false);
const startIndex = (currentPage - 1) * recordperPage; // user per page const startIndex = (currentPage - 1) * recordperPage; // user per page
@@ -103,8 +104,8 @@ const TeamList = () => {
try { try {
setIsLoader(true); setIsLoader(true);
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribedTeam(); const subscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe); setIsSubscribe(subscribe);
} }
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (extUser) { if (extUser) {
@@ -134,7 +135,7 @@ const TeamList = () => {
const handleFormModal = () => { const handleFormModal = () => {
setIsModal(!isModal); setIsModal(!isModal);
}; };
// Get current list // to slice out 10 objects from array for current page
const indexOfLastDoc = currentPage * recordperPage; const indexOfLastDoc = currentPage * recordperPage;
const indexOfFirstDoc = indexOfLastDoc - recordperPage; const indexOfFirstDoc = indexOfLastDoc - recordperPage;
const currentList = teamList?.slice(indexOfFirstDoc, indexOfLastDoc); const currentList = teamList?.slice(indexOfFirstDoc, indexOfLastDoc);
@@ -145,8 +146,19 @@ const TeamList = () => {
}; };
// Change page // Change page
const paginateFront = () => setCurrentPage(currentPage + 1); const paginateFront = () => {
const paginateBack = () => setCurrentPage(currentPage - 1); const lastValue = pageNumbers?.[pageNumbers?.length - 1];
if (currentPage < lastValue) {
setCurrentPage(currentPage + 1);
}
};
const paginateBack = () => {
if (startIndex > 0) {
setCurrentPage(currentPage - 1);
}
};
const handleActionBtn = (act, item) => { const handleActionBtn = (act, item) => {
if (act.action === "edit") { if (act.action === "edit") {
setIsEditModal({ [item.objectId]: true }); setIsEditModal({ [item.objectId]: true });
@@ -225,15 +237,11 @@ const TeamList = () => {
<Loader /> <Loader />
</div> </div>
)} )}
{isSubscribe && isEnableSubscription && !isLoader && ( {validplan[isSubscribe.plan] && isEnableSubscription && !isLoader && (
<> <>
{isAdmin ? ( {isAdmin ? (
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg"> <div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
{isAlert.msg && ( {isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
<Alert type={isAlert.type}>
<div className="ml-3">{isAlert.msg}</div>
</Alert>
)}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]"> <div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<div className="font-light"> <div className="font-light">
{t("report-name.Teams")} {t("report-name.Teams")}
@@ -470,9 +478,9 @@ const TeamList = () => {
)} )}
</> </>
)} )}
{!isSubscribe && isEnableSubscription && !isLoader && ( {isEnableSubscription && !validplan[isSubscribe.plan] && !isLoader && (
<div data-tut="apisubscribe"> <div data-tut="apisubscribe">
<SubscribeCard plan={"TEAMS"} price={"20"} /> <SubscribeCard plan="TEAMS" />
</div> </div>
)} )}
</div> </div>
+236 -123
View File
@@ -25,7 +25,13 @@ import {
textInputWidget, textInputWidget,
radioButtonWidget, radioButtonWidget,
fetchSubscription, fetchSubscription,
getContainerScale getContainerScale,
convertBase64ToFile,
rotatePdfPage,
onClickZoomOut,
onClickZoomIn,
handleRemoveWidgets,
handleRotateWarning
} from "../constant/Utils"; } from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf"; import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css"; import "../styles/AddUser.css";
@@ -41,6 +47,7 @@ import Parse from "parse";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import PdfZoom from "../components/pdf/PdfZoom"; import PdfZoom from "../components/pdf/PdfZoom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
const TemplatePlaceholder = () => { const TemplatePlaceholder = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -55,12 +62,15 @@ const TemplatePlaceholder = () => {
const [xySignature, setXYSignature] = useState({}); const [xySignature, setXYSignature] = useState({});
const [dragKey, setDragKey] = useState(); const [dragKey, setDragKey] = useState();
const [signersdata, setSignersData] = useState([]); const [signersdata, setSignersData] = useState([]);
const [signerObjId, setSignerObjId] = useState();
const [signerPos, setSignerPos] = useState([]); const [signerPos, setSignerPos] = useState([]);
const [isSelectListId, setIsSelectId] = useState(); const [isSelectListId, setIsSelectId] = useState();
const [isSendAlert, setIsSendAlert] = useState(false); const [isSendAlert, setIsSendAlert] = useState(false);
const [isCreateDocModal, setIsCreateDocModal] = useState(false); const [isCreateDocModal, setIsCreateDocModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false);
const [isRotate, setIsRotate] = useState({
status: false,
degree: 0
});
const [isLoading, setIsLoading] = useState({ const [isLoading, setIsLoading] = useState({
isLoad: true, isLoad: true,
message: t("loading-mssg") message: t("loading-mssg")
@@ -73,7 +83,6 @@ const TemplatePlaceholder = () => {
const [tourStatus, setTourStatus] = useState([]); const [tourStatus, setTourStatus] = useState([]);
const [signerUserId, setSignerUserId] = useState(); const [signerUserId, setSignerUserId] = useState();
const [pdfOriginalWH, setPdfOriginalWH] = useState([]); const [pdfOriginalWH, setPdfOriginalWH] = useState([]);
const [contractName, setContractName] = useState("");
const [containerWH, setContainerWH] = useState(); const [containerWH, setContainerWH] = useState();
const signRef = useRef(null); const signRef = useRef(null);
const dragRef = useRef(null); const dragRef = useRef(null);
@@ -91,6 +100,7 @@ const TemplatePlaceholder = () => {
const [isNameModal, setIsNameModal] = useState(false); const [isNameModal, setIsNameModal] = useState(false);
const [isTextSetting, setIsTextSetting] = useState(false); const [isTextSetting, setIsTextSetting] = useState(false);
const [pdfLoad, setPdfLoad] = useState(false); const [pdfLoad, setPdfLoad] = useState(false);
const [pdfRotateBase64, setPdfRotatese64] = useState("");
const color = [ const color = [
"#93a3db", "#93a3db",
"#e6c3db", "#e6c3db",
@@ -181,21 +191,25 @@ const TemplatePlaceholder = () => {
return () => clearTimeout(timer); return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current, isHeader]); }, [divRef.current, isHeader]);
const handleNavigation = () => {
navigate("/subscription");
};
async function checkIsSubscribed() { async function checkIsSubscribed() {
const res = await fetchSubscription(); const res = await fetchSubscription();
const freeplan = res.plan; const plan = res.plan;
const billingDate = res.billingDate; const billingDate = res.billingDate;
if (freeplan === "freeplan") { if (plan === "freeplan") {
return true; return true;
} else if (billingDate) { } else if (billingDate) {
if (new Date(billingDate) > new Date()) { if (new Date(billingDate) > new Date()) {
setIsSubscribe(true); setIsSubscribe(true);
return true; return true;
} else { } else {
navigate(`/subscription`); handleNavigation(plan);
} }
} else { } else {
navigate(`/subscription`); handleNavigation(plan);
} }
} }
// `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present // `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
@@ -225,8 +239,6 @@ const TemplatePlaceholder = () => {
setPdfDetails(documentData); setPdfDetails(documentData);
setIsSigners(true); setIsSigners(true);
if (documentData[0].Signers && documentData[0].Signers.length > 0) { if (documentData[0].Signers && documentData[0].Signers.length > 0) {
setSignerObjId(documentData[0].Signers[0].objectId);
setContractName(documentData[0].Signers[0].className);
setIsSelectId(0); setIsSelectId(0);
if ( if (
documentData[0].Placeholders && documentData[0].Placeholders &&
@@ -365,21 +377,19 @@ const TemplatePlaceholder = () => {
containerWH containerWH
); );
const key = randomId(); const key = randomId();
let filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
const dragTypeValue = item?.text ? item.text : monitor.type; const dragTypeValue = item?.text ? item.text : monitor.type;
const widgetWidth = defaultWidthHeight(dragTypeValue).width; const widgetWidth = defaultWidthHeight(dragTypeValue).width;
const widgetHeight = defaultWidthHeight(dragTypeValue).height; const widgetHeight = defaultWidthHeight(dragTypeValue).height;
let dropData = []; let dropData = [],
currentPagePosition;
let placeHolder; let placeHolder;
if (item === "onclick") { if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement height of the div
const divHeight = divRef.current.getBoundingClientRect().height;
const dropObj = { const dropObj = {
//onclick put placeholder center on pdf //onclick put placeholder center on pdf
xPosition: xPosition: widgetWidth / 4 + containerWH.width / 2,
(containerWH.width / 2 - widgetWidth / 2) / yPosition: widgetHeight + divHeight / 2,
(containerScale * scale),
yPosition:
(containerWH.height / 2 - widgetHeight / 2) /
(containerScale * scale),
isStamp: isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true, (dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key, key: key,
@@ -430,65 +440,40 @@ const TemplatePlaceholder = () => {
pos: dropData pos: dropData
}; };
} }
const { blockColor, Role } = signer; let filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
//adding placholder in existing signer pos array (placaholder) const getPlaceHolder = filterSignerPos?.placeHolder;
if (filterSignerPos.length > 0) { if (getPlaceHolder) {
const getPlaceHolder = filterSignerPos[0].placeHolder; //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( const updatePlace = getPlaceHolder.filter(
(data) => data.pageNumber !== pageNumber (data) => data.pageNumber !== pageNumber
); );
const getPageNumer = getPlaceHolder.filter( const getPos = currentPagePosition?.pos;
(data) => data.pageNumber === pageNumber 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);
//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);
}
} else { } else {
//adding new placeholder for selected signer in pos array (placeholder) //else condition to add placeholder widgets on multiple page first time
let placeHolderPos; const updatesignerPos = signerPos.map((x) =>
if (contractName) { x.Id === uniqueId && x?.placeHolder
placeHolderPos = { ? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
signerPtr: { : x.Id === uniqueId
__type: "Pointer", ? { ...x, placeHolder: [placeHolder] }
className: `${contractName}`, : x
objectId: signerObjId );
}, setSignerPos(updatesignerPos);
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]);
} }
if (dragTypeValue === "dropdown") { if (dragTypeValue === "dropdown") {
@@ -615,10 +600,6 @@ const TemplatePlaceholder = () => {
//function for delete signature block //function for delete signature block
const handleDeleteSign = (key, Id) => { const handleDeleteSign = (key, Id) => {
const updateData = []; const updateData = [];
// const filterSignerPos = signerPos.filter(
// (data) => data.signerObjId === signerId
// );
const filterSignerPos = signerPos.filter((data) => data.Id === Id); const filterSignerPos = signerPos.filter((data) => data.Id === Id);
if (filterSignerPos.length > 0) { if (filterSignerPos.length > 0) {
@@ -651,11 +632,11 @@ const TemplatePlaceholder = () => {
setSignerPos(newUpdateSigner); setSignerPos(newUpdateSigner);
} else { } else {
const updateFilter = signerPos.filter((data) => data.Id !== Id);
const getRemainPage = filterSignerPos[0].placeHolder.filter( const getRemainPage = filterSignerPos[0].placeHolder.filter(
(data) => data.pageNumber !== pageNumber (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) { if (getRemainPage && getRemainPage.length > 0) {
const newUpdatePos = filterSignerPos.map((obj) => { const newUpdatePos = filterSignerPos.map((obj) => {
if (obj.Id === Id) { if (obj.Id === Id) {
@@ -669,7 +650,16 @@ const TemplatePlaceholder = () => {
setSignerPos(signerupdate); setSignerPos(signerupdate);
} else { } 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 const touch = e.touches[0]; // Get the first touch point
mouseX = touch.clientX - divRect.left; mouseX = touch.clientX - divRect.left;
mouseY = touch.clientY - divRect.top; mouseY = touch.clientY - divRect.top;
setSignBtnPosition([ setSignBtnPosition([{ xPos: mouseX, yPos: mouseY }]);
{
xPos: mouseX,
yPos: mouseY
}
]);
} else { } else {
mouseX = e.clientX - divRect.left; mouseX = e.clientX - divRect.left;
mouseY = e.clientY - divRect.top; mouseY = e.clientY - divRect.top;
const xyPosition = { const xyPosition = { xPos: mouseX, yPos: mouseY };
xPos: mouseX,
yPos: mouseY
};
setXYSignature(xyPosition); setXYSignature(xyPosition);
} }
}; };
@@ -712,20 +694,54 @@ const TemplatePlaceholder = () => {
const handleMouseLeave = () => { const handleMouseLeave = () => {
setSignBtnPosition([xySignature]); setSignBtnPosition([xySignature]);
}; };
const alertSendEmail = async () => { const alertSendEmail = async () => {
if (signerPos.length !== signersdata.length) { const isPlaceholderExist = signerPos.every((data) => data.placeHolder);
setIsSendAlert(true); if (isPlaceholderExist) {
} else {
handleSaveTemplate(); 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 () => { const handleSaveTemplate = async () => {
if (signersdata?.length) { if (signersdata?.length) {
const loadObj = { const loadObj = { isLoad: true, message: t("loading-mssg") };
isLoad: true,
message: t("loading-mssg")
};
setIsLoading(loadObj); setIsLoading(loadObj);
setIsSendAlert(false); setIsSendAlert(false);
let signers = []; 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 { try {
const data = { const data = {
Placeholders: signerPos, Placeholders: signerPos,
@@ -751,23 +778,18 @@ const TemplatePlaceholder = () => {
SendinOrder: pdfDetails[0]?.SendinOrder || false, SendinOrder: pdfDetails[0]?.SendinOrder || false,
AutomaticReminders: pdfDetails[0]?.AutomaticReminders, AutomaticReminders: pdfDetails[0]?.AutomaticReminders,
RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery), RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery),
NextReminderDate: pdfDetails[0]?.NextReminderDate NextReminderDate: pdfDetails[0]?.NextReminderDate,
URL: pdfUrl
}; };
const updateTemplate = new Parse.Object("contracts_Template"); const updateTemplate = new Parse.Object("contracts_Template");
updateTemplate.id = templateId; updateTemplate.id = templateId;
for (const key in data) { for (const key in data) {
updateTemplate.set(key, data[key]); updateTemplate.set(key, data[key]);
} }
await updateTemplate.save(null, { await updateTemplate.save();
sessionToken: localStorage.getItem("accesstoken")
});
setIsCreateDocModal(true); setIsCreateDocModal(true);
setIsMailSend(true); setIsMailSend(true);
const loadObj = { setIsLoading({ isLoad: false });
isLoad: false
};
setIsLoading(loadObj);
} catch (e) { } catch (e) {
setIsLoading(false); setIsLoading(false);
alert(t("something-went-wrong-mssg")); alert(t("something-went-wrong-mssg"));
@@ -891,8 +913,21 @@ const TemplatePlaceholder = () => {
const handleCreateDocModal = async () => { const handleCreateDocModal = async () => {
setIsCreateDocModal(false); setIsCreateDocModal(false);
setIsCreateDoc(true); 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 // handle create document
const res = await createDocument(pdfDetails, signerPos, signersdata); const res = await createDocument(
pdfDetails,
signerPos,
signersdata,
pdfUrl
);
if (res.status === "success") { if (res.status === "success") {
navigate(`/placeHolderSign/${res.id}`, { navigate(`/placeHolderSign/${res.id}`, {
state: { title: "Use Template" } state: { title: "Use Template" }
@@ -903,7 +938,6 @@ const TemplatePlaceholder = () => {
setIsCreateDoc(false); setIsCreateDoc(false);
} }
}; };
// `handleAddSigner` is used to open Add Role Modal // `handleAddSigner` is used to open Add Role Modal
const handleAddSigner = () => { const handleAddSigner = () => {
setIsModalRole(true); setIsModalRole(true);
@@ -914,8 +948,6 @@ const TemplatePlaceholder = () => {
// save Role in entry in signerList and user // save Role in entry in signerList and user
const handleAddRole = (e) => { const handleAddRole = (e) => {
e.preventDefault(); e.preventDefault();
setSignerObjId("");
setContractName("");
const count = signersdata.length > 0 ? signersdata.length + 1 : 1; const count = signersdata.length > 0 ? signersdata.length + 1 : 1;
const Id = randomId(); const Id = randomId();
const index = signersdata.length; const index = signersdata.length;
@@ -925,6 +957,15 @@ const TemplatePlaceholder = () => {
blockColor: color[index] blockColor: color[index]
}; };
setSignersData((prevArr) => [...prevArr, obj]); setSignersData((prevArr) => [...prevArr, obj]);
const signerPosObj = {
signerPtr: {},
signerObjId: "",
blockColor: color[index],
Role: roleName || "User " + count,
Id: Id
};
setSignerPos((prev) => [...prev, signerPosObj]);
setIsModalRole(false); setIsModalRole(false);
setRoleName(""); setRoleName("");
setUniqueId(Id); setUniqueId(Id);
@@ -956,11 +997,12 @@ const TemplatePlaceholder = () => {
setIsMailSend(false); 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 // and update entry in signersList
const handleLinkUser = (id) => { const handleLinkUser = (id) => {
setIsAddUser({ [id]: true }); setIsAddUser({ [id]: true });
}; };
// `handleAddUser` is used to adduser
const handleAddUser = (data) => { const handleAddUser = (data) => {
const signerPtr = { const signerPtr = {
__type: "Pointer", __type: "Pointer",
@@ -1089,7 +1131,13 @@ const TemplatePlaceholder = () => {
status: status, status: status,
defaultValue: defaultValue, defaultValue: defaultValue,
isReadOnly: isReadOnly || false, 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, isReadOnly: isReadOnly || false,
defaultValue: defaultValue, 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, name: dropdownName,
status: status, status: status,
values: dropdownOptions, 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) => { const handleWidgetdefaultdata = (defaultdata) => {
@@ -1250,6 +1312,7 @@ const TemplatePlaceholder = () => {
setCurrWidgetsDetails({}); setCurrWidgetsDetails({});
handleNameModal(); handleNameModal();
}; };
const handleNameModal = () => { const handleNameModal = () => {
setIsNameModal(false); setIsNameModal(false);
setCurrWidgetsDetails({}); setCurrWidgetsDetails({});
@@ -1258,6 +1321,38 @@ const TemplatePlaceholder = () => {
setIsCheckbox(false); 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 ( return (
<> <>
<Title title={"Template"} /> <Title title={"Template"} />
@@ -1305,11 +1400,9 @@ const TemplatePlaceholder = () => {
{/* pdf render view */} {/* pdf render view */}
<div className=" w-full md:w-[57%] flex mr-4"> <div className=" w-full md:w-[57%] flex mr-4">
<PdfZoom <PdfZoom
setScale={setScale} clickOnZoomIn={clickOnZoomIn}
scale={scale} clickOnZoomOut={clickOnZoomOut}
containerWH={containerWH} handleRotationFun={handleRotationFun}
setZoomPercent={setZoomPercent}
zoomPercent={zoomPercent}
/> />
<div className="w-full md:w-[95%]"> <div className="w-full md:w-[95%]">
{/* this modal is used show alert set placeholder for all signers before send mail */} {/* this modal is used show alert set placeholder for all signers before send mail */}
@@ -1391,6 +1484,10 @@ const TemplatePlaceholder = () => {
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe} isSubscribe={isSubscribe}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/> />
<DropdownWidgetOption <DropdownWidgetOption
type="checkbox" type="checkbox"
@@ -1402,6 +1499,10 @@ const TemplatePlaceholder = () => {
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe} isSubscribe={isSubscribe}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/> />
<DropdownWidgetOption <DropdownWidgetOption
type="dropdown" type="dropdown"
@@ -1413,6 +1514,10 @@ const TemplatePlaceholder = () => {
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe} isSubscribe={isSubscribe}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/> />
<PlaceholderCopy <PlaceholderCopy
isPageCopy={isPageCopy} isPageCopy={isPageCopy}
@@ -1440,6 +1545,9 @@ const TemplatePlaceholder = () => {
currentSigner={true} currentSigner={true}
setIsEditTemplate={handleEditTemplateModal} setIsEditTemplate={handleEditTemplateModal}
dataTut4="reactourFour" dataTut4="reactourFour"
handleRotationFun={handleRotationFun}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
/> />
<div <div
ref={divRef} ref={divRef}
@@ -1471,7 +1579,6 @@ const TemplatePlaceholder = () => {
signersdata={signersdata} signersdata={signersdata}
setIsPageCopy={setIsPageCopy} setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey} setSignKey={setSignKey}
setSignerObjId={setSignerObjId}
isDragging={isDragging} isDragging={isDragging}
setShowDropdown={setShowDropdown} setShowDropdown={setShowDropdown}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
@@ -1485,6 +1592,11 @@ const TemplatePlaceholder = () => {
setScale={setScale} setScale={setScale}
scale={scale} scale={scale}
setIsSelectId={setIsSelectId} setIsSelectId={setIsSelectId}
pdfRotateBase64={pdfRotateBase64}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
/> />
)} )}
</div> </div>
@@ -1509,9 +1621,7 @@ const TemplatePlaceholder = () => {
signerPos={signerPos} signerPos={signerPos}
signersdata={signersdata} signersdata={signersdata}
isSelectListId={isSelectListId} isSelectListId={isSelectListId}
setSignerObjId={setSignerObjId}
setIsSelectId={setIsSelectId} setIsSelectId={setIsSelectId}
setContractName={setContractName}
isSigners={isSigners} isSigners={isSigners}
setIsShowEmail={setIsShowEmail} setIsShowEmail={setIsShowEmail}
isMailSend={isMailSend} isMailSend={isMailSend}
@@ -1541,10 +1651,8 @@ const TemplatePlaceholder = () => {
setSignerPos={setSignerPos} setSignerPos={setSignerPos}
signersdata={signersdata} signersdata={signersdata}
isSelectListId={isSelectListId} isSelectListId={isSelectListId}
setSignerObjId={setSignerObjId}
setRoleName={setRoleName} setRoleName={setRoleName}
setIsSelectId={setIsSelectId} setIsSelectId={setIsSelectId}
setContractName={setContractName}
handleAddSigner={handleAddSigner} handleAddSigner={handleAddSigner}
setUniqueId={setUniqueId} setUniqueId={setUniqueId}
handleDeleteUser={handleDeleteUser} handleDeleteUser={handleDeleteUser}
@@ -1624,6 +1732,11 @@ const TemplatePlaceholder = () => {
fontColor={fontColor} fontColor={fontColor}
setFontColor={setFontColor} setFontColor={setFontColor}
/> />
<RotateAlert
isRotate={isRotate.status}
setIsRotate={setIsRotate}
handleRemoveWidgets={handleRemovePlaceholder}
/>
</DndProvider> </DndProvider>
</> </>
); );
@@ -21,7 +21,7 @@ const UpdateExistUserAdmin = () => {
try { try {
const isAdminExist = await Parse.Cloud.run("checkadminexist"); const isAdminExist = await Parse.Cloud.run("checkadminexist");
if (isAdminExist !== "not_exist") { if (isAdminExist !== "not_exist") {
console.log("isAdminExist ", isAdminExist); // console.log("isAdminExist ", isAdminExist);
setErrMsg("Admin already exists."); setErrMsg("Admin already exists.");
} }
} catch (err) { } catch (err) {
@@ -65,12 +65,8 @@ const UpdateExistUserAdmin = () => {
}; };
return ( return (
<div className="h-screen flex justify-center"> <div className="h-screen flex justify-center">
<Title title="Add admin" /> <Title title={"Add Admin"} />
{isAlert.msg && ( {isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
<Alert type={isAlert.type}>
<div className="ml-3">{isAlert.msg}</div>
</Alert>
)}
{loader ? ( {loader ? (
<div className="text-[grey] flex justify-center items-center text-lg md:text-2xl"> <div className="text-[grey] flex justify-center items-center text-lg md:text-2xl">
<Loader /> <Loader />
+31 -65
View File
@@ -9,8 +9,9 @@ import Tooltip from "../primitives/Tooltip";
import AddUser from "../components/AddUser"; import AddUser from "../components/AddUser";
import SubscribeCard from "../primitives/SubscribeCard"; import SubscribeCard from "../primitives/SubscribeCard";
import { isEnableSubscription } from "../constant/const"; import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribedTeam } from "../constant/Utils"; import { checkIsSubscribed } from "../constant/Utils";
import Title from "../components/Title"; import Title from "../components/Title";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"]; const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"];
// const actions = []; // const actions = [];
@@ -26,8 +27,9 @@ const UserList = () => {
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isActiveModal, setIsActiveModal] = useState({}); const [isActiveModal, setIsActiveModal] = useState({});
const [isActLoader, setIsActLoader] = useState({}); const [isActLoader, setIsActLoader] = useState({});
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: false });
const [isAdmin, setIsAdmin] = useState(false); const [isAdmin, setIsAdmin] = useState(false);
const [formHeader, setFormHeader] = useState(t("add-user"));
const recordperPage = 10; const recordperPage = 10;
const startIndex = (currentPage - 1) * recordperPage; // user per page const startIndex = (currentPage - 1) * recordperPage; // user per page
@@ -83,6 +85,10 @@ const UserList = () => {
return pages; return pages;
}; };
const pageNumbers = getPaginationRange(); 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(() => { useEffect(() => {
fetchUserList(); fetchUserList();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -91,10 +97,10 @@ const UserList = () => {
try { try {
setIsLoader(true); setIsLoader(true);
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribedTeam(); const subscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe); setIsSubscribe(subscribe);
} else { } else {
setIsSubscribe(true); setIsSubscribe({ plan: "teams-yearly", isValid: true });
} }
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (extUser) { if (extUser) {
@@ -126,13 +132,19 @@ const UserList = () => {
}; };
// Change page // Change page
const paginateFront = () => setCurrentPage(currentPage + 1); const paginateFront = () => {
const paginateBack = () => setCurrentPage(currentPage - 1); const lastValue = pageNumbers?.[pageNumbers?.length - 1];
// const handleActionBtn = (act, item) => { if (currentPage < lastValue) {
// if (act.action === "delete") { setCurrentPage(currentPage + 1);
// setIsDeleteModal({ [item.objectId]: true }); }
// } };
// };
const paginateBack = () => {
if (startIndex > 0) {
setCurrentPage(currentPage - 1);
}
};
const handleUserData = (userData) => { const handleUserData = (userData) => {
setUserList((prev) => [userData, ...prev]); setUserList((prev) => [userData, ...prev]);
}; };
@@ -197,15 +209,11 @@ const UserList = () => {
<Loader /> <Loader />
</div> </div>
)} )}
{isSubscribe && !isLoader && ( {validplan[isSubscribe.plan] && !isLoader && (
<> <>
{isAdmin ? ( {isAdmin ? (
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg"> <div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
{isAlert.msg && ( {isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
<Alert type={isAlert.type}>
<div className="ml-3">{isAlert.msg}</div>
</Alert>
)}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]"> <div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<div className="font-light"> <div className="font-light">
{t("report-name.Users")} {t("report-name.Users")}
@@ -234,7 +242,7 @@ const UserList = () => {
<tbody className="text-[12px]"> <tbody className="text-[12px]">
{userList?.length > 0 && ( {userList?.length > 0 && (
<> <>
{userList.map((item, index) => ( {currentList.map((item, index) => (
<tr className="border-y-[1px]" key={index}> <tr className="border-y-[1px]" key={index}>
{heading.includes("Sr.No") && ( {heading.includes("Sr.No") && (
<th className="px-4 py-2"> <th className="px-4 py-2">
@@ -298,49 +306,6 @@ const UserList = () => {
)} )}
</td> </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> </tr>
))} ))}
</> </>
@@ -397,14 +362,15 @@ const UserList = () => {
</div> </div>
)} )}
<ModalUi <ModalUi
title={t("add-user")}
isOpen={isModal} isOpen={isModal}
title={formHeader}
handleClose={handleFormModal} handleClose={handleFormModal}
> >
<AddUser <AddUser
setIsAlert={setIsAlert} setIsAlert={setIsAlert}
handleUserData={handleUserData} handleUserData={handleUserData}
closePopup={handleFormModal} closePopup={handleFormModal}
setFormHeader={setFormHeader}
/> />
</ModalUi> </ModalUi>
</div> </div>
@@ -422,9 +388,9 @@ const UserList = () => {
)} )}
</> </>
)} )}
{!isSubscribe && isEnableSubscription && !isLoader && ( {isEnableSubscription && !validplan[isSubscribe.plan] && !isLoader && (
<div data-tut="apisubscribe"> <div data-tut="apisubscribe">
<SubscribeCard plan={"TEAMS"} price={"20"} /> <SubscribeCard plan="TEAMS" />
</div> </div>
)} )}
</div> </div>
+157 -89
View File
@@ -10,7 +10,6 @@ import Tooltip from "../primitives/Tooltip";
import { isEnableSubscription, isStaging } from "../constant/const"; import { isEnableSubscription, isStaging } from "../constant/const";
import { import {
checkIsSubscribed, checkIsSubscribed,
checkIsSubscribedTeam,
copytoData, copytoData,
handleSendOTP, handleSendOTP,
openInNewTab openInNewTab
@@ -18,10 +17,13 @@ import {
import Upgrade from "../primitives/Upgrade"; import Upgrade from "../primitives/Upgrade";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader"; import Loader from "../primitives/Loader";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import SelectLanguage from "../components/pdf/SelectLanguage"; import SelectLanguage from "../components/pdf/SelectLanguage";
import { RWebShare } from "react-web-share"; import { RWebShare } from "react-web-share";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import Tour from "reactour";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
function UserProfile() { function UserProfile() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -39,7 +41,7 @@ function UserProfile() {
const [isDisableDocId, setIsDisableDocId] = useState(false); const [isDisableDocId, setIsDisableDocId] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false);
const [isUpgrade, setIsUpgrade] = useState(false); const [isUpgrade, setIsUpgrade] = useState(false);
const [isAlert, setIsAlert] = useState({}); const [isAlert, setIsAlert] = useState({ type: "success", message: "" });
const [publicUserName, setPublicUserName] = useState( const [publicUserName, setPublicUserName] = useState(
extendUser && extendUser?.[0]?.UserName extendUser && extendUser?.[0]?.UserName
); );
@@ -57,7 +59,10 @@ function UserProfile() {
const [tagLine, setTagLine] = useState( const [tagLine, setTagLine] = useState(
extendUser && extendUser?.[0]?.Tagline 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 const getPublicUrl = isStaging
? `https://staging.opensign.me/${extendUser?.[0]?.UserName}` ? `https://staging.opensign.me/${extendUser?.[0]?.UserName}`
: `https://opensign.me/${extendUser?.[0]?.UserName}`; : `https://opensign.me/${extendUser?.[0]?.UserName}`;
@@ -65,17 +70,27 @@ function UserProfile() {
getUserDetail(); getUserDetail();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const getUserDetail = async () => { const getUserDetail = async () => {
setIsLoader(true); setIsLoader(true);
const extClass = localStorage.getItem("Extand_Class"); const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass); 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; const HeaderDocId = jsonSender[0]?.HeaderDocId;
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed(); const subscribe = await checkIsSubscribed();
const getIsTeam = await checkIsSubscribedTeam(); setIsSubscribe(subscribe.isValid);
setIsSubscribe(getIsSubscribe); setIsPlan(subscribe);
setIsTeam(getIsTeam);
} }
if (HeaderDocId) { if (HeaderDocId) {
setIsDisableDocId(HeaderDocId); setIsDisableDocId(HeaderDocId);
@@ -109,13 +124,8 @@ function UserProfile() {
}); });
if (res) { if (res) {
setIsLoader(false); setIsLoader(false);
setIsAlert({ setIsAlert({ type: "danger", message: t("user-name-exist") });
type: "danger", setTimeout(() => setIsAlert({}), 3000);
message: t("user-name-exist")
});
setTimeout(() => {
setIsAlert({});
}, 3000);
return res; return res;
} }
} catch (e) { } catch (e) {
@@ -320,17 +330,80 @@ function UserProfile() {
setJobTitle(extendUser?.[0]?.JobTitle); setJobTitle(extendUser?.[0]?.JobTitle);
setIsDisableDocId(extendUser?.[0]?.HeaderDocId); setIsDisableDocId(extendUser?.[0]?.HeaderDocId);
}; };
const copytoclipboard = () => { const handlePaidRoute = () => {
copytoData(getPublicUrl); navigate("/subscription");
setIsAlert({
type: "success",
message: t("copied")
});
setTimeout(() => {
setIsAlert({});
}, 3000);
}; };
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 ( return (
<React.Fragment> <React.Fragment>
<Title title={"Profile"} /> <Title title={"Profile"} />
@@ -340,12 +413,19 @@ function UserProfile() {
</div> </div>
) : ( ) : (
<div className="flex justify-center items-center w-full relative"> <div className="flex justify-center items-center w-full relative">
{isAlert && ( {isAlert.message && (
<Alert className="z-[1000] fixed top-[10%]" type={isAlert.type}> <Alert className="z-[1000] fixed top-[10%]" type={isAlert.type}>
{isAlert.message} {isAlert.message}
</Alert> </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="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="flex flex-col justify-center items-center my-4">
<div className="w-[200px] h-[200px] overflow-hidden rounded-full"> <div className="w-[200px] h-[200px] overflow-hidden rounded-full">
@@ -382,17 +462,6 @@ function UserProfile() {
</div> </div>
</div> </div>
<ul className="w-full flex flex-col p-2 text-sm"> <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 <li
className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${ className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${
editmode ? "py-1.5" : "py-2" editmode ? "py-1.5" : "py-2"
@@ -403,7 +472,7 @@ function UserProfile() {
<input <input
type="text" type="text"
value={name} 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)} onChange={(e) => SetName(e.target.value)}
/> />
) : ( ) : (
@@ -419,7 +488,7 @@ function UserProfile() {
{editmode ? ( {editmode ? (
<input <input
type="text" 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)} onChange={(e) => SetPhone(e.target.value)}
value={Phone} value={Phone}
/> />
@@ -441,7 +510,7 @@ function UserProfile() {
<input <input
type="text" type="text"
value={company} 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)} onChange={(e) => setCompany(e.target.value)}
/> />
) : ( ) : (
@@ -458,7 +527,7 @@ function UserProfile() {
<input <input
type="text" type="text"
value={jobTitle} 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)} onChange={(e) => setJobTitle(e.target.value)}
/> />
) : ( ) : (
@@ -486,40 +555,30 @@ function UserProfile() {
</li> </li>
{isEnableSubscription && ( {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"> <span className="font-semibold flex gap-1">
{t("public-profile")} :{" "} {t("public-profile")} :{" "}
<Tooltip <Tooltip handleOnlickHelp={handleOnlickHelp} />
maxWidth="max-w-[250px]"
message={t("public-profile-help")}
/>
</span> </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 ? ( {editmode || !extendUser?.[0]?.UserName ? (
<input <>
maxLength={40} <input
style={{ maxLength={40}
border: onChange={handleOnchangeUserName}
!isSubscribe && value={publicUserName}
publicUserName?.length > 0 && disabled={!editmode}
publicUserName?.length < 9 && placeholder="enter user name"
"solid red" className="op-input op-input-bordered op-input-sm w-[180px] focus:outline-none hover:border-base-content text-sm"
}} />
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"
/>
) : ( ) : (
<div className="flex flex-row gap-1 items-center justify-between md:justify-start"> <div className="flex flex-row gap-1 items-center justify-between md:justify-start">
<span <span
rel="noreferrer" rel="noreferrer"
target="_blank" target="_blank"
onClick={() => { onClick={() => openInNewTab(getPublicUrl)}
openInNewTab(getPublicUrl); className="cursor-pointer underline hover:text-blue-800 w-[110px] md:w-[150px] whitespace-nowrap overflow-hidden text-ellipsis"
}}
className="cursor-pointer underline hover:text-blue-800 w-[200px] md:w-[150px] whitespace-nowrap overflow-hidden text-ellipsis"
> >
{isStaging {isStaging
? `staging.opensign.me/${extendUser?.[0]?.UserName}` ? `staging.opensign.me/${extendUser?.[0]?.UserName}`
@@ -527,10 +586,7 @@ function UserProfile() {
</span> </span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<RWebShare <RWebShare
data={{ data={{ url: getPublicUrl, title: "Sign url" }}
url: getPublicUrl,
title: "Sign url"
}}
> >
<button className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm "> <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>{" "} <i className="fa-light fa-share-from-square"></i>{" "}
@@ -547,30 +603,23 @@ function UserProfile() {
)} )}
</div> </div>
</li> </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"> <span className="font-semibold flex gap-1">
{t("tagline")} :{" "} {t("tagline")} :{" "}
<Tooltip <Tooltip handleOnlickHelp={handleOnlickHelp} />
maxWidth="max-w-[250px]"
message={t("tagline-help")}
/>
</span> </span>
<div className="flex md:flex-row flex-col md:items-center"> <div className="flex flex-row md:items-center gap-1">
{editmode ? ( {editmode || !extendUser?.[0]?.Tagline ? (
<input <input
maxLength={40}
onChange={handleOnchangeTagLine} onChange={handleOnchangeTagLine}
value={tagLine} value={tagLine}
disabled={!editmode} disabled={!editmode}
placeholder="enter tagline" 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 <span>{extendUser?.[0]?.Tagline}</span>
value={extendUser?.[0]?.Tagline}
disabled
placeholder="enter tagline"
className="op-input op-input-bordered op-input-xs"
/>
)} )}
</div> </div>
</li> </li>
@@ -581,7 +630,9 @@ function UserProfile() {
<div className="flex justify-between items-center py-2"> <div className="flex justify-between items-center py-2">
<span <span
className={ className={
isTeam ? "font-semibold" : "font-semibold text-gray-300" validplan[isPlan.plan]
? "font-semibold"
: "font-semibold text-gray-300"
} }
> >
{t("disable-documentId")} :{" "} {t("disable-documentId")} :{" "}
@@ -590,17 +641,21 @@ function UserProfile() {
"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid" "https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"
} }
/> />
{!isTeam && isEnableSubscription && <Upgrade />} {!validplan[isPlan.plan] && isEnableSubscription && (
<Upgrade />
)}
</span> </span>
<label <label
className={`${ className={`${
isTeam validplan[isPlan.plan]
? `${editmode ? "cursor-pointer" : ""}` ? `${editmode ? "cursor-pointer" : ""}`
: "pointer-events-none opacity-50" : "pointer-events-none opacity-50"
} relative block items-center mb-0`} } relative block items-center mb-0`}
> >
<input <input
disabled={isTeam ? false : true} disabled={
validplan[isPlan.plan] && editmode ? false : true
}
type="checkbox" type="checkbox"
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white" className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white"
checked={isDisableDocId} checked={isDisableDocId}
@@ -610,6 +665,17 @@ function UserProfile() {
</div> </div>
</li> </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> </ul>
<div <div
className={`${ className={`${
@@ -620,6 +686,7 @@ function UserProfile() {
type="button" type="button"
onClick={(e) => { onClick={(e) => {
if ( if (
editmode &&
!isSubscribe && !isSubscribe &&
publicUserName?.length > 0 && publicUserName?.length > 0 &&
publicUserName?.length < 9 publicUserName?.length < 9
@@ -689,6 +756,7 @@ function UserProfile() {
)} )}
</ModalUi> </ModalUi>
)} )}
{isUpgrade && ( {isUpgrade && (
<div className="op-modal op-modal-open"> <div className="op-modal op-modal-open">
<div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative"> <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> <p>{t("user-name-limit-char")}</p>
<div className="op-card-actions justify-end"> <div className="op-card-actions justify-end">
<button <button
onClick={() => navigate("/subscription")} onClick={() => handlePaidRoute()}
className="op-btn op-btn-accent" className="op-btn op-btn-accent"
> >
{t("upgrade-now")} {t("upgrade-now")}
+6 -6
View File
@@ -19,7 +19,7 @@ function Webhook() {
const [webhook, setWebhook] = useState(); const [webhook, setWebhook] = useState();
const [isLoader, setIsLoader] = useState(true); const [isLoader, setIsLoader] = useState(true);
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: true });
const [error, setError] = useState(""); const [error, setError] = useState("");
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isTour, setIsTour] = useState(false); const [isTour, setIsTour] = useState(false);
@@ -35,8 +35,8 @@ function Webhook() {
]; ];
const fetchWebhook = async () => { const fetchWebhook = async () => {
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed(); const subscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe); setIsSubscribe(subscribe);
} }
try { try {
const extRes = await Parse.Cloud.run("getUserDetails"); const extRes = await Parse.Cloud.run("getUserDetails");
@@ -89,7 +89,7 @@ function Webhook() {
}; };
const handleModal = () => { const handleModal = () => {
if (!isSubscribe && isEnableSubscription) { if (!isSubscribe?.isValid && isEnableSubscription) {
setIsTour(true); setIsTour(true);
} else { } else {
setIsModal(!isModal); setIsModal(!isModal);
@@ -145,7 +145,7 @@ function Webhook() {
</div> </div>
<ModalUi <ModalUi
isOpen={isModal} isOpen={isModal}
title={"Regenerate Token"} title={t("add-webhook")}
handleClose={handleModal} handleClose={handleModal}
> >
{error && <Alert type="danger">{error}</Alert>} {error && <Alert type="danger">{error}</Alert>}
@@ -177,7 +177,7 @@ function Webhook() {
</div> </div>
</ModalUi> </ModalUi>
</div> </div>
{!isSubscribe && isEnableSubscription && ( {isEnableSubscription && !isSubscribe?.isValid && (
<div data-tut="webhooksubscribe"> <div data-tut="webhooksubscribe">
<SubscribeCard /> <SubscribeCard />
</div> </div>
+111 -95
View File
@@ -46,109 +46,125 @@ const AddContact = (props) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setIsLoader(true); setIsLoader(true);
try { if (localStorage.getItem("TenantId")) {
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")
});
}
try { try {
const _users = Parse.Object.extend("User"); const user = Parse.User.current();
const _user = new _users(); const query = new Parse.Query("contracts_Contactbook");
_user.set("name", name); query.equalTo("CreatedBy", user);
_user.set("username", email); query.notEqualTo("IsDeleted", true);
_user.set("email", email); query.equalTo("Email", email);
_user.set("password", email); const res = await query.first();
if (phone) { if (!res) {
_user.set("phone", phone); 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 (localStorage.getItem("TenantId")) {
if (user) { contactQuery.set("TenantId", {
const currentUser = Parse.User.current(); __type: "Pointer",
contactQuery.set( className: "partners_Tenant",
"CreatedBy", objectId: localStorage.getItem("TenantId")
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();
} }
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); setIsLoader(false);
// Reset the form fields
setAddYourself(false);
setName("");
setPhone("");
setEmail("");
} }
} catch (err) { } catch (err) {
console.log("err ", err); // console.log("err", err);
if (err.code === 202) { setIsLoader(false);
const params = { email: email }; alert(t("something-went-wrong-mssg"));
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("");
}
} }
} catch (err) { } else {
// console.log("err", err);
setIsLoader(false); setIsLoader(false);
alert(t("something-went-wrong-mssg")); 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" : "z-[1000] fixed top-20 left-1/2 transform -translate-x-1/2 text-sm"
} `} } `}
> >
<div className={`op-alert ${textcolor} flex items-center`}> <div className={`op-alert ${textcolor}`}>
<span>{children}</span> <span className="ml-3">{children}</span>
</div> </div>
</div> </div>
) )
+334 -188
View File
@@ -11,7 +11,7 @@ import { RWebShare } from "react-web-share";
import Tour from "reactour"; import Tour from "reactour";
import Parse from "parse"; import Parse from "parse";
import { import {
checkIsSubscribedTeam, checkIsSubscribed,
copytoData, copytoData,
fetchUrl, fetchUrl,
replaceMailVaribles replaceMailVaribles
@@ -27,6 +27,7 @@ import BulkSendUi from "../components/BulkSendUi";
import Loader from "./Loader"; import Loader from "./Loader";
import Select from "react-select"; import Select from "react-select";
import SubscribeCard from "./SubscribeCard"; import SubscribeCard from "./SubscribeCard";
import { validplan } from "../json/plansArr";
import { serverUrl_fn } from "../constant/appinfo"; import { serverUrl_fn } from "../constant/appinfo";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -73,15 +74,7 @@ const ReportTable = (props) => {
const extClass = Extand_Class && JSON.parse(Extand_Class); const extClass = Extand_Class && JSON.parse(Extand_Class);
const startIndex = (currentPage - 1) * props.docPerPage; const startIndex = (currentPage - 1) * props.docPerPage;
const { isMoreDocs, setIsNextRecord } = props; 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 getPaginationRange = () => {
const totalPageNumbers = 7; // Adjust this value to show more/less page numbers const totalPageNumbers = 7; // Adjust this value to show more/less page numbers
const pages = []; const pages = [];
@@ -355,10 +348,10 @@ const ReportTable = (props) => {
handleBulkSend(item); handleBulkSend(item);
} else if (act.action === "sharewith") { } else if (act.action === "sharewith") {
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribedTeam(); const subscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe); setIsSubscribe(subscribe);
} else { } else {
setIsSubscribe(true); setIsSubscribe({ plan: "teams-yearly", isValid: true });
} }
if (item?.SharedWith && item?.SharedWith.length > 0) { if (item?.SharedWith && item?.SharedWith.length > 0) {
// below code is used to get existing sharewith teams and formated them as per react-select // 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); setSelectedTeam(formatedList);
} }
setIsShareWith({ [item.objectId]: true }); setIsShareWith({ [item.objectId]: true });
} else if (act.action === "Embed") {
handleEmbedFunction(item);
} }
}; };
// Get current list // Get current list
@@ -377,8 +372,18 @@ const ReportTable = (props) => {
const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc); const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc);
// Change page // Change page
const paginateFront = () => setCurrentPage(currentPage + 1); const paginateFront = () => {
const paginateBack = () => setCurrentPage(currentPage - 1); const lastValue = pageNumbers?.[pageNumbers?.length - 1];
if (currentPage < lastValue) {
setCurrentPage(currentPage + 1);
}
};
const paginateBack = () => {
if (startIndex > 0) {
setCurrentPage(currentPage - 1);
}
};
const handleContactFormModal = () => { const handleContactFormModal = () => {
setIsContactform(!isContactform); setIsContactform(!isContactform);
@@ -396,7 +401,7 @@ const ReportTable = (props) => {
Templates: "contracts_Template" Templates: "contracts_Template"
}; };
try { try {
const serverUrl = serverUrl_fn() const serverUrl = serverUrl_fn();
const cls = clsObj[props.ReportName] || "contracts_Document"; const cls = clsObj[props.ReportName] || "contracts_Document";
const url = serverUrl + `/classes/${cls}/`; const url = serverUrl + `/classes/${cls}/`;
const body = const body =
@@ -448,7 +453,6 @@ const ReportTable = (props) => {
} }
setReason(""); setReason("");
}; };
const handleShare = (item) => { const handleShare = (item) => {
setActLoader({ [item.objectId]: true }); setActLoader({ [item.objectId]: true });
const host = window.location.origin; const host = window.location.origin;
@@ -479,9 +483,21 @@ const ReportTable = (props) => {
}; };
//function to handle revoke/decline docment //function to handle revoke/decline docment
const handleRevoke = async (item) => { const handleRevoke = async (item) => {
const senderUser = localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
setIsRevoke({}); setIsRevoke({});
setActLoader({ [`${item.objectId}`]: true }); 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 await axios
.put( .put(
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${ `${localStorage.getItem("baseUrl")}classes/contracts_Document/${
@@ -790,13 +806,13 @@ const ReportTable = (props) => {
if (count > 1) { if (count > 1) {
setAlertMsg({ setAlertMsg({
type: "success", type: "success",
message: count + t("document-sent-alert") message: count + " " + t("document-sent-alert")
}); });
setTimeout(() => setIsAlert(false), 1500); setTimeout(() => setIsAlert(false), 1500);
} else { } else {
setAlertMsg({ setAlertMsg({
type: "success", type: "success",
message: count + t("document-sent-alert") message: count + " " + t("document-sent-alert")
}); });
setTimeout(() => setIsAlert(false), 1500); setTimeout(() => setIsAlert(false), 1500);
} }
@@ -838,14 +854,10 @@ const ReportTable = (props) => {
console.log("err in fetch template in bulk modal", err); console.log("err in fetch template in bulk modal", err);
setIsBulkSend({}); setIsBulkSend({});
setIsAlert(true); setIsAlert(true);
setAlertMsg({ setAlertMsg({ type: "danger", message: t("something-went-wrong-mssg") });
type: "danger",
message: t("something-went-wrong-mssg")
});
setTimeout(() => setIsAlert(false), 1500); setTimeout(() => setIsAlert(false), 1500);
} }
}; };
//function to make template public and set public role //function to make template public and set public role
const handlePublicTemplate = async (item) => { const handlePublicTemplate = async (item) => {
if (selectedPublicRole || !props.isPublic[item.objectId]) { if (selectedPublicRole || !props.isPublic[item.objectId]) {
@@ -1005,9 +1017,26 @@ const ReportTable = (props) => {
type: "success", type: "success",
message: t("copied") 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 handleShowRole = (item) => {
const getRole = item.Placeholders.find((data) => !data.signerObjId); const getRole = item.Placeholders.find((data) => !data.signerObjId);
return getRole?.Role; return getRole?.Role;
@@ -1046,7 +1075,13 @@ const ReportTable = (props) => {
setTimeout(() => setIsAlert(false), 1500); 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 ( return (
<div className="relative"> <div className="relative">
{Object.keys(actLoader)?.length > 0 && ( {Object.keys(actLoader)?.length > 0 && (
@@ -1221,7 +1256,7 @@ const ReportTable = (props) => {
{formatRow(item?.ExtUserPtr)} {formatRow(item?.ExtUserPtr)}
</td> </td>
<td className="px-4 py-2"> <td className="px-4 py-2">
{item?.Placeholders ? ( {!item?.IsSignyourself && item?.Placeholders ? (
<button <button
onClick={() => handleViewSigners(item)} onClick={() => handleViewSigners(item)}
className="op-link op-link-primary" className="op-link op-link-primary"
@@ -1317,24 +1352,84 @@ const ReportTable = (props) => {
<ModalUi <ModalUi
isOpen isOpen
title={t("public-url")} title={t("public-url")}
handleClose={() => { handleClose={() => setIsPublicProfile({})}
setIsPublicProfile({});
}}
> >
<div className="m-[20px]"> <div className="m-[20px]">
{publicUserName ? ( {publicUserName ? (
<div className="font-normal text-black"> <div className="font-normal text-black">
<p>{t("public-url-copy-mssg")}</p> <p>{t("public-url-copy-mssg")}</p>
<div className=" flex items-center gap-3 mt-2 p-[2px] "> <div className=" flex items-center justify-between gap-3 mt-2 p-[2px] ">
<span className="font-bold"> <div className="w-[280px] whitespace-nowrap overflow-hidden text-ellipsis">
{t("public-url")}: <span className="font-bold">
</span> {t("public-url")} :
<span </span>
onClick={() => copytoProfileLink()} <span
className="underline underline-offset-2 w-[150px] md:w-[350px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer" onClick={() =>
> copytoProfileLink()
{publicUrl()} }
</span> 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>
</div> </div>
) : ( ) : (
@@ -1383,39 +1478,77 @@ const ReportTable = (props) => {
<i className={act.btnIcon}></i> <i className={act.btnIcon}></i>
{act.btnLabel && ( {act.btnLabel && (
<span className="uppercase font-medium"> <span className="uppercase font-medium">
{t(`btnLabel.${act.btnLabel}`)} {act.btnLabel.includes(
"Quick send"
) && isEnableSubscription
? "Bulk Send"
: `${t(
`btnLabel.${act.btnLabel}`
)}`}
</span> </span>
)} )}
{isOption[item.objectId] && {isOption[item.objectId] &&
act.action === "option" && ( act.action === "option" && (
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow bg-base-100 text-base-content rounded-box"> <ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow bg-base-100 text-base-content rounded-box">
{act.subaction?.map((subact) => ( {act.subaction?.map((subact) =>
<li props.isPublic[
key={subact.btnId} item.objectId
onClick={() => ] ? (
handleActionBtn( <li
subact, key={subact.btnId}
item onClick={() =>
) handleActionBtn(
} subact,
title={t( item
`btnLabel.${subact.btnLabel}` )
)} }
> title={t(
<span> `btnLabel.${subact.btnLabel}`
<i )}
className={`${subact.btnIcon} mr-1.5`} >
></i> <span>
{subact.btnLabel && ( <i
<span className="text-[13px] capitalize font-medium"> className={`${subact.btnIcon} mr-1.5`}
{t( ></i>
`btnLabel.${subact.btnLabel}` {subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
)}
</span>
</li>
) : (
subact.action !== "Embed" && (
<li
key={subact.btnId}
onClick={() =>
handleActionBtn(
subact,
item
)
}
title={t(
`btnLabel.${subact.btnLabel}`
)}
>
<span>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
)} )}
</span> </span>
)} </li>
</span> )
</li> )
))} )}
</ul> </ul>
)} )}
</div> </div>
@@ -1478,114 +1611,117 @@ const ReportTable = (props) => {
{isShareWith[item.objectId] && ( {isShareWith[item.objectId] && (
<div className="op-modal op-modal-open"> <div className="op-modal op-modal-open">
<div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative"> <div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative">
{isSubscribe && isEnableSubscription && ( {validplan[isSubscribe.plan] &&
<> isEnableSubscription && (
{item?.Signers?.length > 0 ? ( <>
<div className="h-[150px] flex justify-center items-center mx-2"> {item?.Signers?.length > 0 ? (
<div <div className="h-[150px] flex justify-center items-center mx-2">
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40" <div
onClick={() => setIsShareWith({})} 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>
<div className="text-base-content text-base text-center"> ) : (
{t("share-with-alert")} <>
</div> <h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
</div> {t("share-with")}
) : ( </h3>
<> <div
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]"> className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
{t("share-with")} onClick={() => setIsShareWith({})}
</h3> >
<div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40" </div>
onClick={() => setIsShareWith({})} <form
> className="h-full w-full z-[1300] px-2 mt-3"
onSubmit={(e) =>
</div> handleShareWith(e, item)
<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")
} }
unstyled >
classNames={{ <Select
control: () => // onSortEnd={onSortEnd}
"op-input op-input-bordered op-input-sm border-gray-400 focus:outline-none hover:border-base-content w-full h-full text-[11px]", distance={4}
valueContainer: () => isMulti
"flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]", options={teamList}
multiValue: () => value={selectedTeam}
"op-badge op-badge-primary h-full text-[11px]", onChange={onChange}
multiValueLabel: () => "mb-[2px]", closeMenuOnSelect
menu: () => required={true}
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content", noOptionsMessage={() =>
menuList: () => t("team-not-found")
"shadow-md rounded-lg overflow-hidden", }
option: () => unstyled
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2", classNames={{
noOptionsMessage: () => control: () =>
"p-2 bg-base-200 rounded-lg m-1 p-2" "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]",
<button className="op-btn op-btn-primary ml-[10px] my-3"> multiValue: () =>
{t("submit")} "op-badge op-badge-primary h-full text-[11px]",
</button> multiValueLabel: () =>
</form> "mb-[2px]",
</> menu: () =>
)} "mt-1 shadow-md rounded-lg bg-base-200 text-base-content",
</> menuList: () =>
)} "shadow-md rounded-lg overflow-hidden",
{!isSubscribe && isEnableSubscription && ( option: () =>
<> "bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
<div noOptionsMessage: () =>
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-primary-content absolute right-2 top-2 z-40" "p-2 bg-base-200 rounded-lg m-1 p-2"
onClick={() => setIsShareWith({})} }}
> />
<button className="op-btn op-btn-primary ml-[10px] my-3">
</div> {t("submit")}
<SubscribeCard </button>
plan={"TEAMS"} </form>
price={"20"} </>
/> )}
</> </>
)} )}
{isSubscribe && !isEnableSubscription && ( {isEnableSubscription &&
<> !validplan[isSubscribe.plan] && (
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]"> <>
{t("share-with")} <div
</h3> className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-primary-content absolute right-2 top-2 z-40"
<div onClick={() => setIsShareWith({})}
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>
</div> <SubscribeCard plan="TEAMS" />
<button </>
onClick={(e) => handleShareWith(e, item)} )}
className="op-btn op-btn-primary ml-[10px] my-3" {!isEnableSubscription &&
> validplan[isSubscribe.plan] && (
{t("submit")} <>
</button> <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>
</div> </div>
)} )}
@@ -1623,20 +1759,26 @@ const ReportTable = (props) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{item.Placeholders.map((x, i) => ( {item.Placeholders.map(
<tr key={i} className="text-sm font-medium"> (x, i) =>
{props.ReportName === "Templates" && ( x.Role !== "prefill" && (
<td className="text-[12px] p-2 pl-3 w-[30%]"> <tr
{x.Role && x.Role} key={i}
</td> className="text-sm font-medium"
)} >
<td className="pl-3 text-[12px] py-2 break-all"> {props.ReportName === "Templates" && (
{x.email <td className="text-[12px] p-2 pl-3 w-[30%]">
? x.email {x.Role && x.Role}
: x?.signerPtr?.Email || "-"} </td>
</td> )}
</tr> <td className="pl-3 text-[12px] py-2 break-all">
))} {x.email
? x.email
: x?.signerPtr?.Email || "-"}
</td>
</tr>
)
)}
</tbody> </tbody>
</table> </table>
</ModalUi> </ModalUi>
@@ -1672,11 +1814,15 @@ const ReportTable = (props) => {
{isBulkSend[item.objectId] && ( {isBulkSend[item.objectId] && (
<ModalUi <ModalUi
isOpen isOpen
title={t("quick-send")} title={
isEnableSubscription
? "Bulk send"
: t("quick-send")
}
handleClose={() => setIsBulkSend({})} handleClose={() => setIsBulkSend({})}
> >
{isLoader[item.objectId] ? ( {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 /> <Loader />
</div> </div>
) : ( ) : (
+5 -3
View File
@@ -21,9 +21,11 @@ const ModalUi = ({
> >
{showHeader && ( {showHeader && (
<> <>
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]"> {title && (
{title} <h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
</h3> {title}
</h3>
)}
{showClose && ( {showClose && (
<button <button
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2" 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 "../styles/signature.css";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
function CustomModal({ function CustomModal(props) {
show,
headMsg,
bodyMssg,
footerMessage,
declineDoc,
setIsDecline
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [reason, setReason] = useState(""); const [reason, setReason] = useState("");
return ( return (
show && ( props.show && (
<dialog className="op-modal op-modal-open absolute z-[448]"> <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"> <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]"> <h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{headMsg && headMsg} {props?.headMsg && props?.headMsg}
</h3> </h3>
<div className="p-[10px] px-[20px]"> <div className="p-[10px] px-[20px]">
<p className="text-[15px]">{bodyMssg && bodyMssg}</p> <p className="text-[15px]">{props.bodyMssg && props.bodyMssg}</p>
</div> </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"> <div className="mx-3">
<textarea <textarea
@@ -33,14 +36,13 @@ function CustomModal({
onChange={(e) => setReason(e.target.value)} onChange={(e) => setReason(e.target.value)}
></textarea> ></textarea>
</div> </div>
{/* <div className="h-[1px] bg-[#9f9f9f] w-full"></div> */}
<div className="m-[15px]"> <div className="m-[15px]">
<button <button
className="op-btn op-btn-primary mr-2 px-6" className="op-btn op-btn-primary mr-2 px-6"
type="button" type="button"
onClick={() => { onClick={() => {
setReason(""); setReason("");
declineDoc(reason); props.declineDoc(reason);
}} }}
> >
{t("yes")} {t("yes")}
@@ -50,7 +52,7 @@ function CustomModal({
className="op-btn op-btn-secondary" className="op-btn op-btn-secondary"
onClick={() => { onClick={() => {
setReason(""); setReason("");
setIsDecline({ isDeclined: false }); props.setIsDecline({ isDeclined: false });
}} }}
> >
{t("close")} {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 SubscribeCard = ({ plan, price }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const handlePaidRoute = () => navigate("/subscription");
const premiumPrice = price ? "$" + price : "$9.99";
const addonPrice = "$0.15";
const plandName = plan ? plan.toLowerCase() : "Paid";
return ( return (
<div className="op-card op-bg-primary text-primary-content w-full shadow-lg"> <div className="op-card op-bg-primary text-primary-content w-full shadow-lg">
<div className="op-card-body"> <div className="op-card-body">
<h2 className="op-card-title"> <h2 className="op-card-title">
{t("upgrade-to")} {plan ? plan : "PRO"} {t("plan")} {t("upgrade-to")} {plan ? plan : "Paid"} {t("plan")}
</h2> </h2>
<p> <p>
${price ? price : "29.99"}/month: {t("subscribe-card-plan")} {plandName?.includes("team")
? t("subscribe-card-teamplan")
: t("subscribe-card-plan", { premiumPrice, addonPrice })}
</p> </p>
<div className="op-card-actions justify-end"> <div className="op-card-actions justify-end">
<button <button
onClick={() => navigate("/subscription")} onClick={() => handlePaidRoute()}
className="op-btn op-btn-accent" className="op-btn op-btn-accent"
> >
{t("upgrade-now")} {t("upgrade-now")}
+15 -3
View File
@@ -1,9 +1,21 @@
import React from "react"; import React from "react";
import { Tooltip as ReactTooltip } from "react-tooltip"; import { Tooltip as ReactTooltip } from "react-tooltip";
import { openInNewTab } from "../constant/Utils"; import { openInNewTab } from "../constant/Utils";
const Tooltip = ({ id, message, url, iconColor, maxWidth }) => const Tooltip = ({
url ? ( id,
<button onClick={() => openInNewTab(url)} className={"text-center"}> message,
url,
iconColor,
maxWidth,
handleOnlickHelp
}) =>
url || handleOnlickHelp ? (
<button
onClick={() =>
handleOnlickHelp ? handleOnlickHelp() : openInNewTab(url)
}
className={"text-center cursor-pointer"}
>
<sup> <sup>
<i <i
className="fa-light fa-question rounded-full border-[1px] py-[1.5px] px-[4px] text-[13px]" 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 React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
export default function TourContentWithBtn({ message, isChecked }) { export default function TourContentWithBtn({ message, isChecked, video }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [isCheck, setIsCheck] = useState(false); const [isCheck, setIsCheck] = useState(false);
@@ -13,6 +13,17 @@ export default function TourContentWithBtn({ message, isChecked }) {
return ( return (
<div> <div>
<p>{message}</p> <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"> <label className="flex items-center justify-center mb-0">
<input <input
type="checkbox" type="checkbox"
+2 -2
View File
@@ -37,9 +37,9 @@ const Validate = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const handleLoginBtn = () => { const handleLoginBtn = async () => {
try { try {
Parse.User.logOut(); await Parse.User.logOut();
} catch (err) { } catch (err) {
console.log("err ", err); console.log("err ", err);
} finally { } 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 // Create a root and render the wrapper component with initial props
const root = ReactDOM.createRoot(document.getElementById("script-component")); const root = ReactDOM.createRoot(document.getElementById("script-component"));
root.render( root.render(
<div> <div data-theme="opensigncss">
<Provider store={store}> <Provider store={store}>
<PublicScriptFileWrapper initialProps={{ text: "templateId" }} /> <PublicScriptFileWrapper initialProps={{ text: "templateId" }} />
</Provider> </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", "create-account": "Create Account",
"login": "Login", "login": "Login",
"language": "Language", "language": "Language",
"name": "Name", "name": "Name",
"phone": "Phone", "phone": "Phone",
"phone-optional": "optional",
"email": "Email", "email": "Email",
"company": "Company", "company": "Company",
"job-title": "Job title", "job-title": "Job title",
@@ -38,7 +41,8 @@
"upgrade-now": "Upgrade now", "upgrade-now": "Upgrade now",
"upgrade-to": "Upgrade to", "upgrade-to": "Upgrade to",
"plan": "Plan", "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", "user-name-limit-char": "To have a username less than 8 character please subscribe",
"tour-content": "Don't show this again", "tour-content": "Don't show this again",
"pro": "PRO", "pro": "PRO",
@@ -91,6 +95,7 @@
"API Token": "API Token", "API Token": "API Token",
"Webhook": "Webhook", "Webhook": "Webhook",
"Teams": "Teams", "Teams": "Teams",
"Team-update-successfully": "Team Updated successfully.",
"Users": "Users" "Users": "Users"
} }
}, },
@@ -125,7 +130,8 @@
"Share with team": "Share with team", "Share with team": "Share with team",
"Share": "Share", "Share": "Share",
"View": "View", "View": "View",
"option": "Option" "option": "Option",
"Embed": "Embed"
}, },
"report-heading": { "report-heading": {
"Sr.No": "Sr.No", "Sr.No": "Sr.No",
@@ -212,8 +218,8 @@
"initials": "Initials", "initials": "Initials",
"API": "API", "API": "API",
"api-token": "API Token", "api-token": "API Token",
"regenerate-token": "Regenerate Token", "regenerate-token": "Regenerate Live Token",
"generate-token": "Generate Token", "generate-token": "Generate Live Token",
"view-docs": "View Docs", "view-docs": "View Docs",
"generate-token-alert": "Are you sure you want to regenerate token it will expire old token?", "generate-token-alert": "Are you sure you want to regenerate token it will expire old token?",
"yes": "Yes", "yes": "Yes",
@@ -308,7 +314,7 @@
"contact-details": "Contact Details", "contact-details": "Contact Details",
"verify-email": "Please verify your email !", "verify-email": "Please verify your email !",
"send-otp": " Send OTP", "send-otp": " Send OTP",
"otp-placeholder": "Enter OTP received over email", "otp-placeholder": "Enter verification code received over email",
"loading-doc": "Loading Document..", "loading-doc": "Loading Document..",
"widgets-name": { "widgets-name": {
"signature": "signature", "signature": "signature",
@@ -358,7 +364,7 @@
"initial-teb": "Initials", "initial-teb": "Initials",
"signature-tab": "Signature", "signature-tab": "Signature",
"your-signature": "Your 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?", "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-1": "Reset password link has been sent to your email id",
"reset-password-alert-2": "Please setup email adapter ", "reset-password-alert-2": "Please setup email adapter ",
@@ -368,7 +374,8 @@
"copy-type": { "copy-type": {
"All pages": "All pages", "All pages": "All pages",
"All pages but last": "All pages but last", "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", "options": "Options",
"minimun-check": "Minimun check", "minimun-check": "Minimun check",
@@ -445,7 +452,7 @@
"decline-alert-3": "You can not sign this document as it has been declined/revoked.", "decline-alert-3": "You can not sign this document as it has been declined/revoked.",
"sign here": "Sign here", "sign here": "Sign here",
"guest-email-alert": "verification code is sent to your email", "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.", "user-already-exist": "User already exist.",
"server-error": "Internal server error !", "server-error": "Internal server error !",
"fill-required-details!": "Please fill required details!", "fill-required-details!": "Please fill required details!",
@@ -468,7 +475,7 @@
"role-not-found": "Role not found.", "role-not-found": "Role not found.",
"do-not-access": "You don't have access, please contact the admin.", "do-not-access": "You don't have access, please contact the admin.",
"add-admin": "Add admin", "add-admin": "Add admin",
"opensign-setup": "Opensign Setup", "opensign-setup": "OpenSign Setup",
"join-discord": "Join our discord server", "join-discord": "Join our discord server",
"admin-already-exist": "Admin already exist.", "admin-already-exist": "Admin already exist.",
"password-update-alert-1": "Password updated successfully.", "password-update-alert-1": "Password updated successfully.",
@@ -524,7 +531,7 @@
"filed-required-correctly": "Please fill required details correctly.", "filed-required-correctly": "Please fill required details correctly.",
"admin-created": "Admin created", "admin-created": "Admin created",
"invalid-masterkey": "Invalid masterkey", "invalid-masterkey": "Invalid masterkey",
"opensign-Setup": "Opensign Setup", "opensign-Setup": "OpenSign Setup",
"master-key": " Master key", "master-key": " Master key",
"profile-update-alert": "Profile updated successfully.", "profile-update-alert": "Profile updated successfully.",
"date": "Date", "date": "Date",
@@ -550,7 +557,7 @@
"home-layout-1": "You have logged in successfully! Let's take a look.", "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-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.", "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-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-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.", "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.", "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.", "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.", "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", "enter-email-plaholder": "Add an email address and hit enter",
"success-email-alert": "Email sent successfully!", "success-email-alert": "Email sent successfully!",
@@ -592,5 +600,39 @@
"expired-on-mssg": "This document expired on {{expiredDate}} and is no longer available to sign.", "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": "Please confirm that you have selected at least {{minRequiredCount}} checkboxes.",
"signature-validate-alert-2": "Ensure this field is accurately filled and meets all requirements.", "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 {{adminName}}<{{adminEmail}}>.",
"sent-this-month":"Sent this month"
} }
@@ -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", "create-account": "Créer un compte",
"login": "Se Connecter", "login": "Se Connecter",
"language": "Langue", "language": "Langue",
"name": "Nom et Prénom", "name": "Nom et Prénom",
"phone": "Téléphone", "phone": "Téléphone",
"phone-optional": "(facultatif)",
"email": "E-mail", "email": "E-mail",
"company": "Organisation", "company": "Organisation",
"job-title": "Votre Fonction", "job-title": "Votre Fonction",
@@ -39,10 +42,11 @@
"upgrade-to": "Mettre à niveau vers", "upgrade-to": "Mettre à niveau vers",
"pro": "PRO", "pro": "PRO",
"plan": "Offre", "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", "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", "tour-content": "Ne plus afficher",
"team": QUIPE", "team": quipe",
"docs": "Documents", "docs": "Documents",
"session-expired": "Votre session a expiré.", "session-expired": "Votre session a expiré.",
"user": "Utilisateur", "user": "Utilisateur",
@@ -91,6 +95,7 @@
"API Token": "Jeton API", "API Token": "Jeton API",
"Webhook": "Webhook", "Webhook": "Webhook",
"Teams": "Équipe", "Teams": "Équipe",
"Team-update-successfully": "Équipe mise à jour avec succès.",
"Users": "Utilisateurs" "Users": "Utilisateurs"
} }
}, },
@@ -144,7 +149,8 @@
"Share with team": "Partager avec l'équipe", "Share with team": "Partager avec l'équipe",
"Share": "Partager", "Share": "Partager",
"View": "Voir", "View": "Voir",
"option": "Option" "option": "Option",
"Embed": "Intégrer"
}, },
"report-help": { "report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.", "Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -197,7 +203,7 @@
"parent-folder": "Dossier Parent", "parent-folder": "Dossier Parent",
"create": "Créer", "create": "Créer",
"signers": "Signataires", "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", "add-signer": "Ajouter un signataire",
"contact-not-found": "Contact introuvable", "contact-not-found": "Contact introuvable",
"add-yourself": "Ajoutez-vous", "add-yourself": "Ajoutez-vous",
@@ -211,8 +217,8 @@
"initials": "Initiales", "initials": "Initiales",
"API": "API", "API": "API",
"api-token": "Jeton API", "api-token": "Jeton API",
"regenerate-token": "Régénérer le jeton", "regenerate-token": "Régénérer en direct jeton",
"generate-token": "Générer un jeton", "generate-token": "Générer en direct jeton",
"view-docs": "Afficher les documents", "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?", "generate-token-alert": "Êtes-vous sûr de vouloir régénérer le jeton, votre ancien jeton sera supprimer?",
"yes": "Oui", "yes": "Oui",
@@ -252,11 +258,11 @@
"view": "voir", "view": "voir",
"make-template-public": "Rendre le modèle public", "make-template-public": "Rendre le modèle public",
"make-template-private": "Rendre le modèle privé", "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.", "make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
"public-role": "Rôle public", "public-role": "Rôle public",
"public-url": "URL publique", "public-url": "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.", "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-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
"share-with": "Partager avec", "share-with": "Partager avec",
@@ -274,14 +280,14 @@
"copy-link": "Copier le lien", "copy-link": "Copier le lien",
"copy": "Copier", "copy": "Copier",
"revoke-document": "Révoquer le document", "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": "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", "subject": "Sujet",
"body": "Corps", "body": "Corps",
"add-contact": "Ajouter le contact", "add-contact": "Ajouter le contact",
"add-signer-alert": "Le contact existe déjà !", "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 ! ", "record-revoke-alert": " Enregistrement révoqué avec succès ! ",
"mail-sent-alert": "Courriel envoyé avec succès.", "mail-sent-alert": "Courriel envoyé avec succès.",
"document-sent-alert": "Document envoyé avec succès !", "document-sent-alert": "Document envoyé avec succès !",
@@ -307,7 +313,7 @@
"contact-details": "Coordonnées", "contact-details": "Coordonnées",
"verify-email": "Veuillez vérifier votre email!", "verify-email": "Veuillez vérifier votre email!",
"send-otp": "envoyer un code à usage unique", "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..", "loading-doc": "Chargement du document..",
"widgets-name": { "widgets-name": {
"signature": "signature", "signature": "signature",
@@ -337,7 +343,7 @@
"finish": "terminé", "finish": "terminé",
"mail": "Mail", "mail": "Mail",
"sign-now": "Signez maintenant", "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-mssg": "Les destinataires ajoutés ici recevront une copie du document signé.",
"email-error-1": "veuillez fournir une adresse e-mail correcte", "email-error-1": "veuillez fournir une adresse e-mail correcte",
"email-error-2": "Vous ne pouvez envoyer qu'à dix destinataires à la fois.", "email-error-2": "Vous ne pouvez envoyer qu'à dix destinataires à la fois.",
@@ -357,8 +363,8 @@
"initial-teb": "Initiales", "initial-teb": "Initiales",
"signature-tab": "Signature", "signature-tab": "Signature",
"your-signature": "Votre signature", "your-signature": "Votre signature",
"copy-to-all-pages": "Copier sur toutes les pages", "copy-title": "Copier le widget vers",
"contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?", "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": "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-2": "Veuillez configurer l'adaptateur de messagerie",
"reset-password-alert-3": "Réinitialisez votre mot de passe", "reset-password-alert-3": "Réinitialisez votre mot de passe",
@@ -367,7 +373,8 @@
"copy-type": { "copy-type": {
"All pages": "Toutes les pages", "All pages": "Toutes les pages",
"All pages but last": "Toutes les pages sauf la dernière", "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", "options": "Possibilités",
"minimun-check": "Vérification minimale", "minimun-check": "Vérification minimale",
@@ -393,7 +400,7 @@
"create-document": "Créer un document", "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 ?", "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", "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", "ok": "D'accord",
"radio-group": "Groupe de radio", "radio-group": "Groupe de radio",
"dropdown-options": "Options déroulantes", "dropdown-options": "Options déroulantes",
@@ -409,9 +416,9 @@
"hint": "Indice", "hint": "Indice",
"color": "couleur", "color": "couleur",
"role-help": { "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.", "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.", "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 ?", "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." "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-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-4": "Vous avez envoyé avec succès des mails à tous les",
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?", "placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !", "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-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
"email-subject": "Sujet du courriel", "email-subject": "Sujet du courriel",
"email-body": "corps de l'e-mail", "email-body": "corps de l'e-mail",
"email-placeholder": "Ajouter le 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é.", "decline-alert-3": "Vous ne pouvez pas signer ce document car il a été refusé/révoqué.",
"sign here": "Signer ici", "sign here": "Signer ici",
"guest-email-alert": "le code de vérification a été envoyé sur votre e-mail", "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à.", "user-already-exist": "L'utilisateur existe déjà.",
"server-error": "Erreur interne du serveur !", "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-form": "Formulaire d'inscription",
"sign-up": "S'inscrire", "sign-up": "S'inscrire",
"folder-already-exist!": "le dossier existe déjà !", "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 ? ", "default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les emplacements demandés ? ",
"Your-Signature": "Votre signature", "Your-Signature": "Votre signature",
"auto-sign-all": "Tout signer automatiquement", "auto-sign-all": "Tout signer automatiquement",
@@ -459,15 +466,15 @@
"pdf-download": "Téléchargement PDF", "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. ", "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-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", "fill-folder-name": "Veuillez indiquer le nom du dossier",
"already-exists-this-username": "Un utilisateur existe déjà avec ce nom d'utilisateur !", "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 !", "verification-code-sent": "Un e-mail de vérification a été envoyé à votre adresse e-mail !",
"registered-user-successfully": "Utilisateur enregistré avec succès.", "registered-user-successfully": "Utilisateur enregistré avec succès.",
"role-not-found": "Rôle introuvable.", "role-not-found": "Rôle introuvable.",
"do-not-access": "Vous n'y avez pas accès, veuillez contacter l'administrateur.", "do-not-access": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
"add-admin": "Ajouter un administrateur", "add-admin": "Ajouter un administrateur",
"opensign-setup": "Configuration d'Opensign", "opensign-setup": "Configuration d'OpenSign",
"join-discord": "Rejoignez notre serveur Discord", "join-discord": "Rejoignez notre serveur Discord",
"admin-already-exist": "L'administrateur existe déjà.", "admin-already-exist": "L'administrateur existe déjà.",
"password-update-alert-1": "Mot de passe mis à jour avec succès.", "password-update-alert-1": "Mot de passe mis à jour avec succès.",
@@ -511,19 +518,19 @@
"choose-password": "Choisissez un mot de passe", "choose-password": "Choisissez un mot de passe",
"enter-password": "Entrer le mot de passe", "enter-password": "Entrer le mot de passe",
"sure": "bien sûr", "sure": "bien sûr",
"subscribed-alert": "Vous êtes déjà abonné au forfait !", "subscribed-alert": "Vous êtes déjà abonné au forfait !",
"monthly": "Mensuel", "monthly": "Mensuel",
"yearly-upto": "Annuel (jusqu'à 66 % de réduction)", "yearly-upto": "Annuel (jusqu'à 66 % de réduction)",
"billed-yearly": "Facturé annuellement", "billed-yearly": "Facturé annuellement",
"billed-monthly": "Facturé mensuellement", "billed-monthly": "Facturé mensuellement",
"plansubscription-1": "Hébergez-le vous-même gratuitement", "plansubscription-1": "Hébergez-le vous-même gratuitement",
"visit-github": "Visitez Github", "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.", "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.", "filed-required-correctly": "Veuillez remplir correctement les informations requises.",
"admin-created": "Administrateur créé", "admin-created": "Administrateur créé",
"invalid-masterkey": "Clé principale invalide", "invalid-masterkey": "Clé principale invalide",
"opensign-Setup": "Configuration d'Opensign", "opensign-Setup": "Configuration d'OpenSign",
"master-key": "La clef maitresse", "master-key": "La clef maitresse",
"profile-update-alert": "Mise à jour du profil réussie.", "profile-update-alert": "Mise à jour du profil réussie.",
"date": "Date", "date": "Date",
@@ -546,10 +553,10 @@
"discord": "Discorde" "discord": "Discorde"
}, },
"tour-mssg": { "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-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.", "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", "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-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-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.", "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.", "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.", "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.", "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", "enter-email-plaholder": "Ajoutez une adresse email et appuyez sur Entrée",
"success-email-alert": "E-mail envoyé avec succès!", "success-email-alert": "E-mail envoyé avec succès!",
@@ -591,5 +599,40 @@
"expired-on-mssg": "Ce document a expiré le {{expiredDate}} et n'est plus disponible pour signature.", "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": "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.", "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 {{adminName}}<{{adminEmail}}>.",
"sent-this-month":"envoyé ce mois-ci"
} }
+28 -7
View File
@@ -91,7 +91,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 // Update count in contracts_Users class
const query = new Parse.Query('contracts_Users'); const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', extUserId); query.equalTo('objectId', extUserId);
@@ -100,13 +100,23 @@ export const updateMailCount = async extUserId => {
const contractUser = await query.first({ useMasterKey: true }); const contractUser = await query.first({ useMasterKey: true });
if (contractUser) { if (contractUser) {
contractUser.increment('EmailCount', 1); 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 }); 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) { } catch (error) {
console.log('Error updating EmailCount in contracts_Users: ' + error.message); console.log('Error updating EmailCount in contracts_Users: ' + error.message);
@@ -210,3 +220,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 smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '465' ? false : true;
export const smtpenable = export const smtpenable =
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false; 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) { export default async function saveSubscription(request, response) {
const SubscriptionId = request.body?.data?.subscription?.subscription_id; const SubscriptionId = request.body?.data?.subscription?.subscription_id;
const body = request.body; const body = request.body;
const Email = request.body.data?.subscription?.customer?.email; const Email = request.body.data?.subscription?.customer?.email;
const Next_billing_date = request.body?.data?.subscription?.next_billing_at; const Next_billing_date = request.body?.data?.subscription?.next_billing_at;
const planCode = request.body?.data?.subscription?.plan?.plan_code; 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 { try {
const extUserCls = new Parse.Query('contracts_Users'); const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('Email', Email); extUserCls.equalTo('Email', Email);
@@ -18,6 +44,7 @@ export default async function saveSubscription(request, response) {
}); });
const subscription = await subcriptionCls.first({ useMasterKey: true }); const subscription = await subcriptionCls.first({ useMasterKey: true });
if (subscription) { if (subscription) {
const _resSub = JSON.parse(JSON.stringify(subscription));
const updateSubscription = new Parse.Object('contracts_Subscriptions'); const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subscription.id; updateSubscription.id = subscription.id;
updateSubscription.set('SubscriptionId', SubscriptionId); updateSubscription.set('SubscriptionId', SubscriptionId);
@@ -28,6 +55,81 @@ export default async function saveSubscription(request, response) {
updateSubscription.unset('Next_billing_date'); updateSubscription.unset('Next_billing_date');
} }
updateSubscription.set('PlanCode', planCode); 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 }); await updateSubscription.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'update subscription!' }); return response.status(200).json({ status: 'update subscription!' });
} else { } else {
@@ -55,6 +157,19 @@ export default async function saveSubscription(request, response) {
createSubscription.unset('Next_billing_date'); createSubscription.unset('Next_billing_date');
} }
createSubscription.set('PlanCode', planCode); 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 }); await createSubscription.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'create subscription!' }); 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 // `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) { async function sendDoctoWebhook(doc, WebhookUrl, userId) {
if (WebhookUrl) { if (WebhookUrl) {
const params = { const params = { event: 'created', ...doc };
event: 'created',
...doc,
};
await axios await axios
.post(WebhookUrl, params, { .post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } })
headers: { 'Content-Type': 'application/json' },
})
.then(res => { .then(res => {
try { try {
// console.log('res ', res); // console.log('res ', res);
@@ -62,6 +57,7 @@ export default async function createDocumentWithTemplate(request, response) {
if (!reqToken) { if (!reqToken) {
return response.status(400).json({ error: 'Please Provide API Token' }); return response.status(400).json({ error: 'Please Provide API Token' });
} }
const tokenQuery = new Parse.Query('appToken'); const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', reqToken); tokenQuery.equalTo('token', reqToken);
tokenQuery.include('userId'); tokenQuery.include('userId');
@@ -74,304 +70,344 @@ export default async function createDocumentWithTemplate(request, response) {
className: '_User', className: '_User',
objectId: parseUser.userId.objectId, objectId: parseUser.userId.objectId,
}; };
const templateQuery = new Parse.Query('contracts_Template'); const extUsers = new Parse.Query('contracts_Users');
templateQuery.include('ExtUserPtr'); extUsers.equalTo('UserId', userPtr);
const templateRes = await templateQuery.get(templateId, { useMasterKey: true }); const extUser = await extUsers.first({ 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';
for (const obj of parseSigners) { const subscription = new Parse.Query('contracts_Subscriptions');
const body = { subscription.equalTo('TenantId', {
name: obj?.name || '', __type: 'Pointer',
email: obj?.email || '', className: 'partners_Tenant',
phone: obj?.phone || '', 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 object = new Parse.Object('contracts_Document');
const res = await axios.post(createContactUrl, body, { object.set('Name', template.Name);
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken }, 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 = { object.set('Placeholders', updatedPlaceholder);
__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 { } else {
return { object.set('Signers', templateSigner);
...x,
};
} }
}); object.set('URL', template.URL);
object.set('Placeholders', updatedPlaceholder); object.set('SignedUrl', template.URL);
} else { object.set('SentToOthers', true);
object.set('Signers', templateSigner); if (TimeToCompleteDays) {
} object.set('TimeToCompleteDays', TimeToCompleteDays);
object.set('URL', template.URL); }
object.set('SignedUrl', template.URL); object.set('CreatedBy', template.CreatedBy);
object.set('SentToOthers', true); object.set('ExtUserPtr', {
if (TimeToCompleteDays) { __type: 'Pointer',
object.set('TimeToCompleteDays', TimeToCompleteDays); className: 'contracts_Users',
} objectId: template.ExtUserPtr.objectId,
object.set('CreatedBy', template.CreatedBy); });
object.set('ExtUserPtr', { if (folderId) {
__type: 'Pointer', object.set('Folder', folderPtr);
className: 'contracts_Users', }
objectId: template.ExtUserPtr.objectId, const newACL = new Parse.ACL();
}); newACL.setPublicReadAccess(false);
if (folderId) { newACL.setPublicWriteAccess(false);
object.set('Folder', folderPtr); newACL.setReadAccess(userPtr.objectId, true);
} newACL.setWriteAccess(userPtr.objectId, true);
const newACL = new Parse.ACL(); object.setACL(newACL);
newACL.setPublicReadAccess(false); const res = await object.save(null, { useMasterKey: true });
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(); const newDate = new Date();
newDate.setDate(newDate.getDate() + 15); newDate.setDate(newDate.getDate() + 15);
const localExpireDate = newDate.toLocaleDateString('en-US', { const localExpireDate = newDate.toLocaleDateString('en-US', {
day: 'numeric', day: 'numeric',
month: 'long', month: 'long',
year: 'numeric', year: 'numeric',
}); });
let sender = template.ExtUserPtr.Email; let sender = template.ExtUserPtr.Email;
let sendMail; let sendMail;
if (send_email === false) { if (send_email === false) {
console.log("don't send mail"); console.log("don't send mail");
} else { } else {
let contactMail = contact; let contactMail = contact;
if (sendInOrder) { if (sendInOrder) {
contactMail = contact.slice(); contactMail = contact.slice();
contactMail.splice(1); 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,
};
} }
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 objectId = contactMail[i].contactPtr.objectId;
const html = replaceVar.body; const hostUrl = baseUrl.origin;
let params = { //encode this url value `${res.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function
recipient: contactMail[i].email, const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`);
subject: subject, let signPdf = `${hostUrl}/login/${encodeBase64}`;
from: sender, const openSignUrl = 'https://www.opensignlabs.com/contact-us';
html: html, const orgName = template.ExtUserPtr.Company
extUserId: template.ExtUserPtr.objectId, ? template.ExtUserPtr.Company
}; : '';
sendMail = await axios.post(url, params, { headers: headers }); const themeBGcolor = '#47a3ad';
} catch (error) { const email_html =
console.log('error', error); "<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!' });
} }
} } else {
// if (sendMail.data.result.status === 'success') { if (request.posthog) {
try { request.posthog?.capture({
const doc = { distinctId: parseUser.userId.email,
objectId: res.id, event: 'api_create_document_with_templateid',
file: template?.URL, properties: { response_code: 400 },
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) { return response.status(400).json({ error: 'Please setup template properly!' });
console.log('Err', err);
} }
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 { } else {
if (request.posthog) { if (request.posthog) {
request.posthog?.capture({ request.posthog?.capture({
distinctId: parseUser.userId.email, distinctId: parseUser.userId.email,
event: 'api_create_document_with_templateid', 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 { } else {
if (request.posthog) { return response
request.posthog?.capture({ .status(429)
distinctId: parseUser.userId.email, .json({ error: 'Quota reached, Please buy credits and try again later.' });
event: 'api_create_document_with_templateid',
properties: { response_code: 400 },
});
}
return response.status(400).json({ error: 'Please setup template properly!' });
} }
} else { } else {
if (request.posthog) { return response.status(400).json({
request.posthog?.capture({ error: 'Please purchase or renew your subscription.',
distinctId: parseUser.userId.email, });
event: 'api_create_document_with_templateid',
properties: { response_code: 404 },
});
}
return response.status(404).json({ error: 'Invalid template id!' });
} }
} else { } else {
return response.status(405).json({ error: 'Invalid API Token!' }); 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 // `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) { async function sendDoctoWebhook(doc, WebhookUrl, userId) {
if (WebhookUrl) { if (WebhookUrl) {
const params = { const params = { event: 'created', ...doc };
event: 'created',
...doc,
};
await axios await axios
.post(WebhookUrl, params, { .post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } })
headers: { 'Content-Type': 'application/json' },
})
.then(res => { .then(res => {
try { try {
// console.log('res ', res); // console.log('res ', res);
@@ -89,309 +84,348 @@ export default async function createDocumentwithCoordinate(request, response) {
className: '_User', className: '_User',
objectId: parseUser.userId.objectId, objectId: parseUser.userId.objectId,
}; };
if (signers && signers.length > 0) { const extUsers = new Parse.Query('contracts_Users');
// Check if at least one signature exists among all items in the signers array extUsers.equalTo('UserId', userPtr);
let isSignExist = signers.every(item => const extUser = await extUsers.first({ useMasterKey: true });
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 extUserPtr = { const subscription = new Parse.Query('contracts_Subscriptions');
__type: 'Pointer', subscription.equalTo('TenantId', {
className: 'contracts_Users', __type: 'Pointer',
objectId: extUser.id, className: 'partners_Tenant',
}; objectId: extUser.get('TenantId').id,
});
const object = new Parse.Object('contracts_Document'); subscription.include('ExtUserPtr');
object.set('Name', name); subscription.greaterThanOrEqualTo('Next_billing_date', new Date());
const resSub = await subscription.first({ useMasterKey: true });
if (note) { if (resSub) {
object.set('Note', note); const _resSub = JSON.parse(JSON.stringify(resSub));
} const allowedCredits = _resSub?.AllowedCredits || 0;
if (description) { const addonCredits = _resSub?.AddonCredits || 0;
object.set('Description', description); const totalcredits = allowedCredits + addonCredits;
} if (totalcredits > 0) {
if (sendInOrder) { if (signers && signers.length > 0) {
object.set('SendinOrder', sendInOrder); // Check if at least one signature exists among all items in the signers array
} let isSignExist = signers.every(item =>
object.set('URL', fileUrl); item.widgets.some(data => data?.type === 'signature')
object.set('SignedUrl', fileUrl); );
object.set('SentToOthers', true); if (!isSignExist) {
object.set('CreatedBy', userPtr); return response
object.set('ExtUserPtr', extUserPtr); .status(400)
if (TimeToCompleteDays) { .json({ error: 'Please add at least one signature widget for all signers' });
object.set('TimeToCompleteDays', TimeToCompleteDays); }
} let fileUrl;
object.set('IsSendMail', send_email); if (request.files?.[0]) {
let contact = []; const base64 = fileData?.toString('base64');
if (signers && signers.length > 0) { const file = new Parse.File(request.files?.[0]?.originalname, {
let parseSigners; base64: base64,
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); await file.save({ useMasterKey: true });
const contactPtr = { fileUrl = file.url();
__type: 'Pointer', const buffer = Buffer.from(base64, 'base64');
className: 'contracts_Contactbook', saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
objectId: res.data?.objectId, } else {
}; const filename = sanitizeFileName(`${name}.pdf`);
const newObj = { ...element, contactPtr: contactPtr, index: index }; const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
contact.push(newObj); await file.save({ useMasterKey: true });
} catch (err) { fileUrl = file.url();
// console.log('err ', err.response); const buffer = Buffer.from(base64File, 'base64');
if (err?.response?.data?.objectId) { saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: err.response.data?.objectId,
};
const newObj = { ...element, contactPtr: contactPtr, index: index };
contact.push(newObj);
}
} }
} const contractsUser = new Parse.Query('contracts_Users');
object.set( contractsUser.equalTo('UserId', userPtr);
'Signers', const extUser = await contractsUser.first({ useMasterKey: true });
contact?.map(x => x.contactPtr) const parseExtUser = JSON.parse(JSON.stringify(extUser));
);
let updatePlaceholders = contact.map(signer => {
const placeHolder = [];
for (const widget of signer.widgets) { const extUserPtr = {
const pageNumber = widget.page; __type: 'Pointer',
const options = formatWidgetOptions(widget.type, widget.options); className: 'contracts_Users',
const page = placeHolder.find(page => page.pageNumber === pageNumber); objectId: extUser.id,
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 object = new Parse.Object('contracts_Document');
const hostUrl = baseUrl.origin; object.set('Name', name);
//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 = { if (note) {
recipient: contactMail[i].email, object.set('Note', note);
subject: subject,
from: sender,
html: html,
extUserId: extUser.id,
};
await axios.post(url, params, { headers: headers });
} catch (error) {
console.log('error', error);
} }
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 { } else {
if (request.posthog) { return response.status(400).json({
request.posthog?.capture({ error: 'Please purchase or renew your subscription.',
distinctId: parseUser.userId.email, });
event: 'api_create_document',
properties: { response_code: 400 },
});
}
return response.status(400).json({ error: 'Please provide signers!' });
} }
} else { } else {
return response.status(405).json({ error: 'Invalid API Token!' }); 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 FacebookSign from './parsefunction/FacebookSign.js';
import DocumentAftersave from './parsefunction/DocumentAftersave.js'; import DocumentAftersave from './parsefunction/DocumentAftersave.js';
import ContactbookAftersave from './parsefunction/ContactBookAftersave.js'; import ContactbookAftersave from './parsefunction/ContactBookAftersave.js';
// import ContractUsersAftersave from './parsefunction/ContractUsersAftersave.js';
import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js'; import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js';
import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js'; import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js';
import getUserId from './parsefunction/getUserId.js'; import getUserId from './parsefunction/getUserId.js';
@@ -60,14 +59,21 @@ import addTeam from './parsefunction/addTeam.js';
import updateTeam from './parsefunction/updateTeam.js'; import updateTeam from './parsefunction/updateTeam.js';
import getOrgAdmins from './parsefunction/getOrgAdmins.js'; import getOrgAdmins from './parsefunction/getOrgAdmins.js';
import getAllUserTeamByOrg from './parsefunction/getAllUserTeamByOrg.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. // This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave); Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave);
// Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave);
Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave); Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave);
Parse.Cloud.afterSave('contracts_Teams', TeamsAftersave); Parse.Cloud.afterSave('contracts_Teams', TeamsAftersave);
Parse.Cloud.afterSave('contracts_Subscriptions', SubscriptionAftersave); 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. // 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_Document', DocumentBeforesave);
Parse.Cloud.beforeSave('contracts_Template', TemplateBeforeSave); 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('contracts_Signature', SignatureAfterFind);
Parse.Cloud.afterFind('partners_Tenant', TenantAterFind); 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. // 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('signPdf', PDF);
Parse.Cloud.define('sendmailv3', sendmailv3); Parse.Cloud.define('sendmailv3', sendmailv3);
@@ -129,3 +138,7 @@ Parse.Cloud.define('addteam', addTeam);
Parse.Cloud.define('updateteam', updateTeam); Parse.Cloud.define('updateteam', updateTeam);
Parse.Cloud.define('getorgadmins', getOrgAdmins); Parse.Cloud.define('getorgadmins', getOrgAdmins);
Parse.Cloud.define('getalluserteambyorg', getAllUserTeamByOrg); 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); 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; const oldDocument = request.original;
// Check if SignedUrl field has been added (transition from undefined to defined) // 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 // Update count in contracts_Users class
const query = new Parse.Query('contracts_Users'); const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', oldDocument.get('ExtUserPtr').id); 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'); subscriptionCls.descending('createdAt');
const subcripitions = await subscriptionCls.first({ useMasterKey: true }); const subcripitions = await subscriptionCls.first({ useMasterKey: true });
if (subcripitions?.get('PlanCode') === 'freeplan') { if (subcripitions) {
return { status: 'success', result: 'already subscribed!' }; if (subcripitions?.get('PlanCode') === 'freeplan') {
} else if (subcripitions?.get('Next_billing_date') < new Date()) { return { status: 'success', result: 'already subscribed!' };
try { } else if (subcripitions?.get('Next_billing_date') < new Date()) {
const updateSubscription = new Parse.Object('contracts_Subscriptions'); try {
updateSubscription.id = subcripitions.id; const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.set('PlanCode', 'freeplan'); updateSubscription.id = subcripitions.id;
await updateSubscription.save(null, { useMasterKey: true }); updateSubscription.set('PlanCode', 'freeplan');
return { status: 'success', result: 'subscribed!' }; updateSubscription.set('AllowedCredits', 0);
} catch (err) { updateSubscription.set('PlanCredits', 0);
console.log('err ', err); await updateSubscription.save(null, { useMasterKey: true });
return { status: 'error', result: err.message }; 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 { } else {
try { try {
const createSubscription = new Parse.Object('contracts_Subscriptions'); const createSubscription = new Parse.Object('contracts_Subscriptions');
@@ -50,6 +67,8 @@ export default async function SubscribeFree(request) {
objectId: extUser.get('TenantId').id, objectId: extUser.get('TenantId').id,
}); });
} }
createSubscription.set('AllowedCredits', 0);
createSubscription.set('PlanCredits', 0);
await createSubscription.save(null, { useMasterKey: true }); await createSubscription.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' }; return { status: 'success', result: 'subscribed!' };
} catch (err) { } catch (err) {
@@ -18,9 +18,7 @@ export default async function ZohoDetails(request) {
formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI); formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI);
formData.append('grant_type', 'refresh_token'); formData.append('grant_type', 'refresh_token');
const headers = { const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
'Content-Type': 'application/x-www-form-urlencoded',
};
// Make the POST request using Axios // Make the POST request using Axios
const res = await axios.post(url, formData, { headers }); const res = await axios.post(url, formData, { headers });
// console.log("Access Token:", res.data); // console.log("Access Token:", res.data);
@@ -3,7 +3,33 @@ import { cloudServerUrl } from '../../Utils.js';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID; const appId = process.env.APP_ID;
const 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) { async function sendMail(document, sessionToken) {
const baseUrl = new URL(process.env.PUBLIC_URL); const baseUrl = new URL(process.env.PUBLIC_URL);
@@ -90,7 +116,6 @@ export default async function createBatchDocs(request) {
const sessionToken = request.headers['sessiontoken']; const sessionToken = request.headers['sessiontoken'];
const Documents = JSON.parse(strDocuments); const Documents = JSON.parse(strDocuments);
const Ip = request?.headers?.['x-real-ip'] || ''; const Ip = request?.headers?.['x-real-ip'] || '';
// console.log('Documents ', Documents);
const parseConfig = { const parseConfig = {
baseURL: serverUrl, //localStorage.getItem('baseUrl'), baseURL: serverUrl, //localStorage.getItem('baseUrl'),
headers: { headers: {
@@ -100,84 +125,164 @@ export default async function createBatchDocs(request) {
}, },
}; };
try { try {
const requests = Documents.map(x => { const user = await axios.get(serverUrl + '/users/me', {
const Signers = x.Signers; headers: {
const allSigner = x?.Placeholders?.map( 'X-Parse-Application-Id': appId,
item => Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr 'X-Parse-Session-Token': request.headers['sessiontoken'],
).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 (user.data) {
const extCls = new Parse.Query('contracts_Users');
const response = await axios.post('batch', { requests: requests }, parseConfig); extCls.equalTo('UserId', {
// // Handle the batch query response __type: 'Pointer',
// console.log('Batch query response:', response.data); className: '_User',
if (response.data && response.data.length > 0) { objectId: user.data.objectId,
const updateDocuments = Documents.map((x, i) => ({ });
...x, const resExt = await extCls.first({ useMasterKey: true });
objectId: response.data[i]?.success?.objectId, if (resExt) {
createdAt: response.data[i]?.success?.createdAt, const _resExt = JSON.parse(JSON.stringify(resExt));
})); try {
for (let i = 0; i < updateDocuments.length; i++) { const requests = Documents.map(x => {
sendMail(updateDocuments[i], sessionToken); 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.');
} }
} catch (err) {
// Handle individual responses within response.data.results console.log('err in createbatchdoc', err);
} catch (error) { const code = err?.code || 400;
console.error('Error performing batch query:', error); 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('Signers');
query.include('AuditTrail.UserPtr'); query.include('AuditTrail.UserPtr');
query.include('Placeholders'); query.include('Placeholders');
query.include('DeclineBy');
query.notEqualTo('IsArchive', true); query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true }); const res = await query.first({ useMasterKey: true });
if (res) { if (res) {
@@ -1,8 +1,9 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl } from '../../Utils.js';
export default async function getapitoken(request) { export default async function getapitoken(request) {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
try { try {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const userRes = await axios.get(serverUrl + '/users/me', { const userRes = await axios.get(serverUrl + '/users/me', {
headers: { headers: {
'X-Parse-Application-Id': process.env.APP_ID, '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 }); const res = await tokenQuery.first({ useMasterKey: true });
if (res) { if (res) {
return { status: 'success', result: res.get('token') }; return { status: 'success', result: res.get('token') };
} else {
return { error: 'api token found.' };
} }
} else {
return { error: 'Invalid session token.' };
} }
} catch (err) { } catch (err) {
console.log('Err in getapitoken', err); console.log('Err in getapitoken', err);
if (err.code == 209) { if (err.code == 209) {
return { error: 'Invalid session token' }; return { error: 'Invalid session token.' };
} else { } else {
return { error: "You don't have access!" }; return { error: "You don't have access." };
} }
} }
} }
@@ -28,6 +28,7 @@ export default function reportJson(id, userId) {
'Signers.Email', 'Signers.Email',
'Signers.Phone', 'Signers.Phone',
'Placeholders', 'Placeholders',
'IsSignyourself',
], ],
}; };
@@ -137,6 +138,7 @@ export default function reportJson(id, userId) {
'Signers.Phone', 'Signers.Phone',
'TimeToCompleteDays', 'TimeToCompleteDays',
'Placeholders', 'Placeholders',
'IsSignyourself',
], ],
}; };
// declined documents report // declined documents report
@@ -241,6 +243,7 @@ export default function reportJson(id, userId) {
reportName: 'Recent signature requests', reportName: 'Recent signature requests',
params: { params: {
Type: { $ne: 'Folder' }, Type: { $ne: 'Folder' },
SignedUrl: { $ne: null },
IsCompleted: { $ne: true }, IsCompleted: { $ne: true },
IsDeclined: { $ne: true }, IsDeclined: { $ne: true },
IsArchive: { $ne: true }, IsArchive: { $ne: true },
@@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, planCredits } from '../../Utils.js';
export default async function saveSubscription(request) { export default async function saveSubscription(request) {
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID; const appId = process.env.APP_ID;
@@ -8,7 +8,10 @@ export default async function saveSubscription(request) {
const body = subscription; const body = subscription;
const Next_billing_date = subscription.data.subscription.next_billing_at; const Next_billing_date = subscription.data.subscription.next_billing_at;
const planCode = subscription.data.subscription.plan.plan_code; 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 { try {
const userRes = await axios.get(serverUrl + '/users/me', { const userRes = await axios.get(serverUrl + '/users/me', {
headers: { headers: {
@@ -31,14 +34,113 @@ export default async function saveSubscription(request) {
className: 'partners_Tenant', className: 'partners_Tenant',
objectId: extUser.get('TenantId').id, objectId: extUser.get('TenantId').id,
}); });
const subscription = await subcriptionCls.first({ useMasterKey: true }); const resSubscription = await subcriptionCls.first({ useMasterKey: true });
if (subscription) { 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'); const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subscription.id; updateSubscription.id = resSubscription.id;
updateSubscription.set('SubscriptionId', SubscriptionId); updateSubscription.set('SubscriptionId', SubscriptionId);
updateSubscription.set('SubscriptionDetails', body); updateSubscription.set('SubscriptionDetails', body);
updateSubscription.set('Next_billing_date', new Date(Next_billing_date)); updateSubscription.set('Next_billing_date', new Date(Next_billing_date));
updateSubscription.set('PlanCode', planCode); 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 }); await updateSubscription.save(null, { useMasterKey: true });
return { status: 'update subscription!' }; return { status: 'update subscription!' };
} else { } else {
@@ -62,6 +164,19 @@ export default async function saveSubscription(request) {
}); });
createSubscription.set('Next_billing_date', new Date(Next_billing_date)); createSubscription.set('Next_billing_date', new Date(Next_billing_date));
createSubscription.set('PlanCode', planCode); 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 }); await createSubscription.save(null, { useMasterKey: true });
return { status: 'create subscription!' }; return { status: 'create subscription!' };
} }
@@ -142,8 +142,7 @@ export default async function sendMailGmailProvider(_extRes, template) {
raw: email, raw: email,
}, },
}); });
// console.log('response ', response); console.log('gmail provider res: ', response?.status);
return { code: 200, message: 'Email sent successfully' }; return { code: 200, message: 'Email sent successfully' };
} catch (error) { } catch (error) {
console.error('Error sending email:', error); console.error('Error sending email:', error);
@@ -6,7 +6,7 @@ import Mailgun from 'mailgun.js';
import { smtpenable, smtpsecure, updateMailCount, useLocal } from '../../Utils.js'; import { smtpenable, smtpsecure, updateMailCount, useLocal } from '../../Utils.js';
import sendMailGmailProvider from './sendMailGmailProvider.js'; import sendMailGmailProvider from './sendMailGmailProvider.js';
import { createTransport } from 'nodemailer'; import { createTransport } from 'nodemailer';
async function sendMailProvider(req) { async function sendMailProvider(req, plan, monthchange) {
const mailgunApiKey = process.env.MAILGUN_API_KEY; const mailgunApiKey = process.env.MAILGUN_API_KEY;
try { try {
let transporterSMTP; let transporterSMTP;
@@ -69,18 +69,23 @@ async function sendMailProvider(req) {
}; };
let attachment; let attachment;
// `certificateBuffer` used to create buffer from pdf file const certificatePath = './exports/certificate.pdf';
try { if (fs.existsSync(certificatePath)) {
const certificateBuffer = fs.readFileSync('./exports/certificate.pdf'); try {
const certificate = { // `certificateBuffer` used to create buffer from pdf file
filename: 'certificate.pdf', const certificateBuffer = fs.readFileSync(certificatePath);
content: smtpenable ? certificateBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'), const certificate = {
data: smtpenable ? undefined : certificateBuffer, filename: 'certificate.pdf',
}; content: smtpenable ? certificateBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
attachment = [file, certificate]; data: smtpenable ? undefined : certificateBuffer,
} catch (err) { };
attachment = [file, certificate];
} catch (err) {
attachment = [file];
console.log('Err in read certificate sendmailv3', err);
}
} else {
attachment = [file]; attachment = [file];
console.log('Err in read certificate sendmailv3', err);
} }
const from = req.params.from || ''; const from = req.params.from || '';
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER; const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
@@ -96,38 +101,44 @@ async function sendMailProvider(req) {
}; };
if (transporterSMTP) { if (transporterSMTP) {
const res = await transporterSMTP.sendMail(messageParams); const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res); console.log('smtp transporter res: ', res?.response);
if (!res.err) { if (!res.err) {
if (req.params?.extUserId) { if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId); await updateMailCount(req.params.extUserId, plan, monthchange);
} }
try { if (fs.existsSync(certificatePath)) {
fs.unlinkSync('./exports/certificate.pdf'); try {
} catch (err) { fs.unlinkSync(certificatePath);
console.log('Err in unlink certificate sendmailv3'); } catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
} }
return { status: 'success' }; return { status: 'success' };
} }
} else { } else {
if (mailgunApiKey) { if (mailgunApiKey) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams); const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res); console.log('mailgun res: ', res?.status);
if (res.status === 200) { if (res.status === 200) {
if (req.params?.extUserId) { if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId); await updateMailCount(req.params.extUserId, plan, monthchange);
} }
try { if (fs.existsSync(certificatePath)) {
fs.unlinkSync('./exports/certificate.pdf'); try {
} catch (err) { fs.unlinkSync(certificatePath);
console.log('Err in unlink certificate sendmailv3'); } catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
} }
return { status: 'success' }; return { status: 'success' };
} }
} else { } else {
try { if (fs.existsSync(certificatePath)) {
fs.unlinkSync('./exports/certificate.pdf'); try {
} catch (err) { fs.unlinkSync(certificatePath);
console.log('Err in unlink certificate sendmailv3'); } catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
} }
return { status: 'error' }; return { status: 'error' };
} }
@@ -147,20 +158,20 @@ async function sendMailProvider(req) {
if (transporterSMTP) { if (transporterSMTP) {
const res = await transporterSMTP.sendMail(messageParams); const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res); console.log('smtp transporter res: ', res?.response);
if (!res.err) { if (!res.err) {
if (req.params?.extUserId) { if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId); await updateMailCount(req.params.extUserId, plan, monthchange);
} }
return { status: 'success' }; return { status: 'success' };
} }
} else { } else {
if (mailgunApiKey) { if (mailgunApiKey) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams); const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res); console.log('mailgun res: ', res?.status);
if (res.status === 200) { if (res.status === 200) {
if (req.params?.extUserId) { if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId); await updateMailCount(req.params.extUserId, plan, monthchange);
} }
return { status: 'success' }; return { status: 'success' };
} }
@@ -180,6 +191,7 @@ async function sendmailv3(req) {
const mailProvider = req.params.mailProvider || 'default'; const mailProvider = req.params.mailProvider || 'default';
if (mailProvider) { if (mailProvider) {
try { try {
const Plan = req.params.plan;
const extUserId = req.params.extUserId || ''; const extUserId = req.params.extUserId || '';
const pdfName = req.params.pdfName || ''; const pdfName = req.params.pdfName || '';
const template = { const template = {
@@ -203,8 +215,31 @@ async function sendmailv3(req) {
return { status: 'error' }; return { status: 'error' };
} }
} else { } else {
const nonCustomMail = await sendMailProvider(req); if (Plan && Plan === 'freeplan') {
return nonCustomMail; 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) { } catch (err) {
@@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { cloudServerUrl } from '../../Utils.js'; import { cloudServerUrl, planCredits } from '../../Utils.js';
const serverUrl = cloudServerUrl; //process.env.SERVER_URL; const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const APPID = process.env.APP_ID; const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY; const masterKEY = process.env.MASTER_KEY;
@@ -135,6 +135,8 @@ async function saveSubscription(extUserId, UserId, tenantId, subscription) {
const SubscriptionId = subscription?.data?.subscription?.subscription_id || ''; const SubscriptionId = subscription?.data?.subscription?.subscription_id || '';
const Next_billing_date = subscription?.data?.subscription?.next_billing_at || ''; const Next_billing_date = subscription?.data?.subscription?.next_billing_at || '';
const planCode = subscription?.data?.subscription?.plan?.plan_code || ''; const planCode = subscription?.data?.subscription?.plan?.plan_code || '';
const credits = planCredits?.[planCode] || 0;
try { try {
const createSubscription = new Parse.Object('contracts_Subscriptions'); const createSubscription = new Parse.Object('contracts_Subscriptions');
createSubscription.set('SubscriptionId', SubscriptionId); 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('Next_billing_date', new Date(Next_billing_date));
createSubscription.set('PlanCode', planCode); createSubscription.set('PlanCode', planCode);
if (credits > 0) {
createSubscription.set('AllowedCredits', credits);
createSubscription.set('PlanCredits', credits);
}
await createSubscription.save(null, { useMasterKey: true }); await createSubscription.save(null, { useMasterKey: true });
} catch (err) { } catch (err) {
console.log('err in save subscription pgsignup', 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();
};
+498 -286
View File
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -24,17 +24,17 @@
"@signpdf/placeholder-pdf-lib": "^3.2.4", "@signpdf/placeholder-pdf-lib": "^3.2.4",
"@signpdf/signer-p12": "^3.2.4", "@signpdf/signer-p12": "^3.2.4",
"@signpdf/signpdf": "^3.2.4", "@signpdf/signpdf": "^3.2.4",
"aws-sdk": "^2.1667.0", "aws-sdk": "^2.1687.0",
"axios": "^1.7.3", "axios": "^1.7.7",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.20.0",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"generate-api-key": "^1.0.2", "generate-api-key": "^1.0.2",
"googleapis": "^140.0.1", "googleapis": "^140.0.1",
"jsonschema": "^1.4.1", "jsonschema": "^1.4.1",
"mailgun.js": "^10.2.3", "mailgun.js": "^10.2.3",
"mongoose": "^8.5.2", "mongoose": "^8.6.0",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"multer-s3": "^3.0.1", "multer-s3": "^3.0.1",
"node-forge": "^1.3.1", "node-forge": "^1.3.1",
@@ -44,15 +44,15 @@
"parse-server": "^7.2.0", "parse-server": "^7.2.0",
"parse-server-api-mail-adapter": "^4.0.0", "parse-server-api-mail-adapter": "^4.0.0",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"posthog-node": "^4.0.1", "posthog-node": "^4.2.0",
"ws": "^8.18.0" "ws": "^8.18.0"
}, },
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.25.1", "@babel/eslint-parser": "^7.25.1",
"eslint": "^9.8.0", "eslint": "^9.9.1",
"jasmine": "^5.2.0", "jasmine": "^5.2.0",
"mongodb-runner": "^5.6.4", "mongodb-runner": "^5.6.6",
"nodemon": "^3.1.4", "nodemon": "^3.1.4",
"nyc": "^17.0.0", "nyc": "^17.0.0",
"prettier": "^3.3.3" "prettier": "^3.3.3"
@@ -62,6 +62,6 @@
"parse": "$parse" "parse": "$parse"
}, },
"engines": { "engines": {
"node": ">=12.22.10 <21" "node": "18 || 20 || 22"
} }
} }
+1 -1
View File
@@ -9,7 +9,7 @@ services:
env_file: .env.prod env_file: .env.prod
environment: environment:
- NODE_ENV=production - NODE_ENV=production
- SERVER_URL=${HOST_URL:-https://localhost:3001/app} - SERVER_URL=${HOST_URL:-https://localhost:3001}/app
networks: networks:
- app-network - app-network
mongo: mongo:
+167 -127
View File
@@ -15,15 +15,15 @@
} }
}, },
"node_modules/ansi-escapes": { "node_modules/ansi-escapes": {
"version": "5.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
"integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"type-fest": "^1.0.2" "environment": "^1.0.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=18"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
@@ -78,31 +78,31 @@
} }
}, },
"node_modules/cli-cursor": { "node_modules/cli-cursor": {
"version": "4.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"restore-cursor": "^4.0.0" "restore-cursor": "^5.0.0"
}, },
"engines": { "engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" "node": ">=18"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/cli-truncate": { "node_modules/cli-truncate": {
"version": "3.1.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
"integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"slice-ansi": "^5.0.0", "slice-ansi": "^5.0.0",
"string-width": "^5.0.0" "string-width": "^7.0.0"
}, },
"engines": { "engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" "node": ">=18"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
@@ -115,12 +115,12 @@
"dev": true "dev": true
}, },
"node_modules/commander": { "node_modules/commander": {
"version": "11.1.0", "version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=16" "node": ">=18"
} }
}, },
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
@@ -138,9 +138,9 @@
} }
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
@@ -154,17 +154,23 @@
} }
} }
}, },
"node_modules/eastasianwidth": { "node_modules/emoji-regex": {
"version": "0.2.0", "version": "10.4.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true "dev": true
}, },
"node_modules/emoji-regex": { "node_modules/environment": {
"version": "9.2.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
"dev": true "dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}, },
"node_modules/eventemitter3": { "node_modules/eventemitter3": {
"version": "5.0.1", "version": "5.0.1",
@@ -207,6 +213,18 @@
"node": ">=8" "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": { "node_modules/get-stream": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
@@ -283,30 +301,33 @@
"dev": true "dev": true
}, },
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "2.1.0", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=10" "node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
} }
}, },
"node_modules/lint-staged": { "node_modules/lint-staged": {
"version": "15.1.0", "version": "15.2.9",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.1.0.tgz", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.9.tgz",
"integrity": "sha512-ZPKXWHVlL7uwVpy8OZ7YQjYDAuO5X4kMh0XgZvPNxLcCCngd0PO5jKQyy3+s4TL2EnHoIXIzP1422f/l3nZKMw==", "integrity": "sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"chalk": "5.3.0", "chalk": "~5.3.0",
"commander": "11.1.0", "commander": "~12.1.0",
"debug": "4.3.4", "debug": "~4.3.6",
"execa": "8.0.1", "execa": "~8.0.1",
"lilconfig": "2.1.0", "lilconfig": "~3.1.2",
"listr2": "7.0.2", "listr2": "~8.2.4",
"micromatch": "4.0.5", "micromatch": "~4.0.7",
"pidtree": "0.6.0", "pidtree": "~0.6.0",
"string-argv": "0.3.2", "string-argv": "~0.3.2",
"yaml": "2.3.4" "yaml": "~2.5.0"
}, },
"bin": { "bin": {
"lint-staged": "bin/lint-staged.js" "lint-staged": "bin/lint-staged.js"
@@ -319,41 +340,72 @@
} }
}, },
"node_modules/listr2": { "node_modules/listr2": {
"version": "7.0.2", "version": "8.2.4",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-7.0.2.tgz", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz",
"integrity": "sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g==", "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"cli-truncate": "^3.1.0", "cli-truncate": "^4.0.0",
"colorette": "^2.0.20", "colorette": "^2.0.20",
"eventemitter3": "^5.0.1", "eventemitter3": "^5.0.1",
"log-update": "^5.0.1", "log-update": "^6.1.0",
"rfdc": "^1.3.0", "rfdc": "^1.4.1",
"wrap-ansi": "^8.1.0" "wrap-ansi": "^9.0.0"
}, },
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=18.0.0"
} }
}, },
"node_modules/log-update": { "node_modules/log-update": {
"version": "5.0.1", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
"integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ansi-escapes": "^5.0.0", "ansi-escapes": "^7.0.0",
"cli-cursor": "^4.0.0", "cli-cursor": "^5.0.0",
"slice-ansi": "^5.0.0", "slice-ansi": "^7.1.0",
"strip-ansi": "^7.0.1", "strip-ansi": "^7.1.0",
"wrap-ansi": "^8.0.1" "wrap-ansi": "^9.0.0"
}, },
"engines": { "engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" "node": ">=18"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -361,12 +413,12 @@
"dev": true "dev": true
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.5", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"braces": "^3.0.2", "braces": "^3.0.3",
"picomatch": "^2.3.1" "picomatch": "^2.3.1"
}, },
"engines": { "engines": {
@@ -385,6 +437,18 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -482,55 +546,40 @@
} }
}, },
"node_modules/restore-cursor": { "node_modules/restore-cursor": {
"version": "4.0.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"onetime": "^5.1.0", "onetime": "^7.0.0",
"signal-exit": "^3.0.2" "signal-exit": "^4.1.0"
}, },
"engines": { "engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" "node": ">=18"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/restore-cursor/node_modules/onetime": {
"version": "5.1.2", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"mimic-fn": "^2.1.0" "mimic-function": "^5.0.0"
}, },
"engines": { "engines": {
"node": ">=6" "node": ">=18"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/rfdc": {
"version": "1.3.0", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true "dev": true
}, },
"node_modules/shebang-command": { "node_modules/shebang-command": {
@@ -592,17 +641,17 @@
} }
}, },
"node_modules/string-width": { "node_modules/string-width": {
"version": "5.1.2", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"eastasianwidth": "^0.2.0", "emoji-regex": "^10.3.0",
"emoji-regex": "^9.2.2", "get-east-asian-width": "^1.0.0",
"strip-ansi": "^7.0.1" "strip-ansi": "^7.1.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=18"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
@@ -647,18 +696,6 @@
"node": ">=8.0" "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -675,27 +712,30 @@
} }
}, },
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "8.1.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ansi-styles": "^6.1.0", "ansi-styles": "^6.2.1",
"string-width": "^5.0.1", "string-width": "^7.0.0",
"strip-ansi": "^7.0.1" "strip-ansi": "^7.1.0"
}, },
"engines": { "engines": {
"node": ">=12" "node": ">=18"
}, },
"funding": { "funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1" "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
} }
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.3.4", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
"dev": true, "dev": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
} }