Compare commits

...
338 Commits
Author SHA1 Message Date
RaktimaNXG e71924a264 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-25 16:29:26 +05:30
RaktimaNXG 4ae9748004 feat: add agreement for signing document 2024-09-25 16:28:53 +05:30
prafull-opensignlabsandGitHub eda6113792 Merge pull request #1261 from OpenSignLabs/raktima-patch-2
fix: update npm package name of public sign
2024-09-25 12:04:21 +05:30
RaktimaNXG 4f1509c836 fix: handle condition of embed options 2024-09-25 11:53:15 +05:30
prafull-opensignlabsandGitHub dfcf32ef3e Merge pull request #1260 from OpenSignLabs/validation
build(deps): update dependencies
2024-09-25 11:14:53 +05:30
prafull-opensignlabs 4a95257ee2 build(deps): update googleapis package 2024-09-25 11:13:18 +05:30
prafull-opensignlabs ee1e13ef61 build(deps): update dependencies 2024-09-25 11:00:57 +05:30
prafull-opensignlabsandGitHub c9c099ffc5 Merge pull request #1259 from OpenSignLabs/raktima-patch-4
fix: document download & document redirect on wrong route issue in opensign-drive
2024-09-25 10:23:30 +05:30
RaktimaNXG a80f4fb3c9 fix: document download & document redirect on wrong route issue in opensign-drive 2024-09-25 10:21:16 +05:30
prafull-opensignlabsandGitHub 941f3ac47f Merge pull request #1258 from OpenSignLabs/validation
fix: valid file is not attached to mail in signyourself flow
2024-09-25 10:12:11 +05:30
prafull-opensignlabs 80d3200d57 fix: valid file is not attached to mail in signyourself flow 2024-09-25 10:11:01 +05:30
ZathielandGitHub 55926e25d2 Merge pull request #1206 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSign/eslint-plugin-react-7.36.1
build(deps-dev): bump eslint-plugin-react from 7.35.0 to 7.36.1 in /apps/OpenSign
2024-09-24 19:46:14 +02:00
ZathielandGitHub a31f3ebad7 Merge pull request #1251 from OpenSignLabs/dependabot/npm_and_yarn/apps/OpenSignServer/eslint-9.11.1
build(deps-dev): bump eslint from 9.9.1 to 9.11.1 in /apps/OpenSignServer
2024-09-24 19:44:11 +02:00
RaktimaNXG dc9c32c6ee fix: update npm package name of public sign 2024-09-24 20:19:16 +05:30
dependabot[bot]andGitHub dac3aac2a1 build(deps-dev): bump eslint in /apps/OpenSignServer
Bumps [eslint](https://github.com/eslint/eslint) from 9.9.1 to 9.11.1.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.9.1...v9.11.1)

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

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

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

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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-23 00:19:44 +00:00
AndrewandGitHub b7a22d94db Merge pull request #1236 from OpenSignLabs/validation
refactor: add parenthesis
2024-09-20 20:09:33 +05:30
prafull-opensignlabs 3d854756d1 refactor: add parenthesis 2024-09-20 20:07:17 +05:30
AndrewandGitHub 251e7058f1 Merge pull request #1235 from OpenSignLabs/validation
feat: custom smtp support
2024-09-20 19:59:04 +05:30
prafull-opensignlabs 368eb24217 feat: custom smtp support 2024-09-20 19:57:30 +05:30
AndrewandGitHub f89ef54b82 Merge pull request #1233 from OpenSignLabs/raktima-patch-3
feat: enable dismiss button to hide notification in header
2024-09-20 19:30:15 +05:30
RaktimaNXG b9d9f8772b feat: enable dismiss button to hide notification in header 2024-09-20 18:17:31 +05:30
AndrewandGitHub f2ee03e850 Merge pull request #1232 from OpenSignLabs/raktima-patch-3 2024-09-20 18:11:21 +05:30
RaktimaNXG 92e59a0072 fix: zip file 2024-09-20 17:58:57 +05:30
RaktimaNXG 5394c0b520 refactor code 2024-09-20 16:41:41 +05:30
RaktimaNXG e4a9c6f8c2 fix: remove console 2024-09-20 15:49:50 +05:30
RaktimaNXG e559d037c3 feat: add zip file to dowload pdf and certificate in all download option of completed pdf 2024-09-20 15:33:13 +05:30
RaktimaNXG c9909a0869 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-3 2024-09-20 12:10:44 +05:30
RaktimaNXG 988fc1bbfd fix: error 2024-09-20 12:08:01 +05:30
RaktimaNXG 1831bbcd70 feat: add zip file to download pdf and certificate 2024-09-20 12:04:10 +05:30
prafull-opensignlabsandGitHub ced9bbdbd5 Merge pull request #1230 from OpenSignLabs/fix-Confetti
fix: tour design
2024-09-20 10:15:36 +05:30
RaktimaNXG 15d57d937b fix: tour design 2024-09-19 19:38:25 +05:30
AndrewandGitHub 555979869d Merge pull request #1228 from OpenSignLabs/validation
refactor: provide appName
2024-09-19 19:09:56 +05:30
prafull-opensignlabs 4e6477297e refactor: provide appName 2024-09-19 19:07:54 +05:30
AndrewandGitHub e9e06fc53b Merge pull request #1227 from OpenSignLabs/fix-Confetti 2024-09-19 19:06:46 +05:30
RaktimaNXG ecb2818589 fix: update confetti effect 2024-09-19 19:04:59 +05:30
RaktimaNXG c442689573 fix: remove console 2024-09-19 18:38:32 +05:30
RaktimaNXG 88919f2634 refactor code 2024-09-19 17:47:50 +05:30
AndrewandGitHub 1a1bc41283 Merge pull request #1226 from OpenSignLabs/validation 2024-09-19 17:44:04 +05:30
prafull-opensignlabs 4188caed33 feat: add document logs in in-progress report 2024-09-19 17:42:44 +05:30
RaktimaNXG 50557d8bc5 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-19 17:34:28 +05:30
RaktimaNXG 0f1ef0406e refactor code 2024-09-19 17:19:45 +05:30
prafull-opensignlabsandGitHub ffc742b460 Merge pull request #1224 from OpenSignLabs/raktima-patch-3
fix: ui design in mobile view
2024-09-19 15:57:25 +05:30
RaktimaNXG 0c04cc6ce6 fix: update translation file 2024-09-19 15:27:49 +05:30
RaktimaNXG 4e6dfed838 refactor code 2024-09-19 14:42:13 +05:30
RaktimaNXG fdc0524ad2 fix: ui design in mobile view 2024-09-19 14:35:10 +05:30
AndrewandGitHub 096d5962a3 Merge pull request #1221 from OpenSignLabs/validation 2024-09-19 11:30:35 +05:30
prafull-opensignlabs 9ea36f94a7 fix: enableOTP not working properly in template 2024-09-19 11:28:13 +05:30
AndrewandGitHub 2b66df992f Merge pull request #1220 from OpenSignLabs/validation 2024-09-19 10:27:18 +05:30
prafull-opensignlabs ff3c31975e refactor: remove extra space 2024-09-19 10:26:35 +05:30
AndrewandGitHub beaa5eca69 Merge pull request #1219 from OpenSignLabs/validation 2024-09-18 21:17:16 +05:30
prafull-opensignlabs b98040faac fix: document decline event not trigger on revoke 2024-09-18 21:15:31 +05:30
AndrewandGitHub fbbff0df61 Merge pull request #1218 from OpenSignLabs/validation 2024-09-18 19:57:46 +05:30
prafull-opensignlabs 07d5f393c0 fix: document viewed event not trigger 2024-09-18 19:56:47 +05:30
AndrewandGitHub 8220b15a29 Merge pull request #1217 from OpenSignLabs/validation
refactor: add enableOTP in allowedkeys in update doc
2024-09-18 19:27:42 +05:30
prafull-opensignlabs 7cf638b2be refactor: add enableOTP in allowedkeys in update doc 2024-09-18 19:22:41 +05:30
RaktimaNXG 9852c87be7 fix: design in mobile view 2024-09-18 18:19:08 +05:30
prafull-opensignlabsandGitHub 30fb8f31e3 Merge pull request #1216 from OpenSignLabs/validation
fix: blank page appear when try to add typed signature
2024-09-18 18:10:37 +05:30
prafull-opensignlabs ec4d875547 fix: blank page appear when try to add typed signature 2024-09-18 18:09:18 +05:30
RaktimaNXG 1268f65516 fix: update angular text 2024-09-18 16:58:04 +05:30
RaktimaNXG 6551b2537a feat: implement angular npm package use case in public template 2024-09-18 16:48:20 +05:30
AndrewandGitHub 43eee63df2 Merge pull request #1215 from OpenSignLabs/validation 2024-09-18 14:36:30 +05:30
prafull-opensignlabs 4caf9f598a refactor: change variable from isDisableOTP to isEnableOTP 2024-09-18 14:32:45 +05:30
AndrewandGitHub 107eaca8f0 Merge pull request #1213 from OpenSignLabs/validation
feat: add disable OTP feature to directly sign document without verification
2024-09-17 18:31:15 +05:30
prafull-opensignlabs cc11848346 feat: add disable OTP feature to directly sign document without verification 2024-09-17 18:27:21 +05:30
dependabot[bot]andGitHub b138ccf625 build(deps): bump aws-sdk in /apps/OpenSignServer
Bumps [aws-sdk](https://github.com/aws/aws-sdk-js) from 2.1687.0 to 2.1691.0.
- [Release notes](https://github.com/aws/aws-sdk-js/releases)
- [Commits](https://github.com/aws/aws-sdk-js/compare/v2.1687.0...v2.1691.0)

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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-09 00:11:41 +00:00
prafull-opensignlabs 79b05b4a66 refactor: handle tab in menu 2024-09-06 20:10:56 +05:30
AndrewandGitHub fb9e37e71c Merge pull request #1173 from OpenSignLabs/validation 2024-09-06 19:37:54 +05:30
prafull-opensignlabs cd7a741d53 fix: size of tour 2024-09-06 19:36:27 +05:30
AmolandGitHub 5fdc90b4f9 Merge pull request #1172 from OpenSignLabs/validation
fix: quick send not working
2024-09-06 18:51:45 +05:30
prafull-opensignlabs e775840abb fix: quicksend not working 2024-09-06 18:50:15 +05:30
prafull-opensignlabs d7df54dfa1 fix: quick send not working 2024-09-06 18:45:35 +05:30
AndrewandGitHub 646d165d37 Merge pull request #1171 from OpenSignLabs/validation 2024-09-06 18:04:14 +05:30
prafull-opensignlabs ba0a80cca4 refactor: text change 2024-09-06 18:01:47 +05:30
AndrewandGitHub 9d59858542 Merge pull request #1170 from OpenSignLabs/validation 2024-09-06 17:59:59 +05:30
prafull-opensignlabs aaff0a5697 refactor: text translation 2024-09-06 17:58:18 +05:30
AndrewandGitHub 1bb7cee238 Merge pull request #1169 from OpenSignLabs/validation 2024-09-06 17:46:22 +05:30
prafull-opensignlabs d330e662d3 refactor: correct keys in translation files 2024-09-06 17:14:53 +05:30
prafull-opensignlabs b530ff76c4 refactor: translate text 2024-09-06 16:41:56 +05:30
RaktimaNXG b20aa33a04 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-06 15:23:31 +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
RaktimaNXG a64da4cd85 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-06 09:59:41 +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
RaktimaNXG e80bcfc586 refactor code 2024-09-05 18:41:09 +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
RaktimaNXG de5b8160a2 fix: add embedding functionality for all template & add CopyTemplateId button in template report list options 2024-09-05 16:40:15 +05:30
RaktimaNXG 83df969751 feat: introduce embedding feature after make template public 2024-09-05 12:56:38 +05:30
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
dependabot[bot]andGitHub f04fd16c9e build(deps-dev): bump @babel/runtime-corejs2 in /apps/OpenSign
Bumps [@babel/runtime-corejs2](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime-corejs2) from 7.25.4 to 7.25.6.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.25.6/packages/babel-runtime-corejs2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-02 00:32:16 +00:00
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
131 changed files with 14763 additions and 9920 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)
+1586 -1261
View File
File diff suppressed because it is too large Load Diff
+28 -26
View File
@@ -4,16 +4,17 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.8.2", "@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.8.7", "@lottiefiles/dotlottie-react": "^0.9.0",
"@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": "^4.0.0",
"@reduxjs/toolkit": "^2.2.7", "@reduxjs/toolkit": "^2.2.7",
"axios": "^1.7.3", "axios": "^1.7.7",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"i18next": "^23.12.2", "i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.5.2", "i18next-http-backend": "^2.6.1",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"moment": "^2.30.1", "moment": "^2.30.1",
"nth-check": "^2.1.1", "nth-check": "^2.1.1",
@@ -25,7 +26,7 @@
"react-bootstrap": "^2.10.4", "react-bootstrap": "^2.10.4",
"react-confetti": "^6.1.0", "react-confetti": "^6.1.0",
"react-cookie": "^7.2.0", "react-cookie": "^7.2.0",
"react-datepicker": "^7.3.0", "react-datepicker": "^7.4.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^8.0.3", "react-dnd-multi-backend": "^8.0.3",
@@ -33,17 +34,18 @@
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-gtm-module": "^2.0.11", "react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-i18next": "^15.0.1", "react-i18next": "^15.0.2",
"react-konva": "^18.2.10", "react-konva": "^18.2.10",
"react-pdf": "^9.1.0", "react-pdf": "^9.1.1",
"react-quill-new": "^3.2.2", "react-quill-new": "^3.3.2",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"react-rnd": "^10.4.11", "react-rnd": "^10.4.13",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1", "react-scrollbars-custom": "^4.1.1",
"react-select": "^5.8.0", "react-select": "^5.8.1",
"react-signature-canvas": "^1.0.6", "react-signature-canvas": "^1.0.6",
"react-syntax-highlighter": "^15.5.0",
"react-tooltip": "^5.28.0", "react-tooltip": "^5.28.0",
"react-web-share": "^2.0.2", "react-web-share": "^2.0.2",
"reactour": "^1.19.4", "reactour": "^1.19.4",
@@ -52,14 +54,14 @@
"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": {
"build-webpack": "webpack --config webpack.config.js", "build-webpack": "webpack --config webpack.config.js",
"build": "npm run version && npm run build-webpack && react-scripts build", "build": "npm run version && npm run build-webpack && react-scripts build",
"build-watch": "webpack --config webpack.config.js --watch", "build-watch": "webpack --config webpack.config.js --watch",
"start-dev": "concurrently \"npm run build-watch\" \"react-scripts start\"", "start-dev": "react-scripts start",
"start": "serve -s build", "start": "serve -s build",
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt", "version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"", "version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
@@ -94,27 +96,27 @@
}, },
"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.6",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.20",
"babel-loader": "^9.1.3", "babel-loader": "^9.2.1",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"commitizen": "^4.3.0", "commitizen": "^4.3.0",
"concurrently": "^8.2.2", "concurrently": "^9.0.1",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"daisyui": "^4.12.10", "daisyui": "^4.12.10",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"dotenv-webpack": "^8.1.0", "dotenv-webpack": "^8.1.0",
"eslint": "^8.57.0", "eslint": "^9.11.1",
"eslint-plugin-prettier": "^5.2.1", "eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.34.3", "eslint-plugin-react": "^7.36.1",
"lint-staged": "^15.2.8", "lint-staged": "^15.2.10",
"mini-css-extract-plugin": "^2.9.0", "mini-css-extract-plugin": "^2.9.1",
"postcss": "^8.4.41", "postcss": "^8.4.47",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"pretty-quick": "^4.0.0", "pretty-quick": "^4.0.0",
"tailwindcss": "^3.4.9", "tailwindcss": "^3.4.13",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
}, },
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+319 -185
View File
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import Parse from "parse"; import 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,271 @@ 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 }));
}
if (props.handleBuyUsers) {
props.handleBuyUsers(amount.quantity, _resAddon.addon);
}
}
} catch (err) {
console.log("Err in buy addon", err);
props.setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 2000);
setIsFormLoader(false);
}
};
return ( 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", {
adminEmail: extUser?.TenantId?.EmailAddress
})}
</div>
) : (
<form onSubmit={handleAddOnSubmit}>
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
{t("additional-users")}
</p>
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("Quantity-of-users")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="number"
name="quantity"
value={amount.quantity}
onChange={(e) => handlePricePerUser(e)}
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
required
/>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {planInfo.priceperUser})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full">
{t("Proceed")}
</button>
</form>
)}
</>
)}
</div> </div>
)} )}
<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>
); );
}; };
+335 -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,22 @@ 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: "",
email: "",
isAdmin: true
});
const allowedSigners = 50; const allowedSigners = 50;
useEffect(() => { useEffect(() => {
signatureExist(); signatureExist();
@@ -20,14 +41,71 @@ 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,
email: extUser?.TenantId?.EmailAddress
}));
}
setAdmin((obj) => ({ ...obj, objectId: subscription?.adminId }));
if (subscription?.plan === "freeplan") {
setIsFreePlan(true);
}
const resCredits = await Parse.Cloud.run("allowedcredits");
if (resCredits) {
const allowedcredits = resCredits?.allowedcredits || 0;
const addoncredits = resCredits?.addoncredits || 0;
const totalcredits = allowedcredits + addoncredits;
if (totalcredits > 0) {
if (subscription?.plan !== "freeplan") {
setIsBulkAvailable(true);
}
}
setAmount((obj) => ({ ...obj, totalcredits: totalcredits }));
}
const getPlaceholder = props.item?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
} catch (err) {
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
console.log("Err", err);
}
} else {
setIsBulkAvailable(true);
setAdmin((obj) => ({ ...obj, isAdmin: true }));
const getPlaceholder = props.item?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
}
}; };
useEffect(() => { useEffect(() => {
if (scrollOnNextUpdate && formRef.current) { if (scrollOnNextUpdate && formRef.current) {
@@ -76,29 +154,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 +244,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 +258,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 +272,231 @@ 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", { adminEmail: admin?.email })}
</div>
)}
</>
)}
</>
)}
</>
)} )}
</> </>
); );
+193 -126
View File
@@ -6,30 +6,42 @@ 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 current_notification_version = "1"; // Update this with each new feature release
// Get the dismissed version from local storage
const dismissedVersion = localStorage.getItem("notificationDismissedVersion");
const username = localStorage.getItem("username") || "";
const 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 [showNotification, setShowNotification] = useState(
(!dismissedVersion || dismissedVersion !== current_notification_version) &&
true
);
const toggleDropdown = () => { const toggleDropdown = () => {
setIsOpen(!isOpen); setIsOpen(!isOpen);
if (width <= 768) {
showSidebar();
}
}; };
useEffect(() => { useEffect(() => {
checkSubscription(); checkSubscription();
@@ -37,9 +49,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 +58,31 @@ 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);
}
} else {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
} }
} }
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 +125,174 @@ const Header = ({ showSidebar }) => {
window.open("https://console.opensignlabs.com/"); window.open("https://console.opensignlabs.com/");
} }
}; };
const handleNavigation = () => {
navigate("/subscription");
};
const handleMailUsed = () => {
setIsModal(!isModal);
};
// Handle dismissing the notification
const dismissNotification = () => {
// Store the current version as dismissed in local storage
localStorage.setItem(
"notificationDismissedVersion",
current_notification_version
);
setShowNotification(false);
};
return ( return (
<div className="op-navbar bg-base-100 shadow"> <div>
<div className="flex-none"> {isEnableSubscription && showNotification && (
<button <div className="flex justify-between items-center shadow py-1 bg-[#CAE4FA] p-1">
className="op-btn op-btn-square op-btn-ghost focus:outline-none hover:bg-transparent op-btn-sm no-animation" <div className="text-center text-[14px] ml-auto">
onClick={showSidebar} {t("header-news")}
> <span
<i className="fa-light fa-bars text-xl text-base-content"></i> className="cursor-pointer font-medium underline text-blue-800"
</button> onClick={() => navigate("/profile")}
</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")} {" " + t("header-news-btn") + "."}
</button> </span>
</div> </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 <div
onClick={toggleDropdown} className="ml-auto mr-1 cursor-pointer"
className="cursor-pointer w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-gray-400 overflow-hidden" onClick={() => dismissNotification()}
> >
<i className="fa-light fa-xmark"></i>
</div>
</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;
@@ -9,6 +9,7 @@ import * as HoverCard from "@radix-ui/react-hover-card";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
import FolderModal from "../shared/fields/FolderModal"; import FolderModal from "../shared/fields/FolderModal";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { handleDownloadPdf } from "../../constant/Utils";
function DriveBody(props) { function DriveBody(props) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -100,16 +101,17 @@ function DriveBody(props) {
//function for navigate user to microapp-signature component //function for navigate user to microapp-signature component
const checkPdfStatus = async (data) => { const checkPdfStatus = async (data) => {
const signerExist = data.Signers && data.Signers; const signerExist = data?.Signers;
const isDecline = data.IsDeclined && data.IsDeclined; const isDecline = data?.IsDeclined;
const isPlaceholder = data.Placeholders && data.Placeholders; const isPlaceholder = data?.Placeholders;
const signedUrl = data.SignedUrl; const signedUrl = data?.SignedUrl;
const isSignYourself = data?.IsSignyourself;
//checking if document has completed and request signature flow //checking if document has completed and request signature flow
if (data?.IsCompleted && signerExist?.length > 0) { if (data?.IsCompleted && signerExist?.length > 0) {
navigate(`/recipientSignPdf/${data.objectId}`); navigate(`/recipientSignPdf/${data.objectId}`);
} }
//checking if document has completed and signyour-self flow //checking if document has completed and signyour-self flow
else if (!signerExist && !isPlaceholder) { else if ((!signerExist && !isPlaceholder) || isSignYourself) {
navigate(`/signaturePdf/${data.objectId}`); navigate(`/signaturePdf/${data.objectId}`);
} }
//checking if document has declined by someone //checking if document has declined by someone
@@ -136,12 +138,10 @@ function DriveBody(props) {
} }
}; };
const handleMenuItemClick = (selectType, data) => { const handleMenuItemClick = async (selectType, data) => {
switch (selectType) { switch (selectType) {
case "Download": { case "Download": {
const pdfName = data && data.Name; await handleDownloadPdf([data]);
const pdfUrl = data && data.SignedUrl ? data.SignedUrl : data.URL;
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
break; break;
} }
case "Rename": { case "Rename": {
@@ -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
@@ -3,6 +3,8 @@ import { checkIsSubscribed, getFileName } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade"; import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const"; import { isEnableSubscription } from "../../constant/const";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tooltip } from "react-tooltip";
// import SelectFolder from "../../premitives/SelectFolder"; // import SelectFolder from "../../premitives/SelectFolder";
const EditTemplate = ({ template, onSuccess }) => { const EditTemplate = ({ template, onSuccess }) => {
@@ -14,7 +16,8 @@ const EditTemplate = ({ template, onSuccess }) => {
Description: template?.Description || "", Description: template?.Description || "",
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false", SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
AutomaticReminders: template?.AutomaticReminders || false, AutomaticReminders: template?.AutomaticReminders || false,
RemindOnceInEvery: template?.RemindOnceInEvery || 5 RemindOnceInEvery: template?.RemindOnceInEvery || 5,
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false"
}); });
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => { useEffect(() => {
@@ -23,8 +26,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) => {
@@ -41,7 +44,7 @@ const EditTemplate = ({ template, onSuccess }) => {
e.stopPropagation(); e.stopPropagation();
const isChecked = formData.SendinOrder === "true" ? true : false; const isChecked = formData.SendinOrder === "true" ? true : false;
const AutoReminder = formData?.AutomaticReminders || false; const AutoReminder = formData?.AutomaticReminders || false;
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
let reminderDate = {}; let reminderDate = {};
if (AutoReminder) { if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery); const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
@@ -49,7 +52,12 @@ const EditTemplate = ({ template, onSuccess }) => {
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery); ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
reminderDate = { NextReminderDate: ReminderDate }; reminderDate = { NextReminderDate: ReminderDate };
} }
const data = { ...formData, SendinOrder: isChecked, ...reminderDate }; const data = {
...formData,
SendinOrder: isChecked,
IsEnableOTP: IsEnableOTP,
...reminderDate
};
onSuccess(data); onSuccess(data);
}; };
const handleAutoReminder = () => { const handleAutoReminder = () => {
@@ -184,6 +192,70 @@ const EditTemplate = ({ template, onSuccess }) => {
/> />
</div> </div>
)} )}
{isEnableSubscription && (
<div className="text-xs mt-2">
<label className="block">
<span className={isSubscribe ? "" : " text-gray-300"}>
{t("isenable-otp")}{" "}
<a data-tooltip-id="isenableotp-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<Tooltip id="isenableotp-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("isenable-otp")}</p>
<p>{t("isenable-otp-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("isenable-otp-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("isenable-otp-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("isenable-otp-help.p4")}</p>
</div>
</Tooltip>
</label>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsEnableOTP"
checked={formData.IsEnableOTP === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"false"}
name="IsEnableOTP"
className="op-radio op-radio-xs"
checked={formData.IsEnableOTP === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
)}
<div className="mt-[1rem] flex justify-start"> <div className="mt-[1rem] flex justify-start">
<button type="submit" className="op-btn op-btn-primary"> <button type="submit" className="op-btn op-btn-primary">
{t("submit")} {t("submit")}
@@ -1,6 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import axios from "axios"; import axios from "axios";
import { handleDownloadPdf, handleToPrint } from "../../constant/Utils"; import { handleToPrint } from "../../constant/Utils";
import { themeColor, emailRegex } from "../../constant/const"; import { themeColor, emailRegex } from "../../constant/const";
import Loader from "../../primitives/Loader"; import Loader from "../../primitives/Loader";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
@@ -15,7 +15,8 @@ function EmailComponent({
sender, sender,
setIsAlert, setIsAlert,
extUserId, extUserId,
activeMailAdapter activeMailAdapter,
setIsDownloadModal
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [emailList, setEmailList] = useState([]); const [emailList, setEmailList] = useState([]);
@@ -29,12 +30,34 @@ function EmailComponent({
const pdfName = pdfDetails[0]?.Name; const pdfName = pdfDetails[0]?.Name;
setIsLoading(true); setIsLoading(true);
let sendMail; let sendMail;
const docId = !pdfDetails?.[0]?.IsEnableOTP
? pdfDetails?.[0]?.objectId
: "";
let presignedUrl = pdfUrl;
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl, docId: docId },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
presignedUrl = axiosRes.data.result;
} catch (err) {
console.log("err in getsignedurl", err);
}
for (let i = 0; i < emailList.length; i++) { for (let i = 0; i < emailList.length; i++) {
try { try {
const imgPng = const imgPng =
"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"),
@@ -46,7 +69,7 @@ function EmailComponent({
mailProvider: activeMailAdapter, mailProvider: activeMailAdapter,
extUserId: extUserId, extUserId: extUserId,
pdfName: pdfName, pdfName: pdfName,
url: pdfUrl, url: presignedUrl,
recipient: emailList[i], recipient: emailList[i],
subject: `${sender.name} has signed the doc - ${pdfName}`, subject: `${sender.name} has signed the doc - ${pdfName}`,
from: sender.email, from: sender.email,
@@ -74,26 +97,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);
@@ -140,6 +152,11 @@ function EmailComponent({
} }
} }
}; };
const handleClose = () => {
setIsEmail(false);
setEmailValue("");
setEmailList([]);
};
return ( return (
<div> <div>
{/* isEmail */} {/* isEmail */}
@@ -165,7 +182,9 @@ function EmailComponent({
<div className="flex flex-row"> <div className="flex flex-row">
{!isAndroid && ( {!isAndroid && (
<button <button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)} onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
className="op-btn op-btn-neutral op-btn-sm text-[15px]" className="op-btn op-btn-neutral op-btn-sm text-[15px]"
> >
<i className="fa-light fa-print" aria-hidden="true"></i> <i className="fa-light fa-print" aria-hidden="true"></i>
@@ -174,9 +193,10 @@ function EmailComponent({
)} )}
<button <button
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2" className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
onClick={() => onClick={() => {
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading) handleClose();
} setIsDownloadModal(true);
}}
> >
<i className="fa-light fa-download" aria-hidden="true"></i> <i className="fa-light fa-download" aria-hidden="true"></i>
{t("download")} {t("download")}
@@ -272,11 +292,7 @@ function EmailComponent({
<button <button
type="button" type="button"
className="op-btn op-btn-ghost ml-2" className="op-btn op-btn-ghost ml-2"
onClick={() => { onClick={() => handleClose()}
setIsEmail(false);
setEmailValue("");
setEmailList([]);
}}
> >
{t("close")} {t("close")}
</button> </button>
@@ -0,0 +1,267 @@
import React, { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
import { copytoData } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
function EmbedTab(props) {
const { t } = useTranslation();
const tabName = [
{ title: "React/Next.js", icon: "fa-brands fa-react", color: "#61dafb" },
{ title: "JavaScript", icon: "fa-brands fa-js", color: "#ffd43b" },
{ title: "Angular", icon: "fa-brands fa-angular", color: "#ff5733" }
];
const [activeTab, setActiveTab] = useState(0);
// State to track if the code has been copied
const [isCopied, setIsCopied] = useState(false);
const reactCode = [
{
id: 0,
title: "Installation",
codeString: `
npm install @opensign/react`
},
{
id: 1,
title: "Usage",
codeString: `
import React from "react";
import Opensign from "@opensign/react";
export function App() {
return (
<div className="app">
<Opensign
onLoad={() => console.log("success")}
onLoadError={(error) => console.log(error)}
templateId= {"${props.templateId}"}
/>
</div>
);
}
`
}
];
const angularCode = [
{
id: 0,
title: "Installation",
codeString: `
npm install @opensign/angular`
},
{
id: 1,
title: "Usage",
codeString: `
import { Component } from '@angular/core';
import {OpensignComponent} from "@opensign/angular"
@Component({
selector:'app-root',
standalone: true,
imports: [OpensignComponent],
template: '<opensign templateId={"${props.templateId}"}
(onLoad)="handleLoad()"
(onLoadError)="handleError($event)"
></opensign>',
})
export class AppComponent{
handleLoad() {
console.log("success");
}
handleError(error: string) {
console.log(error);
}
}
`
}
];
const jsCodeString = `
<script
src= "https://app.opensignlabs.com/static/js/public-template.bundle.js"
id="opensign-script"
templateId=${props.templateId}
></script>
`;
const handleCopy = (code, ind) => {
copytoData(code);
setIsCopied({ ...isCopied, [ind]: true });
setTimeout(() => setIsCopied(false), 3000);
};
return (
<div className="mt-4 border-t-[1px]">
<div className="flex justify-center items-center mt-2">
<div role="tablist" className="op-tabs op-tabs-bordered">
{tabName.map((tabData, ind) => (
<div
onClick={() => setActiveTab(ind)}
key={ind}
role="tab"
className={`${
activeTab === ind ? "op-tab-active" : ""
} op-tab flex items-center`}
>
<i
className={`${tabData.icon}`}
style={{ color: tabData.color }}
></i>
<span className="ml-1 text-[10px] font-medium md:font-normal md:text-[15px]">
{tabData.title}
</span>
</div>
))}
</div>
</div>
<div>
{activeTab === 0 ? (
<div className="mt-4">
{reactCode.map((data, ind) => {
return (
<div key={ind}>
<p className="font-medium text-[18px]">
{t(`${data.title}`)}
</p>
<p className="text-[13px] mt-2">
{t("public-template-mssg-1")}
</p>
<div className="relative p-1">
<div
onClick={() => handleCopy(data.codeString, ind)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[ind] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{data.codeString}
</SyntaxHighlighter>
</div>
</div>
);
})}
<p className="font-medium text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
{" "}
{t("public-template-mssg-4")}
<a
href="https://www.npmjs.com/package/@opensign/react"
target="_blank"
rel="noreferrer"
className="cursor-pointer text-blue-700 "
>
{" "}
OpenSign React package{" "}
</a>
{t("public-template-mssg-5")}
</p>
</div>
) : activeTab === 1 ? (
<div className="mt-4">
<div>
{/* <p className="font-medium text-[18px]">{t(`${data.title}`)}</p> */}
<div className="relative p-1">
<div
onClick={() => handleCopy(jsCodeString, 0)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[0] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{jsCodeString}
</SyntaxHighlighter>
</div>
</div>
</div>
) : (
activeTab === 2 && (
<div className="mt-4">
{angularCode.map((data, ind) => {
return (
<div key={ind}>
<p className="font-medium text-[18px]">
{t(`${data.title}`)}
</p>
<p className="text-[13px] mt-2">
{t("angular-npm-mssg-1")}
</p>
<div className="relative p-1">
<div
onClick={() => handleCopy(data.codeString, ind)}
className="absolute top-[20px] right-[20px] cursor-pointer"
>
<i className="fa-light fa-copy text-white mr-[2px]" />
<span className=" text-white">
{isCopied[ind] ? t("copied-code") : t("copy-code")}
</span>
</div>
<SyntaxHighlighter
customStyle={{
borderRadius: "15px"
}}
language="javascript"
style={tomorrow}
>
{data.codeString}
</SyntaxHighlighter>
</div>
</div>
);
})}
<p className="font-medium text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
{" "}
{t("public-template-mssg-4")}
<a
href="https://www.npmjs.com/package/@opensign/angular"
target="_blank"
rel="noreferrer"
className="cursor-pointer text-blue-700 "
>
{" "}
OpenSign Angular package{" "}
</a>
{t("public-template-mssg-5")}
</p>
</div>
)
)}
</div>
</div>
);
}
export default EmbedTab;
+139 -59
View File
@@ -34,7 +34,13 @@ function Header({
setIsEmail, setIsEmail,
completeBtnTitle, completeBtnTitle,
setIsEditTemplate, setIsEditTemplate,
isPublicTemplate isPublicTemplate,
clickOnZoomIn,
clickOnZoomOut,
handleRotationFun,
isDisableRotate,
templateId,
setIsDownloadModal
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const filterPrefill = const filterPrefill =
@@ -48,7 +54,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 +78,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]">
@@ -90,9 +95,13 @@ function Header({
> >
<DropdownMenu.Item <DropdownMenu.Item
className="DropdownMenuItem" className="DropdownMenuItem"
onClick={() => onClick={() => {
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading) if (isCompleted) {
} setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
> >
<div className="flex flex-row"> <div className="flex flex-row">
<i <i
@@ -138,7 +147,7 @@ function Header({
<DropdownMenu.Item <DropdownMenu.Item
className="DropdownMenuItem" className="DropdownMenuItem"
onClick={(e) => onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading) handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
} }
> >
<div className="flex flex-row"> <div className="flex flex-row">
@@ -184,13 +193,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,30 +210,86 @@ 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
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer" className="DropdownMenuItem"
onClick={() => onClick={() =>
handleDownloadPdf( handleDownloadPdf(pdfDetails, setIsDownloading)
pdfDetails,
pdfUrl,
setIsDownloading
)
} }
> >
<i <div className="flex flex-row">
className="fa-light fa-arrow-down mr-[3px]" <i
aria-hidden="true" className="fa-light fa-arrow-down mr-[3px]"
></i> aria-hidden="true"
<span className="font-[500]">{t("download")}</span> ></i>
<span className="font-[500]">
{t("download")}
</span>
</div>
</DropdownMenu.Item>
{!isDisableRotate && (
<>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleRotationFun(90)}
>
<div className="flex flex-row">
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-right")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleRotationFun(-90)}
>
<div className="flex flex-row">
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-left")}
</span>
</div>
</DropdownMenu.Item>
</>
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => clickOnZoomIn()}
>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">{t("zoom-in")}</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => clickOnZoomOut()}
>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("zoom-out")}
</span>
</div>
</DropdownMenu.Item> </DropdownMenu.Item>
</DropdownMenu.Content> </DropdownMenu.Content>
</DropdownMenu.Portal> </DropdownMenu.Portal>
</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>
@@ -298,7 +357,9 @@ function Header({
alreadySign ? ( alreadySign ? (
<div className="flex flex-row"> <div className="flex flex-row">
<button <button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)} onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
type="button" type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow" className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
> >
@@ -326,9 +387,13 @@ function Header({
<button <button
type="button" type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow" className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => onClick={() => {
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading) if (isCompleted) {
} setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
> >
<i <i
className="fa-light fa-download py-[3px]" className="fa-light fa-download py-[3px]"
@@ -339,15 +404,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 +443,18 @@ 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, setIsDownloading)
> }
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i> >
<span className="hidden lg:block">{t("download")}</span> <i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
</button> <span className="hidden lg:block">{t("download")}</span>
</button>
)}
</> </>
)} )}
</div> </div>
@@ -392,7 +476,9 @@ function Header({
</button> </button>
)} )}
<button <button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)} onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
type="button" type="button"
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow" className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
> >
@@ -402,9 +488,9 @@ function Header({
<button <button
type="button" type="button"
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow" className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
onClick={() => onClick={() => {
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading) setIsDownloadModal(true);
} }}
> >
<i className="fa-light fa-download" aria-hidden="true"></i> <i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("download")}</span> <span className="hidden lg:block ml-1">{t("download")}</span>
@@ -423,9 +509,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 +526,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
+10 -8
View File
@@ -31,8 +31,8 @@ function SignPad({
const [isTab, setIsTab] = useState("draw"); const [isTab, setIsTab] = useState("draw");
const [isSignImg, setIsSignImg] = useState(""); const [isSignImg, setIsSignImg] = useState("");
const [signValue, setSignValue] = useState(""); const [signValue, setSignValue] = useState("");
const [textWidth, setTextWidth] = useState(null); const [textWidth, setTextWidth] = useState(0);
const [textHeight, setTextHeight] = useState(null); const [textHeight, setTextHeight] = useState(0);
const [signatureType, setSignatureType] = useState("draw"); const [signatureType, setSignatureType] = useState("draw");
const fontOptions = [ const fontOptions = [
{ value: "Fasthand" }, { value: "Fasthand" },
@@ -51,7 +51,7 @@ function SignPad({
`Parse/${localStorage.getItem("parseAppId")}/currentUser` `Parse/${localStorage.getItem("parseAppId")}/currentUser`
); );
const jsonSender = JSON.parse(senderUser); const jsonSender = JSON.parse(senderUser);
const currentUserName = jsonSender && jsonSender.name; const currentUserName = jsonSender && jsonSender?.name;
//function for clear signature image //function for clear signature image
const handleClear = () => { const handleClear = () => {
@@ -137,7 +137,7 @@ function SignPad({
(isTab === "draw" && isSignImg) || (isTab === "draw" && isSignImg) ||
(isTab === "image" && image) || (isTab === "image" && image) ||
(isTab === "mysignature" && isDefaultSign) || (isTab === "mysignature" && isDefaultSign) ||
(isTab === "type" && textWidth) (isTab === "type" && signValue)
? false ? false
: image : image
? false ? false
@@ -169,10 +169,10 @@ function SignPad({
} }
} }
const trimmedName = currentUserName && currentUserName.trim(); const trimmedName = currentUserName && currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0); const firstCharacter = trimmedName?.charAt(0);
const userName = isInitial ? firstCharacter : currentUserName; const userName = isInitial ? firstCharacter : currentUserName;
setSignValue(userName); setSignValue(userName || "");
setFontSelect("Fasthand"); setFontSelect("Fasthand");
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -200,8 +200,10 @@ function SignPad({
canvasRef.current.fromDataURL(isSignImg); canvasRef.current.fromDataURL(isSignImg);
} }
if (isTab === "type") { if (isTab === "type") {
const trimmedName = signValue ? signValue.trim() : currentUserName.trim(); const trimmedName = signValue
const firstCharacter = trimmedName.charAt(0); ? signValue?.trim()
: currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = isInitial ? firstCharacter : signValue; const userName = isInitial ? firstCharacter : signValue;
setSignValue(userName); setSignValue(userName);
convertToImg(fontSelect, userName); convertToImg(fontSelect, userName);
@@ -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: () =>
+352 -119
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";
@@ -33,14 +33,15 @@ export const openInNewTab = (url, target) => {
export async function fetchSubscription( export async function fetchSubscription(
extUserId, extUserId,
contactObjId, contactObjId,
isGuestSign = false isGuestSign = false,
isPublic = false
) { ) {
try { try {
const Extand_Class = localStorage.getItem("Extand_Class"); const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class); const extClass = Extand_Class && JSON.parse(Extand_Class);
// console.log("extClass ", extClass); // console.log("extClass ", extClass);
let extUser; let extUser;
if (extClass && extClass.length > 0) { if (extClass && extClass.length > 0 && !isPublic) {
extUser = extClass[0].objectId; extUser = extClass[0].objectId;
} else { } else {
extUser = extUserId; extUser = extUserId;
@@ -54,20 +55,70 @@ export async function fetchSubscription(
}; };
const params = isGuestSign const params = isGuestSign
? { contactId: contactObjId } ? { contactId: contactObjId }
: { extUserId: extUser }; : { extUserId: extUser, ispublic: isPublic };
const tenatRes = await axios.post(url, params, { headers: headers }); const tenatRes = await axios.post(url, params, { headers: headers });
let plan, status, billingDate; 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 +126,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 +662,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 +690,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,
@@ -672,7 +709,8 @@ export const createDocument = async (template, placeholders, signerData) => {
Signers: signers, Signers: signers,
SendinOrder: Doc?.SendinOrder || false, SendinOrder: Doc?.SendinOrder || false,
AutomaticReminders: Doc?.AutomaticReminders || false, AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5) RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5),
IsEnableOTP: Doc?.IsEnableOTP || false
}; };
try { try {
@@ -762,7 +800,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 +829,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 +877,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 +885,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 +1086,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 +1291,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 +1414,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 +1427,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 +1448,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 +1462,7 @@ export const multiSignEmbed = async (
1, 1,
page.getSize(), page.getSize(),
optionsFontSize, optionsFontSize,
rgb(0, 0, 0), updateColorInRgb,
font, font,
page page
); );
@@ -1427,20 +1493,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 +1571,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 +1581,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 +1627,7 @@ export const multiSignEmbed = async (
1, 1,
page.getSize(), page.getSize(),
optionsFontSize, optionsFontSize,
rgb(0, 0, 0), updateColorInRgb,
font, font,
page page
); );
@@ -1679,9 +1743,7 @@ export const contactBook = async (objectId) => {
//function for getting document details from contract_Documents class //function for getting document details from contract_Documents class
export const contractDocument = async (documentId) => { export const contractDocument = async (documentId) => {
const data = { const data = { docId: documentId };
docId: documentId
};
const documentDeatils = await axios const documentDeatils = await axios
.post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, { .post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, {
headers: { headers: {
@@ -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 };
@@ -1981,30 +2039,32 @@ export const fetchUrl = async (url, pdfName) => {
console.error("Error downloading the file:", error); console.error("Error downloading the file:", error);
} }
}; };
//handle download signed pdf export const getSignedUrl = async (pdfUrl, docId) => {
export const handleDownloadPdf = async ( //use only axios here due to public template sign
pdfDetails, const axiosRes = await axios.post(
pdfUrl, `${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
setIsDownloading { url: pdfUrl, docId: docId || "" },
) => { {
const pdfName = pdfDetails[0] && pdfDetails[0].Name; headers: {
setIsDownloading("pdf"); "content-type": "Application/json",
try { "X-Parse-Application-Id": localStorage.getItem("parseAppId"),
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl }); "X-Parse-Session-Token": localStorage.getItem("accesstoken")
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
} }
); }
const url = axiosRes.data.result; );
const url = axiosRes.data.result;
return url;
};
//handle download signed pdf
export const handleDownloadPdf = async (pdfDetails, setIsDownloading) => {
const pdfName = pdfDetails[0] && pdfDetails[0]?.Name;
const pdfUrl = pdfDetails?.[0]?.SignedUrl || pdfDetails?.[0]?.URL;
setIsDownloading && setIsDownloading("pdf");
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : "";
try {
const url = await getSignedUrl(pdfUrl, docId);
await fetchUrl(url, pdfName); await fetchUrl(url, pdfName);
setIsDownloading(""); setIsDownloading && setIsDownloading("");
} catch (err) { } catch (err) {
console.log("err in getsignedurl", err); console.log("err in getsignedurl", err);
setIsDownloading(""); setIsDownloading("");
@@ -2017,16 +2077,22 @@ export const sanitizeFileName = (pdfName) => {
return pdfName.replace(/ /g, "_"); return pdfName.replace(/ /g, "_");
}; };
//function for print digital sign pdf //function for print digital sign pdf
export const handleToPrint = async (event, pdfUrl, setIsDownloading) => { export const handleToPrint = async (
event,
pdfUrl,
setIsDownloading,
pdfDetails
) => {
event.preventDefault(); event.preventDefault();
setIsDownloading("pdf"); setIsDownloading("pdf");
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : "";
try { try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl }); // const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
//`localStorage.getItem("baseUrl")` is also use in public-profile flow for public-sign //`localStorage.getItem("baseUrl")` is also use in public-profile flow for public-sign
//if we give this `appInfo.baseUrl` as a base url then in public-profile it will create base url of it's window.location.origin ex- opensign.me which is not base url //if we give this `appInfo.baseUrl` as a base url then in public-profile it will create base url of it's window.location.origin ex- opensign.me which is not base url
const axiosRes = await axios.post( const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`, `${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl }, { url: pdfUrl, docId: docId },
{ {
headers: { headers: {
"content-type": "Application/json", "content-type": "Application/json",
@@ -2066,13 +2132,18 @@ export const handleToPrint = async (event, pdfUrl, setIsDownloading) => {
//handle download signed pdf //handle download signed pdf
export const handleDownloadCertificate = async ( export const handleDownloadCertificate = async (
pdfDetails, pdfDetails,
setIsDownloading setIsDownloading,
isZip
) => { ) => {
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) { if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try { try {
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl); await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl; const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`); if (isZip) {
return certificateUrl;
} else {
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
}
} catch (err) { } catch (err) {
console.log("err in download in certificate", err); console.log("err in download in certificate", err);
} }
@@ -2098,7 +2169,13 @@ export const handleDownloadCertificate = async (
if (doc?.CertificateUrl) { if (doc?.CertificateUrl) {
await fetch(doc?.CertificateUrl); await fetch(doc?.CertificateUrl);
const certificateUrl = doc?.CertificateUrl; const certificateUrl = doc?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`); if (isZip) {
setIsDownloading("");
return certificateUrl;
} else {
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
}
setIsDownloading(""); setIsDownloading("");
} else { } else {
setIsDownloading("certificate"); setIsDownloading("certificate");
@@ -2142,9 +2219,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 +2233,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 +2259,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 +2364,156 @@ 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;
}
};
// `generateTitleFromFilename` to generate Title of document from file name
export function generateTitleFromFilename(filename) {
try {
// Step 1: Trim whitespace
let title = filename.trim();
// Step 2: Remove the file extension (everything after the last '.')
const lastDotIndex = title.lastIndexOf(".");
if (lastDotIndex > 0) {
title = title.substring(0, lastDotIndex);
}
// Step 3: Replace special characters (except Unicode letters, digits, spaces, and hyphens)
title = title.replace(/[^\p{L}\p{N}\s-]/gu, " ");
// Step 4: Replace multiple spaces with a single space
title = title.replace(/\s+/g, " ");
// Step 5: Capitalize first letter of each word (Title Case), handling Unicode characters
title = title.replace(
/\p{L}\S*/gu,
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
);
// Step 6: Restrict length of title (optional, let's say 100 characters)
if (title.length > 100) {
title = title.substring(0, 100).trim();
}
// Step 7: Handle empty or invalid title by falling back to "Untitled Document"
if (!title || title.length === 0) {
return "Untitled Document";
}
return title;
} catch (error) {
// Handle unexpected errors gracefully by returning a default title
console.error("Error generating title from filename:", error);
return "Untitled Document";
}
}
+1 -1
View File
@@ -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 */
+31 -1
View File
@@ -1,3 +1,5 @@
import { isEnableSubscription } from "../constant/const";
export default function reportJson(id) { export default function reportJson(id) {
// console.log("json ", json); // console.log("json ", json);
const head = ["Sr.No", "Title", "Note", "Folder", "File", "Owner", "Signers"]; const head = ["Sr.No", "Title", "Note", "Folder", "File", "Owner", "Signers"];
@@ -10,9 +12,36 @@ export default function reportJson(id) {
"Owner", "Owner",
"Signers" "Signers"
]; ];
const iphead = [
"Sr.No",
"Title",
"Note",
"Folder",
"File",
"Status",
"Signers"
];
const contactbook = ["Sr.No", "Name", "Email", "Phone"]; const contactbook = ["Sr.No", "Name", "Email", "Phone"];
const dashboardReportHead = ["Title", "File", "Owner", "Signers"]; const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"]; const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"];
const templateSubAction = isEnableSubscription
? [
{
btnId: "2434",
btnLabel: "Embed",
hoverLabel: "Embed",
btnIcon: "fa-light fa-code",
action: "Embed"
},
{
btnId: "2434",
btnLabel: "Copy TemplateId",
hoverLabel: "Copy TemplateId",
btnIcon: "fa-light fa-copy",
action: "CopyTemplateId"
}
]
: [];
switch (id) { switch (id) {
// draft documents report // draft documents report
case "ByHuevtCFY": case "ByHuevtCFY":
@@ -63,7 +92,7 @@ export default function reportJson(id) {
case "1MwEuxLEkF": case "1MwEuxLEkF":
return { return {
reportName: "In-progress documents", reportName: "In-progress documents",
heading: head, heading: iphead,
actions: [ actions: [
{ {
btnId: "8901", btnId: "8901",
@@ -354,6 +383,7 @@ export default function reportJson(id) {
redirectUrl: "template", redirectUrl: "template",
action: "redirect" action: "redirect"
}, },
...templateSubAction,
{ {
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;
+7 -5
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);
@@ -250,9 +252,9 @@ const HomeLayout = () => {
} }
}; };
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 {
+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>
+2 -6
View File
@@ -74,7 +74,7 @@ const DebugPdf = () => {
const inferPdfType = async (pdf) => { const inferPdfType = async (pdf) => {
try { try {
const firstPage = await pdf.getPage(2); const firstPage = await pdf.getPage(pdf?.numPages > 1 ? 2 : 1);
const scale = 1; const scale = 1;
const { width, height } = firstPage.getViewport({ scale }); const { width, height } = firstPage.getViewport({ scale });
setPdfDimension({ width: width, height: height }); setPdfDimension({ width: width, height: height });
@@ -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) {
+238 -122
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";
@@ -10,7 +9,12 @@ import SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title"; import Title from "../components/Title";
import PageNotFound from "./PageNotFound"; import PageNotFound from "./PageNotFound";
import { SaveFileSize } from "../constant/saveFileSize"; import { SaveFileSize } from "../constant/saveFileSize";
import { checkIsSubscribed, getFileName, toDataUrl } from "../constant/Utils"; import {
checkIsSubscribed,
generateTitleFromFilename,
getFileName,
toDataUrl
} from "../constant/Utils";
import { PDFDocument } from "pdf-lib"; import { PDFDocument } from "pdf-lib";
import axios from "axios"; import axios from "axios";
import { isEnableSubscription } from "../constant/const"; import { isEnableSubscription } from "../constant/const";
@@ -37,15 +41,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"} />;
}
} }
} }
@@ -66,7 +66,8 @@ const Forms = (props) => {
password: "", password: "",
file: "", file: "",
remindOnceInEvery: 5, remindOnceInEvery: 5,
autoreminder: false autoreminder: false,
IsEnableOTP: "false"
}); });
const [fileupload, setFileUpload] = useState(""); const [fileupload, setFileUpload] = useState("");
const [fileload, setfileload] = useState(false); const [fileload, setfileload] = useState(false);
@@ -79,6 +80,7 @@ const Forms = (props) => {
const [isDecrypting, setIsDecrypting] = useState(false); const [isDecrypting, setIsDecrypting] = useState(false);
const [isCorrectPass, setIsCorrectPass] = useState(true); const [isCorrectPass, setIsCorrectPass] = useState(true);
const [isSubscribe, setIsSubscribe] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false);
const [isAdvanceOpt, setIsAdvanceOpt] = useState(false);
const handleStrInput = (e) => { const handleStrInput = (e) => {
setIsCorrectPass(true); setIsCorrectPass(true);
setFormData({ ...formData, [e.target.name]: e.target.value }); setFormData({ ...formData, [e.target.name]: e.target.value });
@@ -95,8 +97,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);
} }
}; };
@@ -142,12 +144,11 @@ const Forms = (props) => {
} catch (err) { } catch (err) {
console.log("err in sending posthog encryptedpdf", err); console.log("err in sending posthog encryptedpdf", err);
} }
// console.log("err ", err);
try { try {
setIsDecrypting(true); setIsDecrypting(true);
const size = files?.[0].size; const size = files?.[0].size;
const name = generatePdfName(16); const name = generatePdfName(16);
const url = "https://ai.nxglabs.in/decryptpdf"; //"https://ai.nxglabs.in/decryptpdf"; // const url = "https://ai.nxglabs.in/decryptpdf"; //
let formData = new FormData(); let formData = new FormData();
formData.append("file", files[0]); formData.append("file", files[0]);
formData.append("password", ""); formData.append("password", "");
@@ -190,6 +191,9 @@ const Forms = (props) => {
if (parseFile.url()) { if (parseFile.url()) {
setFileUpload(parseFile.url()); setFileUpload(parseFile.url());
setfileload(false); setfileload(false);
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId);
const tenantId = localStorage.getItem("TenantId"); const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, parseFile.url(), tenantId); SaveFileSize(size, parseFile.url(), tenantId);
return parseFile.url(); return parseFile.url();
@@ -263,6 +267,8 @@ const Forms = (props) => {
setfileload(false); setfileload(false);
if (response.url()) { if (response.url()) {
const tenantId = localStorage.getItem("TenantId"); const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId); SaveFileSize(size, response.url(), tenantId);
return response.url(); return response.url();
} }
@@ -290,12 +296,17 @@ const Forms = (props) => {
if (res.data) { if (res.data) {
setFileUpload(res.data.url); setFileUpload(res.data.url);
setfileload(false); setfileload(false);
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
} }
} catch (err) { } catch (err) {
e.target.value = ""; e.target.value = "";
setfileload(false); setfileload(false);
setpercentage(0); setpercentage(0);
console.log("err in libreconverter ", err); console.log("err in libreconverter ", err);
alert(
"We are currently experiencing some issues with processing DOCX files. Please upload the PDF version or contact us on support@opensignlabs.com"
);
} }
} }
} }
@@ -334,6 +345,8 @@ const Forms = (props) => {
setfileload(false); setfileload(false);
if (response.url()) { if (response.url()) {
const tenantId = localStorage.getItem("TenantId"); const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(file.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId); SaveFileSize(size, response.url(), tenantId);
return response.url(); return response.url();
} }
@@ -371,9 +384,10 @@ const Forms = (props) => {
}); });
setFileUpload(response.url()); setFileUpload(response.url());
setfileload(false); setfileload(false);
if (response.url()) { if (response.url()) {
const tenantId = localStorage.getItem("TenantId"); const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(file.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId); SaveFileSize(size, response.url(), tenantId);
return response.url(); return response.url();
} }
@@ -407,6 +421,13 @@ const Forms = (props) => {
object.set("SendinOrder", isChecked); object.set("SendinOrder", isChecked);
object.set("AutomaticReminders", formData.autoreminder); object.set("AutomaticReminders", formData.autoreminder);
object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery)); object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery));
if (isEnableSubscription) {
const IsEnableOTP =
formData?.IsEnableOTP === "false" ? false : true;
object.set("IsEnableOTP", IsEnableOTP);
} else {
object.set("IsEnableOTP", false);
}
} }
object.set("URL", fileupload); object.set("URL", fileupload);
object.set("CreatedBy", Parse.User.createWithoutData(currentUser.id)); object.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
@@ -433,7 +454,17 @@ const Forms = (props) => {
setFormData({ setFormData({
Name: "", Name: "",
Description: "", Description: "",
Note: "" Note:
props.title === "Sign Yourself"
? "Note to myself"
: "Please review and sign this document",
TimeToCompleteDays: 15,
SendinOrder: "true",
password: "",
file: "",
remindOnceInEvery: 5,
autoreminder: false,
IsEnableOTP: "false"
}); });
setFileUpload(""); setFileUpload("");
setpercentage(0); setpercentage(0);
@@ -482,7 +513,8 @@ const Forms = (props) => {
password: "", password: "",
file: "", file: "",
remindOnceInEvery: 5, remindOnceInEvery: 5,
autoreminder: false autoreminder: false,
IsEnableOTP: "false"
}); });
setFileUpload(""); setFileUpload("");
setpercentage(0); setpercentage(0);
@@ -534,6 +566,8 @@ const Forms = (props) => {
setFormData((prev) => ({ ...prev, password: "" })); setFormData((prev) => ({ ...prev, password: "" }));
setFileUpload(parseFile.url()); setFileUpload(parseFile.url());
setfileload(false); setfileload(false);
const title = generateTitleFromFilename(formData?.file?.name);
setFormData((obj) => ({ ...obj, Name: title }));
const tenantId = localStorage.getItem("TenantId"); const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, parseFile.url(), tenantId); SaveFileSize(size, parseFile.url(), tenantId);
return parseFile.url(); return parseFile.url();
@@ -646,7 +680,7 @@ const Forms = (props) => {
)} )}
<div className="text-xs"> <div className="text-xs">
<label className="block"> <label className="block">
{`${t("report-heading.File")} (${t("file-type")} ${ {`${t("report-heading.File")} (${t("file-type")}${
isEnableSubscription ? ", docx)" : ")" isEnableSubscription ? ", docx)" : ")"
}`} }`}
<span className="text-red-500 text-[13px]">*</span> <span className="text-red-500 text-[13px]">*</span>
@@ -759,120 +793,73 @@ const Forms = (props) => {
isReset={isReset} isReset={isReset}
/> />
)} )}
{props.title === "Request Signatures" && ( {props.title !== "Sign Yourself" && (
<div className="text-xs mt-2"> <div className="text-xs mt-2">
<label className="block"> <label className="block">
{t("time-to-complete")} {t("send-in-order")}
<span className="text-red-500 text-[13px]">*</span> <a data-tooltip-id="sendInOrder-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<Tooltip id="sendInOrder-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("send-in-order")}</p>
<p>{t("send-in-order-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("send-in-order-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("send-in-order-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("send-in-order-help.p4")}</p>
</div>
</Tooltip>
</label> </label>
<input <div className="flex items-center gap-2 ml-2 mb-1">
type="number" <input
name="TimeToCompleteDays" type="radio"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" value={"true"}
value={formData.TimeToCompleteDays} className="op-radio op-radio-xs"
onChange={(e) => handleStrInput(e)} name="SendinOrder"
onInvalid={(e) => checked={formData.SendinOrder === "true"}
e.target.setCustomValidity(t("input-required")) onChange={handleStrInput}
} />
onInput={(e) => e.target.setCustomValidity("")} <div className="text-center">{t("yes")}</div>
required </div>
/> <div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"false"}
name="SendinOrder"
className="op-radio op-radio-xs"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div> </div>
)} )}
{props.title !== "Sign Yourself" && ( {isAdvanceOpt && (
<> <div className="overflow-y-auto z-[500] transition-all">
<div className="text-xs mt-2"> {props.title === "Request Signatures" && (
<label className="block">
{t("send-in-order")}
<a data-tooltip-id="sendInOrder-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<Tooltip id="sendInOrder-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("send-in-order")}</p>
<p>{t("send-in-order-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}:</span>
<span>{t("send-in-order-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("send-in-order-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("send-in-order-help.p4")}</p>
</div>
</Tooltip>
</label>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="SendinOrder"
checked={formData.SendinOrder === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div className="flex items-center gap-2 ml-2 mb-1">
<input
type="radio"
value={"false"}
name="SendinOrder"
className="op-radio op-radio-xs"
checked={formData.SendinOrder === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
{isEnableSubscription && (
<div className="text-xs mt-2">
<span
className={
isSubscribe
? "font-semibold"
: "font-semibold text-gray-300"
}
>
{t("auto-reminder")}
{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<label
className={`${
isSubscribe
? "cursor-pointer "
: "pointer-events-none opacity-50"
} relative block items-center mb-0`}
>
<input
type="checkbox"
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white"
checked={formData.autoreminder}
onChange={handleAutoReminder}
/>
</label>
</div>
)}
{formData?.autoreminder === true && (
<div className="text-xs mt-2"> <div className="text-xs mt-2">
<label className="block"> <label className="block">
{t("remind-once")} {t("time-to-complete")}
<span className="text-red-500 text-[13px]">*</span> <span className="text-red-500 text-[13px]">*</span>
</label> </label>
<input <input
type="number" type="number"
value={formData.remindOnceInEvery} name="TimeToCompleteDays"
name="remindOnceInEvery"
className="op-input op-input-bordered op-input-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"
onChange={handleStrInput} value={formData.TimeToCompleteDays}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) => onInvalid={(e) =>
e.target.setCustomValidity(t("input-required")) e.target.setCustomValidity(t("input-required"))
} }
@@ -881,7 +868,136 @@ const Forms = (props) => {
/> />
</div> </div>
)} )}
</> {props.title !== "Sign Yourself" && (
<>
{isEnableSubscription && (
<div className="text-xs mt-2">
<span className={isSubscribe ? "" : " text-gray-300"}>
{t("auto-reminder")}{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<label
className={`${
isSubscribe
? "cursor-pointer "
: "pointer-events-none opacity-50"
} relative block items-center mb-0 mt-1.5`}
>
<input
type="checkbox"
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white"
checked={formData.autoreminder}
onChange={handleAutoReminder}
/>
</label>
</div>
)}
{formData?.autoreminder === true && (
<div className="text-xs mt-2">
<label className="block">
{t("remind-once")}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
value={formData.remindOnceInEvery}
name="remindOnceInEvery"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
onChange={handleStrInput}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
)}
{isEnableSubscription && (
<div className="text-xs mt-2">
<label className="block">
<span className={isSubscribe ? "" : " text-gray-300"}>
{t("isenable-otp")}{" "}
<a
data-tooltip-id="isenableotp-tooltip"
className="ml-1"
>
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
{!isSubscribe && isEnableSubscription && (
<Upgrade />
)}
</span>
<Tooltip id="isenableotp-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("isenable-otp")}</p>
<p>{t("isenable-otp-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
{t("yes")}:{" "}
</span>
<span>{t("isenable-otp-help.p2")}</span>
</li>
<li>
<span className="font-bold">
{t("no")}:{" "}
</span>
<span>{t("isenable-otp-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("isenable-otp-help.p4")}</p>
</div>
</Tooltip>
</label>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsEnableOTP"
checked={formData.IsEnableOTP === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"false"}
name="IsEnableOTP"
className="op-radio op-radio-xs"
checked={formData.IsEnableOTP === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
)}
</>
)}
</div>
)}
{props.title !== "Sign Yourself" && (
<div
onClick={() => setIsAdvanceOpt(!isAdvanceOpt)}
className={`mt-2.5 op-link op-link-primary text-sm`}
>
{isAdvanceOpt
? t("hide-advanced-options")
: t("advanced-options")}
</div>
)} )}
<div className="flex items-center mt-3 gap-2"> <div className="flex items-center mt-3 gap-2">
<button <button
+288 -33
View File
@@ -10,17 +10,39 @@ 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: "",
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 +56,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 +67,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 +131,114 @@ 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,
adminEmail: extUser?.TenantId?.EmailAddress
}));
setIsModal((obj) => ({ ...obj, unauthorized: !obj.unauthorized }));
}
}
};
const handleCloseModal = () => {
setIsModal({ generateapi: false, buyapis: false, unauthorized: false });
};
const handlePricePerAPIs = (e) => {
const quantity = e.target?.value;
const price =
quantity > 0
? (Math.round(quantity * amount.priceperapi * 100) / 100).toFixed(2)
: (Math.round(500 * amount.priceperapi * 100) / 100).toFixed(2);
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleAddOnApiSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsFormLoader(true);
try {
const resAddon = await Parse.Cloud.run("buycredits", {
credits: amount.quantity
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setAmount((obj) => ({
...obj,
quantity: 500,
priceperapi: 0.15,
price: (75.0).toFixed(2),
totalcredits: obj?.totalcredits + _resAddon.addon,
addoncredits: obj?.addoncredits + _resAddon.addon
}));
}
}
setIsModal((obj) => ({ ...obj, buyapis: false }));
} catch (err) {
console.log("Err in buy addon", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
setIsFormLoader(false);
}
};
const handleGenerateTestToken = async (e) => {
e.preventDefault();
setIsLoader(true);
// setIsModal((obj) => ({ ...obj, generateapi: false }));
try {
const url = "https://sandbox.opensignlabs.com/api/generatetesttoken";
const headers = {
"Content-Type": "application/json",
token: apitestToken || "1"
};
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const body = {
name: extUser?.Name,
email: extUser?.Email,
phone: extUser?.Phone,
company: extUser?.Company,
jobTitle: extUser?.JobTitle
};
const res = await axios.post(url, body, { headers: headers });
if (res) {
setApiTestToken(res.data?.token);
setIsAlert({ type: "success", msg: t("token-generated") });
} else {
console.error("Error while generating Token");
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
}
} catch (error) {
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
console.log("while generating Token", error);
} finally {
setIsLoader(false);
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
}
};
return ( 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 +249,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 +320,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 +351,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 +389,68 @@ 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", {
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>
+36 -12
View File
@@ -28,6 +28,28 @@ function GuestLogin() {
const [contactId, setContactId] = useState(contactBookId); const [contactId, setContactId] = useState(contactBookId);
const [sendmail, setSendmail] = useState(); const [sendmail, setSendmail] = useState();
const [contact, setContact] = useState({ name: "", phone: "", email: "" }); const [contact, setContact] = useState({ name: "", phone: "", email: "" });
const navigateToDoc = async (docId, contactId) => {
try {
const docDetails = await Parse.Cloud.run("getDocument", {
docId: docId
});
if (!docDetails.error) {
if (sendmail === "false") {
navigate(
`/load/recipientSignPdf/${docId}/${contactId}?sendmail=${sendmail}`
);
} else {
navigate(`/load/recipientSignPdf/${docId}/${contactId}`);
}
return true;
} else {
return false;
}
} catch (err) {
console.log("err while getting doc", err);
return false;
}
};
useEffect(() => { useEffect(() => {
handleServerUrl(); handleServerUrl();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -70,13 +92,14 @@ function GuestLogin() {
"linkcontacttodoc", "linkcontacttodoc",
params params
); );
// console.log("linkContactRes ", linkContactRes);
setContactId(linkContactRes?.contactId); setContactId(linkContactRes?.contactId);
await navigateToDoc(checkSplit[0], linkContactRes?.contactId);
} catch (err) { } catch (err) {
console.log("Err in link ext contact", err); console.log("Err in link ext contact", err);
} }
} else { } else {
setContactId(checkSplit[2]); setContactId(checkSplit[2]);
await navigateToDoc(checkSplit[0], checkSplit[2]);
} }
} }
@@ -114,15 +137,12 @@ function GuestLogin() {
if (OTP) { if (OTP) {
setLoading(true); setLoading(true);
try { try {
let url = `${serverUrl}functions/AuthLoginAsMail/`; let url = `${serverUrl}functions/AuthLoginAsMail`;
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": parseId "X-Parse-Application-Id": parseId
}; };
let body = { let body = { email: email, otp: OTP };
email: 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"));
@@ -140,7 +160,6 @@ function GuestLogin() {
`Parse/${parseId}/currentUser`, `Parse/${parseId}/currentUser`,
JSON.stringify(_user) JSON.stringify(_user)
); );
// console.log("contractUserDetails ", contractUserDetails);
if (contractUserDetails && contractUserDetails.length > 0) { if (contractUserDetails && contractUserDetails.length > 0) {
localStorage.setItem( localStorage.setItem(
"Extand_Class", "Extand_Class",
@@ -174,10 +193,15 @@ function GuestLogin() {
try { try {
setLoading(true); setLoading(true);
const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params); const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params);
// console.log("linkContactRes ", linkContactRes);
setContactId(linkContactRes.contactId); setContactId(linkContactRes.contactId);
setEnterOtp(true); const IsEnableOTP = await navigateToDoc(
await SendOtp(); documentId,
linkContactRes.contactId
);
if (!IsEnableOTP) {
setEnterOtp(true);
await SendOtp();
}
} catch (err) { } catch (err) {
setLoading(false); setLoading(false);
alert(t("something-went-wrong-mssg")); alert(t("something-went-wrong-mssg"));
@@ -208,7 +232,7 @@ function GuestLogin() {
<div className="w-full md:w-[50%] text-base-content"> <div className="w-full md:w-[50%] text-base-content">
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1> <h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
<legend className="text-[12px] text-[#878787] mt-2 mb-1"> <legend className="text-[12px] text-[#878787] mt-2 mb-1">
{t("guest-email-alert")} {t("get-otp-alert")}
</legend> </legend>
<div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 op-card shadow-md"> <div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 op-card shadow-md">
<input <input
@@ -236,7 +260,7 @@ function GuestLogin() {
> >
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1> <h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
<legend className="text-[12px] text-[#878787] mt-2"> <legend className="text-[12px] text-[#878787] mt-2">
{t("get-verification-code-2")} {t("guest-email-alert")}
</legend> </legend>
<div className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 op-card my-2 shadow-md"> <div className="p-[20px] pt-[15px] outline outline-1 outline-slate-300/50 op-card my-2 shadow-md">
<p className="text-sm">{t("enter-verification-code")}</p> <p className="text-sm">{t("enter-verification-code")}</p>
+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
File diff suppressed because it is too large Load Diff
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 {
await Parse?.User?.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
navigate("/");
};
return ( 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");
+197 -156
View File
@@ -4,7 +4,6 @@ import "../styles/signature.css";
import Parse from "parse"; import Parse from "parse";
import Confetti from "react-confetti"; import Confetti from "react-confetti";
import axios from "axios"; import axios from "axios";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage"; import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { DndProvider } from "react-dnd"; import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend"; import { HTML5Backend } from "react-dnd-html5-backend";
@@ -34,25 +33,31 @@ 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";
import Signedby from "../components/pdf/Signedby"; import Signedby from "../components/pdf/Signedby";
import HandleError from "../primitives/HandleError";
import Header from "../components/pdf/PdfHeader"; import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf"; import RenderPdf from "../components/pdf/RenderPdf";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy"; import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Title from "../components/Title"; import Title from "../components/Title";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption"; import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import TextFontSetting from "../components/pdf/TextFontSetting"; import TextFontSetting from "../components/pdf/TextFontSetting";
import VerifyEmail from "../components/pdf/VerifyEmail"; import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom"; import PdfZoom from "../components/pdf/PdfZoom";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
import Loader from "../primitives/Loader";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
//For signYourself inProgress section signer can add sign and complete doc sign. //For signYourself inProgress section signer can add sign and complete doc sign.
function SignYourSelf() { function SignYourSelf() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -123,52 +128,39 @@ 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 [isDownloadModal, setIsDownloadModal] = useState(false);
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 +194,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 +214,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 +231,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 +254,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 +267,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 +352,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 +386,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 +403,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 +418,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 +471,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 +494,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 +556,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 +717,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 +725,7 @@ function SignYourSelf() {
if ( if (
tenantDetails?.CompletionBody && tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject && tenantDetails?.CompletionSubject &&
getIsSubscribe isSubscribe?.isValid
) { ) {
isCustomCompletionMail = true; isCustomCompletionMail = true;
} }
@@ -781,7 +758,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 +770,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 +791,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 +865,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 +894,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 +916,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 +1056,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 +1078,8 @@ function SignYourSelf() {
handleCloseModal(); handleCloseModal();
} }
} }
setFontSize();
setFontColor();
}; };
const handleCloseModal = () => { const handleCloseModal = () => {
setCurrWidgetsDetails({}); setCurrWidgetsDetails({});
@@ -1154,6 +1123,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"} />
@@ -1173,10 +1192,14 @@ function SignYourSelf() {
)} )}
{isCelebration && ( {isCelebration && (
<div className="relative z-[1000]"> <div className="relative z-[1000]">
<Confetti width={window.innerWidth} height={window.innerHeight} /> <Confetti
width={window.innerWidth}
height={window.innerHeight}
recycle={false} // Prevents confetti from repeating
gravity={0.1} // Adjust the gravity to control the speed
/>
</div> </div>
)} )}
<div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300"> <div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300">
{!isEmailVerified && ( {!isEmailVerified && (
<VerifyEmail <VerifyEmail
@@ -1213,14 +1236,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 +1261,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 +1285,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 +1298,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
@@ -1309,6 +1334,7 @@ function SignYourSelf() {
setIsAlert={setIsAlert} setIsAlert={setIsAlert}
extUserId={extUserId} extUserId={extUserId}
activeMailAdapter={activeMailAdapter} activeMailAdapter={activeMailAdapter}
setIsDownloadModal={setIsDownloadModal}
/> />
{/* pdf header which contain funish back button */} {/* pdf header which contain funish back button */}
<Header <Header
@@ -1324,12 +1350,13 @@ function SignYourSelf() {
isSignYourself={true} isSignYourself={true}
setIsEmail={setIsEmail} setIsEmail={setIsEmail}
isCompleted={isCompleted} isCompleted={isCompleted}
handleRotationFun={handleRotationFun}
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
widgetsDetails={xyPostion}
setIsDownloadModal={setIsDownloadModal}
/> />
<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 +1395,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 +1439,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 +1454,17 @@ function SignYourSelf() {
</button> </button>
</div> </div>
</ModalUi> </ModalUi>
<RotateAlert
isRotate={isRotate.status}
setIsRotate={setIsRotate}
handleRemoveWidgets={handleRemoveWidgets}
/>
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal}
pdfDetails={pdfDetails}
isDocId={true}
/>
<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>
+243 -128
View File
@@ -9,8 +9,6 @@ import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd"; import { useDrag, useDrop } from "react-dnd";
import WidgetComponent from "../components/pdf/WidgetComponent"; import WidgetComponent from "../components/pdf/WidgetComponent";
import Tour from "reactour"; import Tour from "reactour";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import SignerListPlace from "../components/pdf/SignerListPlace"; import SignerListPlace from "../components/pdf/SignerListPlace";
import Header from "../components/pdf/PdfHeader"; import Header from "../components/pdf/PdfHeader";
import WidgetNameModal from "../components/pdf/WidgetNameModal"; import WidgetNameModal from "../components/pdf/WidgetNameModal";
@@ -25,22 +23,32 @@ 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";
import Title from "../components/Title"; import Title from "../components/Title";
import LinkUserModal from "../primitives/LinkUserModal";
import EditTemplate from "../components/pdf/EditTemplate"; import EditTemplate from "../components/pdf/EditTemplate";
import ModalUi from "../primitives/ModalUi";
import AddRoleModal from "../components/pdf/AddRoleModal"; import AddRoleModal from "../components/pdf/AddRoleModal";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy"; import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption"; import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import Parse from "parse"; import Parse from "parse";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import PdfZoom from "../components/pdf/PdfZoom"; import PdfZoom from "../components/pdf/PdfZoom";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import LinkUserModal from "../primitives/LinkUserModal";
const TemplatePlaceholder = () => { const TemplatePlaceholder = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -55,12 +63,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 +84,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 +101,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 +192,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 +240,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 +378,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 +441,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 +601,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 +633,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 +651,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 +682,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 +695,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 +758,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 +779,19 @@ 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,
IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false,
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 +915,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 +940,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 +950,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 +959,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 +999,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 +1133,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 +1171,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 +1189,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 +1222,8 @@ const TemplatePlaceholder = () => {
} }
} }
} }
setFontSize();
setFontColor();
}; };
const handleWidgetdefaultdata = (defaultdata) => { const handleWidgetdefaultdata = (defaultdata) => {
@@ -1250,6 +1314,7 @@ const TemplatePlaceholder = () => {
setCurrWidgetsDetails({}); setCurrWidgetsDetails({});
handleNameModal(); handleNameModal();
}; };
const handleNameModal = () => { const handleNameModal = () => {
setIsNameModal(false); setIsNameModal(false);
setCurrWidgetsDetails({}); setCurrWidgetsDetails({});
@@ -1258,6 +1323,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 +1402,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 +1486,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 +1501,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 +1516,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 +1547,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 +1581,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 +1594,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 +1623,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 +1653,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 +1734,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 />
+246 -116
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 { fetchSubscriptionInfo } 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 = [];
@@ -18,7 +19,11 @@ const UserList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [userList, setUserList] = useState([]); const [userList, setUserList] = useState([]);
const [isLoader, setIsLoader] = useState(false); const [isLoader, setIsLoader] = useState(false);
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState({
form: false,
addseats: false,
options: false
});
const location = useLocation(); const location = useLocation();
const isDashboard = const isDashboard =
location?.pathname === "/dashboard/35KBoSgoAK" ? true : false; location?.pathname === "/dashboard/35KBoSgoAK" ? true : false;
@@ -26,8 +31,16 @@ 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,
priceperUser: 0
});
const [isAdmin, setIsAdmin] = useState(false); const [isAdmin, setIsAdmin] = useState(false);
const [formHeader, setFormHeader] = useState(t("add-user"));
const [usersCount, setUserCounts] = useState({ allowed: 0, totalAllowed: 0 });
const [amount, setAmount] = useState({ quantity: 1, price: 0 });
const [isBuyLoader, setIsBuyLoader] = useState(false);
const recordperPage = 10; const recordperPage = 10;
const startIndex = (currentPage - 1) * recordperPage; // user per page const startIndex = (currentPage - 1) * recordperPage; // user per page
@@ -79,10 +92,13 @@ const UserList = () => {
pages.push(lastPageIndex); pages.push(lastPageIndex);
} }
} }
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
@@ -90,13 +106,43 @@ const UserList = () => {
async function fetchUserList() { async function fetchUserList() {
try { try {
setIsLoader(true); setIsLoader(true);
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (isEnableSubscription) { if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribedTeam(); const subscribe = await fetchSubscriptionInfo();
setIsSubscribe(getIsSubscribe); if (subscribe?.plan_code?.includes("team")) {
const isSupAdmin =
subscribe?.adminId && extUser?.objectId === subscribe?.adminId;
setIsSubscribe({
plan: subscribe.plan_code,
isValid: true,
adminId: subscribe.adminId,
priceperUser: subscribe.price,
isSuperAdmin: isSupAdmin
});
setAmount((prev) => ({ ...prev, price: subscribe.price }));
try {
const res = await Parse.Cloud.run("allowedusers");
setUserCounts((obj) => ({
...obj,
allowed: parseInt(res),
totalAllowed: parseInt(subscribe?.totalAllowedUser) || 0
}));
} catch (err) {
console.log("err while get users", err);
}
} else {
setIsSubscribe({
plan: subscribe.plan_code,
isValid: false,
adminId: subscribe.adminId
});
}
} else { } else {
setIsSubscribe(true); setIsSubscribe({ plan: "teams-yearly", isValid: true });
} }
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (extUser) { if (extUser) {
const admin = const admin =
extUser?.UserRole && extUser?.UserRole &&
@@ -115,26 +161,36 @@ const UserList = () => {
console.log("Err in fetch userlist", err); console.log("Err in fetch userlist", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") }); setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally { } finally {
setTimeout(() => { setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
setIsAlert({ type: "success", msg: "" });
}, 1500);
setIsLoader(false); setIsLoader(false);
} }
} }
const handleFormModal = () => { const handleModal = (modalName) => {
setIsModal(!isModal); setIsModal((obj) => ({ ...obj, [modalName]: !obj[modalName] }));
}; };
// 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]); if (userData) {
setUserCounts((obj) => ({
...obj,
allowed: obj?.allowed > 0 ? obj.allowed - 1 : 1
}));
setUserList((prev) => [userData, ...prev]);
}
}; };
// `formatRow` is used to show data in poper manner like // `formatRow` is used to show data in poper manner like
// if data is of array type then it will join array items with "," // if data is of array type then it will join array items with ","
@@ -150,9 +206,8 @@ const UserList = () => {
return "-"; return "-";
} }
}; };
const handleClose = () => { const handleClose = () => setIsActiveModal({});
setIsActiveModal({});
};
const handleToggleSubmit = async (user) => { const handleToggleSubmit = async (user) => {
const index = userList.findIndex((obj) => obj.objectId === user.objectId); const index = userList.findIndex((obj) => obj.objectId === user.objectId);
if (index !== -1) { if (index !== -1) {
@@ -184,6 +239,49 @@ const UserList = () => {
const handleToggleBtn = (user) => { const handleToggleBtn = (user) => {
setIsActiveModal({ [user.objectId]: true }); setIsActiveModal({ [user.objectId]: true });
}; };
const handleAddOnSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsBuyLoader(true);
try {
const resAddon = await Parse.Cloud.run("buyaddonusers", {
users: parseInt(amount.quantity)
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setUserCounts((obj) => ({
...obj,
allowed: parseInt(obj.allowed) + parseInt(amount.quantity),
totalAllowed: parseInt(_resAddon.addon)
}));
setAmount((obj) => ({ ...obj, quantity: 1 }));
}
}
} catch (err) {
console.log("Err in buy addon", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
setIsBuyLoader(false);
handleModal("addseats");
}
};
const handlePricePerUser = (e) => {
const quantity = e.target.value;
const price = e.target?.value > 0 ? isSubscribe.priceperUser * quantity : 0;
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleBuyUsers = (allowed, totalAllowed) => {
if (allowed && totalAllowed) {
setUserCounts((obj) => ({
...obj,
allowed: parseInt(obj.allowed) + parseInt(allowed),
totalAllowed: parseInt(totalAllowed)
}));
}
};
return ( return (
<div className="relative"> <div className="relative">
<Title title={isAdmin ? "Users" : "Page not found"} /> <Title title={isAdmin ? "Users" : "Page not found"} />
@@ -193,34 +291,57 @@ const UserList = () => {
</div> </div>
)} )}
{Object.keys(isActLoader)?.length > 0 && ( {Object.keys(isActLoader)?.length > 0 && (
<div className="absolute w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-30 rounded-box"> <div className="absolute w-full h-full flex justify-center items-center bg-black/30 z-30 rounded-box">
<Loader /> <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")}{" "}
<span className="text-xs md:text-[13px] font-normal"> <span className="text-xs md:text-[13px] font-normal">
<Tooltip message={t("users-from-teams")} /> <Tooltip message={t("users-from-teams")} />
</span> </span>
</div> </div>
<div <div className="flex flex-row gap-2 items-center">
className="cursor-pointer" {isEnableSubscription && isSubscribe?.isSuperAdmin && (
onClick={() => handleFormModal()} <div
> className="hidden md:flex op-btn op-btn-sm h-[35px] op-btn-primary op-btn-outline text-xs mb-0.5"
<i className="fa-light fa-square-plus text-accent text-[40px]"></i> onClick={() => handleModal("addseats")}
>
{t("buy-users")}
</div>
)}
<div
className="cursor-pointer"
onClick={() => handleModal("form")}
>
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
</div>
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
<div
className="cursor-pointer relative md:hidden mb-0.5"
onClick={() => handleModal("options")}
>
<i className="fa-light fa-ellipsis-vertical fa-xl"></i>
{isModal?.options && (
<ul className="absolute -right-3 top-auto z-[70] w-max op-menu op-menu-xs shadow bg-base-100 text-base-content rounded-box border">
<li onClick={() => handleModal("addseats")}>
<span className="text-[13px] capitalize font-medium">
{t("buy-users")}
</span>
</li>
</ul>
)}
</div>
)}
</div> </div>
</div> </div>
<div className={` overflow-x-auto w-full`}> <div className="overflow-x-auto w-full">
<table className="op-table border-collapse w-full"> <table className="op-table border-collapse w-full">
<thead className="text-[14px]"> <thead className="text-[14px]">
<tr className="border-y-[1px]"> <tr className="border-y-[1px]">
@@ -234,7 +355,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 +419,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>
))} ))}
</> </>
@@ -348,34 +426,42 @@ const UserList = () => {
</tbody> </tbody>
</table> </table>
</div> </div>
<div className="op-join flex flex-wrap items-center p-2"> <div className="flex flex-row justify-between items-center text-xs font-medium">
{userList.length > recordperPage && ( <div className="op-join flex flex-wrap items-center p-2">
<button {userList.length > recordperPage && (
onClick={() => paginateBack()} <button
className="op-join-item op-btn op-btn-sm" onClick={() => paginateBack()}
> className="op-join-item op-btn op-btn-sm"
{t("prev")} >
</button> {t("prev")}
)} </button>
{pageNumbers.map((x, i) => ( )}
<button {pageNumbers.map((x, i) => (
key={i} <button
onClick={() => setCurrentPage(x)} key={i}
disabled={x === "..."} onClick={() => setCurrentPage(x)}
className={`${ disabled={x === "..."}
x === currentPage ? "op-btn-active" : "" className={`${
} op-join-item op-btn op-btn-sm`} x === currentPage ? "op-btn-active" : ""
> } op-join-item op-btn op-btn-sm`}
{x} >
</button> {x}
))} </button>
{userList.length > recordperPage && ( ))}
<button {userList.length > recordperPage && (
onClick={() => paginateFront()} <button
className="op-join-item op-btn op-btn-sm" onClick={() => paginateFront()}
> className="op-join-item op-btn op-btn-sm"
{t("next")} >
</button> {t("next")}
</button>
)}
</div>
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
<div>
{t("available-seats")}: {usersCount.allowed}/
{usersCount.totalAllowed}
</div>
)} )}
</div> </div>
{userList?.length <= 0 && ( {userList?.length <= 0 && (
@@ -397,16 +483,60 @@ const UserList = () => {
</div> </div>
)} )}
<ModalUi <ModalUi
title={t("add-user")} isOpen={isModal.form}
isOpen={isModal} title={formHeader}
handleClose={handleFormModal} handleClose={() => handleModal("form")}
> >
<AddUser <AddUser
setIsAlert={setIsAlert} setIsAlert={setIsAlert}
handleUserData={handleUserData} handleUserData={handleUserData}
closePopup={handleFormModal} handleBuyUsers={handleBuyUsers}
closePopup={() => handleModal("form")}
setFormHeader={setFormHeader}
/> />
</ModalUi> </ModalUi>
<ModalUi
isOpen={isModal.addseats}
title="Add Seats"
handleClose={() => handleModal("addseats")}
>
{isBuyLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<form onSubmit={handleAddOnSubmit} className=" px-3 pb-6 pt-6">
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("Quantity-of-users")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="number"
name="quantity"
value={amount.quantity}
onChange={(e) => handlePricePerUser(e)}
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
required
/>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {isSubscribe.priceperUser})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full">
{t("Proceed")}
</button>
</form>
</ModalUi>
</div> </div>
) : ( ) : (
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box"> <div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
@@ -422,9 +552,9 @@ const UserList = () => {
)} )}
</> </>
)} )}
{!isSubscribe && isEnableSubscription && !isLoader && ( {isEnableSubscription && !validplan[isSubscribe.plan] && !isLoader && (
<div data-tut="apisubscribe"> <div data-tut="apisubscribe">
<SubscribeCard plan={"TEAMS"} price={"20"} /> <SubscribeCard plan="TEAMS" />
</div> </div>
)} )}
</div> </div>
+158 -90
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>{" "}
@@ -540,37 +596,30 @@ function UserProfile() {
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm" className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() => copytoclipboard()} onClick={() => copytoclipboard()}
> >
<i className="fa-light fa-link"></i>{" "} <i className="fa-light fa-copy" />
</button> </button>
</div> </div>
</div> </div>
)} )}
</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"));
} }
+43
View File
@@ -0,0 +1,43 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
function Agreement(props) {
const { t } = useTranslation();
const [isChecked, setIsChecked] = useState(false);
return (
<div className="flex justify-between mx-2 mb-2 pt-1" data-tut="IsAgree">
<div className="flex items-center">
<input
className="mr-3 op-checkbox op-checkbox-m"
type="checkbox"
value={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
}}
/>
I confirm that I have read and understood the &apos;Electronic Record
and Signature Disclosure&apos; and consent to use electronic records and
signatures.
</div>
<div className="flex">
<button
onClick={() => {
if (isChecked) {
props.setIsAgreeTour(false);
props.setIsAgree(true);
}
}}
className="op-btn op-btn-primary op-btn-sm"
>
Agree & Continue
</button>
</div>
</div>
);
}
export default Agreement;
+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} flex justify-center`}>
<span>{children}</span> <span className="px-1">{children}</span>
</div> </div>
</div> </div>
) )
@@ -0,0 +1,136 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import ModalUi from "./ModalUi";
import {
getSignedUrl,
handleDownloadCertificate,
handleDownloadPdf,
sanitizeFileName
} from "../constant/Utils";
import Loader from "./Loader";
import JSZip from "jszip";
import { saveAs } from "file-saver";
function DownloadPdfZip(props) {
const { t } = useTranslation();
const [selectType, setSelectType] = useState(1);
const [isDownloading, setIsDownloading] = useState(false);
const downloadType = [
{ id: 1, label: t("download-pdf") },
{ id: 2, label: t("pdf-certificate") }
];
const handleDownload = async () => {
if (selectType === 1) {
await handleDownloadPdf(props.pdfDetails, setIsDownloading);
setSelectType(1);
props.setIsDownloadModal(false);
} else if (selectType === 2) {
setIsDownloading("pdf");
const zip = new JSZip();
const pdfDetails = props.pdfDetails;
const pdfName = pdfDetails?.[0]?.Name || "Document";
const pdfUrl = pdfDetails?.[0]?.SignedUrl || "";
try {
// Fetch the first PDF (Signed Document)
const docId =
props.isDocId && !pdfDetails?.[0]?.IsEnableOTP
? pdfDetails?.[0]?.objectId
: "";
const signedUrl = await getSignedUrl(pdfUrl, docId);
const pdf1Response = await fetch(signedUrl);
if (!pdf1Response.ok) {
throw new Error(`Failed to fetch PDF: ${signedUrl}`);
}
const pdf1Blob = await pdf1Response.blob();
const isZip = true;
// Fetch the Certificate (or generate its URL dynamically)
const certificateUrl = await handleDownloadCertificate(
pdfDetails,
setIsDownloading,
isZip
);
const pdf2Response = await fetch(certificateUrl);
if (!pdf2Response.ok) {
throw new Error(`Failed to fetch certificate PDF: ${certificateUrl}`);
}
const pdf2Blob = await pdf2Response.blob();
// Add files to ZIP
zip.file(
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`,
pdf1Blob
);
zip.file("Certificate_signed_by_OpenSign™.pdf", pdf2Blob);
// Generate the ZIP and trigger download
const zipBlob = await zip.generateAsync({ type: "blob" });
saveAs(
zipBlob,
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.zip`
);
setSelectType(1);
props.setIsDownloadModal(false);
setIsDownloading("");
} catch (error) {
console.error("Error creating ZIP file:", error);
}
}
};
return (
<ModalUi
isOpen={props.isDownloadModal}
title={t("download-files")}
handleClose={() => props.setIsDownloadModal(false)}
>
<div className="p-[20px] h-full">
{downloadType.map((data, ind) => {
return (
<label
key={ind}
className="flex items-center gap-1 mb-2 cursor-pointer"
>
<input
className="mr-[8px] op-radio op-radio-xs"
type="radio"
value={data.id}
onChange={() => setSelectType(data.id)}
checked={selectType === data.id}
/>
{data.label}
</label>
);
})}
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<button
onClick={() => handleDownload()}
type="submit"
className="op-btn op-btn-primary"
>
{t("download")}
</button>
</div>
{isDownloading === "pdf" && (
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<ModalUi
isOpen={isDownloading === "certificate"}
title={
isDownloading === "certificate"
? t("generating-certificate")
: t("pdf-download")
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
{isDownloading === "certificate"}{" "}
<p>{t("generate-certificate-alert")}</p>
</div>
</ModalUi>
</ModalUi>
);
}
export default DownloadPdfZip;
+421 -220
View File
@@ -11,9 +11,10 @@ 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,
getSignedUrl,
replaceMailVaribles replaceMailVaribles
} from "../constant/Utils"; } from "../constant/Utils";
import Confetti from "react-confetti"; import Confetti from "react-confetti";
@@ -27,8 +28,11 @@ 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";
import DownloadPdfZip from "./DownloadPdfZip";
import EmbedTab from "../components/pdf/EmbedTab";
const ReportTable = (props) => { const ReportTable = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -68,20 +72,15 @@ const ReportTable = (props) => {
const [publicUserName, setIsPublicUserName] = useState(""); const [publicUserName, setIsPublicUserName] = useState("");
const [isViewShare, setIsViewShare] = useState({}); const [isViewShare, setIsViewShare] = useState({});
const [isSubscribe, setIsSubscribe] = useState(true); const [isSubscribe, setIsSubscribe] = useState(true);
const [isModal, setIsModal] = useState({});
const [reason, setReason] = useState(""); const [reason, setReason] = useState("");
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [isEmbed, setIsEmbed] = useState(false);
const Extand_Class = localStorage.getItem("Extand_Class"); const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class); const extClass = Extand_Class && JSON.parse(Extand_Class);
const startIndex = (currentPage - 1) * props.docPerPage; const startIndex = (currentPage - 1) * props.docPerPage;
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 = [];
@@ -273,7 +272,8 @@ const ReportTable = (props) => {
Signers: signers, Signers: signers,
SendinOrder: Doc?.SendinOrder || false, SendinOrder: Doc?.SendinOrder || false,
AutomaticReminders: Doc?.AutomaticReminders || false, AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5 RemindOnceInEvery: Doc?.RemindOnceInEvery || 5,
IsEnableOTP: Doc?.IsEnableOTP || false
}; };
try { try {
const res = await axios.post( const res = await axios.post(
@@ -355,10 +355,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 +369,10 @@ const ReportTable = (props) => {
setSelectedTeam(formatedList); setSelectedTeam(formatedList);
} }
setIsShareWith({ [item.objectId]: true }); setIsShareWith({ [item.objectId]: true });
} else if (act.action === "Embed") {
handleEmbedFunction(item);
} else if (act.action === "CopyTemplateId") {
copyTemplateId(item.objectId);
} }
}; };
// Get current list // Get current list
@@ -377,8 +381,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 +410,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 +462,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 +492,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/${
@@ -499,7 +524,6 @@ const ReportTable = (props) => {
.then(async (result) => { .then(async (result) => {
const res = result.data; const res = result.data;
if (res) { if (res) {
setReason("");
setActLoader({}); setActLoader({});
setIsAlert(true); setIsAlert(true);
setAlertMsg({ setAlertMsg({
@@ -511,7 +535,43 @@ const ReportTable = (props) => {
(x) => x.objectId !== item.objectId (x) => x.objectId !== item.objectId
); );
props.setList(upldatedList); props.setList(upldatedList);
const params = {
event: "declined",
body: {
objectId: item.objectId,
file: item?.SignedUrl || item?.URL,
name: item?.Name,
note: item?.Note || "",
description: item?.Description || "",
signers: item?.Signers?.map((x) => ({
name: x?.Name,
email: x?.Email,
phone: x?.Phone
})),
declinedBy: jsonSender?.email,
declinedReason: reason,
declinedAt: new Date(),
createdAt: item?.createdAt
}
};
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/callwebhook`,
params,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessiontoken: localStorage.getItem("accesstoken")
}
}
);
} catch (err) {
console.log("Err ", err);
}
} }
setReason("");
}) })
.catch((err) => { .catch((err) => {
console.log("err", err); console.log("err", err);
@@ -596,10 +656,15 @@ const ReportTable = (props) => {
setActLoader({ [`${item.objectId}`]: true }); setActLoader({ [`${item.objectId}`]: true });
const url = item?.SignedUrl || item?.URL || ""; const url = item?.SignedUrl || item?.URL || "";
const pdfName = item?.Name || "exported_file"; const pdfName = item?.Name || "exported_file";
const isCompleted = item?.IsCompleted || false;
if (url) { if (url) {
try { try {
const signedUrl = await Parse.Cloud.run("getsignedurl", { url: url }); if (isCompleted) {
await fetchUrl(signedUrl, pdfName); setIsDownloadModal({ [item.objectId]: true });
} else {
const signedUrl = await getSignedUrl(url);
await fetchUrl(signedUrl, pdfName);
}
setActLoader({}); setActLoader({});
} catch (err) { } catch (err) {
console.log("err in getsignedurl", err); console.log("err in getsignedurl", err);
@@ -725,7 +790,7 @@ const ReportTable = (props) => {
sessionToken: localStorage.getItem("accesstoken") sessionToken: localStorage.getItem("accesstoken")
}; };
let params = { let params = {
mailProvider: doc?.ExtUserPtr?.active_mail_adapter, mailProvider: doc?.ExtUserPtr?.active_mail_adapter || "",
extUserId: doc?.ExtUserPtr?.objectId, extUserId: doc?.ExtUserPtr?.objectId,
recipient: userDetails?.Email, recipient: userDetails?.Email,
subject: mail.subject, subject: mail.subject,
@@ -790,13 +855,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 +903,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 +1066,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,6 +1124,14 @@ const ReportTable = (props) => {
setTimeout(() => setIsAlert(false), 1500); setTimeout(() => setIsAlert(false), 1500);
} }
}; };
const handleEmbedFunction = async (item) => {
setIsEmbed(true);
setIsPublicProfile({
[item.objectId]: true
});
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
setIsPublicUserName(extendUser[0]?.UserName || "");
};
return ( return (
<div className="relative"> <div className="relative">
@@ -1054,10 +1140,15 @@ const ReportTable = (props) => {
<Loader /> <Loader />
</div> </div>
)} )}
<div className="p-2 w-full overflow-auto bg-base-100 text-base-content op-card shadow-lg"> <div className="p-2 w-full overflow-hidden bg-base-100 text-base-content op-card shadow-lg">
{isCelebration && ( {isCelebration && (
<div className="relative z-[1000]"> <div className="relative z-[1000]">
<Confetti width={window.innerWidth} height={window.innerHeight} /> <Confetti
width={window.innerWidth}
height={window.innerHeight}
recycle={false} // Prevents confetti from repeating
gravity={0.1} // Adjust the gravity to control the speed
/>
</div> </div>
)} )}
{isAlert && <Alert type={alertMsg.type}>{alertMsg.message}</Alert>} {isAlert && <Alert type={alertMsg.type}>{alertMsg.message}</Alert>}
@@ -1097,8 +1188,8 @@ const ReportTable = (props) => {
</div> </div>
<div <div
className={`${ className={`${
isDashboard && props.List?.length > 0 ? "h-[317px]" : "h-full" isDashboard && props.List?.length > 0 ? "min-h-[317px]" : "h-full"
} w-full`} } overflow-auto w-full`}
> >
<table className="op-table border-collapse w-full "> <table className="op-table border-collapse w-full ">
<thead className="text-[14px]"> <thead className="text-[14px]">
@@ -1134,48 +1225,50 @@ const ReportTable = (props) => {
</td> </td>
<td className="px-4 py-2 ">{item?.Email || "-"}</td> <td className="px-4 py-2 ">{item?.Email || "-"}</td>
<td className="px-4 py-2">{item?.Phone || "-"}</td> <td className="px-4 py-2">{item?.Phone || "-"}</td>
<td className="px-3 py-2 text-white grid grid-cols-2"> <td className="px-3 py-2">
{props.actions?.length > 0 && <div className="text-base-content min-w-max flex flex-row gap-x-2 gap-y-1 justify-start items-center">
props.actions.map((act, index) => ( {props.actions?.length > 0 &&
<button props.actions.map((act, index) => (
key={index} <button
onClick={() => handleActionBtn(act, item)} key={index}
title={t(`btnLabel.${act.hoverLabel}`)} onClick={() => handleActionBtn(act, item)}
className={`${ title={t(`btnLabel.${act.hoverLabel}`)}
act?.btnColor ? act.btnColor : "" className={`${
} op-btn op-btn-sm`} act?.btnColor ? act.btnColor : ""
} op-btn op-btn-sm`}
>
<i className={act.btnIcon}></i>
</button>
))}
{isDeleteModal[item.objectId] && (
<ModalUi
isOpen
title={"Delete Contact"}
handleClose={handleClose}
> >
<i className={act.btnIcon}></i> <div className="m-[20px]">
</button> <div className="text-lg font-normal text-black">
))} {t("contact-delete-alert")}
{isDeleteModal[item.objectId] && ( </div>
<ModalUi <hr className="bg-[#ccc] mt-4 " />
isOpen <div className="flex items-center mt-3 gap-2 text-white">
title={"Delete Contact"} <button
handleClose={handleClose} onClick={() => handleDelete(item)}
> className="op-btn op-btn-primary"
<div className="m-[20px]"> >
<div className="text-lg font-normal text-black"> {t("yes")}
{t("contact-delete-alert")} </button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div> </div>
<hr className="bg-[#ccc] mt-4 " /> </ModalUi>
<div className="flex items-center mt-3 gap-2 text-white"> )}
<button </div>
onClick={() => handleDelete(item)}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
)}
</td> </td>
</tr> </tr>
) : ( ) : (
@@ -1217,11 +1310,57 @@ const ReportTable = (props) => {
{item?.URL ? t("download") : "-"} {item?.URL ? t("download") : "-"}
</button> </button>
</td> </td>
{props.ReportName === "In-progress documents" ? (
<td className="px-4 py-2">
<button
onClick={() =>
item?.AuditTrail?.length > 0 &&
setIsModal({ [item?.objectId]: true })
}
className={`${
item?.AuditTrail?.length
? "border-green-400"
: "cursor-default op-border-primary"
} focus:outline-none w-[60px] border-[2px] text-[12px] rounded-full md:self-center`}
>
{item?.AuditTrail?.length ? "VIEWED" : "SENT"}
</button>
{isModal[item.objectId] && (
<ModalUi
isOpen
title={t("document-logs")}
handleClose={() => setIsModal({})}
>
{item?.AuditTrail?.map((x, i) => (
<div
key={i}
className="pl-3 first:mt-2 text-sm font-medium flex flex-col md:flex-row items-start md:gap-4 border-t-[1px] border-gray-600"
>
<div className="py-2 break-all font-bold md:text-[12px] md:col-span-2 w-full md:w-[210px]">
{x?.UserPtr?.Email || "-"}
</div>
<button className="px-2 cursor-default border-[2px] text-[12px] border-green-400 rounded-full md:self-center">
{x?.Activity?.toUpperCase() || "-"}
</button>
<div className=" text-[12px] py-2">
{x?.Activity === "Signed"
? new Date(x?.SignedOn)?.toUTCString()
: new Date(
x?.ViewedOn
)?.toUTCString() || "-"}
</div>
</div>
))}
</ModalUi>
)}
</td>
) : (
<td className="px-4 py-2">
{formatRow(item?.ExtUserPtr)}
</td>
)}
<td className="px-4 py-2"> <td className="px-4 py-2">
{formatRow(item?.ExtUserPtr)} {!item?.IsSignyourself && item?.Placeholders ? (
</td>
<td className="px-4 py-2">
{item?.Placeholders ? (
<button <button
onClick={() => handleViewSigners(item)} onClick={() => handleViewSigners(item)}
className="op-link op-link-primary" className="op-link op-link-primary"
@@ -1237,11 +1376,7 @@ const ReportTable = (props) => {
<td className=" pl-[20px] py-2"> <td className=" pl-[20px] py-2">
{props.ReportName === "Templates" && ( {props.ReportName === "Templates" && (
<div className="flex flex-row"> <div className="flex flex-row">
<label <label className="cursor-pointer relative inline-flex items-center mb-0">
className={
"cursor-pointer relative inline-flex items-center mb-0"
}
>
<input <input
checked={props.isPublic?.[item.objectId]} checked={props.isPublic?.[item.objectId]}
onChange={(e) => onChange={(e) =>
@@ -1319,23 +1454,62 @@ const ReportTable = (props) => {
title={t("public-url")} title={t("public-url")}
handleClose={() => { handleClose={() => {
setIsPublicProfile({}); setIsPublicProfile({});
setIsEmbed(false);
}} }}
> >
<div className="m-[20px]"> <div className="m-[20px]">
{publicUserName ? ( {isEmbed && !item?.IsPublic ? (
<>
<p>{t("public-template-mssg-6")}</p>
<EmbedTab templateId={item.objectId} />
</>
) : publicUserName ? (
<div className="font-normal text-black"> <div className="font-normal text-black">
<p>{t("public-url-copy-mssg")}</p> <span>{t("public-url-copy")}</span>
<div className=" flex items-center gap-3 mt-2 p-[2px] "> <div className=" flex items-center justify-between gap-3 p-[2px] ">
<span className="font-bold"> <div className="w-[280px] whitespace-nowrap overflow-hidden text-ellipsis">
{t("public-url")}: <span
</span> onClick={() =>
<span copytoProfileLink()
onClick={() => copytoProfileLink()} }
className="underline underline-offset-2 w-[150px] md:w-[350px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer" className="ml-[2px] underline underline-offset-2 cursor-pointer text-blue-800"
> >
{publicUrl()} {publicUrl()}
</span> </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-copy" />
<span className="hidden md:inline-block">
{copied["publicprofile"]
? t("copied")
: t("copy")}
</span>
</button>
</div>
</div> </div>
<p className="text-[13px] mt-[5px]">
{t("public-url-copy-mssg")}
</p>
<EmbedTab templateId={item.objectId} />
</div> </div>
) : ( ) : (
<div className="font-normal text-black"> <div className="font-normal text-black">
@@ -1354,7 +1528,7 @@ const ReportTable = (props) => {
</td> </td>
)} )}
<td className="px-2 py-2"> <td className="px-2 py-2">
<div className="text-base-content flex flex-row gap-x-2 gap-y-1 justify-start items-center"> <div className="text-base-content min-w-max flex flex-row gap-x-2 gap-y-1 justify-start items-center">
{props.actions?.length > 0 && {props.actions?.length > 0 &&
props.actions.map((act, index) => props.actions.map((act, index) =>
props.ReportName === "Templates" ? ( props.ReportName === "Templates" ? (
@@ -1383,7 +1557,13 @@ 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] &&
@@ -1478,114 +1658,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 +1806,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 +1861,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>
) : ( ) : (
@@ -1723,7 +1916,7 @@ const ReportTable = (props) => {
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm" className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() => copytoclipboard(share)} onClick={() => copytoclipboard(share)}
> >
<i className="fa-light fa-link"></i>{" "} <i className="fa-light fa-copy" />
{copied[share.email] {copied[share.email]
? t("copied") ? t("copied")
: t("copy")} : t("copy")}
@@ -1877,6 +2070,14 @@ const ReportTable = (props) => {
</div> </div>
</ModalUi> </ModalUi>
)} )}
{isDownloadModal[item.objectId] && (
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal[item.objectId]}
pdfDetails={[item]}
isDocId={false}
/>
)}
</td> </td>
</tr> </tr>
) )
+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={() => {
props.declineDoc(reason);
setReason(""); setReason("");
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,7 +13,18 @@ export default function TourContentWithBtn({ message, isChecked }) {
return ( return (
<div> <div>
<p>{message}</p> <p>{message}</p>
<label className="flex items-center justify-center mb-0"> {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 mt-3 mb-0">
<input <input
type="checkbox" type="checkbox"
className="op-checkbox op-checkbox-xs mr-1" className="op-checkbox op-checkbox-xs mr-1"
+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
});
+19 -10
View File
@@ -11,28 +11,37 @@ import { isPublicStaging } from "../constant/const";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.mjs`; pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.mjs`;
// Wrapper component to manage props // Wrapper component to manage props
const PublicScriptFileWrapper = ({ initialProps }) => { const PublicScriptFileWrapper = () => {
const [props, setProps] = useState(initialProps); const [props, setProps] = useState("");
// Make the setProps function globally accessible
useEffect(() => { useEffect(() => {
// Assigns the function 'setProps' to the global 'window' object as 'updatePublicTemplateProps'.
// This allows 'setProps' to be accessed and called globally across different scripts or components,
// enabling dynamic updates to the public template's properties.
window.updatePublicTemplateProps = setProps; window.updatePublicTemplateProps = setProps;
const publicScript = document.getElementById("opensign-script");
// condition for using plan js load opensign-script then get templateId
if (publicScript) {
// Get the custom templateId attribute
const templateId = publicScript.getAttribute("templateId");
if (templateId) {
setProps({ templateId: templateId });
} else {
const error = "error: TemplateId is missing";
throw new Error(error);
}
}
return () => { return () => {
window.updatePublicTemplateProps = undefined; window.updatePublicTemplateProps = undefined;
}; };
}, []); }, []);
return <PdfRequestFiles {...props} />; return <PdfRequestFiles {...props} />;
}; };
const appId = isPublicStaging ? "opensign" : "legadranaxn"; const appId = isPublicStaging ? "opensign" : "legadranaxn";
//"opensign"
const serverUrl = isPublicStaging const serverUrl = isPublicStaging
? "https://staging-app.opensignlabs.com/api/app" ? "https://staging-app.opensignlabs.com/api/app"
: "https://app.opensignlabs.com/api/app"; : "https://app.opensignlabs.com/api/app";
// "https://staging-app.opensignlabs.com/api/app"
localStorage.setItem("baseUrl", `${serverUrl}/`); localStorage.setItem("baseUrl", `${serverUrl}/`);
localStorage.setItem("parseAppId", appId); localStorage.setItem("parseAppId", appId);
@@ -56,9 +65,9 @@ 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 />
</Provider> </Provider>
</div> </div>
); );
@@ -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,9 @@
"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",
"Copy TemplateId":"Copy TemplateId"
}, },
"report-heading": { "report-heading": {
"Sr.No": "Sr.No", "Sr.No": "Sr.No",
@@ -145,7 +152,8 @@
"Name": "Name", "Name": "Name",
"Status": "Status", "Status": "Status",
"created-date": "Created Date", "created-date": "Created Date",
"Type": "Type" "Type": "Type",
"Logs": "Logs"
}, },
"report-help": { "report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.", "Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -212,8 +220,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",
@@ -257,7 +265,8 @@
"make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.", "make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.",
"public-role": "Public role", "public-role": "Public role",
"public-url": "Public URL", "public-url": "Public URL",
"public-url-copy-mssg": " Heres your public URL: Copy it or share it with the signer, and you will be able to see all your publicly set templates.", "public-url-copy": "Heres your public URL: ",
"public-url-copy-mssg":"Copy it or share it with the signer, and you will be able to see all your publicly set templates.",
"add-public-url-alert": "Please add your public URL, and you will be able to make a public template.", "add-public-url-alert": "Please add your public URL, and you will be able to make a public template.",
"share-with-alert": "You cannot share a template if any roles already have contacts assigned. Please remove all contact assignments from the roles before sharing the template.", "share-with-alert": "You cannot share a template if any roles already have contacts assigned. Please remove all contact assignments from the roles before sharing the template.",
"share-with": "Share with", "share-with": "Share with",
@@ -308,7 +317,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 +367,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 +377,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 +455,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 +478,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 +534,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 +560,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.",
@@ -565,9 +575,10 @@
"pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .", "pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .",
"placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.", "placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.",
"placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.", "placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.",
"placeholder-sign-3": "Drag or click on a field to add it to the document.", "placeholder-sign-3": "Click this button to add more recipients/signers to the document.",
"placeholder-sign-4": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.", "placeholder-sign-4": "Drag or click on a field to add it to the document.",
"placeholder-sign-5": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.", "placeholder-sign-5": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-6": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like HR Manager and New Employee. Each time you use the template, you can assign the New Employee role to different incoming staff members, while the HR Manager role remains constant, facilitating a seamless onboarding process for each recruit. ", "report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like HR Manager and New Employee. Each time you use the template, you can assign the New Employee role to different incoming staff members, while the HR Manager role remains constant, facilitating a seamless onboarding process for each recruit. ",
"redirect": "Click the 'Use' button to create a new document from an existing template.", "redirect": "Click the 'Use' button to create a new document from an existing template.",
"bulksend": "To quickly create and send multiple documents using an existing template, click the 'Quick Send' button.", "bulksend": "To quickly create and send multiple documents using an existing template, click the 'Quick Send' button.",
@@ -584,13 +595,78 @@
"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!",
"expired-doc-title": "Expired Document", "expired-doc-title": "Expired Document",
"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.",
"reason":"Reason",
"decline-by":"Declined/revoked by",
"document-declined": "Document declined",
"public-template-mssg-1":"To integrate OpenSign into your React or Next.js project, simply run the following command:",
"public-template-mssg-2" :"Ensure you have npm or yarn set up in your project. If youre using Yarn, you can replace npm install with yarn add @opensign/react.",
"public-template-mssg-3" :"Need more details or examples?",
"public-template-mssg-4": "Visit the",
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
"public-template-mssg-6" :"You need to set this template as public before you can utilize this code snippet.",
"copy-code":"COPY",
"copied-code":"COPIED",
"Installation":"Installation",
"Usage" :"Usage",
"insufficient-credits":"Insufficient Signing Credits",
"insufficient-credits-mssg":"The owner of this document currently lacks the necessary OpenSign credits for you to sign. Please reach out to the owner if you require further details.",
"angular-npm-mssg-1": "To integrate OpenSign into your Angular project, simply run the following command:",
"quota-mail-info-head":"Monthly request signatures email limit",
"quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.",
"quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.",
"quota-mail-tip":"Tip: You can still sign <1>unlimited documents</1> by manually sharing the signing request links.",
"quota-mail-head":"Quota Reached",
"unauthorized-modal":"You don't have permission to perform this action, please contact {{adminEmail}}.",
"sent-this-month":"Sent this month",
"available-seats":"Available seats",
"buy-users":"Buy more users",
"isenable-otp": "Enable OTP verification",
"isenable-otp-help": {
"p1": "Would you like to enable the verification process using a one-time password (OTP)?",
"p2": "Selecting this option will enable OTP verification. Users will receive a verification code via email, which they must enter to sign the document.",
"p3": "Selecting this option will disable OTP verification, allowing users to sign the document directly without additional steps.",
"p4": "Please choose the option that best suits your document signing requirements."
},
"advanced-options":"Advanced options",
"hide-advanced-options":"Hide Advanced options",
"download-files":"Download files",
"download-pdf":"Download Pdf",
"pdf-certificate":"Download Pdf + Certificate",
"document-logs":"Document logs"
} }
@@ -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"
} }
}, },
@@ -131,7 +136,8 @@
"Name": "Nom", "Name": "Nom",
"Status": "Statut", "Status": "Statut",
"created-date": "Date de création", "created-date": "Date de création",
"Type": "Saisir" "Type": "Saisir",
"Logs": "Journaux"
}, },
"btnLabel": { "btnLabel": {
"sign": "Signer", "sign": "Signer",
@@ -144,7 +150,9 @@
"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",
"Copy TemplateId": "Copier l'ID du modèle"
}, },
"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 +205,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 +219,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 +260,12 @@
"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": "Voici votre URL publique : ",
"public-url-copy-mssg": "copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
"add-public-url-alert": "Veuillez 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 +283,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 +316,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 +346,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 +366,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 +376,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 +403,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 +419,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 +434,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 +454,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 +469,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 +521,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 +556,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.",
@@ -564,9 +574,10 @@
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.", "pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.", "placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.",
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.", "placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
"placeholder-sign-3": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.", "placeholder-sign-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
"placeholder-sign-4": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.", "placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"placeholder-sign-5": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.", "placeholder-sign-5": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
"placeholder-sign-6": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue", "report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.", "redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
"bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.", "bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.",
@@ -583,7 +594,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 +603,70 @@
"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.",
"reason" :"Raison",
"decline-by" :"Refusé/révoqué par",
"document-declined":"document refusé",
"public-template-mssg-1" :"Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
"public-template-mssg-2" :"Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
"public-template-mssg-3" :"Besoin de plus de détails ou d'exemples ?",
"public-template-mssg-4": "Visitez le",
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
"public-template-mssg-6":"Vous devez définir ce modèle comme public avant de pouvoir utiliser cet extrait de code.",
"copy-code":"COPIE",
"copied-code":"COPIÉ",
"Installation":"Installation",
"Usage" :"Usage",
"insufficient-credits":"Crédits de signature insuffisants",
"insufficient-credits-mssg" :"Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
"angular-npm-mssg-1" :"Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
"quota-mail-info-head":"Limite mensuelle d'e-mails de signatures de demandes",
"quota-mail-info": "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-tip":"Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
"quota-mail-head":"Quota atteint",
"unauthorized-modal":"Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
"sent-this-month":"envoyé ce mois-ci",
"available-seats":"Disponible sièges",
"buy-users":"Acheter plus d'utilisateurs",
"isenable-otp": "Activer la vérification OTP",
"isenable-otp-help": {
"p1": "Souhaitez-vous activer le processus de vérification à l'aide d'un mot de passe à usage unique (OTP)?",
"p2": "La sélection de cette option activera la vérification OTP. Les utilisateurs recevront un code de vérification par e-mail, qu'ils devront saisir pour signer le document.",
"p3": "La sélection de cette option désactivera la vérification OTP, permettant aux utilisateurs de signer le document directement sans étapes supplémentaires.",
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
},
"advanced-options":"Options avancées",
"hide-advanced-options": "Masquer les options avancées",
"download-files" :"Télécharger des fichiers",
"download-pdf" :"Télécharger le PDF",
"pdf-certificate" :"Télécharger Pdf + Certificat",
"document-logs":"Journaux de documents"
} }
+59 -35
View File
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
dotenv.config(); dotenv.config();
export const cloudServerUrl = 'http://localhost:8080/app'; export const cloudServerUrl = 'http://localhost:8080/app';
export const appName = process.env.APP_NAME || 'OpenSign™';
export function customAPIurl() { export function customAPIurl() {
const url = new URL(cloudServerUrl); const url = new URL(cloudServerUrl);
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin; return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
@@ -42,36 +43,38 @@ export function replaceMailVaribles(subject, body, variables) {
export const saveFileUsage = async (size, fileUrl, userId) => { export const saveFileUsage = async (size, fileUrl, userId) => {
//checking server url and save file's size //checking server url and save file's size
try { try {
const tenantQuery = new Parse.Query('partners_Tenant'); if (userId) {
tenantQuery.equalTo('UserId', { const tenantQuery = new Parse.Query('partners_Tenant');
__type: 'Pointer', tenantQuery.equalTo('UserId', {
className: '_User', __type: 'Pointer',
objectId: userId, className: '_User',
}); objectId: userId,
const tenant = await tenantQuery.first(); });
if (tenant) { const tenant = await tenantQuery.first();
const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id }; if (tenant) {
try { const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id };
const tenantCredits = new Parse.Query('partners_TenantCredits'); try {
tenantCredits.equalTo('PartnersTenant', tenantPtr); const tenantCredits = new Parse.Query('partners_TenantCredits');
const res = await tenantCredits.first({ useMasterKey: true }); tenantCredits.equalTo('PartnersTenant', tenantPtr);
if (res) { const res = await tenantCredits.first({ useMasterKey: true });
const response = JSON.parse(JSON.stringify(res)); if (res) {
const usedStorage = response?.usedStorage ? response.usedStorage + size : size; const response = JSON.parse(JSON.stringify(res));
const updateCredit = new Parse.Object('partners_TenantCredits'); const usedStorage = response?.usedStorage ? response.usedStorage + size : size;
updateCredit.id = res.id; const updateCredit = new Parse.Object('partners_TenantCredits');
updateCredit.set('usedStorage', usedStorage); updateCredit.id = res.id;
await updateCredit.save(null, { useMasterKey: true }); updateCredit.set('usedStorage', usedStorage);
} else { await updateCredit.save(null, { useMasterKey: true });
const newCredit = new Parse.Object('partners_TenantCredits'); } else {
newCredit.set('usedStorage', size); const newCredit = new Parse.Object('partners_TenantCredits');
newCredit.set('PartnersTenant', tenantPtr); newCredit.set('usedStorage', size);
await newCredit.save(null, { useMasterKey: true }); newCredit.set('PartnersTenant', tenantPtr);
await newCredit.save(null, { useMasterKey: true });
}
} catch (err) {
console.log('err in save usage', err);
} }
} catch (err) { saveDataFile(size, fileUrl, tenantPtr);
console.log('err in save usage', err);
} }
saveDataFile(size, fileUrl, tenantPtr);
} }
} catch (err) { } catch (err) {
console.log('err in fetch tenant Id', err); console.log('err in fetch tenant Id', err);
@@ -91,7 +94,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 +103,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 +223,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,
};
@@ -8,6 +8,7 @@ import saveInvoice from './saveInvoice.js';
import savePayments from './savePayments.js'; import savePayments from './savePayments.js';
import gooogleauth from './googleauth.js'; import gooogleauth from './googleauth.js';
import autoReminder from './autoReminder.js'; import autoReminder from './autoReminder.js';
import validateSmtp from './validateSmtp.js';
export const app = express(); export const app = express();
dotenv.config(); dotenv.config();
@@ -22,3 +23,4 @@ app.post('/saveinvoice', saveInvoice);
app.post('/savepayment', savePayments); app.post('/savepayment', savePayments);
app.post('/googleauth', gooogleauth); app.post('/googleauth', gooogleauth);
app.post('/sendreminder', autoReminder); app.post('/sendreminder', autoReminder);
app.post('/validatesmtp', validateSmtp);
@@ -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,348 @@ 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); const enableOTP = request.body?.enableOTP === true ? true : false;
object.set('SentToOthers', true); const IsEnableOTP = enableOTP || template?.IsEnableOTP || false;
if (TimeToCompleteDays) { object.set('IsEnableOTP', IsEnableOTP);
object.set('TimeToCompleteDays', TimeToCompleteDays); object.set('CreatedBy', template.CreatedBy);
} object.set('ExtUserPtr', {
object.set('CreatedBy', template.CreatedBy); __type: 'Pointer',
object.set('ExtUserPtr', { className: 'contracts_Users',
__type: 'Pointer', objectId: template.ExtUserPtr.objectId,
className: 'contracts_Users', });
objectId: template.ExtUserPtr.objectId, if (folderId) {
}); object.set('Folder', folderPtr);
if (folderId) { }
object.set('Folder', folderPtr); const newACL = new Parse.ACL();
} newACL.setPublicReadAccess(false);
const newACL = new Parse.ACL(); newACL.setPublicWriteAccess(false);
newACL.setPublicReadAccess(false); newACL.setReadAccess(userPtr.objectId, true);
newACL.setPublicWriteAccess(false); newACL.setWriteAccess(userPtr.objectId, true);
newACL.setReadAccess(userPtr.objectId, true); object.setACL(newACL);
newACL.setWriteAccess(userPtr.objectId, true); const res = await object.save(null, { useMasterKey: 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,
mailProvider: template?.ExtUserPtr?.active_mail_adapter || '',
};
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);
@@ -68,6 +63,7 @@ export default async function createDocumentwithCoordinate(request, response) {
const email_body = request.body.email_body; const email_body = request.body.email_body;
const sendInOrder = request.body.sendInOrder || false; const sendInOrder = request.body.sendInOrder || false;
const TimeToCompleteDays = request.body.timeToCompleteDays || 15; const TimeToCompleteDays = request.body.timeToCompleteDays || 15;
const IsEnableOTP = request.body?.enableOTP === true ? true : false;
// console.log('fileData ', fileData); // console.log('fileData ', fileData);
const protocol = customAPIurl(); const protocol = customAPIurl();
const baseUrl = new URL(process.env.PUBLIC_URL); const baseUrl = new URL(process.env.PUBLIC_URL);
@@ -89,309 +85,350 @@ 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('IsEnableOTP', IsEnableOTP);
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,
mailProvider: parseExtUser?.active_mail_adapter || '',
};
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!' });
@@ -9,6 +9,7 @@ export default async function createTemplate(request, response) {
const SendinOrder = request.body.sendInOrder || false; const SendinOrder = request.body.sendInOrder || false;
const fileData = request.files?.[0] ? request.files[0].buffer : null; const fileData = request.files?.[0] ? request.files[0].buffer : null;
const baseUrl = new URL(process.env.PUBLIC_URL); const baseUrl = new URL(process.env.PUBLIC_URL);
const isEnableOTP = request.body?.enableOTP === true ? true : false;
try { try {
const reqToken = request.headers['x-api-token']; const reqToken = request.headers['x-api-token'];
@@ -65,6 +66,7 @@ export default async function createTemplate(request, response) {
object.set('URL', fileUrl); object.set('URL', fileUrl);
object.set('CreatedBy', userPtr); object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr); object.set('ExtUserPtr', extUserPtr);
object.set('IsEnableOTP', isEnableOTP);
if (SendinOrder) { if (SendinOrder) {
object.set('SendinOrder', SendinOrder); object.set('SendinOrder', SendinOrder);
} }
@@ -17,6 +17,8 @@ export default async function createTemplatewithCoordinate(request, response) {
const base64File = request.body.file; const base64File = request.body.file;
const fileData = request.files?.[0] ? request.files[0].buffer : null; const fileData = request.files?.[0] ? request.files[0].buffer : null;
const SendinOrder = request.body.sendInOrder || false; const SendinOrder = request.body.sendInOrder || false;
const isEnableOTP = request.body?.enableOTP === true ? true : false;
// console.log('fileData ', fileData); // console.log('fileData ', fileData);
const protocol = customAPIurl(); const protocol = customAPIurl();
@@ -80,6 +82,7 @@ export default async function createTemplatewithCoordinate(request, response) {
object.set('URL', fileUrl); object.set('URL', fileUrl);
object.set('CreatedBy', userPtr); object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr); object.set('ExtUserPtr', extUserPtr);
object.set('IsEnableOTP', isEnableOTP);
let contact = []; let contact = [];
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let parseSigners; let parseSigners;
@@ -12,6 +12,8 @@ export default async function draftDocument(request, response) {
const send_email = request.body.send_email || true; const send_email = request.body.send_email || true;
const fileData = request.files?.[0] ? request.files[0].buffer : null; const fileData = request.files?.[0] ? request.files[0].buffer : null;
const SendinOrder = request.body.sendInOrder || false; const SendinOrder = request.body.sendInOrder || false;
const isEnableOTP = request.body?.enableOTP === true ? true : false;
// console.log('fileData ', fileData); // console.log('fileData ', fileData);
const protocol = customAPIurl(); const protocol = customAPIurl();
const baseUrl = new URL(process.env.PUBLIC_URL); const baseUrl = new URL(process.env.PUBLIC_URL);
@@ -83,6 +85,7 @@ export default async function draftDocument(request, response) {
object.set('CreatedBy', userPtr); object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr); object.set('ExtUserPtr', extUserPtr);
object.set('IsSendMail', send_email); object.set('IsSendMail', send_email);
object.set('IsEnableOTP', isEnableOTP);
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let parseSigners; let parseSigners;
if (base64File) { if (base64File) {
@@ -62,6 +62,7 @@ export default async function getDocument(request, response) {
document?.Signers?.map(y => ({ name: y?.Name, email: y?.Email, phone: y?.Phone })) || document?.Signers?.map(y => ({ name: y?.Name, email: y?.Email, phone: y?.Phone })) ||
[], [],
sendInOrder: document?.SendinOrder || false, sendInOrder: document?.SendinOrder || false,
enableOTP: document?.IsEnableOTP || false,
createdAt: document.createdAt, createdAt: document.createdAt,
updatedAt: document.updatedAt, updatedAt: document.updatedAt,
}); });
@@ -100,6 +100,7 @@ export default async function getDocumentList(request, response) {
x?.Signers?.map(y => ({ name: y?.Name, email: y?.Email, phone: y?.Phone })) || x?.Signers?.map(y => ({ name: y?.Name, email: y?.Email, phone: y?.Phone })) ||
[], [],
sendInOrder: x?.SendinOrder || false, sendInOrder: x?.SendinOrder || false,
enableOTP: x?.IsEnableOTP || false,
createdAt: x.createdAt, createdAt: x.createdAt,
updatedAt: x.updatedAt, updatedAt: x.updatedAt,
})); }));
@@ -61,6 +61,7 @@ export default async function getTemplate(request, response) {
), ),
})) || [], })) || [],
sendInOrder: template?.SendinOrder || false, sendInOrder: template?.SendinOrder || false,
enableOTP: template?.IsEnableOTP || false,
createdAt: template.createdAt, createdAt: template.createdAt,
updatedAt: template.updatedAt, updatedAt: template.updatedAt,
}); });
@@ -88,6 +88,7 @@ export default async function getTemplatetList(request, response) {
), ),
})) || [], })) || [],
sendInOrder: template?.SendinOrder || false, sendInOrder: template?.SendinOrder || false,
enableOTP: template?.IsEnableOTP || false,
createdAt: template.createdAt, createdAt: template.createdAt,
updatedAt: template.updatedAt, updatedAt: template.updatedAt,
})); }));
@@ -41,6 +41,7 @@ export default async function resendMail(request, response) {
if (resDoc) { if (resDoc) {
const _resDoc = resDoc.toJSON(); const _resDoc = resDoc.toJSON();
const contact = _resDoc.Signers.find(x => x.Email === userMail); const contact = _resDoc.Signers.find(x => x.Email === userMail);
const activeMailAdapter = _resDoc?.ExtUserPtr?.active_mail_adapter || '';
if (contact) { if (contact) {
try { try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
@@ -131,6 +132,7 @@ export default async function resendMail(request, response) {
from: sender, from: sender,
html: html, html: html,
extUserId: _resDoc.ExtUserPtr.objectId, extUserId: _resDoc.ExtUserPtr.objectId,
mailProvider: activeMailAdapter,
}; };
const res = await axios.post(url, params, { headers: headers }); const res = await axios.post(url, params, { headers: headers });
@@ -10,7 +10,7 @@ export default async function updateDocument(request, response) {
const token = await tokenQuery.first({ useMasterKey: true }); const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) { if (token !== undefined) {
// Valid Token then proceed request // Valid Token then proceed request
const allowedKeys = ['name', 'note', 'description', 'folderId']; const allowedKeys = ['name', 'note', 'description', 'folderId', 'enableOTP'];
const objectKeys = Object.keys(request.body); const objectKeys = Object.keys(request.body);
const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0; const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
const parseUser = JSON.parse(JSON.stringify(token)); const parseUser = JSON.parse(JSON.stringify(token));
@@ -48,6 +48,10 @@ export default async function updateDocument(request, response) {
objectId: request?.body?.folderId, objectId: request?.body?.folderId,
}); });
} }
if (request.body?.enableOTP) {
updateQuery.set('IsEnableOTP', request.body?.enableOTP);
}
const updatedRes = await updateQuery.save(null, { useMasterKey: true }); const updatedRes = await updateQuery.save(null, { useMasterKey: true });
if (updatedRes) { if (updatedRes) {
if (request.posthog) { if (request.posthog) {
@@ -10,7 +10,7 @@ export default async function updateTemplate(request, response) {
const token = await tokenQuery.first({ useMasterKey: true }); const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) { if (token !== undefined) {
// Valid Token then proceed request // Valid Token then proceed request
const allowedKeys = ['name', 'note', 'description', 'folderId']; const allowedKeys = ['name', 'note', 'description', 'folderId', 'enableOTP'];
const objectKeys = Object.keys(request.body); const objectKeys = Object.keys(request.body);
const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0; const isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
const parseUser = JSON.parse(JSON.stringify(token)); const parseUser = JSON.parse(JSON.stringify(token));
@@ -48,6 +48,9 @@ export default async function updateTemplate(request, response) {
objectId: request?.body?.folderId, objectId: request?.body?.folderId,
}); });
} }
if (request.body?.enableOTP) {
updateQuery.set('IsEnableOTP', request.body?.enableOTP);
}
const updatedRes = await updateQuery.save(null, { useMasterKey: true }); const updatedRes = await updateQuery.save(null, { useMasterKey: true });
if (updatedRes) { if (updatedRes) {
if (request.posthog) { if (request.posthog) {
@@ -0,0 +1,72 @@
import { createTransport } from 'nodemailer';
import { appName } from '../../Utils.js';
// Online Javascript Editor for free
// Write, Edit and Run your Javascript code using JS Online Compiler
function generateOtpWithSum10() {
let otp = [];
let sum = 0;
// First, generate 3 random digits (0-9) and add them to the sum
for (let i = 0; i < 3; i++) {
let digit = Math.floor(Math.random() * 10);
otp.push(digit);
sum += digit;
}
// Calculate the last digit to ensure the sum equals 10
let lastDigit = 10 - sum;
// If the last digit is not valid (e.g., greater than 9), regenerate
if (lastDigit >= 0 && lastDigit <= 9) {
otp.push(lastDigit);
} else {
return generateOtpWithSum10(); // Recursively generate again
}
return otp.join('');
}
export default async function validateSmtp(request, response) {
const host = request.body.host;
const port = request.body?.port?.toString();
const username = request.body.username;
const password = request.body.password;
const secure = request.body?.secure;
const email = request.body?.email;
const otp = generateOtpWithSum10();
// console.log('Generated OTP:', otp);
try {
const smtpsecure = secure || port !== '465' ? false : true;
const transporterSMTP = createTransport({
host: host,
port: port,
secure: smtpsecure,
auth: { user: username, pass: password },
});
const from = appName;
const mailsender = username;
const messageParams = {
from: from + ' <' + mailsender + '>',
to: email,
subject: `Your ${appName} SMTP credentials verification code`,
text: 'mail',
html:
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>SMTP Verification Code</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your verification code is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
otp +
'</p></div> </div> </div></body></html>',
};
const res = await transporterSMTP.sendMail(messageParams);
console.log('custom smtp transporter res: ', res?.response);
if (!res.err) {
response.status(200).json({ message: 'success' });
}
} catch (err) {
console.log('err ', err);
const code = err?.responseCode || 400;
const message = err?.response || 'failed!';
response.status(code).json({ error: message });
}
}
+25 -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,26 @@ 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';
import getContact from './parsefunction/getContact.js';
import updateContactTour from './parsefunction/updateContactTour.js';
import declinedocument from './parsefunction/declinedocument.js';
import addcustomsmtp from './parsefunction/addcustomsmtp.js';
import deactivateMailAdapter from './parsefunction/deactivateMailAdapter.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 +90,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 +143,12 @@ 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);
Parse.Cloud.define('getcontact', getContact);
Parse.Cloud.define('updatecontacttour', updateContactTour);
Parse.Cloud.define('declinedoc', declinedocument);
Parse.Cloud.define('addsmtp', addcustomsmtp);
Parse.Cloud.define('deactivatemailadapter', deactivateMailAdapter);
@@ -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;
@@ -62,6 +62,7 @@ const createDocumentFromTemplate = async (template, existContact, index) => {
object.set('CreatedBy', template?.CreatedBy); object.set('CreatedBy', template?.CreatedBy);
object.set('ExtUserPtr', template?.ExtUserPtr); object.set('ExtUserPtr', template?.ExtUserPtr);
object.set('OriginIp', template?.OriginIp || ''); object.set('OriginIp', template?.OriginIp || '');
object.set('IsEnableOTP', template?.IsEnableOTP || false);
let signers = template?.Signers || []; let signers = template?.Signers || [];
const signerobj = { const signerobj = {
__type: 'Pointer', __type: 'Pointer',
@@ -221,6 +222,25 @@ const sendMailToAllSigners = async docId => {
} }
}; };
const deductcount = async _resSub => {
try {
const subscriptionCls = new Parse.Object('contracts_Subscriptions');
subscriptionCls.id = _resSub.objectId;
const allowedCredits = _resSub?.AllowedCredits || 0;
const addonCredits = _resSub?.AddonCredits || 0;
if (allowedCredits > 0) {
const updateAllowedcredits = allowedCredits - 1 || 0;
subscriptionCls.set('AllowedCredits', updateAllowedcredits);
} else {
const updateAddonCredits = addonCredits > 0 ? addonCredits - 1 : 0;
subscriptionCls.set('AddonCredits', updateAddonCredits);
}
await subscriptionCls.save(null, { useMasterKey: true });
} catch (err) {
console.log('Err in deductcount in PublicUserLinkContacttoDoc cloud function', err);
}
};
// `PublicUserLinkContactToDoc` cloud function is used to create contact, add this contact in contracts_Guest role and // `PublicUserLinkContactToDoc` cloud function is used to create contact, add this contact in contracts_Guest role and
// create new document from template and save contact pointer in placeholder, signers and ACL of Document // create new document from template and save contact pointer in placeholder, signers and ACL of Document
export default async function PublicUserLinkContactToDoc(req) { export default async function PublicUserLinkContactToDoc(req) {
@@ -240,126 +260,163 @@ export default async function PublicUserLinkContactToDoc(req) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found.'); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found.');
} }
const _tempRes = JSON.parse(JSON.stringify(tempRes)); const _tempRes = JSON.parse(JSON.stringify(tempRes));
const Placeholders = _tempRes?.Placeholders || []; const subscription = new Parse.Query('contracts_Subscriptions');
let index; subscription.equalTo('TenantId', {
if (role) { __type: 'Pointer',
index = Placeholders?.findIndex(x => x.Role && x.Role === role); className: 'partners_Tenant',
} else { objectId: _tempRes?.ExtUserPtr?.TenantId?.objectId,
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, ''); });
} subscription.include('ExtUserPtr');
if (index !== -1) { const date = new Date();
// Execute the query to check if a contact already exists in the 'contracts_Contactbook' class subscription.greaterThanOrEqualTo('Next_billing_date', date);
const contactCls = new Parse.Query('contracts_Contactbook'); const resSub = await subscription.first({ useMasterKey: true });
contactCls.equalTo('Email', email); if (resSub) {
contactCls.equalTo('CreatedBy', _tempRes.CreatedBy); const _resSub = JSON.parse(JSON.stringify(resSub));
contactCls.notEqualTo('IsDeleted', true); const allowedCredits = _resSub?.AllowedCredits || 0;
const existContact = await contactCls.first({ useMasterKey: true }); const addonCredits = _resSub?.AddonCredits || 0;
if (existContact) { const totalcredits = allowedCredits + addonCredits;
const template_json = JSON.parse(JSON.stringify(tempRes)); if (totalcredits > 0) {
//update contact in placeholder, signers and update ACl in provide document const Placeholders = _tempRes?.Placeholders || [];
const docRes = await createDocumentFromTemplate(template_json, existContact, index); let index;
if (docRes) { if (role) {
//condition will execute only if sendInOrder will be false for send email to all signers at a time. index = Placeholders?.findIndex(x => x.Role && x.Role === role);
if (!template_json?.SendinOrder) { } else {
await sendMailToAllSigners(docRes.id); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, '');
}
return { contactId: existContact.id, docId: docRes.id };
} }
} else { if (index !== -1) {
// Execute the query to check if a user already exists in the 'contracts_Users' class // Execute the query to check if a contact already exists in the 'contracts_Contactbook' class
const extUserQuery = new Parse.Query('contracts_Users'); const contactCls = new Parse.Query('contracts_Contactbook');
extUserQuery.equalTo('Email', email); contactCls.equalTo('Email', email);
const extUser = await extUserQuery.first({ useMasterKey: true }); contactCls.equalTo('CreatedBy', _tempRes.CreatedBy);
if (extUser) { contactCls.notEqualTo('IsDeleted', true);
const _extUser = JSON.parse(JSON.stringify(extUser)); const existContact = await contactCls.first({ useMasterKey: true });
const contact = { if (existContact) {
UserId: _extUser.UserId, const template_json = JSON.parse(JSON.stringify(tempRes));
Name: _extUser.Name, //update contact in placeholder, signers and update ACl in provide document
Email: email, const docRes = await createDocumentFromTemplate(template_json, existContact, index);
Phone: _extUser?.Phone ? _extUser.Phone : '', if (docRes) {
CreatedBy: _tempRes.CreatedBy, await deductcount(_resSub);
TenantId: _tempRes.ExtUserPtr.TenantId, //condition will execute only if sendInOrder will be false for send email to all signers at a time.
}; if (!template_json?.SendinOrder) {
const template_json = JSON.parse(JSON.stringify(tempRes)); await sendMailToAllSigners(docRes.id);
// if user present on platform create contact on the basis of extended user details }
const contactRes = await saveRoleContact(contact); return { contactId: existContact.id, docId: docRes.id };
const docRes = await createDocumentFromTemplate(template_json, contactRes, index);
if (docRes) {
//condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id);
} }
return { contactId: contactRes.id, docId: docRes.id }; } else {
} // Execute the query to check if a user already exists in the 'contracts_Users' class
} else if (name) { const extUserQuery = new Parse.Query('contracts_Users');
try { extUserQuery.equalTo('Email', email);
// Execute the query to check if a user already exists in the '_User' class const extUser = await extUserQuery.first({ useMasterKey: true });
const userQuery = new Parse.Query(Parse.User); if (extUser) {
userQuery.equalTo('email', email); const _extUser = JSON.parse(JSON.stringify(extUser));
const userRes = await userQuery.first({ useMasterKey: true });
if (userRes) {
const contact = { const contact = {
UserId: { __type: 'Pointer', className: '_User', objectId: userRes.id }, UserId: _extUser.UserId,
Name: name, Name: _extUser.Name,
Email: email, Email: email,
Phone: phone, Phone: _extUser?.Phone ? _extUser.Phone : '',
CreatedBy: _tempRes.CreatedBy, CreatedBy: _tempRes.CreatedBy,
TenantId: _tempRes.ExtUserPtr.TenantId, TenantId: _tempRes.ExtUserPtr.TenantId,
}; };
const template_json = JSON.parse(JSON.stringify(tempRes)); const template_json = JSON.parse(JSON.stringify(tempRes));
// Create new contract on the basis provided contact details by user and userId from _User class // if user present on platform create contact on the basis of extended user details
const contactRes = await saveRoleContact(contact); const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(template_json, contactRes, index); const docRes = await createDocumentFromTemplate(template_json, contactRes, index);
if (docRes) { if (docRes) {
await deductcount(_resSub);
//condition will execute only if sendInOrder will be false for send email to all signers at a time. //condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) { if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id); await sendMailToAllSigners(docRes.id);
} }
return { contactId: contactRes.id, docId: docRes.id }; return { contactId: contactRes.id, docId: docRes.id };
} }
} else if (name) {
try {
// Execute the query to check if a user already exists in the '_User' class
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('email', email);
const userRes = await userQuery.first({ useMasterKey: true });
if (userRes) {
const contact = {
UserId: { __type: 'Pointer', className: '_User', objectId: userRes.id },
Name: name,
Email: email,
Phone: phone,
CreatedBy: _tempRes.CreatedBy,
TenantId: _tempRes.ExtUserPtr.TenantId,
};
const template_json = JSON.parse(JSON.stringify(tempRes));
// Create new contract on the basis provided contact details by user and userId from _User class
const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(
template_json,
contactRes,
index
);
if (docRes) {
await deductcount(_resSub);
//condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id);
}
return { contactId: contactRes.id, docId: docRes.id };
}
} else {
// create new user in _User class on the basis of details provide by user
const _users = Parse.Object.extend('User');
const _user = new _users();
_user.set('name', name);
_user.set('username', email);
_user.set('email', email);
_user.set('password', email);
if (phone) {
_user.set('phone', phone);
}
const newUserRes = await _user.save();
const contact = {
UserId: { __type: 'Pointer', className: '_User', objectId: newUserRes.id },
Name: name,
Email: email,
Phone: phone,
CreatedBy: _tempRes.CreatedBy,
TenantId: _tempRes.ExtUserPtr.TenantId,
};
const template_json = JSON.parse(JSON.stringify(tempRes));
// Create new contract on the basis provided contact details by user and userId from _User class
const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(
template_json,
contactRes,
index
);
if (docRes) {
await deductcount(_resSub);
//condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id);
}
return { contactId: contactRes.id, docId: docRes.id };
}
}
} catch (err) {
console.log('Err', err);
}
} else { } else {
// create new user in _User class on the basis of details provide by user throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
const _users = Parse.Object.extend('User');
const _user = new _users();
_user.set('name', name);
_user.set('username', email);
_user.set('email', email);
_user.set('password', email);
if (phone) {
_user.set('phone', phone);
}
const newUserRes = await _user.save();
const contact = {
UserId: { __type: 'Pointer', className: '_User', objectId: newUserRes.id },
Name: name,
Email: email,
Phone: phone,
CreatedBy: _tempRes.CreatedBy,
TenantId: _tempRes.ExtUserPtr.TenantId,
};
const template_json = JSON.parse(JSON.stringify(tempRes));
// Create new contract on the basis provided contact details by user and userId from _User class
const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(template_json, contactRes, index);
if (docRes) {
//condition will execute only if sendInOrder will be false for send email to all signers at a time.
if (!template_json?.SendinOrder) {
await sendMailToAllSigners(docRes.id);
}
return { contactId: contactRes.id, docId: docRes.id };
}
} }
} catch (err) {
console.log('Err', err);
} }
} else { } else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); throw new Parse.Error(
Parse.Error.OBJECT_NOT_FOUND,
'Please provide required parameters!'
);
} }
} else {
throw new Parse.Error(Parse.Error.REQUEST_LIMIT_EXCEEDED, 'Insufficient Credit');
} }
} else { } else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Please provide required parameters!'); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Plan expired');
} }
} else { } else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found.'); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found.');
@@ -1,4 +1,4 @@
import { smtpenable, updateMailCount } from '../../Utils.js'; import { appName, smtpenable, updateMailCount } from '../../Utils.js';
async function getDocument(docId) { async function getDocument(docId) {
try { try {
const query = new Parse.Query('contracts_Document'); const query = new Parse.Query('contracts_Document');
@@ -28,12 +28,12 @@ async function sendMailOTPv1(request) {
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER; const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
try { try {
await Parse.Cloud.sendEmail({ await Parse.Cloud.sendEmail({
from: 'Opensign™' + ' <' + mailsender + '>', from: appName + ' <' + mailsender + '>',
recipient: recipient, recipient: recipient,
subject: 'Your OpenSign™ OTP', subject: `Your ${appName} OTP`,
text: 'This email is a test.', text: 'This email is a test.',
html: html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for OpenSign™ verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>" + `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for ${appName} verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
code + code +
'</p></div> </div> </div></body></html>', '</p></div> </div> </div></body></html>',
}); });

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