Compare commits

..
130 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
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 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
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
RaktimaNXG b20aa33a04 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-2 2024-09-06 15:23:31 +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
RaktimaNXG e80bcfc586 refactor code 2024-09-05 18:41:09 +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
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
77 changed files with 5063 additions and 3358 deletions
+1314 -811
View File
File diff suppressed because it is too large Load Diff
+22 -20
View File
@@ -4,16 +4,17 @@
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.8.9",
"@lottiefiles/dotlottie-react": "^0.9.0",
"@pdf-lib/fontkit": "^1.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",
"axios": "^1.7.5",
"axios": "^1.7.7",
"file-saver": "^2.0.5",
"i18next": "^23.14.0",
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"nth-check": "^2.1.1",
@@ -25,7 +26,7 @@
"react-bootstrap": "^2.10.4",
"react-confetti": "^6.1.0",
"react-cookie": "^7.2.0",
"react-datepicker": "^7.3.0",
"react-datepicker": "^7.4.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^8.0.3",
@@ -33,17 +34,18 @@
"react-dom": "^18.2.0",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.0.1",
"react-i18next": "^15.0.2",
"react-konva": "^18.2.10",
"react-pdf": "^9.1.0",
"react-quill-new": "^3.3.0",
"react-pdf": "^9.1.1",
"react-quill-new": "^3.3.2",
"react-redux": "^9.1.2",
"react-rnd": "^10.4.11",
"react-router-dom": "^6.26.1",
"react-rnd": "^10.4.13",
"react-router-dom": "^6.26.2",
"react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.8.0",
"react-select": "^5.8.1",
"react-signature-canvas": "^1.0.6",
"react-syntax-highlighter": "^15.5.0",
"react-tooltip": "^5.28.0",
"react-web-share": "^2.0.2",
"reactour": "^1.19.4",
@@ -59,7 +61,7 @@
"build-webpack": "webpack --config webpack.config.js",
"build": "npm run version && npm run build-webpack && react-scripts build",
"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",
"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\"",
@@ -96,25 +98,25 @@
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.4",
"@babel/preset-react": "^7.24.7",
"@babel/runtime-corejs2": "^7.25.4",
"@babel/runtime-corejs2": "^7.25.6",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.1.3",
"babel-loader": "^9.2.1",
"clean-webpack-plugin": "^4.0.0",
"commitizen": "^4.3.0",
"concurrently": "^8.2.2",
"concurrently": "^9.0.1",
"css-loader": "^7.1.2",
"daisyui": "^4.12.10",
"dotenv": "^16.4.5",
"dotenv-webpack": "^8.1.0",
"eslint": "^8.57.0",
"eslint": "^9.11.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.34.3",
"lint-staged": "^15.2.8",
"eslint-plugin-react": "^7.36.1",
"lint-staged": "^15.2.10",
"mini-css-extract-plugin": "^2.9.1",
"postcss": "^8.4.45",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"pretty-quick": "^4.0.0",
"tailwindcss": "^3.4.10",
"tailwindcss": "^3.4.13",
"terser-webpack-plugin": "^5.3.10",
"webpack-cli": "^5.1.4"
},
@@ -131,7 +131,8 @@
"Share": "Share",
"View": "View",
"option": "Option",
"Embed": "Embed"
"Embed": "Embed",
"Copy TemplateId":"Copy TemplateId"
},
"report-heading": {
"Sr.No": "Sr.No",
@@ -151,7 +152,8 @@
"Name": "Name",
"Status": "Status",
"created-date": "Created Date",
"Type": "Type"
"Type": "Type",
"Logs": "Logs"
},
"report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -263,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.",
"public-role": "Public role",
"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.",
"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",
@@ -572,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 .",
"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-3": "Drag or click on a field to add it 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-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-3": "Click this button to add more recipients/signers to the document.",
"placeholder-sign-4": "Drag or click on a field to add it to the document.",
"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. ",
"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.",
@@ -630,12 +634,40 @@
"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 {{adminName}}<{{adminEmail}}>.",
"sent-this-month":"Sent this month"
"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"
}
@@ -136,7 +136,8 @@
"Name": "Nom",
"Status": "Statut",
"created-date": "Date de création",
"Type": "Saisir"
"Type": "Saisir",
"Logs": "Journaux"
},
"btnLabel": {
"sign": "Signer",
@@ -150,7 +151,8 @@
"Share": "Partager",
"View": "Voir",
"option": "Option",
"Embed": "Intégrer"
"Embed": "Intégrer",
"Copy TemplateId": "Copier l'ID du modèle"
},
"report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -262,7 +264,8 @@
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
"public-role": "Rôle public",
"public-url": "URL publique",
"public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
"public-url-copy": "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.",
"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",
@@ -571,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.",
"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-3": "Faites glisser ou cliquez sur un champ pour l'ajouter 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-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-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
"placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"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",
"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'.",
@@ -629,12 +633,40 @@
"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 {{adminName}}<{{adminEmail}}>.",
"sent-this-month":"envoyé ce mois-ci"
"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"
}
File diff suppressed because one or more lines are too long
+4 -2
View File
@@ -301,6 +301,9 @@ const AddUser = (props) => {
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);
@@ -495,8 +498,7 @@ const AddUser = (props) => {
{planInfo.adminId !== extUser?.objectId ? (
<div className="mb-3 mt-1 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", {
adminName: extUser?.CreatedBy?.name,
adminEmail: extUser?.CreatedBy?.email
adminEmail: extUser?.TenantId?.EmailAddress
})}
</div>
) : (
+2 -7
View File
@@ -31,7 +31,6 @@ const BulkSendUi = (props) => {
const [isFreePlan, setIsFreePlan] = useState(false);
const [admin, setAdmin] = useState({
objectId: "",
name: "",
email: "",
isAdmin: true
});
@@ -63,8 +62,7 @@ const BulkSendUi = (props) => {
setAdmin((obj) => ({
...obj,
isAdmin: false,
name: extUser?.CreatedBy?.name,
email: extUser?.CreatedBy?.email
email: extUser?.TenantId?.EmailAddress
}));
}
setAdmin((obj) => ({ ...obj, objectId: subscription?.adminId }));
@@ -491,10 +489,7 @@ const BulkSendUi = (props) => {
</form>
) : (
<div className="mx-8 mt-4 mb-8 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", {
adminName: admin?.name,
adminEmail: admin?.email
})}
{t("unauthorized-modal", { adminEmail: admin?.email })}
</div>
)}
</>
+42 -9
View File
@@ -20,6 +20,9 @@ const Header = ({ showSidebar }) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { width } = useWindowSize();
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 [isOpen, setIsOpen] = useState(false);
@@ -30,9 +33,15 @@ const Header = ({ showSidebar }) => {
);
const [emailUsed, setEmailUsed] = useState(0);
const [isModal, setIsModal] = useState(false);
const [showNotification, setShowNotification] = useState(
(!dismissedVersion || dismissedVersion !== current_notification_version) &&
true
);
const toggleDropdown = () => {
setIsOpen(!isOpen);
if (width <= 768) {
showSidebar();
}
};
useEffect(() => {
checkSubscription();
@@ -58,6 +67,13 @@ const Header = ({ showSidebar }) => {
} 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") || "");
}
}
}
@@ -115,17 +131,34 @@ const Header = ({ showSidebar }) => {
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 (
<div>
{isEnableSubscription && (
<div className="shadow py-1 text-center bg-[#CAE4FA] text-[14px] p-1">
{t("header-news")}
<span
className="cursor-pointer font-medium underline text-blue-800"
onClick={() => navigate("/profile")}
{isEnableSubscription && showNotification && (
<div className="flex justify-between items-center shadow py-1 bg-[#CAE4FA] p-1">
<div className="text-center text-[14px] ml-auto">
{t("header-news")}
<span
className="cursor-pointer font-medium underline text-blue-800"
onClick={() => navigate("/profile")}
>
{" " + t("header-news-btn") + "."}
</span>
</div>
<div
className="ml-auto mr-1 cursor-pointer"
onClick={() => dismissNotification()}
>
{" " + t("header-news-btn") + "."}
</span>
<i className="fa-light fa-xmark"></i>
</div>
</div>
)}
<div className="op-navbar bg-base-100 shadow">
@@ -9,6 +9,7 @@ import * as HoverCard from "@radix-ui/react-hover-card";
import ModalUi from "../../primitives/ModalUi";
import FolderModal from "../shared/fields/FolderModal";
import { useTranslation } from "react-i18next";
import { handleDownloadPdf } from "../../constant/Utils";
function DriveBody(props) {
const { t } = useTranslation();
@@ -100,16 +101,17 @@ function DriveBody(props) {
//function for navigate user to microapp-signature component
const checkPdfStatus = async (data) => {
const signerExist = data.Signers && data.Signers;
const isDecline = data.IsDeclined && data.IsDeclined;
const isPlaceholder = data.Placeholders && data.Placeholders;
const signedUrl = data.SignedUrl;
const signerExist = data?.Signers;
const isDecline = data?.IsDeclined;
const isPlaceholder = data?.Placeholders;
const signedUrl = data?.SignedUrl;
const isSignYourself = data?.IsSignyourself;
//checking if document has completed and request signature flow
if (data?.IsCompleted && signerExist?.length > 0) {
navigate(`/recipientSignPdf/${data.objectId}`);
}
//checking if document has completed and signyour-self flow
else if (!signerExist && !isPlaceholder) {
else if ((!signerExist && !isPlaceholder) || isSignYourself) {
navigate(`/signaturePdf/${data.objectId}`);
}
//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) {
case "Download": {
const pdfName = data && data.Name;
const pdfUrl = data && data.SignedUrl ? data.SignedUrl : data.URL;
saveAs(pdfUrl, `${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`);
await handleDownloadPdf([data]);
break;
}
case "Rename": {
@@ -3,6 +3,8 @@ import { checkIsSubscribed, getFileName } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
import { useTranslation } from "react-i18next";
import { Tooltip } from "react-tooltip";
// import SelectFolder from "../../premitives/SelectFolder";
const EditTemplate = ({ template, onSuccess }) => {
@@ -14,7 +16,8 @@ const EditTemplate = ({ template, onSuccess }) => {
Description: template?.Description || "",
SendinOrder: template?.SendinOrder ? `${template?.SendinOrder}` : "false",
AutomaticReminders: template?.AutomaticReminders || false,
RemindOnceInEvery: template?.RemindOnceInEvery || 5
RemindOnceInEvery: template?.RemindOnceInEvery || 5,
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false"
});
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => {
@@ -41,7 +44,7 @@ const EditTemplate = ({ template, onSuccess }) => {
e.stopPropagation();
const isChecked = formData.SendinOrder === "true" ? true : false;
const AutoReminder = formData?.AutomaticReminders || false;
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
let reminderDate = {};
if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
@@ -49,7 +52,12 @@ const EditTemplate = ({ template, onSuccess }) => {
ReminderDate.setDate(ReminderDate.getDate() + RemindOnceInEvery);
reminderDate = { NextReminderDate: ReminderDate };
}
const data = { ...formData, SendinOrder: isChecked, ...reminderDate };
const data = {
...formData,
SendinOrder: isChecked,
IsEnableOTP: IsEnableOTP,
...reminderDate
};
onSuccess(data);
};
const handleAutoReminder = () => {
@@ -184,6 +192,70 @@ const EditTemplate = ({ template, onSuccess }) => {
/>
</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">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import axios from "axios";
import { handleDownloadPdf, handleToPrint } from "../../constant/Utils";
import { handleToPrint } from "../../constant/Utils";
import { themeColor, emailRegex } from "../../constant/const";
import Loader from "../../primitives/Loader";
import ModalUi from "../../primitives/ModalUi";
@@ -15,7 +15,8 @@ function EmailComponent({
sender,
setIsAlert,
extUserId,
activeMailAdapter
activeMailAdapter,
setIsDownloadModal
}) {
const { t } = useTranslation();
const [emailList, setEmailList] = useState([]);
@@ -29,6 +30,28 @@ function EmailComponent({
const pdfName = pdfDetails[0]?.Name;
setIsLoading(true);
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++) {
try {
const imgPng =
@@ -46,7 +69,7 @@ function EmailComponent({
mailProvider: activeMailAdapter,
extUserId: extUserId,
pdfName: pdfName,
url: pdfUrl,
url: presignedUrl,
recipient: emailList[i],
subject: `${sender.name} has signed the doc - ${pdfName}`,
from: sender.email,
@@ -129,6 +152,11 @@ function EmailComponent({
}
}
};
const handleClose = () => {
setIsEmail(false);
setEmailValue("");
setEmailList([]);
};
return (
<div>
{/* isEmail */}
@@ -154,7 +182,9 @@ function EmailComponent({
<div className="flex flex-row">
{!isAndroid && (
<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]"
>
<i className="fa-light fa-print" aria-hidden="true"></i>
@@ -163,9 +193,10 @@ function EmailComponent({
)}
<button
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
onClick={() => {
handleClose();
setIsDownloadModal(true);
}}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
{t("download")}
@@ -261,11 +292,7 @@ function EmailComponent({
<button
type="button"
className="op-btn op-btn-ghost ml-2"
onClick={() => {
setIsEmail(false);
setEmailValue("");
setEmailList([]);
}}
onClick={() => handleClose()}
>
{t("close")}
</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;
+64 -45
View File
@@ -39,7 +39,8 @@ function Header({
clickOnZoomOut,
handleRotationFun,
isDisableRotate,
templateId
templateId,
setIsDownloadModal
}) {
const { t } = useTranslation();
const filterPrefill =
@@ -94,9 +95,13 @@ function Header({
>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
onClick={() => {
if (isCompleted) {
setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
>
<div className="flex flex-row">
<i
@@ -142,7 +147,7 @@ function Header({
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading)
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
>
<div className="flex flex-row">
@@ -209,57 +214,67 @@ function Header({
sideOffset={5}
>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfDetails[0]?.URL,
setIsDownloading
)
handleDownloadPdf(pdfDetails, setIsDownloading)
}
>
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
<span className="font-[500]">{t("download")}</span>
<div className="flex flex-row">
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
<span className="font-[500]">
{t("download")}
</span>
</div>
</DropdownMenu.Item>
{!isDisableRotate && (
<>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() => handleRotationFun(90)}
>
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-right")}
</span>
<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="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() => handleRotationFun(-90)}
>
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-left")}
</span>
<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="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() => clickOnZoomIn()}
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
<span className="font-[500]">{t("zoom-in")}</span>
<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="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() => clickOnZoomOut()}
>
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
<span className="font-[500]">{t("zoom-out")}</span>
<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.Content>
</DropdownMenu.Portal>
@@ -342,7 +357,9 @@ function Header({
alreadySign ? (
<div className="flex flex-row">
<button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
>
@@ -370,9 +387,13 @@ function Header({
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
onClick={() => {
if (isCompleted) {
setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
>
<i
className="fa-light fa-download py-[3px]"
@@ -427,11 +448,7 @@ function Header({
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
handleDownloadPdf(pdfDetails, setIsDownloading)
}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
@@ -459,7 +476,9 @@ function Header({
</button>
)}
<button
onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)}
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
}
type="button"
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
>
@@ -469,9 +488,9 @@ function Header({
<button
type="button"
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
onClick={() => {
setIsDownloadModal(true);
}}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("download")}</span>
+10 -8
View File
@@ -31,8 +31,8 @@ function SignPad({
const [isTab, setIsTab] = useState("draw");
const [isSignImg, setIsSignImg] = useState("");
const [signValue, setSignValue] = useState("");
const [textWidth, setTextWidth] = useState(null);
const [textHeight, setTextHeight] = useState(null);
const [textWidth, setTextWidth] = useState(0);
const [textHeight, setTextHeight] = useState(0);
const [signatureType, setSignatureType] = useState("draw");
const fontOptions = [
{ value: "Fasthand" },
@@ -51,7 +51,7 @@ function SignPad({
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
const currentUserName = jsonSender && jsonSender.name;
const currentUserName = jsonSender && jsonSender?.name;
//function for clear signature image
const handleClear = () => {
@@ -137,7 +137,7 @@ function SignPad({
(isTab === "draw" && isSignImg) ||
(isTab === "image" && image) ||
(isTab === "mysignature" && isDefaultSign) ||
(isTab === "type" && textWidth)
(isTab === "type" && signValue)
? false
: image
? false
@@ -169,10 +169,10 @@ function SignPad({
}
}
const trimmedName = currentUserName && currentUserName.trim();
const trimmedName = currentUserName && currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = isInitial ? firstCharacter : currentUserName;
setSignValue(userName);
setSignValue(userName || "");
setFontSelect("Fasthand");
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -200,8 +200,10 @@ function SignPad({
canvasRef.current.fromDataURL(isSignImg);
}
if (isTab === "type") {
const trimmedName = signValue ? signValue.trim() : currentUserName.trim();
const firstCharacter = trimmedName.charAt(0);
const trimmedName = signValue
? signValue?.trim()
: currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = isInitial ? firstCharacter : signValue;
setSignValue(userName);
convertToImg(fontSelect, userName);
+95 -34
View File
@@ -33,14 +33,15 @@ export const openInNewTab = (url, target) => {
export async function fetchSubscription(
extUserId,
contactObjId,
isGuestSign = false
isGuestSign = false,
isPublic = false
) {
try {
const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class);
// console.log("extClass ", extClass);
let extUser;
if (extClass && extClass.length > 0) {
if (extClass && extClass.length > 0 && !isPublic) {
extUser = extClass[0].objectId;
} else {
extUser = extUserId;
@@ -54,7 +55,7 @@ export async function fetchSubscription(
};
const params = isGuestSign
? { contactId: contactObjId }
: { extUserId: extUser };
: { extUserId: extUser, ispublic: isPublic };
const tenatRes = await axios.post(url, params, { headers: headers });
let plan, status, billingDate, adminId;
if (isGuestSign) {
@@ -708,7 +709,8 @@ export const createDocument = async (
Signers: signers,
SendinOrder: Doc?.SendinOrder || false,
AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5)
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5),
IsEnableOTP: Doc?.IsEnableOTP || false
};
try {
@@ -1741,9 +1743,7 @@ export const contactBook = async (objectId) => {
//function for getting document details from contract_Documents class
export const contractDocument = async (documentId) => {
const data = {
docId: documentId
};
const data = { docId: documentId };
const documentDeatils = await axios
.post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, {
headers: {
@@ -2039,30 +2039,32 @@ export const fetchUrl = async (url, pdfName) => {
console.error("Error downloading the file:", error);
}
};
//handle download signed pdf
export const handleDownloadPdf = async (
pdfDetails,
pdfUrl,
setIsDownloading
) => {
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
setIsDownloading("pdf");
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
`${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")
}
export const getSignedUrl = async (pdfUrl, docId) => {
//use only axios here due to public template sign
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")
}
);
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);
setIsDownloading("");
setIsDownloading && setIsDownloading("");
} catch (err) {
console.log("err in getsignedurl", err);
setIsDownloading("");
@@ -2075,16 +2077,22 @@ export const sanitizeFileName = (pdfName) => {
return pdfName.replace(/ /g, "_");
};
//function for print digital sign pdf
export const handleToPrint = async (event, pdfUrl, setIsDownloading) => {
export const handleToPrint = async (
event,
pdfUrl,
setIsDownloading,
pdfDetails
) => {
event.preventDefault();
setIsDownloading("pdf");
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : "";
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
//`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
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl },
{ url: pdfUrl, docId: docId },
{
headers: {
"content-type": "Application/json",
@@ -2124,13 +2132,18 @@ export const handleToPrint = async (event, pdfUrl, setIsDownloading) => {
//handle download signed pdf
export const handleDownloadCertificate = async (
pdfDetails,
setIsDownloading
setIsDownloading,
isZip
) => {
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try {
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
if (isZip) {
return certificateUrl;
} else {
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
}
} catch (err) {
console.log("err in download in certificate", err);
}
@@ -2156,7 +2169,13 @@ export const handleDownloadCertificate = async (
if (doc?.CertificateUrl) {
await fetch(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("");
} else {
setIsDownloading("certificate");
@@ -2456,3 +2475,45 @@ export const handleRotateWarning = (signerPos, pageNumber) => {
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";
}
}
+31 -8
View File
@@ -1,3 +1,5 @@
import { isEnableSubscription } from "../constant/const";
export default function reportJson(id) {
// console.log("json ", json);
const head = ["Sr.No", "Title", "Note", "Folder", "File", "Owner", "Signers"];
@@ -10,9 +12,36 @@ export default function reportJson(id) {
"Owner",
"Signers"
];
const iphead = [
"Sr.No",
"Title",
"Note",
"Folder",
"File",
"Status",
"Signers"
];
const contactbook = ["Sr.No", "Name", "Email", "Phone"];
const dashboardReportHead = ["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) {
// draft documents report
case "ByHuevtCFY":
@@ -63,7 +92,7 @@ export default function reportJson(id) {
case "1MwEuxLEkF":
return {
reportName: "In-progress documents",
heading: head,
heading: iphead,
actions: [
{
btnId: "8901",
@@ -354,13 +383,7 @@ export default function reportJson(id) {
redirectUrl: "template",
action: "redirect"
},
{
btnId: "2434",
btnLabel: "Embed",
hoverLabel: "Embed",
btnIcon: "fa-light fa-code",
action: "Embed"
},
...templateSubAction,
{
btnId: "1834",
btnLabel: "Delete",
+2 -2
View File
@@ -252,9 +252,9 @@ const HomeLayout = () => {
}
};
const handleLoginBtn = () => {
const handleLoginBtn = async () => {
try {
Parse?.User?.logOut();
await Parse?.User?.logOut();
} catch (err) {
console.log("err ", err);
} finally {
+1 -1
View File
@@ -74,7 +74,7 @@ const DebugPdf = () => {
const inferPdfType = async (pdf) => {
try {
const firstPage = await pdf.getPage(2);
const firstPage = await pdf.getPage(pdf?.numPages > 1 ? 2 : 1);
const scale = 1;
const { width, height } = firstPage.getViewport({ scale });
setPdfDimension({ width: width, height: height });
+232 -111
View File
@@ -9,7 +9,12 @@ import SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
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 axios from "axios";
import { isEnableSubscription } from "../constant/const";
@@ -61,7 +66,8 @@ const Forms = (props) => {
password: "",
file: "",
remindOnceInEvery: 5,
autoreminder: false
autoreminder: false,
IsEnableOTP: "false"
});
const [fileupload, setFileUpload] = useState("");
const [fileload, setfileload] = useState(false);
@@ -74,6 +80,7 @@ const Forms = (props) => {
const [isDecrypting, setIsDecrypting] = useState(false);
const [isCorrectPass, setIsCorrectPass] = useState(true);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isAdvanceOpt, setIsAdvanceOpt] = useState(false);
const handleStrInput = (e) => {
setIsCorrectPass(true);
setFormData({ ...formData, [e.target.name]: e.target.value });
@@ -137,12 +144,11 @@ const Forms = (props) => {
} catch (err) {
console.log("err in sending posthog encryptedpdf", err);
}
// console.log("err ", err);
try {
setIsDecrypting(true);
const size = files?.[0].size;
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();
formData.append("file", files[0]);
formData.append("password", "");
@@ -185,6 +191,9 @@ const Forms = (props) => {
if (parseFile.url()) {
setFileUpload(parseFile.url());
setfileload(false);
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId);
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, parseFile.url(), tenantId);
return parseFile.url();
@@ -258,6 +267,8 @@ const Forms = (props) => {
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
@@ -285,12 +296,17 @@ const Forms = (props) => {
if (res.data) {
setFileUpload(res.data.url);
setfileload(false);
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
}
} catch (err) {
e.target.value = "";
setfileload(false);
setpercentage(0);
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"
);
}
}
}
@@ -329,6 +345,8 @@ const Forms = (props) => {
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(file.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
@@ -366,9 +384,10 @@ const Forms = (props) => {
});
setFileUpload(response.url());
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(file.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
@@ -402,6 +421,13 @@ const Forms = (props) => {
object.set("SendinOrder", isChecked);
object.set("AutomaticReminders", formData.autoreminder);
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("CreatedBy", Parse.User.createWithoutData(currentUser.id));
@@ -428,7 +454,17 @@ const Forms = (props) => {
setFormData({
Name: "",
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("");
setpercentage(0);
@@ -477,7 +513,8 @@ const Forms = (props) => {
password: "",
file: "",
remindOnceInEvery: 5,
autoreminder: false
autoreminder: false,
IsEnableOTP: "false"
});
setFileUpload("");
setpercentage(0);
@@ -529,6 +566,8 @@ const Forms = (props) => {
setFormData((prev) => ({ ...prev, password: "" }));
setFileUpload(parseFile.url());
setfileload(false);
const title = generateTitleFromFilename(formData?.file?.name);
setFormData((obj) => ({ ...obj, Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, parseFile.url(), tenantId);
return parseFile.url();
@@ -641,7 +680,7 @@ const Forms = (props) => {
)}
<div className="text-xs">
<label className="block">
{`${t("report-heading.File")} (${t("file-type")} ${
{`${t("report-heading.File")} (${t("file-type")}${
isEnableSubscription ? ", docx)" : ")"
}`}
<span className="text-red-500 text-[13px]">*</span>
@@ -754,120 +793,73 @@ const Forms = (props) => {
isReset={isReset}
/>
)}
{props.title === "Request Signatures" && (
{props.title !== "Sign Yourself" && (
<div className="text-xs mt-2">
<label className="block">
{t("time-to-complete")}
<span className="text-red-500 text-[13px]">*</span>
{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>
<input
type="number"
name="TimeToCompleteDays"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={formData.TimeToCompleteDays}
onChange={(e) => handleStrInput(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<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>
)}
{props.title !== "Sign Yourself" && (
<>
<div className="text-xs mt-2">
<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 && (
{isAdvanceOpt && (
<div className="overflow-y-auto z-[500] transition-all">
{props.title === "Request Signatures" && (
<div className="text-xs mt-2">
<label className="block">
{t("remind-once")}
{t("time-to-complete")}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
value={formData.remindOnceInEvery}
name="remindOnceInEvery"
name="TimeToCompleteDays"
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) =>
e.target.setCustomValidity(t("input-required"))
}
@@ -876,7 +868,136 @@ const Forms = (props) => {
/>
</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">
<button
+1 -4
View File
@@ -28,7 +28,6 @@ function GenerateToken() {
plan: "",
isValid: false,
adminId: "",
adminName: "",
adminEmail: ""
});
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
@@ -154,8 +153,7 @@ function GenerateToken() {
} else {
setIsSubscribe((obj) => ({
...obj,
adminName: extUser?.CreatedBy?.name,
adminEmail: extUser?.CreatedBy?.email
adminEmail: extUser?.TenantId?.EmailAddress
}));
setIsModal((obj) => ({ ...obj, unauthorized: !obj.unauthorized }));
}
@@ -447,7 +445,6 @@ function GenerateToken() {
>
<div className="m-8 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", {
adminName: isSubscribe?.adminName,
adminEmail: isSubscribe?.adminEmail
})}
</div>
+36 -12
View File
@@ -28,6 +28,28 @@ function GuestLogin() {
const [contactId, setContactId] = useState(contactBookId);
const [sendmail, setSendmail] = useState();
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(() => {
handleServerUrl();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -70,13 +92,14 @@ function GuestLogin() {
"linkcontacttodoc",
params
);
// console.log("linkContactRes ", linkContactRes);
setContactId(linkContactRes?.contactId);
await navigateToDoc(checkSplit[0], linkContactRes?.contactId);
} catch (err) {
console.log("Err in link ext contact", err);
}
} else {
setContactId(checkSplit[2]);
await navigateToDoc(checkSplit[0], checkSplit[2]);
}
}
@@ -114,15 +137,12 @@ function GuestLogin() {
if (OTP) {
setLoading(true);
try {
let url = `${serverUrl}functions/AuthLoginAsMail/`;
let url = `${serverUrl}functions/AuthLoginAsMail`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseId
};
let body = {
email: email,
otp: OTP
};
let body = { email: email, otp: OTP };
let user = await axios.post(url, body, { headers: headers });
if (user.data.result === "Invalid Otp") {
alert(t("invalid-otp"));
@@ -140,7 +160,6 @@ function GuestLogin() {
`Parse/${parseId}/currentUser`,
JSON.stringify(_user)
);
// console.log("contractUserDetails ", contractUserDetails);
if (contractUserDetails && contractUserDetails.length > 0) {
localStorage.setItem(
"Extand_Class",
@@ -174,10 +193,15 @@ function GuestLogin() {
try {
setLoading(true);
const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params);
// console.log("linkContactRes ", linkContactRes);
setContactId(linkContactRes.contactId);
setEnterOtp(true);
await SendOtp();
const IsEnableOTP = await navigateToDoc(
documentId,
linkContactRes.contactId
);
if (!IsEnableOTP) {
setEnterOtp(true);
await SendOtp();
}
} catch (err) {
setLoading(false);
alert(t("something-went-wrong-mssg"));
@@ -208,7 +232,7 @@ function GuestLogin() {
<div className="w-full md:w-[50%] text-base-content">
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
<legend className="text-[12px] text-[#878787] mt-2 mb-1">
{t("guest-email-alert")}
{t("get-otp-alert")}
</legend>
<div className="p-[20px] outline outline-1 outline-slate-300/50 my-2 op-card shadow-md">
<input
@@ -236,7 +260,7 @@ function GuestLogin() {
>
<h1 className="text-2xl md:text-[30px]">{t("welcome")}</h1>
<legend className="text-[12px] text-[#878787] mt-2">
{t("get-verification-code-2")}
{t("guest-email-alert")}
</legend>
<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>
+341 -234
View File
@@ -28,7 +28,6 @@ import {
contractUsers,
handleSendOTP,
contactBook,
handleDownloadPdf,
handleToPrint,
handleDownloadCertificate,
getDefaultSignature,
@@ -36,21 +35,23 @@ import {
onClickZoomOut,
fetchUrl
} from "../constant/Utils";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
import PdfDeclineModal from "../primitives/PdfDeclineModal";
import Title from "../components/Title";
import DefaultSignature from "../components/pdf/DefaultSignature";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Loader from "../primitives/Loader";
import { useSelector } from "react-redux";
import SignerListComponent from "../components/pdf/SignerListComponent";
import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom";
import { useTranslation } from "react-i18next";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
import Loader from "../primitives/Loader";
import PdfDeclineModal from "../primitives/PdfDeclineModal";
import Agreement from "../primitives/Agreement";
function PdfRequestFiles(props) {
const { t } = useTranslation();
@@ -136,29 +137,32 @@ function PdfRequestFiles(props) {
const [plancode, setPlanCode] = useState("");
const isHeader = useSelector((state) => state.showHeader);
const divRef = useRef(null);
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [isAgree, setIsAgree] = useState(false);
const [isAgreeTour, setIsAgreeTour] = useState(false);
const isMobile = window.innerWidth < 767;
let isGuestSignFlow = false;
let sendmail;
let getDocId = "";
const route = !props.templateId && window.location.pathname; //'/load/recipientSignPdf/TOAVuhXbfw/fPAKdK1qgX'
//window.location.search = ?sendmail=false
const getQuery =
!props.templateId &&
window.location?.search &&
window.location?.search?.split("?"); //['','sendmail=false']
//'sendmail=false'
let contactBookId = "";
const route = !props.templateId && window.location.pathname;
const getQuery = !props.templateId && window.location?.search?.split("?"); //['','sendmail=false']
if (getQuery) {
sendmail = getQuery[1].split("=")[1]; //false
sendmail = getQuery?.[1]?.split("=")[1]; //false
}
const checkSplit = route && route?.split("/"); // ['', 'load', 'recipientSignPdf', 'TOAVuhXbfw', 'fPAKdK1qgX']
if (checkSplit && checkSplit.length > 4) {
const routeId = route && route?.split("/"); // ['', 'load', 'recipientSignPdf', ':docId', ':contactBookId']
if (routeId && routeId.length > 4) {
// this condition will be occur only in guest flow in which load routeId will be include
isGuestSignFlow = true;
getDocId = checkSplit[3];
getDocId = routeId[3];
contactBookId = routeId[4];
} else {
getDocId = checkSplit[2];
// this condition will be occur only in if user is logged in and load routeId will be exclude
getDocId = routeId[2];
contactBookId = routeId?.[3] || "";
}
let getDocumentId = getDocId || documentId;
useEffect(() => {
@@ -243,7 +247,13 @@ function PdfRequestFiles(props) {
};
async function checkIsSubscribed(extUserId, contactId) {
const isGuestSign = isGuestSignFlow || false;
const res = await fetchSubscription(extUserId, contactId, isGuestSign);
const isPublic = props.templateId ? true : false;
const res = await fetchSubscription(
extUserId,
contactId,
isGuestSign,
isPublic
);
const plan = res.plan;
const billingDate = res?.billingDate;
const status = res?.status;
@@ -291,19 +301,21 @@ function PdfRequestFiles(props) {
}
}
);
const documentData =
templateDeatils.data && templateDeatils.data.result
? [templateDeatils.data.result]
: [];
const documentData = templateDeatils?.data?.result
? [templateDeatils?.data?.result]
: [];
if (documentData && documentData[0]?.error) {
props?.setTemplateStatus({
status: "Invalid"
});
props?.setTemplateStatus &&
props?.setTemplateStatus({
status: "Invalid"
});
throw new Error("error: Invalid TemplateId");
} else if (documentData && documentData.length > 0) {
if (documentData[0]?.IsPublic) {
props?.setTemplateStatus({
status: "Success"
});
props?.setTemplateStatus &&
props?.setTemplateStatus({
status: "Success"
});
const url =
documentData[0] &&
(documentData[0]?.SignedUrl || documentData[0]?.URL);
@@ -347,22 +359,34 @@ function PdfRequestFiles(props) {
isLoad: false
});
} else {
props?.setTemplateStatus({
status: "Private"
});
props?.setTemplateStatus &&
props?.setTemplateStatus({
status: "Private"
});
setIsLoading(false);
setHandleError(t("something-went-wrong-mssg"));
console.error("error: TemplateId is not public");
return;
}
} else {
props?.setTemplateStatus({
status: "Invalid"
});
props?.setTemplateStatus &&
props?.setTemplateStatus({
status: "Invalid"
});
setIsLoading(false);
setHandleError(t("something-went-wrong-mssg"));
console.error("error: Invalid TemplateId");
return;
}
} catch (err) {
console.log("err in get template details ", err);
setIsLoading(false);
if (err?.response?.data?.code === 101) {
setHandleError(t("error-template"));
} else {
setHandleError(t("something-went-wrong-mssg"));
}
console.error("error: Invalid TemplateId");
return;
}
};
//function for get document details for perticular signer with signer'object id
@@ -372,9 +396,10 @@ function PdfRequestFiles(props) {
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
// `currUserId` will be contactId or extUserId
let currUserId;
//getting document details
const documentData = await contractDocument(documentId || docId);
const documentData = await contractDocument(docId);
if (documentData && documentData.length > 0) {
const url =
documentData[0] &&
@@ -399,13 +424,13 @@ function PdfRequestFiles(props) {
const expireUpdateDate = new Date(expireDate).getTime();
const currDate = new Date().getTime();
const getSigners = documentData[0].Signers;
const getCurrentSigner =
getSigners &&
getSigners.filter(
(data) => data.UserId.objectId === jsonSender?.objectId
);
const getCurrentSigner = getSigners?.find(
(data) => data.UserId.objectId === jsonSender?.objectId
);
currUserId = getCurrentSigner[0] ? getCurrentSigner[0].objectId : "";
currUserId = getCurrentSigner?.objectId
? getCurrentSigner.objectId
: contactBookId || "";
if (isEnableSubscription) {
await checkIsSubscribed(
documentData[0]?.ExtUserPtr?.objectId,
@@ -428,10 +453,7 @@ function PdfRequestFiles(props) {
setIsCelebration(true);
setTimeout(() => setIsCelebration(false), 5000);
} else if (declined) {
const currentDecline = {
currnt: "another",
isDeclined: true
};
const currentDecline = { currnt: "another", isDeclined: true };
setIsDecline(currentDecline);
} else if (currDate > expireUpdateDate) {
const expireDateFormat = moment(new Date(expireDate)).format(
@@ -457,36 +479,24 @@ function PdfRequestFiles(props) {
}
}
}
const audittrailData =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
(data) => data.Activity === "Signed"
);
const audittrailData = documentData?.[0]?.AuditTrail?.filter(
(data) => data.Activity === "Signed"
);
const checkAlreadySign =
documentData[0].AuditTrail &&
documentData[0].AuditTrail.length > 0 &&
documentData[0].AuditTrail.filter(
documentData?.[0]?.AuditTrail?.some(
(data) =>
data.UserPtr?.objectId === currUserId &&
data?.UserPtr?.objectId === currUserId &&
data.Activity === "Signed"
);
if (
checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0
) {
) || false;
if (checkAlreadySign) {
setAlreadySign(true);
} else {
const obj = documentData?.[0];
setSendInOrder(obj?.SendinOrder || false);
if (
obj &&
obj.Signers &&
obj.Signers.length > 0 &&
obj.Placeholders &&
obj.Placeholders.length > 0
obj?.Signers?.length > 0 &&
obj?.Placeholders?.length > 0
) {
const params = {
event: "viewed",
@@ -502,7 +512,10 @@ function PdfRequestFiles(props) {
email: x?.Email,
phone: x?.Phone
})),
viewedBy: jsonSender.email,
viewedBy:
documentData?.[0].Signers?.find(
(x) => x.objectId === currUserId
)?.Email || jsonSender?.email,
viewedAt: new Date(),
createdAt: documentData?.[0].createdAt
}
@@ -583,40 +596,51 @@ function PdfRequestFiles(props) {
//checking if condition current user already sign or owner does not exist as a signer or document has been declined by someone or document has been expired
//then stop to display tour message
if (
(checkAlreadySign &&
checkAlreadySign[0] &&
checkAlreadySign.length > 0) ||
checkAlreadySign ||
!currUserId ||
declined ||
currDate > expireUpdateDate
) {
setRequestSignTour(true);
} else {
//else condition to check current user exist in contracts_Users class and check tour message status
//if not then check user exist in contracts_Contactbook class and check tour message statu
const res = await contractUsers();
if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
} else if (res[0] && res?.length) {
setContractName("_Users");
currUserId = res[0].objectId;
setSignerUserId(currUserId);
const tourData = res[0].TourStatus && res[0].TourStatus;
if (tourData && tourData.length > 0) {
const checkTourRequest = tourData.filter(
(data) => data?.requestSign
const isEnableOTP = documentData?.[0]?.IsEnableOTP || false;
if (!isEnableOTP) {
try {
const resContact = await axios.post(
`${localStorage.getItem("baseUrl")}functions/getcontact`,
{
contactId: currUserId
},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
}
}
);
setTourStatus(tourData);
setRequestSignTour(checkTourRequest[0]?.requestSign || false);
const contact = resContact?.data?.result;
setContractName("_Contactbook");
setSignerUserId(contact?.objectId);
const tourData = contact?.TourStatus && contact?.TourStatus;
if (tourData && tourData.length > 0) {
const checkTourRequest =
tourData?.some((data) => data?.requestSign) || false;
setTourStatus(tourData);
setRequestSignTour(checkTourRequest);
}
} catch (err) {
console.log("err while getting tourstatus", err);
}
} else if (res?.length === 0) {
const res = await contactBook(currUserId);
} else {
//else condition to check current user exist in contracts_Users class and check tour message status
//if not then check user exist in contracts_Contactbook class and check tour message status
const res = await contractUsers();
if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
} else if (res[0] && res.length) {
setContractName("_Contactbook");
const objectId = res[0].objectId;
setSignerUserId(objectId);
} else if (res[0] && res?.length) {
setContractName("_Users");
currUserId = res[0].objectId;
setSignerUserId(currUserId);
const tourData = res[0].TourStatus && res[0].TourStatus;
if (tourData && tourData.length > 0) {
const checkTourRequest = tourData.filter(
@@ -625,8 +649,25 @@ function PdfRequestFiles(props) {
setTourStatus(tourData);
setRequestSignTour(checkTourRequest[0]?.requestSign || false);
}
} else if (res.length === 0) {
setHandleError(t("user-not-exist"));
} else if (res?.length === 0) {
const res = await contactBook(currUserId);
if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
} else if (res[0] && res.length) {
setContractName("_Contactbook");
const objectId = res[0].objectId;
setSignerUserId(objectId);
const tourData = res[0].TourStatus && res[0].TourStatus;
if (tourData && tourData.length > 0) {
const checkTourRequest = tourData.filter(
(data) => data?.requestSign
);
setTourStatus(tourData);
setRequestSignTour(checkTourRequest[0]?.requestSign || false);
}
} else if (res.length === 0) {
setHandleError(t("user-not-exist"));
}
}
}
}
@@ -643,12 +684,12 @@ function PdfRequestFiles(props) {
setIsUiLoading({ isLoad: false });
}
//function to get default signatur eof current user from `contracts_Signature` class
const defaultSignRes = await getDefaultSignature(jsonSender.objectId);
const defaultSignRes = await getDefaultSignature(jsonSender?.objectId);
if (defaultSignRes?.status === "success") {
setDefaultSignImg(defaultSignRes?.res?.defaultSignature);
setMyInitial(defaultSignRes?.res?.defaultInitial);
} else if (defaultSignRes?.status === "error") {
setHandleError("Error: Something went wrong!");
const sign = defaultSignRes?.res?.defaultSignature || "";
const initials = defaultSignRes?.res?.defaultInitial || "";
setDefaultSignImg(sign);
setMyInitial(initials);
}
setIsLoading({ isLoad: false });
} catch (err) {
@@ -664,20 +705,30 @@ function PdfRequestFiles(props) {
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
let currentUser = JSON.parse(localuser);
let isEmailVerified = currentUser?.emailVerified;
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
//if emailVerified data is not present in local user details then fetch again in _User class
if (!currentUser?.emailVerified) {
const userQuery = new Parse.Query(Parse.User);
const getUser = await userQuery.get(currentUser?.objectId, {
sessionToken: currentUser?.sessionToken
});
if (getUser) {
currentUser = JSON.parse(JSON.stringify(getUser));
if (isEnableOTP) {
try {
if (!currentUser?.emailVerified) {
const userQuery = new Parse.Query(Parse.User);
const getUser = await userQuery.get(currentUser?.objectId, {
sessionToken:
currentUser?.sessionToken || localStorage.getItem("accesstoken")
});
if (getUser) {
currentUser = JSON.parse(JSON.stringify(getUser));
}
}
isEmailVerified = currentUser?.emailVerified;
setIsEmailVerified(isEmailVerified);
} catch (err) {
console.log("err in get email verification ", err);
setHandleError(t("something-went-wrong-mssg"));
}
}
let isEmailVerified = currentUser?.emailVerified;
//check if isEmailVerified then go on next step
if (isEmailVerified) {
setIsEmailVerified(isEmailVerified);
if (!isEnableOTP || isEmailVerified) {
try {
const checkUser = signerPos.filter(
(data) => data.signerObjId === signerObjectId
@@ -858,17 +909,9 @@ function PdfRequestFiles(props) {
// console.log("pdfte", pdfBytes);
//get ExistUserPtr object id of user class to get tenantDetails
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
//get ExistUserPtr email to get userDetails
const res = await contractUsers();
let activeMailAdapter = "";
if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({ isLoad: false });
} else if (!res || res?.length === 0) {
activeMailAdapter = "";
} else if (res[0] && res.length) {
activeMailAdapter = res[0]?.active_mail_adapter;
}
let activeMailAdapter =
pdfDetails?.[0]?.ExtUserPtr?.active_mail_adapter;
//function for call to embed signature in pdf and get digital signature pdf
const resSign = await signPdfFun(
pdfBytes,
@@ -880,13 +923,13 @@ function PdfRequestFiles(props) {
widgets
);
if (resSign && resSign.status === "success") {
setPdfUrl(res.data);
setPdfUrl(resSign.data);
setIsSigned(true);
setSignedSigners([]);
setUnSignedSigners([]);
getDocumentDetails(true);
const index = pdfDetails?.[0].Signers.findIndex(
(x) => x.Email === currentUser?.email
getDocumentDetails(documentId, true);
const index = pdfDetails?.[0]?.Signers.findIndex(
(x) => x.objectId === signerObjectId
);
const newIndex = index + 1;
const usermail = {
@@ -1054,21 +1097,6 @@ function PdfRequestFiles(props) {
alertMessage: t("something-went-wrong-mssg")
});
}
} else {
//else verify users email
try {
const userQuery = new Parse.Query(Parse.User);
const user = await userQuery.get(currentUser?.objectId, {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
isEmailVerified = user?.get("emailVerified");
setIsEmailVerified(isEmailVerified);
}
} catch (e) {
console.log("error in save user's emailVerified in user class");
setHandleError(t("something-went-wrong-mssg"));
}
}
}
@@ -1216,31 +1244,26 @@ function PdfRequestFiles(props) {
);
const jsonSender = JSON.parse(senderUser);
setIsDecline({ isDeclined: false });
const data = {
IsDeclined: true,
DeclineReason: reason,
DeclineBy: {
__type: "Pointer",
className: "_User",
objectId: jsonSender?.objectId
}
};
setIsUiLoading(true);
const email =
pdfDetails?.[0].Signers?.find((x) => x.objectId === signerObjectId)
?.Email || jsonSender?.email;
const userId =
pdfDetails?.[0].Signers?.find((x) => x.objectId === signerObjectId)
?.UserId?.objectId || jsonSender?.objectId;
const params = {
docId: pdfDetails?.[0].objectId,
reason: reason,
userId: userId
};
await axios
.put(
`${localStorage.getItem(
"baseUrl"
)}classes/contracts_Document/${documentId}`,
data,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
.post(`${localStorage.getItem("baseUrl")}functions/declinedoc`, params, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
)
})
.then(async (result) => {
const res = result.data;
if (res) {
@@ -1260,7 +1283,8 @@ function PdfRequestFiles(props) {
email: x?.Email,
phone: x?.Phone
})),
declinedBy: jsonSender.email,
declinedBy: email,
declinedReason: reason,
declinedAt: new Date(),
createdAt: pdfDetails?.[0].createdAt
}
@@ -1286,7 +1310,11 @@ function PdfRequestFiles(props) {
.catch((err) => {
console.log("error updating field is decline ", err);
setIsUiLoading(false);
alert(t("something-went-wrong-mssg"));
setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
});
};
//function to add default signature for all requested placeholder of sign
@@ -1314,38 +1342,58 @@ function PdfRequestFiles(props) {
const closeRequestSignTour = async () => {
setRequestSignTour(true);
if (isDontShow) {
let updatedTourStatus = [];
if (tourStatus.length > 0) {
updatedTourStatus = [...tourStatus];
const requestSignIndex = tourStatus.findIndex(
(obj) => obj["requestSign"] === false || obj["requestSign"] === true
);
if (requestSignIndex !== -1) {
updatedTourStatus[requestSignIndex] = { requestSign: true };
} else {
updatedTourStatus.push({ requestSign: true });
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
if (!isEnableOTP) {
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
{
contactId: signerObjectId
},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
}
}
);
} catch (e) {
console.log("update tour messages error", e);
}
} else {
updatedTourStatus = [{ requestSign: true }];
}
try {
await axios.put(
`${localStorage.getItem(
"baseUrl"
)}classes/contracts${contractName}/${signerUserId}`,
{
TourStatus: updatedTourStatus
},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
let updatedTourStatus = [];
if (tourStatus.length > 0) {
updatedTourStatus = [...tourStatus];
const requestSignIndex = tourStatus.findIndex(
(obj) => obj["requestSign"] === false || obj["requestSign"] === true
);
if (requestSignIndex !== -1) {
updatedTourStatus[requestSignIndex] = { requestSign: true };
} else {
updatedTourStatus.push({ requestSign: true });
}
);
} catch (e) {
console.log("update tour messages error", e);
} else {
updatedTourStatus = [{ requestSign: true }];
}
try {
await axios.put(
`${localStorage.getItem(
"baseUrl"
)}classes/contracts${contractName}/${signerUserId}`,
{
TourStatus: updatedTourStatus
},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
} catch (e) {
console.log("update tour messages error", e);
}
}
}
};
@@ -1467,11 +1515,32 @@ function PdfRequestFiles(props) {
await SendOtp();
} else {
console.log("error in public-sign to create user details");
alert(t("something-went-wrong-mssg"));
setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
} catch (e) {
console.log("e", e);
// setIsLoader(false);
if (
e?.response?.data?.error === "Insufficient Credit" ||
e?.response?.data?.error === "Plan expired"
) {
handleCloseOtp();
setIsAlert({
title: t("insufficient-credits"),
isShow: true,
alertMessage: t("insufficient-credits-mssg")
});
} else {
handleCloseOtp();
setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
}
};
@@ -1503,7 +1572,11 @@ function PdfRequestFiles(props) {
}
} catch (error) {
console.log("error in verify otp in public-sign", error);
alert(t("something-went-wrong-mssg"));
setIsAlert({
title: "Error",
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
};
@@ -1602,6 +1675,17 @@ function PdfRequestFiles(props) {
</div>
);
};
const AgreementTour = [
{
selector: '[data-tut="IsAgree"]',
content: "Please agree & continue to start signing",
position: "top",
style: { fontSize: "13px" }
}
];
const handleCloseAgreeTour = () => {
setIsAgreeTour(false);
};
return (
<DndProvider backend={HTML5Backend}>
<Title title={props.templateId ? "Public Sign" : "Request Sign"} />
@@ -1624,7 +1708,23 @@ function PdfRequestFiles(props) {
) : handleError ? (
<HandleError handleError={handleError} />
) : (
<div>
<div onClick={() => !isAgree && setIsAgreeTour(true)}>
{!isAgree && (
<Agreement
setIsAgree={setIsAgree}
setIsAgreeTour={setIsAgreeTour}
/>
)}
<Tour
showNumber={false}
showNavigation={false}
showNavigationNumber={false}
onRequestClose={handleCloseAgreeTour}
steps={AgreementTour}
isOpen={isAgreeTour}
rounded={5}
closeWithMask={false}
/>
{isUiLoading && (
<div className="absolute h-[100vh] w-full flex flex-col justify-center items-center z-[999] bg-[#e6f2f2] bg-opacity-80">
<Loader />
@@ -1638,9 +1738,12 @@ function PdfRequestFiles(props) {
<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
style={{
pointerEvents:
@@ -1650,33 +1753,13 @@ function PdfRequestFiles(props) {
: "auto"
}}
className={`${
props.templateId && "m-1 border-[0.5px] border-gray-300"
(props.templateId || isGuestSignFlow) &&
"mx-2 border-[0.5px] border-gray-300"
} relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300`}
>
{!requestSignTour &&
signerObjectId &&
requestSignTourFunction()}
<ModalUi
isOpen={isAlert.isShow}
title={t("alert-message")}
handleClose={() =>
setIsAlert({ isShow: false, alertMessage: "" })
}
>
<div className="h-full p-[20px]">
<p>{isAlert.alertMessage}</p>
<button
onClick={() =>
setIsAlert({ isShow: false, alertMessage: "" })
}
type="button"
className="op-btn op-btn-primary mt-3 px-4"
>
{t("ok")}
</button>
</div>
</ModalUi>
<Tour
showNumber={false}
showNavigation={false}
@@ -1691,11 +1774,7 @@ function PdfRequestFiles(props) {
{/* this modal is used to show decline alert */}
<PdfDeclineModal
show={isDecline.isDeclined}
headMsg={
pdfDetails[0]?.IsDeclined
? t("document-declined")
: t("document-decline")
}
headMsg={t("document-declined")}
bodyMssg={
isDecline.currnt === "Sure"
? t("decline-alert-1")
@@ -1938,7 +2017,12 @@ function PdfRequestFiles(props) {
<div className="flex mt-4 gap-1 px-[15px]">
<button
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading)
handleToPrint(
e,
pdfUrl,
setIsDownloading,
pdfDetails
)
}
type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-neutral"
@@ -1972,13 +2056,13 @@ function PdfRequestFiles(props) {
<button
type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-primary"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
onClick={() => {
setIsCompleted((prev) => ({
...prev,
isModal: false
}));
setIsDownloadModal(true);
}}
>
<i
className="fa-light fa-download"
@@ -2058,6 +2142,7 @@ function PdfRequestFiles(props) {
clickOnZoomOut={clickOnZoomOut}
isDisableRotate={true}
templateId={props.templateId}
setIsDownloadModal={setIsDownloadModal}
/>
<div ref={divRef} data-tut="pdfArea" className="h-[95%]">
@@ -2184,6 +2269,28 @@ function PdfRequestFiles(props) {
</button>
</div>
</ModalUi>
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal}
pdfDetails={pdfDetails}
isDocId={true}
/>
<ModalUi
isOpen={isAlert.isShow}
title={isAlert?.title || t("alert-message")}
handleClose={() => setIsAlert({ isShow: false, alertMessage: "" })}
>
<div className="h-full p-[20px]">
<p>{isAlert.alertMessage}</p>
<button
onClick={() => setIsAlert({ isShow: false, alertMessage: "" })}
type="button"
className="op-btn op-btn-primary mt-3 px-4"
>
{t("close")}
</button>
</div>
</ModalUi>
</>
)}
</DndProvider>
+13 -14
View File
@@ -11,8 +11,6 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import WidgetComponent from "../components/pdf/WidgetComponent";
import Tour from "reactour";
import { useLocation, useParams } from "react-router-dom";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import SignerListPlace from "../components/pdf/SignerListPlace";
import Header from "../components/pdf/PdfHeader";
import { RWebShare } from "react-web-share";
@@ -45,23 +43,25 @@ import {
import RenderPdf from "../components/pdf/RenderPdf";
import { useNavigate } from "react-router-dom";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import LinkUserModal from "../primitives/LinkUserModal";
import Title from "../components/Title";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import WidgetNameModal from "../components/pdf/WidgetNameModal";
import { SaveFileSize } from "../constant/saveFileSize";
import { EmailBody } from "../components/pdf/EmailBody";
import Upgrade from "../primitives/Upgrade";
import Alert from "../primitives/Alert";
import Loader from "../primitives/Loader";
import { useSelector } from "react-redux";
import PdfZoom from "../components/pdf/PdfZoom";
import LottieWithLoader from "../primitives/DotLottieReact";
import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
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";
import LinkUserModal from "../primitives/LinkUserModal";
import QuotaCard from "../primitives/QuotaCard";
import LottieWithLoader from "../primitives/DotLottieReact";
import Alert from "../primitives/Alert";
import Upgrade from "../primitives/Upgrade";
function PlaceHolderSign() {
const { t } = useTranslation();
@@ -1047,7 +1047,7 @@ function PlaceHolderSign() {
type="button"
className="flex flex-row items-center op-link op-link-primary"
>
<i className="fa-light fa-link" aria-hidden="true"></i>
<i className="fa-light fa-copy" />
<span className=" hidden md:block ml-1 ">{t("copy-link")}</span>
</button>
<RWebShare data={{ url: data.url, title: t("sign-url") }}>
@@ -1250,7 +1250,7 @@ function PlaceHolderSign() {
selector: '[data-tut="addWidgets"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.placeholder-sign-3")}
message={t("tour-mssg.placeholder-sign-4")}
isChecked={handleDontShow}
/>
),
@@ -1261,19 +1261,18 @@ function PlaceHolderSign() {
selector: '[data-tut="pdfArea"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.placeholder-sign-4")}
message={t("tour-mssg.placeholder-sign-5")}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
},
{
selector: '[data-tut="headerArea"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.placeholder-sign-5")}
message={t("tour-mssg.placeholder-sign-6")}
isChecked={handleDontShow}
/>
),
+2 -2
View File
@@ -121,9 +121,9 @@ const PlanSubscriptions = () => {
};
const handleLogout = async () => {
try {
Parse?.User?.logOut();
await Parse?.User?.logOut();
} catch (err) {
console.log("Err", err);
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
+21 -6
View File
@@ -4,7 +4,6 @@ import "../styles/signature.css";
import Parse from "parse";
import Confetti from "react-confetti";
import axios from "axios";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
@@ -42,21 +41,23 @@ import {
import { useParams } from "react-router-dom";
import Tour from "reactour";
import Signedby from "../components/pdf/Signedby";
import HandleError from "../primitives/HandleError";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Title from "../components/Title";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import { useSelector } from "react-redux";
import TextFontSetting from "../components/pdf/TextFontSetting";
import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
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.
function SignYourSelf() {
const { t } = useTranslation();
@@ -130,6 +131,7 @@ function SignYourSelf() {
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 nodeRef = useRef(null);
const [, drop] = useDrop({
@@ -1190,7 +1192,12 @@ function SignYourSelf() {
)}
{isCelebration && (
<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 className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300">
@@ -1327,6 +1334,7 @@ function SignYourSelf() {
setIsAlert={setIsAlert}
extUserId={extUserId}
activeMailAdapter={activeMailAdapter}
setIsDownloadModal={setIsDownloadModal}
/>
{/* pdf header which contain funish back button */}
<Header
@@ -1346,6 +1354,7 @@ function SignYourSelf() {
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
widgetsDetails={xyPostion}
setIsDownloadModal={setIsDownloadModal}
/>
<div ref={divRef} data-tut="reactourSecond" className="h-full">
{containerWH?.width && containerWH?.height && (
@@ -1450,6 +1459,12 @@ function SignYourSelf() {
setIsRotate={setIsRotate}
handleRemoveWidgets={handleRemoveWidgets}
/>
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal}
pdfDetails={pdfDetails}
isDocId={true}
/>
<TextFontSetting
isTextSetting={isTextSetting}
setIsTextSetting={setIsTextSetting}
@@ -9,8 +9,6 @@ import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd";
import WidgetComponent from "../components/pdf/WidgetComponent";
import Tour from "reactour";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import SignerListPlace from "../components/pdf/SignerListPlace";
import Header from "../components/pdf/PdfHeader";
import WidgetNameModal from "../components/pdf/WidgetNameModal";
@@ -36,18 +34,21 @@ import {
import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css";
import Title from "../components/Title";
import LinkUserModal from "../primitives/LinkUserModal";
import EditTemplate from "../components/pdf/EditTemplate";
import ModalUi from "../primitives/ModalUi";
import AddRoleModal from "../components/pdf/AddRoleModal";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import Parse from "parse";
import { useSelector } from "react-redux";
import PdfZoom from "../components/pdf/PdfZoom";
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 { t } = useTranslation();
const navigate = useNavigate();
@@ -779,6 +780,7 @@ const TemplatePlaceholder = () => {
AutomaticReminders: pdfDetails[0]?.AutomaticReminders,
RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery),
NextReminderDate: pdfDetails[0]?.NextReminderDate,
IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false,
URL: pdfUrl
};
const updateTemplate = new Parse.Object("contracts_Template");
+219 -55
View File
@@ -9,7 +9,7 @@ import Tooltip from "../primitives/Tooltip";
import AddUser from "../components/AddUser";
import SubscribeCard from "../primitives/SubscribeCard";
import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribed } from "../constant/Utils";
import { fetchSubscriptionInfo } from "../constant/Utils";
import Title from "../components/Title";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next";
@@ -19,7 +19,11 @@ const UserList = () => {
const { t } = useTranslation();
const [userList, setUserList] = useState([]);
const [isLoader, setIsLoader] = useState(false);
const [isModal, setIsModal] = useState(false);
const [isModal, setIsModal] = useState({
form: false,
addseats: false,
options: false
});
const location = useLocation();
const isDashboard =
location?.pathname === "/dashboard/35KBoSgoAK" ? true : false;
@@ -27,9 +31,16 @@ const UserList = () => {
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isActiveModal, setIsActiveModal] = useState({});
const [isActLoader, setIsActLoader] = useState({});
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: false });
const [isSubscribe, setIsSubscribe] = useState({
plan: "",
isValid: false,
priceperUser: 0
});
const [isAdmin, setIsAdmin] = useState(false);
const [formHeader, setFormHeader] = useState(t("add-user"));
const [usersCount, setUserCounts] = useState({ allowed: 0, totalAllowed: 0 });
const [amount, setAmount] = useState({ quantity: 1, price: 0 });
const [isBuyLoader, setIsBuyLoader] = useState(false);
const recordperPage = 10;
const startIndex = (currentPage - 1) * recordperPage; // user per page
@@ -81,7 +92,6 @@ const UserList = () => {
pages.push(lastPageIndex);
}
}
return pages;
};
const pageNumbers = getPaginationRange();
@@ -96,13 +106,43 @@ const UserList = () => {
async function fetchUserList() {
try {
setIsLoader(true);
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (isEnableSubscription) {
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe);
const subscribe = await fetchSubscriptionInfo();
if (subscribe?.plan_code?.includes("team")) {
const isSupAdmin =
subscribe?.adminId && extUser?.objectId === subscribe?.adminId;
setIsSubscribe({
plan: subscribe.plan_code,
isValid: true,
adminId: subscribe.adminId,
priceperUser: subscribe.price,
isSuperAdmin: isSupAdmin
});
setAmount((prev) => ({ ...prev, price: subscribe.price }));
try {
const res = await Parse.Cloud.run("allowedusers");
setUserCounts((obj) => ({
...obj,
allowed: parseInt(res),
totalAllowed: parseInt(subscribe?.totalAllowedUser) || 0
}));
} catch (err) {
console.log("err while get users", err);
}
} else {
setIsSubscribe({
plan: subscribe.plan_code,
isValid: false,
adminId: subscribe.adminId
});
}
} else {
setIsSubscribe({ plan: "teams-yearly", isValid: true });
}
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (extUser) {
const admin =
extUser?.UserRole &&
@@ -121,14 +161,12 @@ const UserList = () => {
console.log("Err in fetch userlist", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => {
setIsAlert({ type: "success", msg: "" });
}, 1500);
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
setIsLoader(false);
}
}
const handleFormModal = () => {
setIsModal(!isModal);
const handleModal = (modalName) => {
setIsModal((obj) => ({ ...obj, [modalName]: !obj[modalName] }));
};
// Change page
@@ -146,7 +184,13 @@ const UserList = () => {
};
const handleUserData = (userData) => {
setUserList((prev) => [userData, ...prev]);
if (userData) {
setUserCounts((obj) => ({
...obj,
allowed: obj?.allowed > 0 ? obj.allowed - 1 : 1
}));
setUserList((prev) => [userData, ...prev]);
}
};
// `formatRow` is used to show data in poper manner like
// if data is of array type then it will join array items with ","
@@ -162,9 +206,8 @@ const UserList = () => {
return "-";
}
};
const handleClose = () => {
setIsActiveModal({});
};
const handleClose = () => setIsActiveModal({});
const handleToggleSubmit = async (user) => {
const index = userList.findIndex((obj) => obj.objectId === user.objectId);
if (index !== -1) {
@@ -196,6 +239,49 @@ const UserList = () => {
const handleToggleBtn = (user) => {
setIsActiveModal({ [user.objectId]: true });
};
const handleAddOnSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsBuyLoader(true);
try {
const resAddon = await Parse.Cloud.run("buyaddonusers", {
users: parseInt(amount.quantity)
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setUserCounts((obj) => ({
...obj,
allowed: parseInt(obj.allowed) + parseInt(amount.quantity),
totalAllowed: parseInt(_resAddon.addon)
}));
setAmount((obj) => ({ ...obj, quantity: 1 }));
}
}
} catch (err) {
console.log("Err in buy addon", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
setIsBuyLoader(false);
handleModal("addseats");
}
};
const handlePricePerUser = (e) => {
const quantity = e.target.value;
const price = e.target?.value > 0 ? isSubscribe.priceperUser * quantity : 0;
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleBuyUsers = (allowed, totalAllowed) => {
if (allowed && totalAllowed) {
setUserCounts((obj) => ({
...obj,
allowed: parseInt(obj.allowed) + parseInt(allowed),
totalAllowed: parseInt(totalAllowed)
}));
}
};
return (
<div className="relative">
<Title title={isAdmin ? "Users" : "Page not found"} />
@@ -205,7 +291,7 @@ const UserList = () => {
</div>
)}
{Object.keys(isActLoader)?.length > 0 && (
<div className="absolute w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-30 rounded-box">
<div className="absolute w-full h-full flex justify-center items-center bg-black/30 z-30 rounded-box">
<Loader />
</div>
)}
@@ -216,19 +302,46 @@ const UserList = () => {
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<div className="font-light">
{t("report-name.Users")}
{t("report-name.Users")}{" "}
<span className="text-xs md:text-[13px] font-normal">
<Tooltip message={t("users-from-teams")} />
</span>
</div>
<div
className="cursor-pointer"
onClick={() => handleFormModal()}
>
<i className="fa-light fa-square-plus text-accent text-[40px]"></i>
<div className="flex flex-row gap-2 items-center">
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
<div
className="hidden md:flex op-btn op-btn-sm h-[35px] op-btn-primary op-btn-outline text-xs mb-0.5"
onClick={() => handleModal("addseats")}
>
{t("buy-users")}
</div>
)}
<div
className="cursor-pointer"
onClick={() => handleModal("form")}
>
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
</div>
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
<div
className="cursor-pointer relative md:hidden mb-0.5"
onClick={() => handleModal("options")}
>
<i className="fa-light fa-ellipsis-vertical fa-xl"></i>
{isModal?.options && (
<ul className="absolute -right-3 top-auto z-[70] w-max op-menu op-menu-xs shadow bg-base-100 text-base-content rounded-box border">
<li onClick={() => handleModal("addseats")}>
<span className="text-[13px] capitalize font-medium">
{t("buy-users")}
</span>
</li>
</ul>
)}
</div>
)}
</div>
</div>
<div className={` overflow-x-auto w-full`}>
<div className="overflow-x-auto w-full">
<table className="op-table border-collapse w-full">
<thead className="text-[14px]">
<tr className="border-y-[1px]">
@@ -313,34 +426,42 @@ const UserList = () => {
</tbody>
</table>
</div>
<div className="op-join flex flex-wrap items-center p-2">
{userList.length > recordperPage && (
<button
onClick={() => paginateBack()}
className="op-join-item op-btn op-btn-sm"
>
{t("prev")}
</button>
)}
{pageNumbers.map((x, i) => (
<button
key={i}
onClick={() => setCurrentPage(x)}
disabled={x === "..."}
className={`${
x === currentPage ? "op-btn-active" : ""
} op-join-item op-btn op-btn-sm`}
>
{x}
</button>
))}
{userList.length > recordperPage && (
<button
onClick={() => paginateFront()}
className="op-join-item op-btn op-btn-sm"
>
{t("next")}
</button>
<div className="flex flex-row justify-between items-center text-xs font-medium">
<div className="op-join flex flex-wrap items-center p-2">
{userList.length > recordperPage && (
<button
onClick={() => paginateBack()}
className="op-join-item op-btn op-btn-sm"
>
{t("prev")}
</button>
)}
{pageNumbers.map((x, i) => (
<button
key={i}
onClick={() => setCurrentPage(x)}
disabled={x === "..."}
className={`${
x === currentPage ? "op-btn-active" : ""
} op-join-item op-btn op-btn-sm`}
>
{x}
</button>
))}
{userList.length > recordperPage && (
<button
onClick={() => paginateFront()}
className="op-join-item op-btn op-btn-sm"
>
{t("next")}
</button>
)}
</div>
{isEnableSubscription && isSubscribe?.isSuperAdmin && (
<div>
{t("available-seats")}: {usersCount.allowed}/
{usersCount.totalAllowed}
</div>
)}
</div>
{userList?.length <= 0 && (
@@ -362,17 +483,60 @@ const UserList = () => {
</div>
)}
<ModalUi
isOpen={isModal}
isOpen={isModal.form}
title={formHeader}
handleClose={handleFormModal}
handleClose={() => handleModal("form")}
>
<AddUser
setIsAlert={setIsAlert}
handleUserData={handleUserData}
closePopup={handleFormModal}
handleBuyUsers={handleBuyUsers}
closePopup={() => handleModal("form")}
setFormHeader={setFormHeader}
/>
</ModalUi>
<ModalUi
isOpen={isModal.addseats}
title="Add Seats"
handleClose={() => handleModal("addseats")}
>
{isBuyLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<form onSubmit={handleAddOnSubmit} className=" px-3 pb-6 pt-6">
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("Quantity-of-users")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="number"
name="quantity"
value={amount.quantity}
onChange={(e) => handlePricePerUser(e)}
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
required
/>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {isSubscribe.priceperUser})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full">
{t("Proceed")}
</button>
</form>
</ModalUi>
</div>
) : (
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
+1 -1
View File
@@ -596,7 +596,7 @@ function UserProfile() {
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() => copytoclipboard()}
>
<i className="fa-light fa-link"></i>{" "}
<i className="fa-light fa-copy" />
</button>
</div>
</div>
+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"
} `}
>
<div className={`op-alert ${textcolor}`}>
<span className="ml-3">{children}</span>
<div className={`op-alert ${textcolor} flex justify-center`}>
<span className="px-1">{children}</span>
</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;
+211 -156
View File
@@ -14,6 +14,7 @@ import {
checkIsSubscribed,
copytoData,
fetchUrl,
getSignedUrl,
replaceMailVaribles
} from "../constant/Utils";
import Confetti from "react-confetti";
@@ -30,6 +31,8 @@ import SubscribeCard from "./SubscribeCard";
import { validplan } from "../json/plansArr";
import { serverUrl_fn } from "../constant/appinfo";
import { useTranslation } from "react-i18next";
import DownloadPdfZip from "./DownloadPdfZip";
import EmbedTab from "../components/pdf/EmbedTab";
const ReportTable = (props) => {
const { t } = useTranslation();
@@ -69,7 +72,10 @@ const ReportTable = (props) => {
const [publicUserName, setIsPublicUserName] = useState("");
const [isViewShare, setIsViewShare] = useState({});
const [isSubscribe, setIsSubscribe] = useState(true);
const [isModal, setIsModal] = useState({});
const [reason, setReason] = useState("");
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [isEmbed, setIsEmbed] = useState(false);
const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class);
const startIndex = (currentPage - 1) * props.docPerPage;
@@ -266,7 +272,8 @@ const ReportTable = (props) => {
Signers: signers,
SendinOrder: Doc?.SendinOrder || false,
AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5,
IsEnableOTP: Doc?.IsEnableOTP || false
};
try {
const res = await axios.post(
@@ -364,6 +371,8 @@ const ReportTable = (props) => {
setIsShareWith({ [item.objectId]: true });
} else if (act.action === "Embed") {
handleEmbedFunction(item);
} else if (act.action === "CopyTemplateId") {
copyTemplateId(item.objectId);
}
};
// Get current list
@@ -515,7 +524,6 @@ const ReportTable = (props) => {
.then(async (result) => {
const res = result.data;
if (res) {
setReason("");
setActLoader({});
setIsAlert(true);
setAlertMsg({
@@ -527,7 +535,43 @@ const ReportTable = (props) => {
(x) => x.objectId !== item.objectId
);
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) => {
console.log("err", err);
@@ -612,10 +656,15 @@ const ReportTable = (props) => {
setActLoader({ [`${item.objectId}`]: true });
const url = item?.SignedUrl || item?.URL || "";
const pdfName = item?.Name || "exported_file";
const isCompleted = item?.IsCompleted || false;
if (url) {
try {
const signedUrl = await Parse.Cloud.run("getsignedurl", { url: url });
await fetchUrl(signedUrl, pdfName);
if (isCompleted) {
setIsDownloadModal({ [item.objectId]: true });
} else {
const signedUrl = await getSignedUrl(url);
await fetchUrl(signedUrl, pdfName);
}
setActLoader({});
} catch (err) {
console.log("err in getsignedurl", err);
@@ -741,7 +790,7 @@ const ReportTable = (props) => {
sessionToken: localStorage.getItem("accesstoken")
};
let params = {
mailProvider: doc?.ExtUserPtr?.active_mail_adapter,
mailProvider: doc?.ExtUserPtr?.active_mail_adapter || "",
extUserId: doc?.ExtUserPtr?.objectId,
recipient: userDetails?.Email,
subject: mail.subject,
@@ -1076,12 +1125,14 @@ const ReportTable = (props) => {
}
};
const handleEmbedFunction = async (item) => {
setIsEmbed(true);
setIsPublicProfile({
[item.objectId]: props.isPublic[item.objectId]
[item.objectId]: true
});
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
setIsPublicUserName(extendUser[0]?.UserName || "");
};
return (
<div className="relative">
{Object.keys(actLoader)?.length > 0 && (
@@ -1089,10 +1140,15 @@ const ReportTable = (props) => {
<Loader />
</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 && (
<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>
)}
{isAlert && <Alert type={alertMsg.type}>{alertMsg.message}</Alert>}
@@ -1132,8 +1188,8 @@ const ReportTable = (props) => {
</div>
<div
className={`${
isDashboard && props.List?.length > 0 ? "h-[317px]" : "h-full"
} w-full`}
isDashboard && props.List?.length > 0 ? "min-h-[317px]" : "h-full"
} overflow-auto w-full`}
>
<table className="op-table border-collapse w-full ">
<thead className="text-[14px]">
@@ -1169,48 +1225,50 @@ const ReportTable = (props) => {
</td>
<td className="px-4 py-2 ">{item?.Email || "-"}</td>
<td className="px-4 py-2">{item?.Phone || "-"}</td>
<td className="px-3 py-2 text-white grid grid-cols-2">
{props.actions?.length > 0 &&
props.actions.map((act, index) => (
<button
key={index}
onClick={() => handleActionBtn(act, item)}
title={t(`btnLabel.${act.hoverLabel}`)}
className={`${
act?.btnColor ? act.btnColor : ""
} op-btn op-btn-sm`}
<td className="px-3 py-2">
<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.map((act, index) => (
<button
key={index}
onClick={() => handleActionBtn(act, item)}
title={t(`btnLabel.${act.hoverLabel}`)}
className={`${
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>
</button>
))}
{isDeleteModal[item.objectId] && (
<ModalUi
isOpen
title={"Delete Contact"}
handleClose={handleClose}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("contact-delete-alert")}
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("contact-delete-alert")}
</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"
>
{t("no")}
</button>
</div>
</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"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
)}
</ModalUi>
)}
</div>
</td>
</tr>
) : (
@@ -1252,9 +1310,55 @@ const ReportTable = (props) => {
{item?.URL ? t("download") : "-"}
</button>
</td>
<td className="px-4 py-2">
{formatRow(item?.ExtUserPtr)}
</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">
{!item?.IsSignyourself && item?.Placeholders ? (
<button
@@ -1272,11 +1376,7 @@ const ReportTable = (props) => {
<td className=" pl-[20px] py-2">
{props.ReportName === "Templates" && (
<div className="flex flex-row">
<label
className={
"cursor-pointer relative inline-flex items-center mb-0"
}
>
<label className="cursor-pointer relative inline-flex items-center mb-0">
<input
checked={props.isPublic?.[item.objectId]}
onChange={(e) =>
@@ -1352,22 +1452,27 @@ const ReportTable = (props) => {
<ModalUi
isOpen
title={t("public-url")}
handleClose={() => setIsPublicProfile({})}
handleClose={() => {
setIsPublicProfile({});
setIsEmbed(false);
}}
>
<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">
<p>{t("public-url-copy-mssg")}</p>
<div className=" flex items-center justify-between gap-3 mt-2 p-[2px] ">
<span>{t("public-url-copy")}</span>
<div className=" flex items-center justify-between gap-3 p-[2px] ">
<div className="w-[280px] whitespace-nowrap overflow-hidden text-ellipsis">
<span className="font-bold">
{t("public-url")} :
</span>
<span
onClick={() =>
copytoProfileLink()
}
className="ml-[2px] underline underline-offset-2 cursor-pointer"
className="ml-[2px] underline underline-offset-2 cursor-pointer text-blue-800"
>
{publicUrl()}
</span>
@@ -1392,7 +1497,7 @@ const ReportTable = (props) => {
copytoProfileLink()
}
>
<i className="fa-light fa-link"></i>{" "}
<i className="fa-light fa-copy" />
<span className="hidden md:inline-block">
{copied["publicprofile"]
? t("copied")
@@ -1401,36 +1506,10 @@ const ReportTable = (props) => {
</button>
</div>
</div>
<div className=" flex items-center justify-between gap-3 mt-2 p-[2px] ">
<div>
<span className="font-bold">
{t("templateid")} :
</span>
<span
onClick={() =>
copyTemplateId(item.objectId)
}
className="underline ml-[2px] underline-offset-2 w-[150px] md:w-[350px] whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer"
>
{item.objectId}
</span>
</div>
<div className="flex items-center gap-2">
<button
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() =>
copyTemplateId(item.objectId)
}
>
<i className="fa-light fa-link"></i>{" "}
<span className="hidden md:inline-block">
{copied["templateid"]
? t("copied")
: t("copy")}
</span>
</button>
</div>
</div>
<p className="text-[13px] mt-[5px]">
{t("public-url-copy-mssg")}
</p>
<EmbedTab templateId={item.objectId} />
</div>
) : (
<div className="font-normal text-black">
@@ -1449,7 +1528,7 @@ const ReportTable = (props) => {
</td>
)}
<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.map((act, index) =>
props.ReportName === "Templates" ? (
@@ -1490,65 +1569,33 @@ const ReportTable = (props) => {
{isOption[item.objectId] &&
act.action === "option" && (
<ul className="absolute -right-1 top-auto z-[70] w-max op-dropdown-content op-menu shadow bg-base-100 text-base-content rounded-box">
{act.subaction?.map((subact) =>
props.isPublic[
item.objectId
] ? (
<li
key={subact.btnId}
onClick={() =>
handleActionBtn(
subact,
item
)
}
title={t(
`btnLabel.${subact.btnLabel}`
)}
>
<span>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
)}
</span>
</li>
) : (
subact.action !== "Embed" && (
<li
key={subact.btnId}
onClick={() =>
handleActionBtn(
subact,
item
)
}
title={t(
`btnLabel.${subact.btnLabel}`
)}
>
<span>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
{act.subaction?.map((subact) => (
<li
key={subact.btnId}
onClick={() =>
handleActionBtn(
subact,
item
)
}
title={t(
`btnLabel.${subact.btnLabel}`
)}
>
<span>
<i
className={`${subact.btnIcon} mr-1.5`}
></i>
{subact.btnLabel && (
<span className="text-[13px] capitalize font-medium">
{t(
`btnLabel.${subact.btnLabel}`
)}
</span>
</li>
)
)
)}
)}
</span>
</li>
))}
</ul>
)}
</div>
@@ -1869,7 +1916,7 @@ const ReportTable = (props) => {
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
onClick={() => copytoclipboard(share)}
>
<i className="fa-light fa-link"></i>{" "}
<i className="fa-light fa-copy" />
{copied[share.email]
? t("copied")
: t("copy")}
@@ -2023,6 +2070,14 @@ const ReportTable = (props) => {
</div>
</ModalUi>
)}
{isDownloadModal[item.objectId] && (
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal[item.objectId]}
pdfDetails={[item]}
isDocId={false}
/>
)}
</td>
</tr>
)
@@ -41,8 +41,8 @@ function CustomModal(props) {
className="op-btn op-btn-primary mr-2 px-6"
type="button"
onClick={() => {
setReason("");
props.declineDoc(reason);
setReason("");
}}
>
{t("yes")}
@@ -24,7 +24,7 @@ export default function TourContentWithBtn({ message, isChecked, video }) {
></iframe>
</div>
)}
<label className="flex items-center justify-center mb-0">
<label className="flex items-center justify-center mt-3 mb-0">
<input
type="checkbox"
className="op-checkbox op-checkbox-xs mr-1"
+18 -9
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`;
// Wrapper component to manage props
const PublicScriptFileWrapper = ({ initialProps }) => {
const [props, setProps] = useState(initialProps);
const PublicScriptFileWrapper = () => {
const [props, setProps] = useState("");
// Make the setProps function globally accessible
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;
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 () => {
window.updatePublicTemplateProps = undefined;
};
}, []);
return <PdfRequestFiles {...props} />;
};
const appId = isPublicStaging ? "opensign" : "legadranaxn";
//"opensign"
const serverUrl = isPublicStaging
? "https://staging-app.opensignlabs.com/api/app"
: "https://app.opensignlabs.com/api/app";
// "https://staging-app.opensignlabs.com/api/app"
localStorage.setItem("baseUrl", `${serverUrl}/`);
localStorage.setItem("parseAppId", appId);
@@ -58,7 +67,7 @@ const root = ReactDOM.createRoot(document.getElementById("script-component"));
root.render(
<div data-theme="opensigncss">
<Provider store={store}>
<PublicScriptFileWrapper initialProps={{ text: "templateId" }} />
<PublicScriptFileWrapper />
</Provider>
</div>
);
@@ -131,7 +131,8 @@
"Share": "Share",
"View": "View",
"option": "Option",
"Embed": "Embed"
"Embed": "Embed",
"Copy TemplateId":"Copy TemplateId"
},
"report-heading": {
"Sr.No": "Sr.No",
@@ -151,7 +152,8 @@
"Name": "Name",
"Status": "Status",
"created-date": "Created Date",
"Type": "Type"
"Type": "Type",
"Logs": "Logs"
},
"report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -263,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.",
"public-role": "Public role",
"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.",
"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",
@@ -572,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 .",
"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-3": "Drag or click on a field to add it 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-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-3": "Click this button to add more recipients/signers to the document.",
"placeholder-sign-4": "Drag or click on a field to add it to the document.",
"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. ",
"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.",
@@ -593,7 +597,7 @@
"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.",
"public-template": "This video demonstrates how to set up your personalized public profile, such as https://opensign.me/your-username. Youll also learn how to customize your tagline and make your templates available for public signing."
},
},
"enter-email-plaholder": "Add an email address and hit enter",
"success-email-alert": "Email sent successfully!",
"expired-doc-title": "Expired Document",
@@ -627,12 +631,42 @@
"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 {{adminName}}<{{adminEmail}}>.",
"sent-this-month":"Sent this month"
"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"
}
@@ -136,7 +136,8 @@
"Name": "Nom",
"Status": "Statut",
"created-date": "Date de création",
"Type": "Saisir"
"Type": "Saisir",
"Logs": "Journaux"
},
"btnLabel": {
"sign": "Signer",
@@ -150,7 +151,8 @@
"Share": "Partager",
"View": "Voir",
"option": "Option",
"Embed": "Intégrer"
"Embed": "Intégrer",
"Copy TemplateId": "Copier l'ID du modèle"
},
"report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -262,7 +264,8 @@
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
"public-role": "Rôle public",
"public-url": "URL publique",
"public-url-copy-mssg": "Voici votre URL publique : copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
"public-url-copy": "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.",
"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",
@@ -571,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.",
"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-3": "Faites glisser ou cliquez sur un champ pour l'ajouter 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-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-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
"placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"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",
"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'.",
@@ -626,13 +630,43 @@
"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":"Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
"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 {{adminName}}<{{adminEmail}}>.",
"sent-this-month":"envoyé ce mois-ci"
"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"
}
+31 -28
View File
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
dotenv.config();
export const cloudServerUrl = 'http://localhost:8080/app';
export const appName = process.env.APP_NAME || 'OpenSign™';
export function customAPIurl() {
const url = new URL(cloudServerUrl);
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) => {
//checking server url and save file's size
try {
const tenantQuery = new Parse.Query('partners_Tenant');
tenantQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
const tenant = await tenantQuery.first();
if (tenant) {
const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id };
try {
const tenantCredits = new Parse.Query('partners_TenantCredits');
tenantCredits.equalTo('PartnersTenant', tenantPtr);
const res = await tenantCredits.first({ useMasterKey: true });
if (res) {
const response = JSON.parse(JSON.stringify(res));
const usedStorage = response?.usedStorage ? response.usedStorage + size : size;
const updateCredit = new Parse.Object('partners_TenantCredits');
updateCredit.id = res.id;
updateCredit.set('usedStorage', usedStorage);
await updateCredit.save(null, { useMasterKey: true });
} else {
const newCredit = new Parse.Object('partners_TenantCredits');
newCredit.set('usedStorage', size);
newCredit.set('PartnersTenant', tenantPtr);
await newCredit.save(null, { useMasterKey: true });
if (userId) {
const tenantQuery = new Parse.Query('partners_Tenant');
tenantQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
const tenant = await tenantQuery.first();
if (tenant) {
const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id };
try {
const tenantCredits = new Parse.Query('partners_TenantCredits');
tenantCredits.equalTo('PartnersTenant', tenantPtr);
const res = await tenantCredits.first({ useMasterKey: true });
if (res) {
const response = JSON.parse(JSON.stringify(res));
const usedStorage = response?.usedStorage ? response.usedStorage + size : size;
const updateCredit = new Parse.Object('partners_TenantCredits');
updateCredit.id = res.id;
updateCredit.set('usedStorage', usedStorage);
await updateCredit.save(null, { useMasterKey: true });
} else {
const newCredit = new Parse.Object('partners_TenantCredits');
newCredit.set('usedStorage', size);
newCredit.set('PartnersTenant', tenantPtr);
await newCredit.save(null, { useMasterKey: true });
}
} catch (err) {
console.log('err in save usage', err);
}
} catch (err) {
console.log('err in save usage', err);
saveDataFile(size, fileUrl, tenantPtr);
}
saveDataFile(size, fileUrl, tenantPtr);
}
} catch (err) {
console.log('err in fetch tenant Id', err);
@@ -8,6 +8,7 @@ import saveInvoice from './saveInvoice.js';
import savePayments from './savePayments.js';
import gooogleauth from './googleauth.js';
import autoReminder from './autoReminder.js';
import validateSmtp from './validateSmtp.js';
export const app = express();
dotenv.config();
@@ -22,3 +23,4 @@ app.post('/saveinvoice', saveInvoice);
app.post('/savepayment', savePayments);
app.post('/googleauth', gooogleauth);
app.post('/sendreminder', autoReminder);
app.post('/validatesmtp', validateSmtp);
@@ -196,6 +196,9 @@ export default async function createDocumentWithTemplate(request, response) {
if (TimeToCompleteDays) {
object.set('TimeToCompleteDays', TimeToCompleteDays);
}
const enableOTP = request.body?.enableOTP === true ? true : false;
const IsEnableOTP = enableOTP || template?.IsEnableOTP || false;
object.set('IsEnableOTP', IsEnableOTP);
object.set('CreatedBy', template.CreatedBy);
object.set('ExtUserPtr', {
__type: 'Pointer',
@@ -312,6 +315,7 @@ export default async function createDocumentWithTemplate(request, response) {
from: sender,
html: html,
extUserId: template.ExtUserPtr.objectId,
mailProvider: template?.ExtUserPtr?.active_mail_adapter || '',
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
@@ -63,6 +63,7 @@ export default async function createDocumentwithCoordinate(request, response) {
const email_body = request.body.email_body;
const sendInOrder = request.body.sendInOrder || false;
const TimeToCompleteDays = request.body.timeToCompleteDays || 15;
const IsEnableOTP = request.body?.enableOTP === true ? true : false;
// console.log('fileData ', fileData);
const protocol = customAPIurl();
const baseUrl = new URL(process.env.PUBLIC_URL);
@@ -162,6 +163,7 @@ export default async function createDocumentwithCoordinate(request, response) {
if (TimeToCompleteDays) {
object.set('TimeToCompleteDays', TimeToCompleteDays);
}
object.set('IsEnableOTP', IsEnableOTP);
object.set('IsSendMail', send_email);
let contact = [];
if (signers && signers.length > 0) {
@@ -369,6 +371,7 @@ export default async function createDocumentwithCoordinate(request, response) {
from: sender,
html: html,
extUserId: extUser.id,
mailProvider: parseExtUser?.active_mail_adapter || '',
};
await axios.post(url, params, { headers: headers });
@@ -9,6 +9,7 @@ export default async function createTemplate(request, response) {
const SendinOrder = request.body.sendInOrder || false;
const fileData = request.files?.[0] ? request.files[0].buffer : null;
const baseUrl = new URL(process.env.PUBLIC_URL);
const isEnableOTP = request.body?.enableOTP === true ? true : false;
try {
const reqToken = request.headers['x-api-token'];
@@ -65,6 +66,7 @@ export default async function createTemplate(request, response) {
object.set('URL', fileUrl);
object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr);
object.set('IsEnableOTP', isEnableOTP);
if (SendinOrder) {
object.set('SendinOrder', SendinOrder);
}
@@ -17,6 +17,8 @@ export default async function createTemplatewithCoordinate(request, response) {
const base64File = request.body.file;
const fileData = request.files?.[0] ? request.files[0].buffer : null;
const SendinOrder = request.body.sendInOrder || false;
const isEnableOTP = request.body?.enableOTP === true ? true : false;
// console.log('fileData ', fileData);
const protocol = customAPIurl();
@@ -80,6 +82,7 @@ export default async function createTemplatewithCoordinate(request, response) {
object.set('URL', fileUrl);
object.set('CreatedBy', userPtr);
object.set('ExtUserPtr', extUserPtr);
object.set('IsEnableOTP', isEnableOTP);
let contact = [];
if (signers && signers.length > 0) {
let parseSigners;
@@ -12,6 +12,8 @@ export default async function draftDocument(request, response) {
const send_email = request.body.send_email || true;
const fileData = request.files?.[0] ? request.files[0].buffer : null;
const SendinOrder = request.body.sendInOrder || false;
const isEnableOTP = request.body?.enableOTP === true ? true : false;
// console.log('fileData ', fileData);
const protocol = customAPIurl();
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('ExtUserPtr', extUserPtr);
object.set('IsSendMail', send_email);
object.set('IsEnableOTP', isEnableOTP);
if (signers && signers.length > 0) {
let parseSigners;
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 })) ||
[],
sendInOrder: document?.SendinOrder || false,
enableOTP: document?.IsEnableOTP || false,
createdAt: document.createdAt,
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 })) ||
[],
sendInOrder: x?.SendinOrder || false,
enableOTP: x?.IsEnableOTP || false,
createdAt: x.createdAt,
updatedAt: x.updatedAt,
}));
@@ -61,6 +61,7 @@ export default async function getTemplate(request, response) {
),
})) || [],
sendInOrder: template?.SendinOrder || false,
enableOTP: template?.IsEnableOTP || false,
createdAt: template.createdAt,
updatedAt: template.updatedAt,
});
@@ -88,6 +88,7 @@ export default async function getTemplatetList(request, response) {
),
})) || [],
sendInOrder: template?.SendinOrder || false,
enableOTP: template?.IsEnableOTP || false,
createdAt: template.createdAt,
updatedAt: template.updatedAt,
}));
@@ -41,6 +41,7 @@ export default async function resendMail(request, response) {
if (resDoc) {
const _resDoc = resDoc.toJSON();
const contact = _resDoc.Signers.find(x => x.Email === userMail);
const activeMailAdapter = _resDoc?.ExtUserPtr?.active_mail_adapter || '';
if (contact) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
@@ -131,6 +132,7 @@ export default async function resendMail(request, response) {
from: sender,
html: html,
extUserId: _resDoc.ExtUserPtr.objectId,
mailProvider: activeMailAdapter,
};
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 });
if (token !== undefined) {
// 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 isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
const parseUser = JSON.parse(JSON.stringify(token));
@@ -48,6 +48,10 @@ export default async function updateDocument(request, response) {
objectId: request?.body?.folderId,
});
}
if (request.body?.enableOTP) {
updateQuery.set('IsEnableOTP', request.body?.enableOTP);
}
const updatedRes = await updateQuery.save(null, { useMasterKey: true });
if (updatedRes) {
if (request.posthog) {
@@ -10,7 +10,7 @@ export default async function updateTemplate(request, response) {
const token = await tokenQuery.first({ useMasterKey: true });
if (token !== undefined) {
// 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 isValid = objectKeys.every(key => allowedKeys.includes(key)) && objectKeys.length > 0;
const parseUser = JSON.parse(JSON.stringify(token));
@@ -48,6 +48,9 @@ export default async function updateTemplate(request, response) {
objectId: request?.body?.folderId,
});
}
if (request.body?.enableOTP) {
updateQuery.set('IsEnableOTP', request.body?.enableOTP);
}
const updatedRes = await updateQuery.save(null, { useMasterKey: true });
if (updatedRes) {
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 });
}
}
+10
View File
@@ -65,6 +65,11 @@ 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.
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
@@ -142,3 +147,8 @@ 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);
@@ -62,6 +62,7 @@ const createDocumentFromTemplate = async (template, existContact, index) => {
object.set('CreatedBy', template?.CreatedBy);
object.set('ExtUserPtr', template?.ExtUserPtr);
object.set('OriginIp', template?.OriginIp || '');
object.set('IsEnableOTP', template?.IsEnableOTP || false);
let signers = template?.Signers || [];
const signerobj = {
__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
// create new document from template and save contact pointer in placeholder, signers and ACL of Document
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.');
}
const _tempRes = JSON.parse(JSON.stringify(tempRes));
const Placeholders = _tempRes?.Placeholders || [];
let index;
if (role) {
index = Placeholders?.findIndex(x => x.Role && x.Role === role);
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, '');
}
if (index !== -1) {
// Execute the query to check if a contact already exists in the 'contracts_Contactbook' class
const contactCls = new Parse.Query('contracts_Contactbook');
contactCls.equalTo('Email', email);
contactCls.equalTo('CreatedBy', _tempRes.CreatedBy);
contactCls.notEqualTo('IsDeleted', true);
const existContact = await contactCls.first({ useMasterKey: true });
if (existContact) {
const template_json = JSON.parse(JSON.stringify(tempRes));
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(template_json, existContact, 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: existContact.id, docId: docRes.id };
const subscription = new Parse.Query('contracts_Subscriptions');
subscription.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: _tempRes?.ExtUserPtr?.TenantId?.objectId,
});
subscription.include('ExtUserPtr');
const date = new Date();
subscription.greaterThanOrEqualTo('Next_billing_date', 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 Placeholders = _tempRes?.Placeholders || [];
let index;
if (role) {
index = Placeholders?.findIndex(x => x.Role && x.Role === role);
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, '');
}
} else {
// Execute the query to check if a user already exists in the 'contracts_Users' class
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('Email', email);
const extUser = await extUserQuery.first({ useMasterKey: true });
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
const contact = {
UserId: _extUser.UserId,
Name: _extUser.Name,
Email: email,
Phone: _extUser?.Phone ? _extUser.Phone : '',
CreatedBy: _tempRes.CreatedBy,
TenantId: _tempRes.ExtUserPtr.TenantId,
};
const template_json = JSON.parse(JSON.stringify(tempRes));
// if user present on platform create contact on the basis of extended user details
const contactRes = await saveRoleContact(contact);
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);
if (index !== -1) {
// Execute the query to check if a contact already exists in the 'contracts_Contactbook' class
const contactCls = new Parse.Query('contracts_Contactbook');
contactCls.equalTo('Email', email);
contactCls.equalTo('CreatedBy', _tempRes.CreatedBy);
contactCls.notEqualTo('IsDeleted', true);
const existContact = await contactCls.first({ useMasterKey: true });
if (existContact) {
const template_json = JSON.parse(JSON.stringify(tempRes));
//update contact in placeholder, signers and update ACl in provide document
const docRes = await createDocumentFromTemplate(template_json, existContact, 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: existContact.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) {
} else {
// Execute the query to check if a user already exists in the 'contracts_Users' class
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('Email', email);
const extUser = await extUserQuery.first({ useMasterKey: true });
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
const contact = {
UserId: { __type: 'Pointer', className: '_User', objectId: userRes.id },
Name: name,
UserId: _extUser.UserId,
Name: _extUser.Name,
Email: email,
Phone: phone,
Phone: _extUser?.Phone ? _extUser.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
// if user present on platform create contact on the basis of extended user details
const contactRes = await saveRoleContact(contact);
//update contact in placeholder, signers and update ACl in provide document
const 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 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 {
// 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) {
//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 };
}
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
}
} catch (err) {
console.log('Err', err);
}
} 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 {
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 {
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) {
try {
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;
try {
await Parse.Cloud.sendEmail({
from: 'Opensign™' + ' <' + mailsender + '>',
from: appName + ' <' + mailsender + '>',
recipient: recipient,
subject: 'Your OpenSign™ OTP',
subject: `Your ${appName} OTP`,
text: 'This email is a test.',
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 +
'</p></div> </div> </div></body></html>',
});
@@ -0,0 +1,33 @@
export default async function addcustomsmtp(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
const host = request.params.host;
const port = request.params.port;
const username = request.params.username;
const password = request.params.password;
if (host && port && username && password) {
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const extUserCls = new Parse.Object('contracts_Users');
extUserCls.id = extUser.id;
extUserCls.set('SmtpConfig', { host, port, username, password });
extUserCls.set('active_mail_adapter', 'smtp');
const updateExtUser = await extUserCls.save(null, { useMasterKey: true });
// console.log('updateExtUser ', updateExtUser);
return updateExtUser.updatedAt;
}
return extUser;
} catch (err) {
console.log('Err in add custom smtp', err);
const code = err.code || 400;
const msg = err.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide all parameters.');
}
}
@@ -7,99 +7,101 @@ export default async function callWebhook(request) {
const contactId = request.params.contactId;
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = process.env.APP_ID;
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
const userId = userRes.data && userRes.data.objectId;
if (userId) {
if (event === 'viewed' && contactId) {
const docQuery = new Parse.Query('contracts_Document');
const res = await docQuery.get(docId, { useMasterKey: true });
if (res) {
const _res = res.toJSON();
const userPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactId,
};
const date = new Date().toISOString();
const obj = {
UserPtr: userPtr,
SignedUrl: _res.SignedUrl,
Activity: 'Viewed',
ipAddress: request.headers['x-real-ip'],
ViewedOn: date,
};
const isUserExist = _res?.AuditTrail?.some(
x => x.UserPtr.objectId === contactId && x?.ViewedOn
);
if (!isUserExist) {
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = res.id;
if (_res?.AuditTrail && _res?.AuditTrail?.length > 0) {
updateDoc.set('AuditTrail', [..._res?.AuditTrail, obj]);
} else {
updateDoc.set('AuditTrail', [obj]);
try {
const docQuery = new Parse.Query('contracts_Document');
const docRes = await docQuery.get(docId, { useMasterKey: true });
const isEnableOTP = docRes?.get('IsEnableOTP') || false;
let userId;
if (isEnableOTP) {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
userId = userRes.data && userRes.data.objectId;
}
if (!isEnableOTP || userId) {
if (event === 'viewed' && contactId) {
if (docRes) {
const _docRes = docRes.toJSON();
const userPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactId,
};
const date = new Date().toISOString();
const obj = {
UserPtr: userPtr,
SignedUrl: _docRes.SignedUrl,
Activity: 'Viewed',
ipAddress: request.headers['x-real-ip'],
ViewedOn: date,
};
const isUserExist = _docRes?.AuditTrail?.some(
x => x.UserPtr.objectId === contactId && x?.ViewedOn
);
if (!isUserExist) {
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = docRes.id;
if (_docRes?.AuditTrail && _docRes?.AuditTrail?.length > 0) {
updateDoc.set('AuditTrail', [..._docRes?.AuditTrail, obj]);
} else {
updateDoc.set('AuditTrail', [obj]);
}
await updateDoc.save(null, { useMasterKey: true });
}
await updateDoc.save(null, { useMasterKey: true });
}
}
}
const docQuery = new Parse.Query('contracts_Document');
const resDoc = await docQuery.get(docId, { useMasterKey: true });
const extendcls = new Parse.Query('contracts_Users');
extendcls.equalTo('objectId', resDoc.get('ExtUserPtr')?.id);
// extendcls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const res = await extendcls.first({ useMasterKey: true });
if (res) {
const extUser = JSON.parse(JSON.stringify(res));
if (extUser?.Webhook) {
const params = {
event: event,
...body,
};
await axios
.post(extUser?.Webhook, params, {
headers: { 'Content-Type': 'application/json' },
})
.then(res => {
try {
// console.log('res ', res);
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', res?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
})
.catch(err => {
console.log('Err send data to webhook', err.message);
try {
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', err?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
});
const extendcls = new Parse.Query('contracts_Users');
extendcls.equalTo('objectId', docRes.get('ExtUserPtr')?.id);
// extendcls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const resExt = await extendcls.first({ useMasterKey: true });
if (resExt) {
const extUser = JSON.parse(JSON.stringify(resExt));
if (extUser?.Webhook) {
const params = { event: event, ...body };
await axios
.post(extUser?.Webhook, params, {
headers: { 'Content-Type': 'application/json' },
})
.then(res => {
try {
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', res?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
})
.catch(err => {
console.log('Err send data to webhook', err.message);
try {
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', err?.status);
webhook.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
webhook.save(null, { useMasterKey: true });
} catch (err) {
console.log('err save in contracts_Webhook', err.message);
}
});
}
return { message: 'webhook called!' };
}
return { message: 'webhook called!' };
} else {
return { message: 'User not found!' };
}
} else {
return { message: 'User not found!' };
} catch (err) {
console.log('Err in callwebhook', err);
return { message: 'Something went wrong!' };
}
}
@@ -74,6 +74,7 @@ async function sendMail(document, sessionToken) {
const orgName = document.ExtUserPtr.Company ? document.ExtUserPtr.Company : '';
const themeBGcolor = '#47a3ad';
let params = {
mailProvider: document?.ExtUserPtr?.active_mail_adapter || '',
extUserId: document.ExtUserPtr.objectId,
recipient: objectId ? existSigner?.Email : signerMail[i].email,
subject: `${document.ExtUserPtr.Name} has requested you to sign "${document.Name}"`,
@@ -199,6 +200,7 @@ export default async function createBatchDocs(request) {
TimeToCompleteDays: x.TimeToCompleteDays || 15,
OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate },
IsEnableOTP: x?.IsEnableOTP || false,
},
};
});
@@ -0,0 +1,24 @@
export default async function deactivateMailAdapter(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const extUserCls = new Parse.Object('contracts_Users');
extUserCls.id = extUser.id;
extUserCls.unset('active_mail_adapter');
const updateExtUser = await extUserCls.save(null, { useMasterKey: true });
// console.log('updateExtUser ', updateExtUser);
return updateExtUser.updatedAt;
}
return extUser;
} catch (err) {
console.log('Err in add custom smtp', 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 declinedocument(request) {
const docId = request.params.docId;
const reason = request.params?.reason || '';
const declineBy = {
__type: 'Pointer',
className: '_User',
objectId: request.params?.userId,
};
try {
const docCls = new Parse.Query('contracts_Document');
const updateDoc = await docCls.get(docId, { useMasterKey: true });
if (updateDoc) {
const isEnableOTP = updateDoc?.get('IsEnableOTP') || false;
if (!isEnableOTP) {
updateDoc.set('IsDeclined', true);
updateDoc.set('DeclineReason', reason);
updateDoc.set('DeclineBy', declineBy);
await updateDoc.save(null, { useMasterKey: true });
return 'document declined';
} else {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
updateDoc.set('IsDeclined', true);
updateDoc.set('DeclineReason', reason);
updateDoc.set('DeclineBy', declineBy);
await updateDoc.save(null, { useMasterKey: true });
return 'document declined';
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
} catch (err) {
console.log('err while decling doc', err);
throw err;
}
}
@@ -0,0 +1,11 @@
export default async function getContact(request) {
const contactId = request.params.contactId;
try {
const contactCls = new Parse.Query('contracts_Contactbook');
const contactRes = await contactCls.get(contactId, { useMasterKey: true });
return contactRes;
} catch (err) {
console.log('Err in contracts_Contactbook class ', err);
throw err;
}
}
@@ -6,14 +6,7 @@ export default async function getDocument(request) {
const docId = request.params.docId;
try {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
const userId = userRes.data && userRes.data.objectId;
if (docId && userId) {
if (docId) {
try {
const query = new Parse.Query('contracts_Document');
query.equalTo('objectId', docId);
@@ -26,11 +19,32 @@ export default async function getDocument(request) {
query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true });
if (res) {
const acl = res.getACL();
if (acl && acl.getReadAccess(userId)) {
const IsEnableOTP = res?.get('IsEnableOTP') || false;
if (!IsEnableOTP) {
return res;
} else {
return { error: "You don't have access of this document!" };
if (request?.headers?.['sessiontoken']) {
try {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
const userId = userRes.data && userRes.data?.objectId;
const acl = res.getACL();
if (userId && acl && acl.getReadAccess(userId)) {
return res;
} else {
return { error: "You don't have access of this document!" };
}
} catch (err) {
console.log('err user in not authenticated', err);
return { error: "You don't have access of this document!" };
}
} else {
return { error: "You don't have access of this document!" };
}
}
} else {
return { error: "You don't have access of this document!" };
@@ -28,15 +28,42 @@ export default function getPresignedUrl(url) {
export async function getSignedUrl(request) {
try {
const docId = request.params.docId || '';
const url = request.params.url;
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
if (docId) {
try {
const query = new Parse.Query('contracts_Document');
query.equalTo('objectId', docId);
query.notEqualTo('IsEnableOTP', true);
query.include('CreatedBy');
query.include('Signers');
query.include('AuditTrail.UserPtr');
query.include('Placeholders');
query.include('DeclineBy');
query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true });
if (res) {
if (useLocal !== 'true') {
const presignedUrl = getPresignedUrl(url);
return presignedUrl;
} else {
return url;
}
}
} catch (err) {
console.log('Err in presigned url', err);
throw err;
}
} else {
if (useLocal !== 'true') {
const presignedUrl = getPresignedUrl(url);
return presignedUrl;
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
} else {
return url;
if (useLocal !== 'true') {
const presignedUrl = getPresignedUrl(url);
return presignedUrl;
} else {
return url;
}
}
}
} catch (err) {
@@ -50,37 +50,24 @@ export default async function getSubscription(request) {
}
} else if (contactId) {
try {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
const userId = userRes.data && userRes.data.objectId;
if (userId) {
const contactCls = new Parse.Query('contracts_Contactbook');
const contactUser = await contactCls.get(contactId, { useMasterKey: true });
if (contactUser) {
const subscriptionCls = new Parse.Query('contracts_Subscriptions');
subscriptionCls.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: contactUser.get('TenantId').id,
});
subscriptionCls.descending('createdAt');
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
if (subcripitions) {
const _subcripitions = JSON.parse(JSON.stringify(subcripitions));
if (_subcripitions.PlanCode === 'freeplan') {
return { status: 'success', result: { isSubscribed: false, plan: 'freeplan' } };
} else if (_subcripitions?.Next_billing_date?.iso) {
if (new Date(_subcripitions.Next_billing_date.iso) > new Date()) {
return { status: 'success', result: { isSubscribed: true } };
} else {
return { status: 'success', result: { isSubscribed: false } };
}
const contactCls = new Parse.Query('contracts_Contactbook');
const contactUser = await contactCls.get(contactId, { useMasterKey: true });
if (contactUser) {
const subscriptionCls = new Parse.Query('contracts_Subscriptions');
subscriptionCls.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: contactUser.get('TenantId').id,
});
subscriptionCls.descending('createdAt');
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
if (subcripitions) {
const _subcripitions = JSON.parse(JSON.stringify(subcripitions));
if (_subcripitions.PlanCode === 'freeplan') {
return { status: 'success', result: { isSubscribed: false, plan: 'freeplan' } };
} else if (_subcripitions?.Next_billing_date?.iso) {
if (new Date(_subcripitions.Next_billing_date.iso) > new Date()) {
return { status: 'success', result: { isSubscribed: true } };
} else {
return { status: 'success', result: { isSubscribed: false } };
}
@@ -88,10 +75,10 @@ export default async function getSubscription(request) {
return { status: 'success', result: { isSubscribed: false } };
}
} else {
return { status: 'error', result: 'User not found!' };
return { status: 'success', result: { isSubscribed: false } };
}
} else {
return { status: 'error', result: 'Invalid session token!' };
return { status: 'error', result: 'User not found!' };
}
} catch (err) {
console.log('Err in get subscription2', err.message);
@@ -12,6 +12,8 @@ async function getUserDetails(request) {
}
userQuery.include('TenantId');
userQuery.include('UserId');
userQuery.include('CreatedBy');
userQuery.exclude('CreatedBy.authData');
userQuery.exclude('google_refresh_token');
if (userId) {
userQuery.equalTo('CreatedBy', { __type: 'Pointer', className: '_User', objectId: userId });
@@ -32,6 +32,7 @@ export default async function GenerateCertificate(docDetails) {
const OriginIp = docDetails?.OriginIp || '';
const company = docDetails?.ExtUserPtr?.Company || '';
const createdAt = docDetails?.DocSentAt?.iso || docDetails.createdAt;
const IsEnableOTP = docDetails?.IsEnableOTP || false;
const auditTrail =
docDetails?.Signers?.length > 0
? docDetails.AuditTrail.map(x => {
@@ -348,22 +349,22 @@ export default async function GenerateCertificate(docDetails) {
font: timesRomanFont,
color: textValueColor,
});
page.drawText('Security level :', {
x: half + 55,
y: yPosition4 + 10,
size: timeText,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(`Email, OTP Auth`, {
x: half + 125,
y: yPosition4 + 10,
size: timeText,
font: timesRomanFont,
color: textValueColor,
});
if (IsEnableOTP) {
page.drawText('Security level :', {
x: half + 55,
y: yPosition4 + 10,
size: timeText,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText('Email, OTP Auth', {
x: half + 125,
y: yPosition4 + 10,
size: timeText,
font: timesRomanFont,
color: textValueColor,
});
}
page.drawText('Signature :', {
x: 30,
y: yPosition5,
+151 -189
View File
@@ -29,11 +29,7 @@ async function uploadFile(pdfName, filepath) {
// `updateDoc` is used to update signedUrl, AuditTrail, Iscompleted in document
async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
try {
const UserPtr = {
__type: 'Pointer',
className: className,
objectId: userId,
};
const UserPtr = { __type: 'Pointer', className: className, objectId: userId };
const obj = {
UserPtr: UserPtr,
SignedUrl: url,
@@ -66,11 +62,7 @@ async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
} else {
isCompleted = true;
}
const body = {
SignedUrl: url,
AuditTrail: updateAuditTrail,
IsCompleted: isCompleted,
};
const body = { SignedUrl: url, AuditTrail: updateAuditTrail, IsCompleted: isCompleted };
const signedRes = await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, {
headers: {
'Content-Type': 'application/json',
@@ -164,29 +156,16 @@ async function sendCompletedMail(obj) {
'X-Parse-Master-Key': masterKEY,
},
});
// console.log('Res ', res);
}
// `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) {
let signers = [];
if (signUser) {
signers = {
name: signUser?.Name,
email: signUser?.Email,
phone: signUser?.Phone,
};
signers = { name: signUser?.Name, email: signUser?.Email, phone: signUser?.Phone };
} else {
signers = doc?.Signers?.map(x => ({
name: x.Name,
email: x.Email,
phone: x.Phone,
})) || [
{
name: doc?.ExtUserPtr?.Name,
email: doc?.ExtUserPtr?.Email,
phone: doc?.ExtUserPtr?.Phone,
},
signers = doc?.Signers?.map(x => ({ name: x.Name, email: x.Email, phone: x.Phone })) || [
{ name: doc?.ExtUserPtr?.Name, email: doc?.ExtUserPtr?.Email, phone: doc?.ExtUserPtr?.Phone },
];
}
@@ -213,7 +192,6 @@ async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) {
})
.then(res => {
try {
// console.log('res ', res);
const webhook = new Parse.Object('contracts_Webhook');
webhook.set('Log', res?.status);
webhook.set('UserId', {
@@ -241,7 +219,6 @@ async function sendDoctoWebhook(doc, Url, event, signUser, certificateUrl) {
console.log('err save in contracts_Webhook', err.message);
}
});
// console.log('res ', res.data);
}
}
@@ -281,15 +258,9 @@ const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailPro
if (doc.IsSendMail === false) {
console.log("don't send mail");
} else {
const mailObj = {
url: url,
isCustomMail: isCustomMail,
doc: doc,
mailProvider: mailProvider,
};
const mailObj = { url: url, isCustomMail: isCustomMail, doc: doc, mailProvider: mailProvider };
sendCompletedMail(mailObj);
}
saveFileUsage(CertificateBuffer.length, file.imageUrl, userId);
sendDoctoWebhook(doc, url, 'completed', '', file.imageUrl);
};
@@ -301,169 +272,160 @@ const sendMailsaveCertifcate = async (doc, P12Buffer, url, isCustomMail, mailPro
*/
async function PDF(req) {
try {
if (!req?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
const userIP = req.headers['x-real-ip']; // client IPaddress
const docId = req.params.docId;
const reqUserId = req.params.userId;
const isCustomMail = req.params.isCustomCompletionMail || false;
const mailProvider = req.params.mailProvider || '';
const sign = req.params.signature || '';
// below bode is used to get info of docId
const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr,Signers');
docQuery.equalTo('objectId', docId);
const resDoc = await docQuery.first({ useMasterKey: true });
if (!resDoc) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
const IsEnableOTP = resDoc?.get('IsEnableOTP') || false;
// if `IsEnableOTP` is false then we don't have to check authentication
if (IsEnableOTP) {
if (!req?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
}
const _resDoc = resDoc?.toJSON();
let signUser;
let className;
// `reqUserId` is send throught pdfrequest signing flow
if (reqUserId) {
// to get contracts_Contactbook details for currentuser from reqUserId
const _contractUser = _resDoc.Signers.find(x => x.objectId === reqUserId);
if (_contractUser) {
signUser = _contractUser;
className = 'contracts_Contactbook';
}
} else {
const password = '';
const authUser = req?.user?.toJSON();
const docId = req.params.docId;
const reqUserId = req.params.userId;
const isCustomMail = req.params.isCustomCompletionMail || false;
const mailProvider = req.params.mailProvider || '';
const sign = req.params.signature || '';
// below bode is used to get info of docId
const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr,Signers');
docQuery.equalTo('objectId', docId);
const resDoc = await docQuery.first({ useMasterKey: true });
if (!resDoc) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
const _resDoc = resDoc?.toJSON();
className = 'contracts_Users';
signUser = _resDoc.ExtUserPtr;
}
let signUser;
let className;
// `reqUserId` is send throught pdfrequest signing flow
if (reqUserId) {
// to get contracts_Contactbook details for currentuser from reqUserId
const _contractUser = _resDoc.Signers.find(x => x.objectId === reqUserId);
if (_contractUser) {
signUser = _contractUser;
className = 'contracts_Contactbook';
}
const username = signUser.Name;
const userEmail = signUser.Email;
if (req.params.pdfFile) {
// `PdfBuffer` used to create buffer from pdf file
let PdfBuffer = Buffer.from(req.params.pdfFile, 'base64');
// `P12Buffer` used to create buffer from p12 certificate
const pfxFile = process.env.PFX_BASE64;
// const P12Buffer = fs.readFileSync();
const P12Buffer = Buffer.from(pfxFile, 'base64');
const p12Cert = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId };
const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP };
let updateAuditTrail;
if (_resDoc.AuditTrail && _resDoc.AuditTrail.length > 0) {
updateAuditTrail = [..._resDoc.AuditTrail, obj];
} else {
className = 'contracts_Users';
signUser = _resDoc.ExtUserPtr;
updateAuditTrail = [obj];
}
const username = signUser.Name;
const userEmail = signUser.Email;
if (req.params.pdfFile) {
// `PdfBuffer` used to create buffer from pdf file
let PdfBuffer = Buffer.from(req.params.pdfFile, 'base64');
// `P12Buffer` used to create buffer from p12 certificate
const pfxFile = process.env.PFX_BASE64;
// const P12Buffer = fs.readFileSync();
const P12Buffer = Buffer.from(pfxFile, 'base64');
const p12Cert = new P12Signer(P12Buffer, { passphrase: process.env.PASS_PHRASE || null });
const UserPtr = {
__type: 'Pointer',
className: className,
objectId: signUser.objectId,
};
const obj = {
UserPtr: UserPtr,
SignedUrl: '',
Activity: 'Signed',
ipAddress: req.headers['x-real-ip'],
};
let updateAuditTrail;
if (_resDoc.AuditTrail && _resDoc.AuditTrail.length > 0) {
updateAuditTrail = [..._resDoc.AuditTrail, obj];
} else {
updateAuditTrail = [obj];
}
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
let isCompleted = false;
if (_resDoc.Signers && _resDoc.Signers.length > 0) {
if (auditTrail.length === _resDoc.Signers.length) {
isCompleted = true;
}
} else {
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
let isCompleted = false;
if (_resDoc.Signers && _resDoc.Signers.length > 0) {
if (auditTrail.length === _resDoc.Signers.length) {
isCompleted = true;
}
const randomNumber = Math.floor(Math.random() * 5000);
const name = `exported_file_${randomNumber}.pdf`;
const pdfName = `./exports/${name}`;
let pdfSize = PdfBuffer.length;
if (isCompleted) {
const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>');
if (signersName && signersName.length > 0) {
// `pdflibAddPlaceholder` is used to add code of only digitial sign without widget
const pdfDoc = await PDFDocument.load(PdfBuffer);
pdflibAddPlaceholder({
pdfDoc: pdfDoc,
reason: 'Digitally signed by OpenSign for ' + signersName?.join(', '),
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
} else {
// `pdflibAddPlaceholder` is used to add code of only digitial sign without widget (signyourself)
const pdfDoc = await PDFDocument.load(PdfBuffer);
pdflibAddPlaceholder({
pdfDoc: pdfDoc,
reason: 'Digitally signed by OpenSign for ' + username + ' <' + userEmail + '>',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
}
//`new signPDF` create new instance of pdfBuffer and p12Buffer
const OBJ = new SignPdf();
// `signedDocs` is used to signpdf digitally
const signedDocs = await OBJ.sign(PdfBuffer, p12Cert);
//`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, signedDocs);
pdfSize = signedDocs.length;
} else {
//`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, PdfBuffer);
pdfSize = PdfBuffer.length;
}
// `uploadFile` is used to upload pdf to aws s3 and get it's url
const data = await uploadFile(name, pdfName);
if (data && data.imageUrl) {
// `axios` is used to update signed pdf url in contracts_Document classes for given DocId
const updatedDoc = await updateDoc(
req.params.docId, //docId
data.imageUrl, // url
signUser.objectId, // userID
req.headers['x-real-ip'], // client ipAddress,
_resDoc, // auditTrail, signers, etc data
className,
sign
);
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
saveFileUsage(pdfSize, data.imageUrl, authUser.objectId);
if (updatedDoc && updatedDoc.isCompleted) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail };
sendMailsaveCertifcate(
doc,
P12Buffer,
data.imageUrl,
isCustomMail,
mailProvider,
authUser.objectId
);
}
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
fs.unlinkSync(pdfName);
console.log(`New Signed PDF created called: ${pdfName}`);
if (updatedDoc.message === 'success') {
return { status: 'success', data: data.imageUrl };
} else {
const error = new Error('Please provide required parameters!');
error.code = 400; // Set the error code (e.g., 400 for bad request)
throw error;
}
}
} else {
const error = new Error('Pdf file not present!');
error.code = 400; // Set the error code (e.g., 400 for bad request)
throw error;
isCompleted = true;
}
const randomNumber = Math.floor(Math.random() * 5000);
const name = `exported_file_${randomNumber}.pdf`;
const pdfName = `./exports/${name}`;
let pdfSize = PdfBuffer.length;
if (isCompleted) {
const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>');
if (signersName && signersName.length > 0) {
// `pdflibAddPlaceholder` is used to add code of only digitial sign without widget
const pdfDoc = await PDFDocument.load(PdfBuffer);
pdflibAddPlaceholder({
pdfDoc: pdfDoc,
reason: 'Digitally signed by OpenSign for ' + signersName?.join(', '),
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
} else {
// `pdflibAddPlaceholder` is used to add code of only digitial sign without widget (signyourself)
const pdfDoc = await PDFDocument.load(PdfBuffer);
pdflibAddPlaceholder({
pdfDoc: pdfDoc,
reason: 'Digitally signed by OpenSign for ' + username + ' <' + userEmail + '>',
location: 'n/a',
name: eSignName,
contactInfo: eSigncontact,
signatureLength: 15000,
});
const pdfWithPlaceholderBytes = await pdfDoc.save();
PdfBuffer = Buffer.from(pdfWithPlaceholderBytes);
}
//`new signPDF` create new instance of pdfBuffer and p12Buffer
const OBJ = new SignPdf();
// `signedDocs` is used to signpdf digitally
const signedDocs = await OBJ.sign(PdfBuffer, p12Cert);
//`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, signedDocs);
pdfSize = signedDocs.length;
} else {
//`saveUrl` is used to save signed pdf in exports folder
const saveUrl = fs.writeFileSync(pdfName, PdfBuffer);
pdfSize = PdfBuffer.length;
}
// `uploadFile` is used to upload pdf to aws s3 and get it's url
const data = await uploadFile(name, pdfName);
if (data && data.imageUrl) {
// `axios` is used to update signed pdf url in contracts_Document classes for given DocId
const updatedDoc = await updateDoc(
req.params.docId, //docId
data.imageUrl, // url
signUser.objectId, // userID
userIP, // client ipAddress,
_resDoc, // auditTrail, signers, etc data
className, // className based on flow
sign // sign base64
);
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
if (updatedDoc && updatedDoc.isCompleted) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail };
sendMailsaveCertifcate(
doc,
P12Buffer,
data.imageUrl,
isCustomMail,
mailProvider,
_resDoc?.CreatedBy?.objectId
);
}
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
fs.unlinkSync(pdfName);
console.log(`New Signed PDF created called: ${pdfName}`);
if (updatedDoc.message === 'success') {
return { status: 'success', data: data.imageUrl };
} else {
const error = new Error('Please provide required parameters!');
error.code = 400; // Set the error code (e.g., 400 for bad request)
throw error;
}
}
} else {
const error = new Error('Pdf file not present!');
error.code = 400; // Set the error code (e.g., 400 for bad request)
throw error;
}
} catch (err) {
console.log('Err in signpdf', err);
@@ -65,6 +65,8 @@ export default function reportJson(id, userId) {
'Folder.Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.Email',
'Signers.Phone',
@@ -100,6 +102,8 @@ export default function reportJson(id, userId) {
'Folder.Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.Email',
'Signers.Phone',
@@ -139,6 +143,7 @@ export default function reportJson(id, userId) {
'TimeToCompleteDays',
'Placeholders',
'IsSignyourself',
'IsCompleted',
],
};
// declined documents report
@@ -227,6 +232,8 @@ export default function reportJson(id, userId) {
'Folder.Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.Email',
'Signers.Phone',
@@ -268,6 +275,8 @@ export default function reportJson(id, userId) {
'Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.UserId',
'AuditTrail',
@@ -335,6 +344,7 @@ export default function reportJson(id, userId) {
'Folder.Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.Email',
'Signers.Phone',
@@ -68,7 +68,11 @@ const makeEmail = async (to, from, subject, html, url, pdfName) => {
type: 'application/pdf',
path: './exports/certificate.pdf',
};
attachments = [file, certificate];
if (fs.existsSync(certificate.path)) {
attachments = [file, certificate];
} else {
attachments = [file];
}
} catch (err) {
attachments = [file];
console.log('Err in read certificate sendmailv3', err);
@@ -143,6 +147,14 @@ export default async function sendMailGmailProvider(_extRes, template) {
},
});
console.log('gmail provider res: ', response?.status);
const certificatePath = './exports/certificate.pdf';
if (fs.existsSync(certificatePath)) {
try {
fs.unlinkSync(certificatePath);
} catch (err) {
console.log('Err in unlink certificate sendmailgmail provider');
}
}
return { code: 200, message: 'Email sent successfully' };
} catch (error) {
console.error('Error sending email:', error);
@@ -187,6 +187,111 @@ async function sendMailProvider(req, plan, monthchange) {
}
}
}
async function sendcustomsmtp(extRes, req) {
const smtpsecure = extRes.SmtpConfig.port !== '465' ? false : true;
const transporterSMTP = createTransport({
host: extRes.SmtpConfig.host,
port: extRes.SmtpConfig.port,
secure: smtpsecure,
auth: { user: extRes.SmtpConfig.username, pass: extRes.SmtpConfig.password },
});
if (req.params.url) {
let Pdf = fs.createWriteStream('test.pdf');
const writeToLocalDisk = () => {
return new Promise((resolve, reject) => {
if (useLocal !== 'true') {
https.get(req.params.url, async function (response) {
response.pipe(Pdf);
response.on('end', () => resolve('success'));
});
} else {
const path = new URL(req.params.url)?.pathname;
const localurl = 'http://localhost:8080' + path;
http.get(localurl, async function (response) {
response.pipe(Pdf);
response.on('end', () => resolve('success'));
});
}
});
};
// `writeToLocalDisk` is used to create pdf file from doc url
const ress = await writeToLocalDisk();
if (ress) {
function readTolocal() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let PdfBuffer = fs.readFileSync(Pdf.path);
resolve(PdfBuffer);
}, 100);
});
}
// `PdfBuffer` used to create buffer from pdf file
let PdfBuffer = await readTolocal();
const pdfName = req.params.pdfName ? `${req.params.pdfName}.pdf` : 'exported.pdf';
const file = { filename: pdfName, content: PdfBuffer };
let attachment;
const certificatePath = './exports/certificate.pdf';
if (fs.existsSync(certificatePath)) {
try {
// `certificateBuffer` used to create buffer from pdf file
const certificateBuffer = fs.readFileSync(certificatePath);
const certificate = { filename: 'certificate.pdf', content: certificateBuffer };
attachment = [file, certificate];
} catch (err) {
attachment = [file];
console.log('Err in read certificate sendmailv3', err);
}
} else {
attachment = [file];
}
const from = req.params.from || '';
const mailsender = extRes.SmtpConfig.username;
const messageParams = {
from: from + ' <' + mailsender + '>',
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
attachments: attachment,
};
const res = await transporterSMTP.sendMail(messageParams);
console.log('custom smtp transporter res: ', res?.response);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId); //, plan, monthchange
}
if (fs.existsSync(certificatePath)) {
try {
fs.unlinkSync(certificatePath);
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
}
return { status: 'success', code: 200 };
}
}
} else {
const from = req.params.from || '';
const mailsender = extRes.SmtpConfig.username;
const messageParams = {
from: from + ' <' + mailsender + '>',
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
};
const res = await transporterSMTP.sendMail(messageParams);
console.log('custom smtp transporter res: ', res?.response);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId); //, plan, monthchange
}
return { status: 'success', code: 200 };
}
}
}
async function sendmailv3(req) {
const mailProvider = req.params.mailProvider || 'default';
if (mailProvider) {
@@ -206,7 +311,11 @@ async function sendmailv3(req) {
const extRes = await extUserQuery.get(extUserId, { useMasterKey: true });
if (extRes) {
const _extRes = JSON.parse(JSON.stringify(extRes));
if (_extRes.google_refresh_token && mailProvider === 'google') {
if (
_extRes.active_mail_adapter === 'google' &&
_extRes.google_refresh_token &&
mailProvider === 'google'
) {
const res = await sendMailGmailProvider(_extRes, template);
if (res.code === 200) {
await updateMailCount(req.params.extUserId);
@@ -214,6 +323,14 @@ async function sendmailv3(req) {
} else {
return { status: 'error' };
}
} else if (_extRes.active_mail_adapter === 'smtp' && mailProvider === 'smtp') {
const res = await sendcustomsmtp(_extRes, req);
if (res.code === 200) {
await updateMailCount(req.params.extUserId);
return { status: 'success' };
} else {
return { status: 'error' };
}
} else {
if (Plan && Plan === 'freeplan') {
let MonthlyFreeEmails = _extRes?.MonthlyFreeEmails || 0;
@@ -0,0 +1,33 @@
export default async function updateContactTour(request) {
const contactId = request.params.contactId;
try {
const contactCls = new Parse.Query('contracts_Contactbook');
const contactRes = await contactCls.get(contactId, { useMasterKey: true });
if (contactRes) {
const _contactRes = JSON.parse(JSON.stringify(contactRes));
const tourStatus = _contactRes?.TourStatus?.length > 0 ? _contactRes.TourStatus : [];
let updatedTourStatus = [];
if (tourStatus.length > 0) {
updatedTourStatus = [...tourStatus];
const requestSignIndex = tourStatus.findIndex(
obj => obj['requestSign'] === false || obj['requestSign'] === true
);
if (requestSignIndex !== -1) {
updatedTourStatus[requestSignIndex] = { requestSign: true };
} else {
updatedTourStatus.push({ requestSign: true });
}
} else {
updatedTourStatus = [{ requestSign: true }];
}
contactRes.set('TourStatus', updatedTourStatus);
const updateRes = await contactRes.save(null, { useMasterKey: true });
return updateRes;
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'contact not found.');
}
} catch (err) {
console.log('Err in contracts_Contactbook class ', err);
throw err;
}
}
@@ -0,0 +1,29 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
const templateSchema = new Parse.Schema('contracts_Template');
templateSchema.addBoolean('IsEnableOTP');
await templateSchema.update();
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.addBoolean('IsEnableOTP');
return schema.update();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
const templateSchema = new Parse.Schema('contracts_Template');
templateSchema.deleteField('IsEnableOTP');
await templateSchema.update();
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.deleteField('IsEnableOTP');
return schema.update();
};
@@ -10,7 +10,7 @@
<br />
<p><a href="{{{link}}}" style="background-color: lightskyblue; cursor: pointer; border-radius: 5px; padding: 10px; border-style: solid; border-width: 2px; text-decoration: none; font-weight: bolder; color:blue">Verify email</a></p>
<br />
<p><a href="https://www.opensignlabs.com">OpenSign™</a></p>
<p><a href="https://www.opensignlabs.com">{{appName}}</a></p>
</body>
</html>
@@ -1 +1 @@
OpenSign™ Email Address Verification
{{appName}} Email Address Verification
+4 -9
View File
@@ -17,7 +17,7 @@ import { exec } from 'child_process';
import { createTransport } from 'nodemailer';
import { app as v1 } from './cloud/customRoute/v1/apiV1.js';
import { PostHog } from 'posthog-node';
import { cloudServerUrl, smtpenable, smtpsecure, useLocal } from './Utils.js';
import { appName, cloudServerUrl, smtpenable, smtpsecure, useLocal } from './Utils.js';
import { SSOAuth } from './auth/authadapter.js';
let fsAdapter;
if (useLocal !== 'true') {
@@ -102,7 +102,7 @@ export const config = {
verifyUserEmails: false,
publicServerURL: process.env.SERVER_URL || cloudServerUrl,
// Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'Opensign',
appName: appName,
allowClientClassCreation: false,
allowExpiredAuthDataToken: false,
encodeParseObjectInCloudFunction: true,
@@ -112,7 +112,7 @@ export const config = {
module: 'parse-server-api-mail-adapter',
options: {
// The email address from which emails are sent.
sender: 'Opensign™' + ' <' + mailsender + '>',
sender: appName + ' <' + mailsender + '>',
// The email templates.
templates: {
// The template used by Parse Server to send an email for password
@@ -141,12 +141,7 @@ export const config = {
}
: {}),
filesAdapter: fsAdapter,
auth: {
google: {
enabled: true,
},
sso: SSOAuth,
},
auth: { google: { enabled: true }, sso: SSOAuth },
};
// Client-keys like the javascript key or the .NET key are not necessary with parse-server
// If you wish you require them, you can set them as options in the initialization above:
+318 -825
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -24,24 +24,24 @@
"@signpdf/placeholder-pdf-lib": "^3.2.4",
"@signpdf/signer-p12": "^3.2.4",
"@signpdf/signpdf": "^3.2.4",
"aws-sdk": "^2.1687.0",
"aws-sdk": "^2.1691.0",
"axios": "^1.7.7",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.20.0",
"express": "^4.21.0",
"form-data": "^4.0.0",
"generate-api-key": "^1.0.2",
"googleapis": "^140.0.1",
"googleapis": "^144.0.0",
"jsonschema": "^1.4.1",
"mailgun.js": "^10.2.3",
"mongoose": "^8.6.0",
"mongoose": "^8.6.3",
"multer": "^1.4.5-lts.1",
"multer-s3": "^3.0.1",
"node-forge": "^1.3.1",
"nodemailer": "^6.9.14",
"nodemailer": "^6.9.15",
"parse": "^5.3.0",
"parse-dbtool": "^1.2.0",
"parse-server": "^7.2.0",
"parse-server": "^7.3.0-alpha.8",
"parse-server-api-mail-adapter": "^4.0.0",
"pdf-lib": "^1.17.1",
"posthog-node": "^4.2.0",
@@ -50,11 +50,11 @@
"type": "module",
"devDependencies": {
"@babel/eslint-parser": "^7.25.1",
"eslint": "^9.9.1",
"jasmine": "^5.2.0",
"eslint": "^9.11.1",
"jasmine": "^5.3.0",
"mongodb-runner": "^5.6.6",
"nodemon": "^3.1.4",
"nyc": "^17.0.0",
"nodemon": "^3.1.7",
"nyc": "^17.1.0",
"prettier": "^3.3.3"
},
"overrides": {