Compare commits

...
157 Commits
Author SHA1 Message Date
AndrewandGitHub 498e0c6be6 Merge pull request #624 from OpenSignLabs/fix_issue 2024-04-21 11:08:48 +05:30
RaktimaNXG b4d73b2b44 fix: text input validation issue in template flow 2024-04-21 10:30:33 +05:30
prafull-opensignlabsandGitHub 440d253124 Merge pull request #621 from OpenSignLabs/fix_issue
fix: text input validation issue if user not select any validation
2024-04-18 19:40:39 +05:30
RaktimaNXG 2b6bf7bcaa fix: server url and parse appid issue to save file in parters classess 2024-04-18 19:25:21 +05:30
RaktimaNXG cb6d9e9b9b fix: text input validation issue if user not select any validation or free user also 2024-04-18 17:20:44 +05:30
prafull-opensignlabsandGitHub ebeeedc96c Merge pull request #620 from OpenSignLabs/fix_issue
fix: change the message to show for user who is not the last signer after signs the document
2024-04-18 15:42:14 +05:30
RaktimaNXG 5cecf3390b fix: change the message to show for user who is not the last signer after signs the document 2024-04-18 15:24:29 +05:30
prafull-opensignlabsandGitHub 9890d568fd Merge pull request #619 from OpenSignLabs/fix_issue
feat: add tour message on add role button when user try to add widget without add any role
2024-04-18 14:46:51 +05:30
RaktimaNXG 06bcae64be feat: add tour message on add role button when user try to add widget without add any role 2024-04-18 14:39:32 +05:30
RaktimaandGitHub fb017c4bcd Merge pull request #618 from OpenSignLabs/patch-7
refactor: change widget warning
2024-04-18 14:29:17 +05:30
prafull-opensignlabs 01d3edd262 refactor: change user icon tour message 2024-04-18 14:17:28 +05:30
prafull-opensignlabs 41a025715e refactor: change widget warning 2024-04-18 14:14:57 +05:30
prafull-opensignlabsandGitHub 306768fbd2 Merge pull request #617 from OpenSignLabs/fix_issue
fix: input number type validation issue with space and fix send mail modal design
2024-04-18 14:04:59 +05:30
RaktimaNXG d82da53258 fix: input number type validation issue with space and fix send mail modal design 2024-04-18 14:02:33 +05:30
prafull-opensignlabsandGitHub 673245df02 Merge pull request #616 from OpenSignLabs/encode_url
fix: encode url parameters
2024-04-18 12:51:29 +05:30
prafull-opensignlabs 42549e5792 fix: encode url parameters 2024-04-18 12:49:24 +05:30
prafull-opensignlabsandGitHub cc8b4b7157 Merge pull request #614 from OpenSignLabs/fix_issue
fix: onclick upgrade to customize email button redirect new window of subscription page
2024-04-17 21:05:01 +05:30
RaktimaNXG 4cbd7e74f7 fix: onclick upgrade to customize email button redirect new window of subscription page 2024-04-17 21:02:05 +05:30
prafull-opensignlabsandGitHub b300a5bb39 Merge pull request #613 from OpenSignLabs/fix_issue
fix : logo issue in guest login flow
2024-04-17 20:37:26 +05:30
RaktimaNXG 5422fefc54 fix: add default logo from assets 2024-04-17 20:34:48 +05:30
RaktimaNXG 248b0cac0b fix : logo issue in guest login flow 2024-04-17 20:28:18 +05:30
prafull-opensignlabsandGitHub a388a7c345 Merge pull request #612 from OpenSignLabs/fix_issue
fix: issue of signUrl and subscription details in guest login flow
2024-04-17 20:14:47 +05:30
RaktimaNXG fc564ca37b fix: check subscription details issue in guest login flow 2024-04-17 20:11:19 +05:30
RaktimaNXG 37b2e4c97a fix: in page section show signed pdf after sign successfully 2024-04-17 19:52:36 +05:30
prafull-opensignlabsandGitHub a3bc9817e7 Merge pull request #611 from OpenSignLabs/feat_report
fix: z-index of loader in report
2024-04-17 19:42:51 +05:30
prafull-opensignlabs 8a216e6c0d fix: z-index of loader in report 2024-04-17 19:42:12 +05:30
prafull-opensignlabsandGitHub 613b1cfe07 Merge pull request #610 from OpenSignLabs/fix_issue
fix: invalid document url convert in arrayBuffer issue
2024-04-17 19:19:49 +05:30
RaktimaNXG ee77534e35 fix: change enter label to enter text for text widget plaaceholder 2024-04-17 19:12:56 +05:30
RaktimaNXG 737b33db1e fix: add try catch to convert pdf array buffer 2024-04-17 19:04:57 +05:30
RaktimaNXG ac214d0467 fix: invalid document url convert in arrayBuffer issue 2024-04-17 18:26:12 +05:30
prafull-opensignlabsandGitHub f8b21e7a93 Merge pull request #609 from OpenSignLabs/fix_issue
fix: opensign-drive and template report tour message
2024-04-17 16:55:02 +05:30
RaktimaNXG 7e6df13b71 fix: change template report option tour message 2024-04-17 16:53:18 +05:30
RaktimaNXG 3e584cec1a feat: add console application url in header dropdown list 2024-04-17 16:38:47 +05:30
RaktimaNXG bc75dd109f fix: remove console log 2024-04-17 16:17:25 +05:30
RaktimaNXG 6b3abed782 fix:template report tour messsage 2024-04-17 16:14:21 +05:30
RaktimaNXG f163958cd0 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into fix_issue 2024-04-17 14:36:36 +05:30
RaktimaNXG ecd461b96c fix: remove changes 2024-04-17 14:36:13 +05:30
RaktimaNXG 1371c2c09a fix: opensign-drive tour message error andtemplate report tour message 2024-04-17 14:30:53 +05:30
AndrewandGitHub 3121f35158 Merge pull request #606 from OpenSignLabs/prafull-opensignlabs-patch-6 2024-04-17 12:13:30 +05:30
prafull-opensignlabsandGitHub 3c0db9473c feat: send custom completion mail if available 2024-04-17 12:12:08 +05:30
prafull-opensignlabsandGitHub 4f2b72dd4d Merge pull request #604 from OpenSignLabs/fix_issue
fix: remove add recipient button from signyour-self flow in mobile view
2024-04-16 20:03:52 +05:30
AmolandGitHub 7d9af1db95 Merge pull request #605 from OpenSignLabs/feat_report
style: change design of buttons in report
2024-04-16 19:55:32 +05:30
prafull-opensignlabs 0e38783560 style: change design of buttons in report 2024-04-16 19:54:22 +05:30
RaktimaNXG 6880ae35a8 fix: remove add recipient button from signyour-self flow in mobile view 2024-04-16 19:31:21 +05:30
prafull-opensignlabsandGitHub 9a07c67b69 Merge pull request #602 from OpenSignLabs/report_decline
feat: add revoke button in inprogress report to revoke/decline document
2024-04-16 17:28:01 +05:30
RaktimaandGitHub 33bfdeef15 Update GetReportDisplay.js 2024-04-16 17:27:26 +05:30
RaktimaandGitHub a404247604 Update PdfRequestFiles.js 2024-04-16 17:02:11 +05:30
RaktimaandGitHub c48579daa7 Update GetReportDisplay.js 2024-04-16 16:59:18 +05:30
AmolandGitHub 509f8f9a07 Merge pull request #603 from OpenSignLabs/plan_sub
refactor: update extra-info of user
2024-04-16 15:59:27 +05:30
prafull-opensignlabs 5b85d5b277 refactor: update extrainfo of user 2024-04-16 15:57:37 +05:30
RaktimaNXG bbf3170398 feat: add revoke button in inprogress report to revoke/decline document 2024-04-16 15:01:36 +05:30
prafull-opensignlabsandGitHub c172a7c55c Merge pull request #598 from OpenSignLabs/tour_message
feat : add recipient button in placeholder flow to add new signers in document signer's list
2024-04-16 14:01:28 +05:30
RaktimaNXG 90f3e0066b fix: remove already attached signers from add user link modal in dropdown 2024-04-16 13:36:10 +05:30
RaktimaNXG b6e028a2c0 fix: add comment 2024-04-16 13:09:30 +05:30
RaktimaNXG 11384192ba refactor code 2024-04-16 12:08:50 +05:30
prafull-opensignlabsandGitHub 9df63265fb Merge branch 'staging' into tour_message 2024-04-16 12:02:42 +05:30
RaktimaNXG ff6a8ce755 fix: customize email is not working for pro user in placeholder flow 2024-04-16 11:06:02 +05:30
AmolandGitHub 60c1aa253a Merge pull request #601 from OpenSignLabs/plan_sub 2024-04-15 19:49:49 +05:30
prafull-opensignlabs ad2fec0d66 fix: save planname and next billing date 2024-04-15 19:43:40 +05:30
AmolandGitHub 0451a6cae0 Merge pull request #600 from OpenSignLabs/plan_sub 2024-04-15 19:15:40 +05:30
prafull-opensignlabs 0d5f8981fe refactor: change in checking subscription 2024-04-15 19:14:24 +05:30
RaktimaNXG cfd8be0eee fix: add delete button to delete signers list in request send flow 2024-04-15 18:42:03 +05:30
RaktimaNXG 86e5ef6ef5 fix: invalid session token issue in recipient signature flow 2024-04-15 16:30:01 +05:30
RaktimaNXG 1af47c8425 fix: recipient color mismatch issue 2024-04-15 16:01:11 +05:30
AmolandGitHub f0395cb2c7 Merge pull request #599 from OpenSignLabs/feat_report
feat: add delete button in all reports and share button in in-progess report
2024-04-15 15:25:21 +05:30
prafull-opensignlabs 598aa2975f fix: share link not generate correctly 2024-04-15 14:28:23 +05:30
prafull-opensignlabs 0742291216 feat: add delete button in reports 2024-04-15 13:20:15 +05:30
RaktimaNXG 94eeceeaf9 feat : add recipient button in placeholder flow to add new signers in document signer's list 2024-04-15 12:58:16 +05:30
prafull-opensignlabsandGitHub 970fbecb7b Merge pull request #592 from OpenSignLabs/tour_message
fix: issue of return result when subscription result empty in getSubscription cloud function
2024-04-12 17:54:01 +05:30
RaktimaNXG 361e082930 fix: issue of return result when subscription result empty 2024-04-12 17:51:40 +05:30
AndrewandGitHub 3f9b6eb5a9 Merge pull request #591 from OpenSignLabs/plan 2024-04-12 17:24:33 +05:30
prafull-opensignlabs 2265bee61f feat: add widgets in API 2024-04-12 17:13:27 +05:30
prafull-opensignlabsandGitHub 6390706d8d Merge pull request #590 from OpenSignLabs/tour_message
fix: auto sign issue, change template report and open sign drive tour field name
2024-04-12 15:12:03 +05:30
RaktimaNXG 37e03c1026 fix: update getSubscriptions cloud function 2024-04-12 15:01:29 +05:30
RaktimaNXG c6fa6c710d fix: auto sign issue , change template report and sopen sign drive tour field name 2024-04-12 11:32:51 +05:30
AndrewandGitHub 2e921e30b6 Merge pull request #588 from OpenSignLabs/plan 2024-04-11 16:02:16 +05:30
prafull-opensignlabs 686a773d34 fix: update suibscription issue 2024-04-11 16:01:14 +05:30
AndrewandGitHub b03e1b8f9d Merge pull request #587 from OpenSignLabs/plan 2024-04-11 13:54:37 +05:30
prafull-opensignlabs f1ceae794f fix: logo fetch issue 2024-04-11 13:40:08 +05:30
AndrewandGitHub 5a8bcb8f9a Merge pull request #586 from OpenSignLabs/plan 2024-04-11 11:23:42 +05:30
prafull-opensignlabs ce70da7d8e fix: not able to get response of invoices 2024-04-11 11:14:37 +05:30
AmolandGitHub db03dfa641 Merge pull request #584 from OpenSignLabs/tour_message
feat: add tour message for template report and opensign drive
2024-04-10 19:02:39 +05:30
RaktimaNXG 7e70a5e111 remove console 2024-04-10 18:48:58 +05:30
RaktimaNXG f5f18f1630 fix: change tour message 2024-04-10 18:47:08 +05:30
AmolandGitHub fa6856c649 Merge pull request #583 from OpenSignLabs/user_profile 2024-04-10 16:28:14 +05:30
RaktimaNXG 3b4946e47b feat: add company and job title in user profile for modification 2024-04-10 16:21:17 +05:30
RaktimaNXG 05fe68d458 feat : add tour message on template report and opensign-drive 2024-04-10 15:57:05 +05:30
AmolandGitHub 2f3cdca821 Merge pull request #582 from OpenSignLabs/custom_logo 2024-04-10 11:04:02 +05:30
RaktimaNXG 4d8a54a6c5 fix: remove style attribute and use classname of tailwind in share signers list 2024-04-10 10:51:03 +05:30
RaktimaNXG 4fe5bdaad8 fix: signers list issue in share option 2024-04-10 10:45:59 +05:30
AmolandGitHub b95ede5273 Merge pull request #581 from OpenSignLabs/custom_logo 2024-04-09 19:16:39 +05:30
RaktimaNXG 5b2684a9f4 refactor: remove unnecessary code 2024-04-09 18:52:34 +05:30
RaktimaNXG d779720f26 implement share button option to share recipient's sign url after finishing a doc creation using request signature flow 2024-04-09 18:48:32 +05:30
AmolandGitHub 3cf8029978 Merge pull request #580 from OpenSignLabs/plan
chore: add test plan
2024-04-09 17:40:16 +05:30
prafull-opensignlabs 0bb857a942 chore: update url 2024-04-09 17:37:43 +05:30
prafull-opensignlabs 103d32edcb chore: add test plan 2024-04-09 17:35:26 +05:30
AmolandGitHub eca6ad374b Merge pull request #578 from OpenSignLabs/patch-1
fix: uncaught error in analytics
2024-04-09 15:24:48 +05:30
prafull-opensignlabs d2efe88266 fix: uncaught error in anaytics 2024-04-09 15:22:23 +05:30
prafull-opensignlabsandGitHub 450e5ec7f7 Merge pull request #577 from OpenSignLabs/patch-1
fix: path issue in server
2024-04-09 13:37:22 +05:30
prafull-opensignlabs 3bbbda91b8 fix: path issue in server 2024-04-09 13:35:23 +05:30
prafull-opensignlabsandGitHub a48db306bb Merge pull request #576 from OpenSignLabs/patch-1
fix: save invoice and payment analytics
2024-04-09 11:30:34 +05:30
prafull-opensignlabsandGitHub 2a60b30c75 Merge pull request #575 from OpenSignLabs/custom_logo
fix:  variables are not getting replace in custom email issue
2024-04-09 11:30:25 +05:30
prafull-opensignlabs 19cfa9d27d fix save invoice and payment analytics 2024-04-09 11:29:33 +05:30
RaktimaNXG 0fc12c957e fix: variables are not getting replace in custom email 2024-04-09 10:51:18 +05:30
prafull-opensignlabsandGitHub 6fd332f8b4 Merge pull request #568 from OpenSignLabs/custom_logo
feat: implement custom email template of request email and completion email
2024-04-08 20:26:06 +05:30
AmolandGitHub 39f7ccecaa Merge pull request #574 from OpenSignLabs/patch-1 2024-04-08 20:25:20 +05:30
prafull-opensignlabs 3bd306809b fix: change path of favicon and manifest 2024-04-08 20:24:32 +05:30
RaktimaNXG 04e9d13206 fix: change costomize upgrade message 2024-04-08 19:39:57 +05:30
AmolandGitHub b24739d61b Merge pull request #573 from OpenSignLabs/patch-1 2024-04-08 19:38:01 +05:30
prafull-opensignlabs 9dda211605 fix: classname of save subscription analytics 2024-04-08 19:35:00 +05:30
AmolandGitHub 93708a6875 Merge pull request #572 from OpenSignLabs/patch-1 2024-04-08 19:27:31 +05:30
prafull-opensignlabs e78f6f6df4 fix: save subscription analytics 2024-04-08 19:26:57 +05:30
AmolandGitHub ccaaca75ba Merge pull request #571 from OpenSignLabs/patch-1 2024-04-08 19:22:33 +05:30
prafull-opensignlabs dedff36b35 fix: save subscriptions in analytics 2024-04-08 19:21:50 +05:30
RaktimaNXG 4d561ca19e fix: opacity of costomize button 2024-04-08 19:11:10 +05:30
RaktimaNXG f70e9e2a3f fix: design of customize in email 2024-04-08 19:01:13 +05:30
AmolandGitHub e4a3ce405e Merge pull request #570 from OpenSignLabs/patch-1 2024-04-08 18:54:58 +05:30
prafull-opensignlabs c3be2a1fef fix: update query in fetch analytics 2024-04-08 18:54:13 +05:30
AmolandGitHub 8b2f7f1e95 Merge pull request #569 from OpenSignLabs/patch-1 2024-04-08 18:42:02 +05:30
RaktimaNXG 55465a035f fix: add function to hcnage variable string name to variable value for email body and subject 2024-04-08 18:02:03 +05:30
RaktimaNXG d30105299c fix: remove loading=true from login and signup page 2024-04-08 17:24:14 +05:30
prafull-opensignlabs 78b038da13 feat: implement of functionality to save and fetch analytics 2024-04-08 17:18:37 +05:30
RaktimaNXG d230ad2655 feat: add migration script of update contracts_document class to add field RequestBody and RequestSubject 2024-04-08 17:17:29 +05:30
AmolandGitHub 367c824918 Merge pull request #567 from OpenSignLabs/patch-1 2024-04-08 17:15:01 +05:30
RaktimaNXG 5f8affb1bb feat: implement custom email template of request email and completion email 2024-04-08 17:13:04 +05:30
prafull-opensignlabs 3bd23f83cf fix: completion certificate not generating for old completed documents 2024-04-08 17:05:46 +05:30
AmolandGitHub ab1dbecab2 Merge pull request #564 from OpenSignLabs/patch-1 2024-04-05 22:39:43 +05:30
prafull-opensignlabs 44c6aa9b0b fix: import issue and logo 2024-04-05 22:38:21 +05:30
AmolandGitHub ad922c16c7 Merge pull request #562 from OpenSignLabs/patch-1
fix: remove public access from do space and generate signed url wherever PDF files are accessed
2024-04-05 21:30:14 +05:30
prafull-opensignlabsandGitHub 87ee2e881e Merge branch 'staging' into patch-1 2024-04-05 21:23:28 +05:30
prafull-opensignlabs 3659a1de55 fix: remove public access from do space and generate signed url wherever PDF files are accessed 2024-04-05 21:21:19 +05:30
AmolandGitHub cc49a2fd4e Merge pull request #560 from OpenSignLabs/custom_logo 2024-04-04 20:29:46 +05:30
RaktimaNXG babd43e710 feat: display custom app logo saved by user from console app 2024-04-04 19:28:45 +05:30
prafull-opensignlabsandGitHub 8dde38edf1 Merge pull request #558 from OpenSignLabs/patch-1
feat: attach digital sign to completion certificate
2024-04-04 15:29:27 +05:30
prafull-opensignlabs 6c16d00031 feat: attach digital sign to completion certificate 2024-04-04 15:26:41 +05:30
prafull-opensignlabsandGitHub 99f444a8a1 Merge pull request #557 from OpenSignLabs/cookies_setup
fix: remove unnecessary session value to save in cookies session
2024-04-04 12:56:55 +05:30
RaktimaNXG f0149f6fd8 fix: redirect console user domain name 2024-04-04 12:06:21 +05:30
RaktimaNXG bdb7d9be8f fix: remove unnecessary session value to save in cookies session 2024-04-04 12:02:08 +05:30
prafull-opensignlabsandGitHub 27dcd7fefd Merge pull request #556 from OpenSignLabs/cookies_setup
feat: save user's details in session
2024-04-03 20:33:47 +05:30
RaktimaNXG a2467ef1a6 refactor code 2024-04-03 20:30:46 +05:30
RaktimaNXG 824b0a5407 fix: remove unnecessary cookies key value 2024-04-03 20:15:17 +05:30
RaktimaNXG ac8195a52d Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into cookies_setup 2024-04-03 15:29:41 +05:30
RaktimaNXG e0920bf5d8 feat: save user's details in cookies session 2024-04-03 15:28:48 +05:30
prafull-opensignlabsandGitHub 4a5f030d87 Merge pull request #555 from OpenSignLabs/patch-1
fix: Expired document message appears partially before appearing fully
2024-04-02 14:54:37 +05:30
prafull-opensignlabs ba839b0fc8 fix: Expired document message appears partially before appearing fully 2024-04-02 14:53:16 +05:30
prafull-opensignlabsandGitHub 27130fd223 Merge pull request #554 from OpenSignLabs/patch-1
fix: Expired report query
2024-04-02 14:17:08 +05:30
prafull-opensignlabs 19ba9e3091 fix: expired report query 2024-04-02 14:15:03 +05:30
prafull-opensignlabs 8c64a89f08 feat: add pro logo in header for paid user 2024-04-02 13:09:55 +05:30
prafull-opensignlabsandGitHub 1c3d2d5c9a Merge pull request #553 from OpenSignLabs/sorting_in_drive
fix: sorting by name issue in open-sign drive
2024-04-02 12:34:00 +05:30
prafull-opensignlabs b36df32b19 fix: design of subscription plans 2024-04-02 12:29:14 +05:30
RaktimaNXG 155a3eee14 fix: remove unnecessary code 2024-04-02 12:25:02 +05:30
prafull-opensignlabsandGitHub 7bb46782a3 Merge pull request #549 from OpenSignLabs/Input_text_widget
feat: make all type text input widgets consistent with the text widget
2024-04-02 12:18:44 +05:30
RaktimaNXG 40961936d0 fix: sorting issue by name in open-sign drive 2024-04-02 11:54:03 +05:30
RaktimaNXG db310b0d73 fix: remove text validation from name,company,job title widgets 2024-04-02 11:01:46 +05:30
prafull-opensignlabsandGitHub f0e9c6b3e4 Merge pull request #552 from OpenSignLabs/staging
sync branch
2024-04-02 09:46:08 +05:30
RaktimaNXG 9d81fce956 fix: input text width 2024-04-01 19:26:04 +05:30
RaktimaNXG c61be7c35b fix: all text type fields similar to text widget 2024-04-01 15:38:06 +05:30
85 changed files with 4660 additions and 1976 deletions
+175 -5
View File
@@ -22,6 +22,7 @@
"radix-ui": "^1.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.2",
"react-cookie": "^7.1.4",
"react-datepicker": "^6.4.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
@@ -33,6 +34,7 @@
"react-konva": "^18.2.10",
"react-modal": "^3.16.1",
"react-pdf": "^7.7.1",
"react-quill": "^2.0.0",
"react-redux": "^9.1.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.22.3",
@@ -41,6 +43,7 @@
"react-select": "^5.8.0",
"react-signature-canvas": "^1.0.6",
"react-tooltip": "^5.26.3",
"react-web-share": "^2.0.2",
"reactour": "^1.19.2",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
@@ -6054,6 +6057,11 @@
"@types/node": "*"
}
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
"node_modules/@types/eslint": {
"version": "8.44.4",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.4.tgz",
@@ -6108,11 +6116,9 @@
}
},
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.3.tgz",
"integrity": "sha512-Wny3a2UXn5FEA1l7gc6BbpoV5mD1XijZqgkp4TRgDCDL5r3B5ieOFGUX5h3n78Tr1MEG7BfvoM8qeztdvNU0fw==",
"optional": true,
"peer": true,
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
@@ -6205,6 +6211,14 @@
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz",
"integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg=="
},
"node_modules/@types/quill": {
"version": "1.3.10",
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
"dependencies": {
"parchment": "^1.1.2"
}
},
"node_modules/@types/range-parser": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz",
@@ -9428,6 +9442,25 @@
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
"integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA=="
},
"node_modules/deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
"dependencies": {
"is-arguments": "^1.1.1",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.5.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -10986,6 +11019,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -12612,6 +12650,21 @@
"node": ">= 10"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
@@ -16841,6 +16894,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -17194,6 +17262,11 @@
"tslib": "^2.0.3"
}
},
"node_modules/parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -19134,6 +19207,50 @@
}
]
},
"node_modules/quill": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
"dependencies": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.2",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
}
},
"node_modules/quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"dependencies": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/quill-delta/node_modules/fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
},
"node_modules/quill/node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/quill/node_modules/eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
},
"node_modules/radix-ui": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.0.1.tgz",
@@ -19354,6 +19471,19 @@
"react": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-cookie": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.1.4.tgz",
"integrity": "sha512-wDxxa/HYaSXSMlyWJvJ5uZTzIVtQTPf1gMksFgwAz/2/W3lCtY8r4OChCXMPE7wax0PAdMY97UkNJedGv7KnDw==",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.5",
"hoist-non-react-statics": "^3.3.2",
"universal-cookie": "^7.0.0"
},
"peerDependencies": {
"react": ">= 16.3.0"
}
},
"node_modules/react-datepicker": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-6.4.0.tgz",
@@ -19732,6 +19862,20 @@
}
}
},
"node_modules/react-quill": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz",
"integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==",
"dependencies": {
"@types/quill": "^1.3.10",
"lodash": "^4.17.4",
"quill": "^1.3.7"
},
"peerDependencies": {
"react": "^16 || ^17 || ^18",
"react-dom": "^16 || ^17 || ^18"
}
},
"node_modules/react-reconciler": {
"version": "0.29.0",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz",
@@ -20082,6 +20226,15 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/react-web-share": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-web-share/-/react-web-share-2.0.2.tgz",
"integrity": "sha512-bGm1TJc6xtC0MhAnYFsrzT4En7l2oAdLTgCK7nk7b4xGTg3L5gOldiPrmYQ8a/dEXNydgekvOj/tfjduIlIRVA==",
"peerDependencies": {
"react": "^16 || ^17 || ^18",
"react-dom": "^16 || ^17 || ^18"
}
},
"node_modules/reactour": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/reactour/-/reactour-1.19.2.tgz",
@@ -22701,6 +22854,23 @@
"node": ">=8"
}
},
"node_modules/universal-cookie": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.1.4.tgz",
"integrity": "sha512-Q+DVJsdykStWRMtXr2Pdj3EF98qZHUH/fXv/gwFz/unyToy1Ek1w5GsWt53Pf38tT8Gbcy5QNsj61Xe9TggP4g==",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^0.6.0"
}
},
"node_modules/universal-cookie/node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/universalify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+3
View File
@@ -17,6 +17,7 @@
"radix-ui": "^1.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.2",
"react-cookie": "^7.1.4",
"react-datepicker": "^6.4.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
@@ -28,6 +29,7 @@
"react-konva": "^18.2.10",
"react-modal": "^3.16.1",
"react-pdf": "^7.7.1",
"react-quill": "^2.0.0",
"react-redux": "^9.1.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.22.3",
@@ -36,6 +38,7 @@
"react-select": "^5.8.0",
"react-signature-canvas": "^1.0.6",
"react-tooltip": "^5.26.3",
"react-web-share": "^2.0.2",
"reactour": "^1.19.2",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
+2 -2
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="icon" href="/favicon.ico" />
<!-- <link rel="icon" type="image/png"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAALlJREFUaEPtmN0NwjAMBpNxYDKYiM1Yp90g93CKStH1NbIdfz921Dker2Pc+Js1cDF7MXAxASMGYkAigBI6vh9ZwoXP53uZoAYcvhwdA3mAVbI2aSb+9zFKU4IURB6j/HoPUIEa2G3iGIAhQQDlAUIoE2diabIklISS0NoFPebo77RF6OenEF3QntOm128he0GKrwHyACFoz2MgBqSGkpAEcHs47oHtN5AFakACqMNjQEMoE8SABFCHn4HE2zGHSLeEAAAAAElFTkSuQmCC"
sizes="40x40" /> -->
@@ -15,7 +15,7 @@
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="manifest" href="/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
+28 -3
View File
@@ -4,18 +4,20 @@ import FullScreenButton from "./FullScreenButton";
import { useNavigate } from "react-router-dom";
import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize";
import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import { checkIsSubscribed, getAppLogo, openInNewTab } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const";
const Header = ({ showSidebar }) => {
const navigate = useNavigate();
const { width } = useWindowSize();
let applogo = localStorage.getItem("appLogo") || "";
let username = localStorage.getItem("username");
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true);
const [isPro, setIsPro] = useState(false);
const [applogo, setAppLogo] = useState(
localStorage.getItem("appLogo") || " "
);
const toggleDropdown = () => {
setIsOpen(!isOpen);
@@ -27,9 +29,19 @@ const Header = ({ showSidebar }) => {
async function checkSubscription() {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
if (getIsSubscribe) {
const applogo = await getAppLogo();
if (applogo) {
setAppLogo(applogo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
setIsPro(getIsSubscribe);
setIsSubscribe(getIsSubscribe);
}
}
const closeDropdown = () => {
setIsOpen(false);
Parse.User.logOut();
@@ -105,6 +117,11 @@ const Header = ({ showSidebar }) => {
</button>
</div>
)}
{isPro && (
<div className="w-[35px] h-[35px] rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
PRO
</div>
)}
<div>
<FullScreenButton />
</div>
@@ -159,6 +176,14 @@ const Header = ({ showSidebar }) => {
>
<i className="fa-solid fa-lock"></i> Change Password
</li>
<li
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
onClick={() => {
window.open("https://console.opensignlabs.com/");
}}
>
<i className="fa-regular fa-id-card"></i> Console
</li>
<li
className="hover:bg-gray-100 rounded-b-lg py-1 px-2 cursor-pointer font-normal"
onClick={closeDropdown}
@@ -149,7 +149,7 @@ const DashboardCard = (props) => {
}
})
.then((res) => {
const listData = res.data?.result.filter(
const listData = res.data?.result?.filter(
(x) => x.Signers.length > 0
);
let arr = [];
@@ -97,11 +97,13 @@ function DashboardReport(props) {
setIsMoreDocs(false);
}
setIsNextRecord(false);
setList((prevRecord) =>
prevRecord.length > 0
? [...prevRecord, ...res.data.result]
: res.data.result
);
if (!res.data.result.error) {
setList((prevRecord) =>
prevRecord.length > 0
? [...prevRecord, ...res.data.result]
: res.data.result
);
}
}
setIsLoader(false);
} catch (err) {
@@ -110,6 +112,7 @@ function DashboardReport(props) {
console.log("err ", err);
setIsLoader(false);
}
setIsLoader(false);
}
} else {
setIsLoader(false);
@@ -70,6 +70,7 @@ function DriveBody(props) {
return item;
});
props.setPdfData(updatedData);
props.sortingData(null, null, updatedData);
await axios
.put(
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
@@ -214,8 +215,8 @@ function DriveBody(props) {
? true
: false
: selecFolderId
? false
: true;
? false
: true;
if (!checkExist) {
if (moveFolderId) {
updateData = {
@@ -384,6 +385,7 @@ function DriveBody(props) {
flexDirection: "column",
cursor: "pointer"
}}
data-tut={props.dataTutSeventh}
>
<img
alt="no img"
@@ -451,6 +453,7 @@ function DriveBody(props) {
className="pdf-icon"
src={pdfLogo}
onClick={() => checkPdfStatus(data)}
data-tut={props.dataTutSixth}
/>
{rename === data.objectId ? (
<input
@@ -1,5 +1,6 @@
import React, { useState } from "react";
import "../../styles/AddUser.css";
import { getFileName } from "../../constant/Utils";
// import SelectFolder from "../../premitives/SelectFolder";
const EditTemplate = ({ template, onSuccess }) => {
@@ -45,7 +46,7 @@ const EditTemplate = ({ template, onSuccess }) => {
fontWeight: "700"
}}
>
{template.URL?.split("/")?.pop()?.split("_")[1]}
{getFileName(template.URL)}
</div>
</div>
<div className="form-section">
@@ -0,0 +1,157 @@
import React from "react";
import { Quill } from "react-quill";
// Custom Undo button icon component for Quill editor. You can import it directly
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
// handle them correctly
const CustomUndo = () => (
<svg viewBox="0 0 18 18">
<polygon className="ql-fill ql-stroke" points="6 10 4 12 2 10 6 10" />
<path
className="ql-stroke"
d="M8.09,13.91A4.6,4.6,0,0,0,9,14,5,5,0,1,0,4,9"
/>
</svg>
);
// Redo button icon component for Quill editor
const CustomRedo = () => (
<svg viewBox="0 0 18 18">
<polygon className="ql-fill ql-stroke" points="12 10 14 12 16 10 12 10" />
<path
className="ql-stroke"
d="M9.91,13.91A4.6,4.6,0,0,1,9,14a5,5,0,1,1,5-5"
/>
</svg>
);
// Undo and redo functions for Custom Toolbar
function undoChange() {
this.quill.history.undo();
}
function redoChange() {
this.quill.history.redo();
}
// Add sizes to whitelist and register them
const Size = Quill.import("formats/size");
Size.whitelist = ["extra-small", "small", "medium", "large"];
Quill.register(Size, true);
// Add fonts to whitelist and register them
const Font = Quill.import("formats/font");
Font.whitelist = [
"arial",
"comic-sans",
"courier-new",
"georgia",
"helvetica",
"lucida"
];
Quill.register(Font, true);
// Modules object for setting up the Quill editor
export const module1 = {
toolbar: {
container: "#toolbar1",
handlers: {
undo: undoChange,
redo: redoChange
}
},
history: {
delay: 500,
maxStack: 100,
userOnly: true
}
};
// Formats objects for setting up the Quill editor
export const formats = [
"header",
"font",
"size",
"bold",
"italic",
"underline",
"align",
"strike",
"script",
"blockquote",
"background",
"list",
"bullet",
"indent",
"link",
"image",
"color",
"code-block"
];
// Quill Toolbar component
export const QuillToolbar = ({ containerId }) => (
<div id={containerId}>
<span className="ql-formats">
<select className="ql-font" defaultValue="arial">
<option value="arial">Arial</option>
<option value="comic-sans">Comic Sans</option>
<option value="courier-new">Courier New</option>
<option value="georgia">Georgia</option>
<option value="helvetica">Helvetica</option>
<option value="lucida">Lucida</option>
</select>
<select className="ql-size" defaultValue="medium">
<option value="extra-small">Size 1</option>
<option value="small">Size 2</option>
<option value="medium">Size 3</option>
<option value="large">Size 4</option>
</select>
<select className="ql-header" defaultValue="3">
<option value="1">Heading</option>
<option value="2">Subheading</option>
<option value="3">Normal</option>
</select>
</span>
<span className="ql-formats">
<button className="ql-bold" />
<button className="ql-italic" />
<button className="ql-underline" />
<button className="ql-strike" />
</span>
<span className="ql-formats">
<button className="ql-list" value="ordered" />
<button className="ql-list" value="bullet" />
<button className="ql-indent" value="-1" />
<button className="ql-indent" value="+1" />
</span>
<span className="ql-formats">
<button className="ql-script" value="super" />
<button className="ql-script" value="sub" />
<button className="ql-blockquote" />
<button className="ql-direction" />
</span>
<span className="ql-formats">
<select className="ql-align" />
<select className="ql-color" />
<select className="ql-background" />
</span>
<span className="ql-formats">
<button className="ql-link" />
<button className="ql-image" />
<button className="ql-video" />
</span>
<span className="ql-formats">
<button className="ql-formula" />
<button className="ql-code-block" />
<button className="ql-clean" />
</span>
<span className="ql-formats">
<button className="ql-undo">
<CustomUndo />
</button>
<button className="ql-redo">
<CustomRedo />
</button>
</span>
</div>
);
export default QuillToolbar;
@@ -0,0 +1,52 @@
import React from "react";
import Tooltip from "../../primitives/Tooltip";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import EditorToolbar, { module1, formats } from "./EditorToolbar";
export function EmailBody(props) {
return (
<React.Fragment>
<div className={`flex justify-center py-1 p-2`}>
<div className=" flex flex-col">
<form>
<div className="text-lg font-normal text-black">
<div className=" m-2 md:m-10 p-3 md:p-10 shadow-md hover:shadow-lg border-[1px] border-indigo-800 rounded-md">
<label className="text-sm ml-2">
Subject <Tooltip message={"email subject"} />
</label>
<input
required
value={props.requestSubject}
onChange={(e) => props.setRequestSubject(e.target.value)}
placeholder="${senderName} has requested you to sign ${documentName}"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
/>
<label className="text-sm ml-2 mt-3">
Body <Tooltip message={"email body"} />
</label>
<div className="px-3 py-2 w-full focus:outline-none text-xs">
<EditorToolbar containerId="toolbar1" />
<ReactQuill
theme="snow"
value={props.requestBody}
// value={typedText}
placeholder="add body of email "
// readOnly={loading}
// onChangeSelection={handleTextSelection} // Listen for text selection
ref={props.editorRef}
modules={module1}
formats={formats}
onChange={props.handleOnchangeRequest}
/>
</div>
</div>
</div>
</form>
</div>
</div>
</React.Fragment>
);
}
+161 -317
View File
@@ -1,7 +1,5 @@
import React from "react";
import PrevNext from "./PrevNext";
import { PDFDownloadLink } from "@react-pdf/renderer";
import Certificate from "./Certificate";
import printModule from "print-js";
import { getBase64FromUrl } from "../../constant/Utils";
import { saveAs } from "file-saver";
@@ -9,18 +7,17 @@ import "../../styles/signature.css";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { useNavigate } from "react-router-dom";
import { themeColor } from "../../constant/const";
import Certificate from "./Certificate";
import { PDFDownloadLink } from "@react-pdf/renderer";
function Header({
isPdfRequestFiles,
isPlaceholder,
recipient,
setIsDecline,
pageNumber,
isAlreadySign,
allPages,
changePage,
pdfUrl,
documentStatus,
embedWidgetsData,
pdfDetails,
signerPos,
@@ -94,8 +91,21 @@ function Header({
// Replace spaces with underscore
return pdfName.replace(/ /g, "_");
};
//certificate generate and download component in mobile view
const CertificateDropDown = () => {
//handle download signed pdf
const handleDownloadCertificate = async () => {
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try {
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
} catch (err) {
console.log("err in download in certificate", err);
}
}
};
const GenerateCertificate = () => {
//after generate download certifcate pdf
const handleDownload = (pdfBlob, fileName) => {
if (pdfBlob) {
@@ -122,76 +132,20 @@ function Header({
document={<Certificate pdfData={pdfDetails} />}
>
{({ blob, loading }) => (
<>
{loading ? (
<div
style={{
border: "none",
backgroundColor: "#fff"
}}
>
<i
className="fa-solid fa-award"
style={{ marginRight: "2px" }}
aria-hidden="true"
></i>
Certificate
</div>
) : (
<div
style={{ border: "none", backgroundColor: "#fff" }}
onClick={() =>
handleDownload(
blob,
`completion certificate-${
pdfDetails[0] && pdfDetails[0].Name
}.pdf`
)
}
>
<i
className="fa-solid fa-award"
style={{ marginRight: "2px" }}
aria-hidden="true"
></i>
Certificate
</div>
)}
</>
)}
</PDFDownloadLink>
);
};
const CertificateComponent = () => {
return (
<PDFDownloadLink
style={{ textDecoration: "none" }}
document={<Certificate pdfData={pdfDetails} />}
fileName={`completion certificate-${
pdfDetails[0] && pdfDetails[0].Name
}.pdf`}
>
{({ loading }) => (
<button
type="button"
className="defaultBtn certificateBtn"
onClick={() =>
handleDownload(
blob,
`completion certificate-${
pdfDetails[0] && pdfDetails[0].Name
}.pdf`
)
}
disabled={loading}
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
className=" md:bg-[#08bc66] border-none focus:outline-none flex flex-row items-center md:shadow md:rounded-[3px] py-[3px] md:px-[11px] md:text-white text-black md:font-[500] text-[13px] mr-[5px]"
>
<i
className="fa-solid fa-award"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Certificate
<i className="fa-solid fa-award py-[3px]" aria-hidden="true"></i>
<span className="md:hidden lg:block ml-1">Certificate</span>
</button>
)}
</PDFDownloadLink>
@@ -210,11 +164,7 @@ function Header({
}}
>
<div className="preBtn2">
<div
onClick={() => {
navigate(-1);
}}
>
<div onClick={() => navigate(-1)}>
<i
className="fa fa-arrow-left"
aria-hidden="true"
@@ -266,7 +216,6 @@ function Header({
<i className="fa fa-ellipsis-v" aria-hidden="true"></i>
</div>
</DropdownMenu.Trigger>
<DropdownMenu.Portal>
<DropdownMenu.Content
className="DropdownMenuContent"
@@ -290,42 +239,53 @@ function Header({
Download
</div>
</DropdownMenu.Item>
{recipient && pdfDetails[0] && pdfDetails.length > 0 ? (
<DropdownMenu.Item className="DropdownMenuItem">
<CertificateDropDown />
</DropdownMenu.Item>
) : isPdfRequestFiles &&
alreadySign &&
isCompleted.isCertificate ? (
<DropdownMenu.Item className="DropdownMenuItem">
<CertificateDropDown />
</DropdownMenu.Item>
) : (
isSignYourself && (
<>
<DropdownMenu.Item className="DropdownMenuItem">
<CertificateDropDown />
</DropdownMenu.Item>
{isCompleted && (
<>
{pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => setIsEmail(true)}
onClick={() => handleDownloadCertificate()}
>
<div
style={{
display: "flex",
flexDirection: "row"
border: "none",
backgroundColor: "#fff"
}}
>
<i
className="fa fa-envelope"
className="fa-solid fa-award"
style={{ marginRight: "2px" }}
aria-hidden="true"
></i>
Mail
Certificate
</div>
</DropdownMenu.Item>
</>
)
) : (
<DropdownMenu.Item className="DropdownMenuItem">
<GenerateCertificate />
</DropdownMenu.Item>
)}
</>
)}
{isSignYourself && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => setIsEmail(true)}
>
<div
style={{
display: "flex",
flexDirection: "row"
}}
>
<i
className="fa fa-envelope"
style={{ marginRight: "2px" }}
aria-hidden="true"
></i>
Mail
</div>
</DropdownMenu.Item>
)}
<DropdownMenu.Item
className="DropdownMenuItem"
@@ -337,7 +297,6 @@ function Header({
flexDirection: "row"
}}
>
{" "}
<i
className="fa fa-print"
aria-hidden="true"
@@ -416,217 +375,126 @@ function Header({
</div>
</div>
) : (
<div className="signatureHeader headerBtn">
<div className="flex flex-wrap justify-between items-center mt-[5px]">
<PrevNext
pageNumber={pageNumber}
allPages={allPages}
changePage={changePage}
/>
{recipient ? (
pdfUrl || isAlreadySign.mssg ? (
<div style={{ display: "flex", flexDirection: "row" }}>
{pdfDetails && pdfDetails.length > 0 && (
<CertificateComponent />
)}
<button
onClick={handleToPrint}
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
className="defaultBtn printBtn"
>
<i
className="fa fa-print"
style={{
fontSize: "15px",
marginRight: "3px",
color: "white"
}}
aria-hidden="true"
></i>
Print
</button>
<button
type="button"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
className="defaultBtn downloadBtn"
onClick={() => handleDownloadPdf()}
>
<i
className="fa fa-download"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Download
</button>
</div>
) : (
<div style={{ display: "flex", flexDirection: "row" }}>
<button
style={{
background: "#de4337"
}}
className="finishBtn hoverCss"
onClick={() => {
handleDeclinePdfAlert();
}}
>
Decline
</button>
<button
data-tut="reactourThird"
style={{
background: !pdfUrl ? "#188ae2" : "#d3edeb"
}}
className="finishBtn sendHover"
onClick={() => {
if (!pdfUrl) {
embedWidgetsData();
}
}}
>
Finish
</button>
</div>
)
) : isPlaceholder ? (
{isPlaceholder ? (
<>
{!isMailSend &&
signersdata.length > 0 &&
signersdata.length !== filterPrefill.length && (
<div>
{filterPrefill.length === 0 ? (
<span style={{ fontSize: "13px", color: "#f5405e" }}>
Add {signersdata.length - filterPrefill.length}{" "}
recipients signature
</span>
) : (
<span style={{ fontSize: "13px", color: "#f5405e" }}>
Add {signersdata.length - filterPrefill.length} more
recipients signature
</span>
)}
</div>
)}
<div>
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none">
{!isMailSend &&
signersdata.length > 0 &&
signersdata.length !== filterPrefill.length && (
<div>
{filterPrefill.length === 0 ? (
<span style={{ fontSize: "13px", color: "#f5405e" }}>
Add {signersdata.length - filterPrefill.length}{" "}
recipients signature
</span>
) : (
<span style={{ fontSize: "13px", color: "#f5405e" }}>
Add {signersdata.length - filterPrefill.length} more
recipients signature
</span>
)}
</div>
)}
</div>
<div className="flex">
{setIsEditTemplate && (
<button
onClick={() => setIsEditTemplate(true)}
style={{
border: "none",
outline: "none",
textAlign: "center"
}}
className="outline-none border-none text-center mr-[5px]"
>
<i className="fa-solid fa-gear fa-lg"></i>
</button>
)}
<button
onClick={() => {
navigate(-1);
}}
onClick={() => navigate(-1)}
type="button"
className="defaultBtn backBtn"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-black font-[500] text-sm mr-[5px] bg-white"
>
Back
</button>
<button
disabled={isMailSend && true}
data-tut="reactourFour"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] font-[500] text-sm mr-[5px]"
style={{
background: isMailSend ? "rgb(203, 203, 203)" : "#188ae2",
color: isMailSend && "rgb(77, 75, 75)"
color: isMailSend ? "rgb(77, 75, 75)" : "white"
}}
onClick={() => {
alertSendEmail();
}}
className={isMailSend ? "sendMail" : "sendMail sendHover"}
>
{completeBtnTitle ? completeBtnTitle : "Send"}
{completeBtnTitle
? completeBtnTitle
: isMailSend
? "Sent"
: "Send"}
</button>
</div>
</>
) : isPdfRequestFiles ? (
alreadySign ? (
<div style={{ display: "flex", flexDirection: "row" }}>
{isCompleted.isCertificate && <CertificateComponent />}
{isCompleted && (
<>
{pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
<button
type="button"
onClick={() => handleDownloadCertificate()}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
>
<i
className="fa-solid fa-award py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block ml-1">
Certificate
</span>
</button>
) : (
<GenerateCertificate />
)}
</>
)}
<button
onClick={handleToPrint}
type="button"
className="defaultBtn printBtn"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
>
<i
className="fa fa-print"
style={{
fontSize: "15px",
marginRight: "3px",
color: "white"
}}
aria-hidden="true"
></i>
Print
<i className="fa fa-print py-[3px]" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">Print</span>
</button>
<button
type="button"
className="defaultBtn downloadBtn"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() => handleDownloadPdf()}
>
<i
className="fa fa-download"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Download
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">Download</span>
</button>
</div>
) : (
<div>
<div className="flex">
<button
onClick={() => {
navigate(-1);
}}
onClick={() => navigate(-1)}
type="button"
className="defaultBtn backBtn"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-black font-[500] text-sm mr-[5px] bg-white"
>
Back
</button>
{currentSigner && (
<>
<button
style={{
background: "#de4337"
}}
className="finishBtn hoverCss"
style={{ background: "#de4337" }}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() => {
handleDeclinePdfAlert();
}}
@@ -638,7 +506,7 @@ function Header({
background: "#188ae2"
}}
type="button"
className="finishBtn sendHover"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
onClick={() => {
embedWidgetsData();
}}
@@ -649,93 +517,69 @@ function Header({
)}
</div>
)
) : pdfUrl || (documentStatus && documentStatus.isCompleted) ? (
) : isCompleted ? (
<div style={{ display: "flex", flexDirection: "row" }}>
<CertificateComponent />
{isCompleted && (
<>
{pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
<button
type="button"
onClick={() => handleDownloadCertificate()}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#08bc66]"
>
<i
className="fa-solid fa-award py-[3px]"
aria-hidden="true"
></i>
<span className="hidden lg:block ml-1">Certificate</span>
</button>
) : (
<GenerateCertificate />
)}
</>
)}
<button
onClick={handleToPrint}
type="button"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
className="defaultBtn printBtn"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
>
<i
className="fa fa-print"
style={{
fontSize: "15px",
marginRight: "3px",
color: "white"
}}
aria-hidden="true"
></i>
Print
<i className="fa fa-print py-[3px]" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">Print</span>
</button>
<button
type="button"
className="defaultBtn downloadBtn"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
marginLeft: "10px"
}}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
onClick={() => handleDownloadPdf()}
>
<i
className="fa fa-download"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Download
<i className="fa fa-download py-[3px]" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">Download</span>
</button>
<button
type="button"
className="defaultBtn mailBtn"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
marginLeft: "10px"
}}
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#3ba7e5]"
onClick={() => setIsEmail(true)}
>
<i
className="fa fa-envelope"
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Mail
<i className="fa fa-envelope py-[3px]" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">Mail</span>
</button>
</div>
) : (
<div>
<div className="flex">
<button
onClick={() => {
navigate(-1);
}}
type="button"
className="defaultBtn backBtn"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-black font-[500] text-sm mr-[5px] bg-white"
>
Back
</button>
<button
style={{
background: "#188ae2"
}}
type="button"
className="finishBtn sendHover"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
onClick={() => {
if (!pdfUrl) {
embedWidgetsData();
@@ -416,7 +416,7 @@ function Placeholder(props) {
{props.pos.type !== textWidget && !props.isSignYourself && (
<i
data-tut="reactourLinkUser"
data-tut="assignSigner"
className="fa-regular fa-user signUserIcon"
onClick={(e) => {
e.stopPropagation();
@@ -542,7 +542,15 @@ function Placeholder(props) {
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key}
lockAspectRatio={
props.pos.type !== textWidget &&
// props.pos.type !== textWidget
![
textWidget,
"email",
"name",
"company",
"job title",
textInputWidget
].includes(props.pos.type) &&
(props.pos.Width
? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width /
@@ -38,15 +38,17 @@ function PlaceholderType(props) {
"December"
];
const validateExpression = (regexValidation) => {
let regexObject = regexValidation;
if (props.pos?.options.validation.type === "regex") {
regexObject = RegexParser(regexValidation);
}
// new RegExp(regexValidation);
let isValidate = regexObject.test(textValue);
if (!isValidate) {
props?.setValidateAlert(true);
inputRef.current.focus();
if (textValue) {
let regexObject = regexValidation;
if (props.pos?.options?.validation?.type === "regex") {
regexObject = RegexParser(regexValidation);
}
// new RegExp(regexValidation);
let isValidate = regexObject.test(textValue);
if (!isValidate) {
props?.setValidateAlert(true);
inputRef.current.focus();
}
}
};
@@ -61,7 +63,7 @@ function PlaceholderType(props) {
validateExpression(regexValidation);
break;
case "number":
regexValidation = /^\d+$/;
regexValidation = /^[0-9\s]*$/;
validateExpression(regexValidation);
break;
case "text":
@@ -87,11 +89,11 @@ function PlaceholderType(props) {
case "number":
setValidatePlaceholder("12345");
break;
case textInputWidget:
setValidatePlaceholder("enter text");
case "text":
setValidatePlaceholder("please enter text");
break;
default:
setValidatePlaceholder("enter text");
setValidatePlaceholder("please enter value");
}
}
@@ -135,7 +137,7 @@ function PlaceholderType(props) {
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos]);
}, [props.pos?.options?.defaultValue]);
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div
@@ -288,7 +290,7 @@ function PlaceholderType(props) {
};
//handle height on enter press in text area
const handleEnterPress = (e) => {
const height = 15;
const height = 18;
if (e.key === "Enter") {
//function to save height of text area
onChangeHeightOfTextArea(
@@ -403,19 +405,12 @@ function PlaceholderType(props) {
case textInputWidget:
return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input
className="inputPlaceholder"
<textarea
ref={inputRef}
placeholder={validatePlaceholder}
style={{ fontSize: calculateFontSize() }}
placeholder={validatePlaceholder || "please enter value"}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
type="text"
tabIndex="0"
disabled={
props.isNeedSign && props.data?.signerObjId !== props.signerObjId
? true
: props.isPlaceholder
}
onBlur={handleInputBlur}
onChange={(e) => {
setTextValue(e.target.value);
@@ -429,6 +424,13 @@ function PlaceholderType(props) {
false
);
}}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/>
) : (
<div
@@ -506,15 +508,12 @@ function PlaceholderType(props) {
case "name":
return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input
tabIndex="0"
<textarea
ref={inputRef}
placeholder={"name"}
style={{ fontSize: calculateFontSize() }}
className="inputPlaceholder"
type="text"
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onBlur={handleInputBlur}
onChange={(e) => {
const isDefault = false;
handleTextValid(e);
@@ -528,6 +527,13 @@ function PlaceholderType(props) {
isDefault
);
}}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/>
) : (
<div
@@ -542,14 +548,12 @@ function PlaceholderType(props) {
case "company":
return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input
className="inputPlaceholder"
type="text"
<textarea
ref={inputRef}
placeholder={"company"}
style={{ fontSize: calculateFontSize() }}
placeholder="Enter text"
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onBlur={handleInputBlur}
onChange={(e) => {
handleTextValid(e);
onChangeInput(
@@ -562,6 +566,13 @@ function PlaceholderType(props) {
false
);
}}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/>
) : (
<div
@@ -576,14 +587,12 @@ function PlaceholderType(props) {
case "job title":
return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input
className="inputPlaceholder"
type="text"
<textarea
ref={inputRef}
placeholder={"job title"}
style={{ fontSize: calculateFontSize() }}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onBlur={handleInputBlur}
onChange={(e) => {
handleTextValid(e);
onChangeInput(
@@ -596,6 +605,13 @@ function PlaceholderType(props) {
false
);
}}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/>
) : (
<div
@@ -692,12 +708,11 @@ function PlaceholderType(props) {
case "email":
return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input
className="inputPlaceholder"
type="text"
<textarea
ref={inputRef}
placeholder={"email"}
style={{ fontSize: calculateFontSize() }}
placeholder={"enter email"}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onBlur={handleInputBlur}
onChange={(e) => {
@@ -712,6 +727,13 @@ function PlaceholderType(props) {
false
);
}}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/>
) : (
<div
@@ -760,7 +782,7 @@ function PlaceholderType(props) {
case textWidget:
return (
<textarea
placeholder="Enter label"
placeholder="Enter text"
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -782,7 +804,7 @@ function PlaceholderType(props) {
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
style={{ whiteSpace: "pre-wrap", overflow: "hidden" }}
cols="50"
/>
);
+20 -25
View File
@@ -1,4 +1,5 @@
import React from "react";
import { themeColor } from "../../constant/const";
function PrevNext({ pageNumber, allPages, changePage }) {
//for go to previous page
@@ -14,41 +15,35 @@ function PrevNext({ pageNumber, allPages, changePage }) {
<div>
<div className="preBtn1">
<button
style={{
padding: "3px 20px ",
fontSize: "10px",
background: "#d3edeb",
fontWeight: "600",
border: "0px",
marginRight: "5px"
}}
className="py-[3px] px-[10px] text-xs bg-[#d3edeb] font-[600] mr-[5px]"
disabled={pageNumber <= 1}
onClick={previousPage}
>
Prev
<span className="block lg:hidden">
<i
className="fa fa-backward"
aria-hidden="true"
style={{ color: themeColor, cursor: "pointer" }}
></i>
</span>
<span className="lg:block hidden">Prev</span>
</button>
<span
style={{
fontSize: "13px",
fontWeight: "600"
}}
>
<span className="text-xs font-[500]">
{pageNumber || (allPages ? 1 : "--")} of {allPages || "--"}
</span>
<button
style={{
padding: "3px 20px ",
fontSize: "10px",
background: "#d3edeb",
fontWeight: "600",
marginLeft: "5px",
border: "0px"
}}
className="py-[3px] px-[10px] text-xs bg-[#d3edeb] font-[600] ml-[5px]"
disabled={pageNumber >= allPages}
onClick={nextPage}
>
Next
<span className="block lg:hidden">
<i
className="fa fa-forward"
aria-hidden="true"
style={{ color: themeColor, cursor: "pointer" }}
></i>
</span>
<span className="lg:block hidden">Next</span>
</button>
</div>
</div>
@@ -230,7 +230,11 @@ const RecipientList = (props) => {
</span>
</>
)}
{obj.Name && <span className={"useEmail"}>{obj.Role}</span>}
{obj.Name && (
<span className={"useEmail"}>
{obj?.Role || obj?.Email}
</span>
)}
</div>
</div>
{isMobile && props.sendInOrder && (
@@ -4,7 +4,6 @@ import { Document, Page } from "react-pdf";
import { themeColor } from "../../constant/const";
function RenderAllPdfPage({
pdfUrl,
signPdfUrl,
allPages,
setAllPages,
@@ -50,8 +49,7 @@ function RenderAllPdfPage({
<Document
loading={"Loading Document.."}
onLoadSuccess={onDocumentLoad}
file={pdfUrl ? pdfUrl : signPdfUrl}
// file="https://api.printnode.com/static/test/pdf/multipage.pdf"
file={signPdfUrl}
>
{Array.from(new Array(allPages), (el, index) => (
<div
@@ -296,9 +296,11 @@ function RenderPdf({
{type && (
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
)}
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{`(${Role})`}
</div>
{Role && (
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
{`(${Role})`}
</div>
)}
</>
);
} else {
@@ -71,7 +71,7 @@ function SignerListPlace(props) {
<div className="signerList">
<RecipientList {...props} />
</div>
{props.handleAddSigner && (
{props.handleAddSigner ? (
<div
data-tut="reactourAddbtn"
className="p-[10px] my-[2px] flex flex-row items-center justify-center border-[1px] border-[#47a3ad] hover:bg-[#47a3ad] text-[#47a3ad] hover:text-white cursor-pointer"
@@ -84,6 +84,19 @@ function SignerListPlace(props) {
<i className="fa-solid fa-plus"></i>
<span style={{ marginLeft: 2 }}>Add role</span>
</div>
) : (
<div
data-tut="reactourAddbtn"
className="p-[10px] my-[2px] flex flex-row items-center justify-center border-[1px] border-[#47a3ad] hover:bg-[#47a3ad] text-[#47a3ad] hover:text-white cursor-pointer"
onClick={() => props.setIsAddSigner(true)}
style={{
opacity: props.isMailSend && "0.5",
pointerEvents: props.isMailSend && "none"
}}
>
<i className="fa-solid fa-plus"></i>
<span style={{ marginLeft: 2 }}>Add recipients</span>
</div>
)}
</div>
);
@@ -38,7 +38,8 @@ function WidgetComponent({
sendInOrder,
isTemplateFlow,
setBlockColor,
blockColor
blockColor,
setIsAddSigner
}) {
const [isSignersModal, setIsSignersModal] = useState(false);
@@ -319,7 +320,7 @@ function WidgetComponent({
</span>
</div>
)}
{handleAddSigner && (
{handleAddSigner ? (
<div
data-tut="reactourAddbtn"
style={{
@@ -333,6 +334,22 @@ function WidgetComponent({
<i className="fa-solid fa-plus"></i>
<span style={{ marginLeft: 2 }}>Add role</span>
</div>
) : (
setIsAddSigner && (
<div
data-tut="reactourAddbtn"
style={{
margin: "5px 0 5px 0",
backgroundColor: themeColor,
color: "white"
}}
className="p-[10px] my-[2px] flex flex-row items-center justify-center border-[1px] border-[#47a3ad] hover:bg-[#47a3ad] text-[#47a3ad] hover:text-white cursor-pointer"
onClick={() => setIsAddSigner(true)}
>
<i className="fa-solid fa-plus"></i>
<span style={{ marginLeft: 2 }}>Add recipients</span>
</div>
)
)}
<div
@@ -391,7 +408,7 @@ function WidgetComponent({
)}
{isSignersModal && (
<ModalUi
title={"Roles"}
title={title ? title : "Recipients"}
isOpen={isSignersModal}
handleClose={handleModal}
>
@@ -424,7 +441,7 @@ function WidgetComponent({
textAlign: "center"
}}
>
Please add a role
Please add a {title ? title : "recipients"}
</div>
)}
</ModalUi>
@@ -13,7 +13,7 @@ const WidgetNameModal = (props) => {
defaultValue: "",
status: "required",
hint: "",
textvalidate: "text"
textvalidate: ""
});
const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"];
@@ -48,7 +48,7 @@ const WidgetNameModal = (props) => {
defaultValue: "",
status: "required",
hint: "",
textvalidate: "text"
textvalidate: ""
});
}
};
@@ -82,7 +82,7 @@ const WidgetNameModal = (props) => {
function handleBlurRegex() {
if (!formdata.textvalidate) {
setFormdata({ ...formdata, textvalidate: "text" });
setFormdata({ ...formdata, textvalidate: "" });
} else {
if (formdata.defaultValue) {
const regexObject = RegexParser(
@@ -53,9 +53,22 @@ const SelectSigners = (props) => {
const contactRes = await contactbook.find();
if (contactRes) {
const res = JSON.parse(JSON.stringify(contactRes));
// console.log("userList ", res);
setUserList(res);
return await res.map((item) => ({
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
//and filter signers from total signer's list which already present in document's signers list
const compareArrays = (res, signerObj) => {
return res.filter(
(item1) =>
!signerObj.find((item2) => item2.objectId === item1.objectId)
);
};
//get update signer's List if signersdata is present
const updateSignersList =
props?.signersData && compareArrays(res, props?.signersData);
const result = updateSignersList ? updateSignersList : res;
setUserList(result);
return await result.map((item) => ({
label: item.Email,
value: item.objectId
}));
@@ -41,7 +41,7 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
<div className="flex px-2 py-3 gap-2 items-center shadow-md">
<div className="w-[75px] h-[75px] rounded-full ring-[2px] ring-offset-2 ring-gray-400 overflow-hidden">
<img
className="w-full h-full object-cover"
className="w-full h-full object-contain"
src={image}
alt="Profile"
/>
+170 -45
View File
@@ -1,6 +1,6 @@
import axios from "axios";
import moment from "moment";
import { themeColor } from "./const";
import { isEnableSubscription, themeColor } from "./const";
import React from "react";
import { rgb } from "pdf-lib";
import Parse from "parse";
@@ -13,25 +13,50 @@ export const openInNewTab = (url) => {
window.open(url, "_blank", "noopener,noreferrer");
};
export async function fetchSubscription(extUserId) {
try {
const extClass = localStorage.getItem("Extand_Class");
let extUser;
if (extClass) {
const jsonSender = JSON.parse(extClass);
extUser = jsonSender[0].objectId;
} else {
extUser = extUserId;
}
const baseURL = localStorage.getItem("baseUrl");
const url = `${baseURL}functions/getsubscriptions`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const params = { extUserId: extUser };
const tenatRes = await axios.post(url, params, { headers: headers });
const plan = tenatRes.data?.result?.result?.PlanCode;
const billingDate = tenatRes.data?.result?.result?.Next_billing_date?.iso;
return { plan, billingDate };
} catch (err) {
console.log("Err in fetch subscription", err);
return { plan: "", billingDate: "" };
}
}
//function to get subcripition details from Extand user class
export async function checkIsSubscribed() {
const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass);
const user = await Parse.Cloud.run("getUserDetails", {
email: jsonSender[0].Email
});
const freeplan = user?.get("Plan") && user?.get("Plan")?.plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
return false;
} else if (billingDate) {
if (billingDate > new Date()) {
return true;
try {
const res = await fetchSubscription();
if (res.plan === "freeplan") {
return false;
} else if (res.billingDate) {
if (new Date(res.billingDate) > new Date()) {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
} catch (err) {
console.log("Err in fetch subscription", err);
return false;
}
}
@@ -298,15 +323,15 @@ export const addWidgetOptions = (type) => {
options: { isReadOnly: false, isHideLabel: false }
};
case textInputWidget:
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
return { ...defaultOpt };
case "initials":
return defaultOpt;
case "name":
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
return { ...defaultOpt };
case "company":
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
return { ...defaultOpt };
case "job title":
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
return { ...defaultOpt };
case "date":
return {
...defaultOpt,
@@ -501,16 +526,33 @@ export const signPdfFun = async (
base64Url,
documentId,
signerObjectId,
setIsAlert
setIsAlert,
objectId,
isSubscribed
) => {
let singleSign;
let singleSign,
isCustomCompletionMail = false;
//get tenant details
const tenantDetails = await getTenantDetails(objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert("User does not exist");
} else {
if (
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject &&
(!isEnableSubscription || isSubscribed)
) {
isCustomCompletionMail = true;
}
}
singleSign = {
pdfFile: base64Url,
docId: documentId,
userId: signerObjectId
userId: signerObjectId,
isCustomCompletionMail: isCustomCompletionMail
};
const response = await axios
.post(`${localStorage.getItem("baseUrl")}functions/signPdf`, singleSign, {
headers: {
@@ -1135,10 +1177,10 @@ export const multiSignEmbed = async (
const newUrl = await convertPNGtoJPEG(signUrl);
signUrl = newUrl;
}
const checkUrl = urlValidator(signUrl);
if (checkUrl) {
signUrl = signUrl + "?get";
}
// const checkUrl = urlValidator(signUrl);
// if (checkUrl) {
// signUrl = signUrl + "?get";
// }
const res = await fetch(signUrl);
return res.arrayBuffer();
}
@@ -1273,6 +1315,7 @@ export const multiSignEmbed = async (
}
};
const widgetTypeExist = [
textWidget,
textInputWidget,
"name",
"company",
@@ -1328,7 +1371,7 @@ export const multiSignEmbed = async (
checkbox.enableReadOnly();
});
}
} else if (position.type === textWidget) {
} else if (widgetTypeExist) {
const font = await pdfDoc.embedFont("Helvetica");
const fontSize = 12;
let textContent;
@@ -1404,24 +1447,8 @@ export const multiSignEmbed = async (
color: rgb(0, 0, 0),
size: fontSize
});
y -= 15; // Adjust the line height as needed
y -= 18; // Adjust the line height as needed
}
} else if (widgetTypeExist) {
const font = await pdfDoc.embedFont("Helvetica");
const fontSize = 12;
let textContent;
if (position?.options?.response) {
textContent = position.options?.response;
} else if (position?.options?.defaultValue) {
textContent = position?.options?.defaultValue;
}
page.drawText(textContent, {
x: xPos(position),
y: yPos(position) + 10,
size: fontSize,
font,
color: rgb(0, 0, 0)
});
} else if (position.type === "dropdown") {
const dropdownRandomId = "dropdown" + randomId();
const dropdown = form.createDropdown(dropdownRandomId);
@@ -1792,3 +1819,101 @@ export const handleCopyNextToWidget = (
setXyPostion(updatePlaceholder);
}
};
export const getFileName = (fileUrl) => {
if (fileUrl) {
const url = new URL(fileUrl);
const filename = url.pathname.substring(url.pathname.indexOf("_") + 1);
return filename || "";
} else {
return "";
}
};
//fetch tenant app logo from `partners_Tenant` class by domain name
export const getAppLogo = async () => {
const domainName = window.location.host;
try {
const tenantCreditsQuery = new Parse.Query("partners_Tenant");
tenantCreditsQuery.equalTo("Domain", domainName);
const res = await tenantCreditsQuery.first();
if (res) {
const updateRes = JSON.parse(JSON.stringify(res));
return updateRes?.Logo;
}
} catch (e) {
return null;
}
};
export const getTenantDetails = async (objectId) => {
try {
const tenantCreditsQuery = new Parse.Query("partners_Tenant");
tenantCreditsQuery.equalTo("UserId", {
__type: "Pointer",
className: "_User",
objectId: objectId
});
const res = await tenantCreditsQuery.first();
if (res) {
const updateRes = JSON.parse(JSON.stringify(res));
return updateRes;
}
} catch (e) {
return "user does not exist!";
}
};
//function to convert variable string name to variable value of email body and subject
export function replaceMailVaribles(subject, body, variables) {
let replacedSubject = subject;
let replacedBody = body;
for (const variable in variables) {
const regex = new RegExp(`{{${variable}}}`, "g");
if (subject) {
replacedSubject = replacedSubject.replace(regex, variables[variable]);
}
if (body) {
replacedBody = replacedBody.replace(regex, variables[variable]);
}
}
const result = {
subject: replacedSubject,
body: replacedBody
};
return result;
}
export const copytoData = (text) => {
// navigator.clipboard.writeText(text);
if (navigator.clipboard) {
navigator.clipboard.writeText(text);
} else {
// Fallback for browsers that don't support navigator.clipboard
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
}
};
export const convertPdfArrayBuffer = async (url) => {
try {
const response = await fetch(url);
// Check if the response was successful (status 200)
if (!response.ok) {
return "Error";
}
// Convert the response to ArrayBuffer
const arrayBuffer = await response.arrayBuffer();
return arrayBuffer;
} catch (error) {
console.error("Error fetching data:", error);
return "Error";
}
};
+10 -7
View File
@@ -1,6 +1,10 @@
import axios from "axios";
const parseAppId = localStorage.getItem("parseAppId");
const serverUrl = localStorage.getItem("baseUrl");
const parseAppId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID
: "opensign";
const serverUrl = process.env.REACT_APP_SERVERURL
? process.env.REACT_APP_SERVERURL
: window.location.origin + "/api/app";
export const SaveFileSize = async (size, imageUrl, tenantId) => {
//checking server url and save file's size
@@ -12,7 +16,7 @@ export const SaveFileSize = async (size, imageUrl, tenantId) => {
const _tenantPtr = JSON.stringify(tenantPtr);
try {
const res = await axios.get(
`${serverUrl}classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
`${serverUrl}/classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
{
headers: {
"Content-Type": "application/json",
@@ -30,7 +34,7 @@ export const SaveFileSize = async (size, imageUrl, tenantId) => {
: size
};
await axios.put(
`${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`,
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
data,
{
headers: {
@@ -41,7 +45,7 @@ export const SaveFileSize = async (size, imageUrl, tenantId) => {
);
} else {
data = { usedStorage: size, PartnersTenant: tenantPtr };
await axios.post(`${serverUrl}classes/partners_TenantCredits`, data, {
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
@@ -64,7 +68,7 @@ const saveDataFile = async (size, imageUrl, tenantPtr) => {
// console.log("data save",file, data)
try {
await axios.post(`${serverUrl}classes/partners_DataFiles`, data, {
await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
@@ -74,4 +78,3 @@ const saveDataFile = async (size, imageUrl, tenantPtr) => {
console.log("err in save usage ", err);
}
};
+9 -6
View File
@@ -4,6 +4,7 @@ import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import { store } from "./redux/store";
import { CookiesProvider } from "react-cookie";
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
import {
@@ -62,10 +63,12 @@ if (process.env.REACT_APP_GTM) {
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<DndProvider options={HTML5toTouch}>
<Preview>{generatePreview}</Preview>
<App />
</DndProvider>
</Provider>
<CookiesProvider defaultSetOptions={{ path: "/" }}>
<Provider store={store}>
<DndProvider options={HTML5toTouch}>
<Preview>{generatePreview}</Preview>
<App />
</DndProvider>
</Provider>
</CookiesProvider>
);
+205 -35
View File
@@ -12,14 +12,26 @@ export default function reportJson(id) {
heading: head,
actions: [
{
btnLabel: "sign",
btnId: "1231",
hoverLabel: "Edit",
btnColor: "#4bd396",
textColor: "white",
btnIcon: "fa fa-plus",
redirectUrl: "draftDocument"
btnIcon: "fa-solid fa-pen",
redirectUrl: "draftDocument",
action: "redirect"
},
{
btnId: "2142",
hoverLabel: "Delete",
btnColor: "#ff4848",
textColor: "white",
btnIcon: "fa fa-trash",
redirectUrl: "",
action: "delete"
}
],
helpMsg: "These are documents you have started but have not finalized for sending."
helpMsg:
"These are documents you have started but have not finalized for sending."
};
// Need your sign report
case "4Hhwbp482K":
@@ -28,11 +40,14 @@ export default function reportJson(id) {
heading: head,
actions: [
{
btnId: "4536",
btnLabel: "sign",
hoverLabel: "Sign",
btnColor: "#3ac9d6",
textColor: "white",
btnIcon: "fa fa-eye",
redirectUrl: "pdfRequestFiles"
btnIcon: "fa-solid fa-signature",
redirectUrl: "pdfRequestFiles",
action: "redirect"
}
],
helpMsg:
@@ -45,11 +60,48 @@ export default function reportJson(id) {
heading: head,
actions: [
{
btnLabel: "View",
btnId: "8901",
hoverLabel: "Share",
btnColor: "#4bd396",
textColor: "white",
btnIcon: "fa-solid fa-share",
redirectUrl: "",
action: "share"
},
{
btnId: "1588",
hoverLabel: "View",
btnColor: "#3ac9d6",
textColor: "white",
btnIcon: "fa fa-eye",
redirectUrl: "pdfRequestFiles"
redirectUrl: "pdfRequestFiles",
action: "redirect"
},
{
btnId: "2234",
hoverLabel: "option",
btnColor: "transparent",
textColor: "black",
btnIcon: "fa-solid fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "1688",
btnLabel: "Revoke",
hoverLabel: "Revoke",
btnIcon: "fa-solid fa-file-circle-xmark",
redirectUrl: "",
action: "revoke"
},
{
btnId: "1488",
btnLabel: "Delete",
hoverLabel: "Delete",
btnIcon: "fa fa-trash",
redirectUrl: "",
action: "delete"
}
]
}
],
helpMsg:
@@ -62,14 +114,26 @@ export default function reportJson(id) {
heading: head,
actions: [
{
btnLabel: "View",
btnId: "1378",
hoverLabel: "Edit",
btnColor: "#4bd396",
textColor: "white",
btnIcon: "fa fa-eye",
redirectUrl: "draftDocument"
redirectUrl: "draftDocument",
action: "redirect"
},
{
btnId: "1278",
hoverLabel: "Delete",
btnColor: "#ff4848",
textColor: "white",
btnIcon: "fa fa-trash",
redirectUrl: "",
action: "delete"
}
],
helpMsg: "This is a list of documents that have been signed by all parties."
helpMsg:
"This is a list of documents that have been signed by all parties."
};
// declined documents report
case "UPr2Fm5WY3":
@@ -78,14 +142,26 @@ export default function reportJson(id) {
heading: head,
actions: [
{
btnLabel: "View",
btnId: "1458",
hoverLabel: "View",
btnColor: "#4bd396",
textColor: "white",
btnIcon: "fa fa-eye",
redirectUrl: "draftDocument"
redirectUrl: "draftDocument",
action: "redirect"
},
{
btnId: "1358",
hoverLabel: "Delete",
btnColor: "#ff4848",
textColor: "white",
btnIcon: "fa fa-trash",
redirectUrl: "",
action: "delete"
}
],
helpMsg: "This is a list of documents that have been declined by one of the signers."
helpMsg:
"This is a list of documents that have been declined by one of the signers."
};
// Expired Documents report
case "zNqBHXHsYH":
@@ -94,14 +170,26 @@ export default function reportJson(id) {
heading: head,
actions: [
{
btnLabel: "View",
btnId: "1898",
hoverLabel: "View",
btnColor: "#4bd396",
textColor: "white",
btnIcon: "fa fa-eye",
redirectUrl: "draftDocument"
redirectUrl: "draftDocument",
action: "redirect"
},
{
btnId: "1998",
hoverLabel: "Delete",
btnColor: "#ff4848",
textColor: "white",
btnIcon: "fa fa-trash",
redirectUrl: "",
action: "delete"
}
],
helpMsg: "This is a list of documents that have reached their expiration date."
helpMsg:
"This is a list of documents that have reached their expiration date."
};
// Recently sent for signatures report show on dashboard
case "d9k3UfYHBc":
@@ -110,11 +198,48 @@ export default function reportJson(id) {
heading: dashboardReportHead,
actions: [
{
btnLabel: "View",
btnId: "1999",
hoverLabel: "Share",
btnColor: "#4bd396",
textColor: "white",
btnIcon: "fa-solid fa-share",
redirectUrl: "",
action: "share"
},
{
btnId: "1999",
hoverLabel: "View",
btnColor: "#3ac9d6",
textColor: "white",
btnIcon: "fa fa-eye",
redirectUrl: "pdfRequestFiles"
redirectUrl: "pdfRequestFiles",
action: "redirect"
},
{
btnId: "2234",
hoverLabel: "option",
btnColor: "transparent",
textColor: "black",
btnIcon: "fa-solid fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "6788",
btnLabel: "Revoke",
hoverLabel: "Revoke",
btnIcon: "fa-solid fa-file-circle-xmark",
redirectUrl: "",
action: "revoke"
},
{
btnId: "2000",
btnLabel: "Delete",
hoverLabel: "Delete",
btnIcon: "fa fa-trash",
redirectUrl: "",
action: "delete"
}
]
}
]
};
@@ -125,11 +250,14 @@ export default function reportJson(id) {
heading: dashboardReportHead,
actions: [
{
btnLabel: "Sign",
btnColor: "#4bd396",
btnId: "2001",
btnLabel: "sign",
hoverLabel: "Sign",
btnColor: "#3ac9d6",
textColor: "white",
btnIcon: "fa fa-eye",
redirectUrl: "pdfRequestFiles"
btnIcon: "fa-solid fa-signature",
redirectUrl: "pdfRequestFiles",
action: "redirect"
}
]
};
@@ -140,11 +268,22 @@ export default function reportJson(id) {
heading: ["Title", "Note", "Folder", "File", "Owner", "Signers"],
actions: [
{
btnLabel: "sign",
btnId: "2003",
hoverLabel: "Edit",
btnColor: "#4bd396",
textColor: "white",
btnIcon: "fa fa-plus",
redirectUrl: "draftDocument"
btnIcon: "fa-solid fa-pen",
redirectUrl: "draftDocument",
action: "redirect"
},
{
btnId: "2004",
hoverLabel: "Delete",
btnColor: "#ff4848",
textColor: "white",
btnIcon: "fa fa-trash",
redirectUrl: "",
action: "delete"
}
]
};
@@ -155,14 +294,17 @@ export default function reportJson(id) {
heading: contactbook,
actions: [
{
btnLabel: "",
btnId: "2204",
hoverLabel: "Delete",
btnColor: "#f55a42",
textColor: "white",
btnIcon: "fa-solid fa-trash"
btnIcon: "fa-solid fa-trash",
action: "delete"
}
],
form: "ContactBook",
helpMsg: "This is a list of contacts/signers added by you. These will appear as suggestions when you try to add signers to a new document."
helpMsg:
"This is a list of contacts/signers added by you. These will appear as suggestions when you try to add signers to a new document."
};
// template report
case "6TeaPr321t":
@@ -171,18 +313,46 @@ export default function reportJson(id) {
heading: head,
actions: [
{
btnId: "2234",
btnLabel: "Use",
hoverLabel: "Use",
btnColor: "#4bd396",
textColor: "white",
btnIcon: "fa fa-plus",
redirectUrl: "placeHolderSign"
redirectUrl: "placeHolderSign",
action: "redirect",
selector: "reactourSecond",
message:
"Click the 'Use' button to create a new document from an existing template. "
},
{
btnLabel: "Edit",
btnColor: "#00c9d5",
textColor: "white",
btnIcon: "fa fa-plus",
redirectUrl: "template"
btnId: "2234",
hoverLabel: "option",
btnColor: "transparent",
textColor: "black",
btnIcon: "fa-solid fa-ellipsis-vertical fa-lg",
action: "option",
selector: "reactourThird",
message:
"This menu reveals more options such as Edit & Delete. Use the 'Edit' button to add signer roles, modify fields, and update your template. Changes will apply to all future documents created from this template but wont affect existing documents.Use the Delete button you can delete template. ",
subaction: [
{
btnId: "2434",
btnLabel: "Edit",
hoverLabel: "Edit",
btnIcon: "fa-solid fa-pen",
redirectUrl: "template",
action: "redirect"
},
{
btnId: "1834",
btnLabel: "Delete",
hoverLabel: "Delete",
btnIcon: "fa fa-trash",
redirectUrl: "",
action: "delete"
}
]
}
],
helpMsg:
+78
View File
@@ -0,0 +1,78 @@
const plans = [
{
planName: "OPENSIGN™ FREE",
currency: "",
monthlyPrice: "Free",
yearlyPrice: "Free",
subtitle: "Free Unlimited E-signatures, Forever.",
btnText: "Subscribe",
url: "",
target: "_blank",
benefits: [
"Unlimited envelopes",
"Sign documents yourself",
"Request signatures from others",
"14 field types",
"Automatic e-signatures",
"Completion certificates",
"Send in order",
"Document templates",
"Import from Dropbox",
"Contact book",
"Document expiry support",
"Decline document support",
"Email notifications",
"Recipient authentication using OTP",
"And much more"
]
},
{
planName: "OPENSIGN™ PROFESSIONAL",
currency: "$",
monthlyPrice: "29.99",
yearlyPrice: "329.99",
subtitle: "Exclusive Access to advanced features.",
btnText: "Subscribe",
url:
window.location.origin === "https://staging-app.opensignlabs.com"
? "https://billing.zoho.in/subscribe/9627f62a09df1c8ce500f2f4dc8328dd84ecda20eeae13878ce581d60240f206/pro-weekly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d1a09dfa8085585cdd4ec4d7f32137f3/professional-monthly",
yearlyUrl:
window.location.origin === "https://staging-app.opensignlabs.com"
? "https://billing.zoho.in/subscribe/9627f62a09df1c8ce500f2f4dc8328ddc184411129224f1f29ed84f6cef3b862/pro-yearly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly",
target: "_self",
benefits: [
"Everything in OpenSign™ professional",
"Field validations",
"Regular expression validations",
"Organize docs in OpenSign™ Drive",
"Webhooks",
"Zapier integration",
"API Access",
"100 API signatures included",
"DocumentId removal from signed docs",
"Custom email templates(coming soon)"
]
},
{
planName: "OPENSIGN™ ENTERPRISE",
currency: "",
monthlyPrice: "Request Price",
yearlyPrice: "Request Price",
subtitle: "Scalable Features with priority support.",
btnText: "Contact us",
url: "https://www.opensignlabs.com/contact-us",
target: "_blank",
benefits: [
"All features",
"Custom domain",
"Custom branding",
"Uptime SLA",
"SSO support",
"Priority support"
]
}
];
export default plans;
-70
View File
@@ -1,70 +0,0 @@
[
{
"planName": "OPENSIGN™ FREE",
"currency": "",
"monthlyPrice": "Free",
"yearlyPrice": "Free",
"subtitle": "Free Unlimited E-signatures, Forever.",
"btnText": "Subscribe",
"url": "",
"target": "_blank",
"benefits": [
"Unlimited envelopes",
"Sign documents yourself",
"Request signatures from others",
"14 field types",
"Automatic e-signatures",
"Completion certificates",
"Send in order",
"Document templates",
"Import from Dropbox",
"Contact book",
"Document expiry support",
"Decline document support",
"Email notifications",
"Recipient authentication using OTP",
"And much more"
]
},
{
"planName": "OPENSIGN™ PROFESSIONAL",
"currency": "$",
"monthlyPrice": "29.99",
"yearlyPrice": "329.99",
"subtitle": "Exclusive Access to advanced features.",
"btnText": "Subscribe",
"url": "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d1a09dfa8085585cdd4ec4d7f32137f3/professional-monthly",
"yearlyUrl": "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly",
"target": "_self",
"benefits": [
"Everything in OpenSign™ professional",
"Field validations",
"Regular expression validations",
"Organize docs in OpenSign™ Drive",
"Webhooks",
"Zapier integration",
"API Access",
"100 API signatures included",
"DocumentId removal from signed docs",
"Custom email templates(coming soon)"
]
},
{
"planName": "OPENSIGN™ ENTERPRISE",
"currency": "",
"monthlyPrice": "Request Price",
"yearlyPrice": "Request Price",
"subtitle": "Scalable Features with priority support.",
"btnText": "Contact us",
"url": "https://www.opensignlabs.com/contact-us",
"target": "_blank",
"benefits": [
"All features",
"Custom domain",
"Custom branding",
"Uptime SLA",
"SSO support",
"Priority support"
]
}
]
+27 -10
View File
@@ -10,6 +10,8 @@ import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import { useNavigate, useLocation, Outlet } from "react-router-dom";
import { isEnableSubscription } from "../constant/const";
import { useCookies } from "react-cookie";
import { fetchSubscription } from "../constant/Utils";
const HomeLayout = () => {
const navigate = useNavigate();
@@ -24,6 +26,7 @@ const HomeLayout = () => {
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
const [tourConfigs, setTourConfigs] = useState([]);
const [, setCookie] = useCookies(["accesstoken", "main_Domain"]);
useEffect(() => {
(async () => {
@@ -34,6 +37,7 @@ const HomeLayout = () => {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
checkIsSubscribed();
} else {
setIsUserValid(false);
@@ -43,23 +47,36 @@ const HomeLayout = () => {
setIsUserValid(false);
}
})();
saveCookies();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function to use save data in cookies storage
const saveCookies = () => {
const main_Domain = window.location.origin;
const domainName = window.location.hostname; //app.opensignlabs.com
// Find the index of the first dot in the string
const indexOfFirstDot = domainName.indexOf(".");
// Remove the first dot and get the substring starting from the next character
const updateDomain = domainName.substring(indexOfFirstDot); //.opensignlabs.com
setCookie("accesstoken", localStorage.getItem("accesstoken"), {
secure: true,
domain: updateDomain
});
setCookie("main_Domain", main_Domain, {
secure: true,
domain: updateDomain
});
};
async function checkIsSubscribed() {
const currentUser = Parse.User.current();
const user = await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
});
if (isEnableSubscription) {
const freeplan = user?.get("Plan") && user?.get("Plan").plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
const res = await fetchSubscription();
if (res.plan === "freeplan") {
setIsUserValid(true);
setIsLoader(false);
} else if (billingDate) {
if (billingDate > new Date()) {
} else if (res.billingDate) {
if (new Date(res.billingDate) > new Date()) {
setIsUserValid(true);
setIsLoader(false);
} else {
+1
View File
@@ -6,6 +6,7 @@ import { useNavigate, useParams } from "react-router-dom";
import Title from "../components/Title";
import { useDispatch } from "react-redux";
import { saveTourSteps } from "../redux/reducers/TourStepsReducer";
const Dashboard = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
+4 -5
View File
@@ -11,6 +11,7 @@ import SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
import { SaveFileSize } from "../constant/saveFileSize";
import { getFileName } from "../constant/Utils";
// `Form` render all type of Form on this basis of their provided in path
function Form() {
@@ -254,6 +255,7 @@ const Forms = (props) => {
setpercentage(0);
setTimeout(() => setIsReset(false), 50);
};
return (
<div className="shadow-md rounded my-2 p-3 bg-[#ffffff] md:border-[1px] md:border-gray-600/50">
<Title title={props?.title} />
@@ -298,13 +300,10 @@ const Forms = (props) => {
<div className="flex gap-2 justify-center items-center">
<div className="flex justify-between items-center px-2 py-2 w-full font-bold rounded border-[1px] border-[#ccc] text-gray-500 bg-white text-[13px]">
<div className="break-all">
file selected :{" "}
{fileupload?.split("/")?.pop()?.split("_")[1]}
file selected : {getFileName(fileupload)}
</div>
<div
onClick={() => {
setFileUpload([]);
}}
onClick={() => setFileUpload([])}
className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500"
>
<i className="fa-solid fa-xmark"></i>
+2 -2
View File
@@ -5,7 +5,7 @@ import { useNavigate } from "react-router-dom";
import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi";
import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import { checkIsSubscribed, copytoData, openInNewTab } from "../constant/Utils";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
@@ -92,7 +92,7 @@ function GenerateToken() {
};
const copytoclipboard = (text) => {
navigator.clipboard.writeText(text);
copytoData(text);
setCopied(true);
setTimeout(() => {
setCopied(false);
+25 -9
View File
@@ -3,8 +3,9 @@ import { useNavigate, useParams } from "react-router-dom";
import "../styles/loginPage.css";
import loader from "../assets/images/loader2.gif";
import axios from "axios";
import { themeColor } from "../constant/const";
import { contractUsers } from "../constant/Utils";
import { isEnableSubscription, themeColor } from "../constant/const";
import { contractUsers, getAppLogo } from "../constant/Utils";
import logo from "../assets/images/logo.png";
function GuestLogin() {
const { id, userMail, contactBookId, serverUrl } = useParams();
@@ -14,14 +15,27 @@ function GuestLogin() {
const [EnterOTP, setEnterOtp] = useState(false);
const [loading, setLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [appLogo, setAppLogo] = useState("");
useEffect(() => {
handleServerUrl();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function generate serverUrl and parseAppId from url and save it in local storage
const handleServerUrl = () => {
const handleServerUrl = async () => {
if (isEnableSubscription) {
const applogo = await getAppLogo();
if (applogo) {
setAppLogo(applogo);
} else {
setAppLogo(logo);
}
} else {
setAppLogo(logo);
}
//split url in array from '&'
localStorage.clear();
const checkSplit = serverUrl.split("&");
@@ -164,12 +178,14 @@ function GuestLogin() {
}}
>
<div className="main_head">
<div className="main-logo">
<img
alt="sign img"
src="https://qikinnovation.ams3.digitaloceanspaces.com/logo.png"
width="100%"
/>
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
{appLogo && (
<img
src={appLogo}
className="object-contain h-full"
alt="logo"
/>
)}
</div>
</div>
+41 -41
View File
@@ -19,6 +19,7 @@ import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import { fetchSubscription, getAppLogo } from "../constant/Utils";
function Login() {
const navigate = useNavigate();
const location = useLocation();
@@ -43,7 +44,7 @@ function Login() {
Destination: ""
});
const [isModal, setIsModal] = useState(false);
const image = appInfo?.applogo || undefined;
const [image, setImage] = useState();
useEffect(() => {
if (localStorage.getItem("accesstoken")) {
@@ -51,8 +52,23 @@ function Login() {
GetLoginData();
}
dispatch(fetchAppInfo());
saveLogo();
// eslint-disable-next-line
}, []);
const saveLogo = async () => {
if (isEnableSubscription) {
const logo = await getAppLogo();
if (logo) {
setImage(logo);
} else {
setImage(appInfo?.applogo || undefined);
}
} else {
setImage(appInfo?.applogo || undefined);
}
};
const handleChange = (event) => {
const { name, value } = event.target;
setState({ ...state, [name]: value });
@@ -153,7 +169,7 @@ function Login() {
await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
}).then(
(result) => {
async (result) => {
let tenentInfo = [];
const results = [result];
if (results) {
@@ -254,16 +270,15 @@ function Login() {
"userDetails",
JSON.stringify(LocalUserDetails)
);
const freeplan =
results[0].get("Plan") &&
results[0].get("Plan").plan_code;
const billingDate =
results[0].get("Next_billing_date") &&
results[0].get("Next_billing_date");
const res = await fetchSubscription();
const freeplan = res.plan;
const billingDate = res.billingDate;
if (freeplan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (billingDate > new Date()) {
if (
new Date(billingDate) > new Date()
) {
localStorage.removeItem(
"userDetails"
);
@@ -798,7 +813,7 @@ function Login() {
await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
}).then(
(result) => {
async (result) => {
let tenentInfo = [];
const results = [result];
if (results) {
@@ -850,17 +865,13 @@ function Login() {
"userDetails",
JSON.stringify(LocalUserDetails)
);
const billingDate =
results[0].get("Next_billing_date") &&
results[0].get("Next_billing_date");
const freeplan =
results[0]?.get("Plan") &&
results[0]?.get("Plan").plan_code;
const res = await fetchSubscription();
const billingDate = res.billingDate;
const freeplan = res.plan;
if (freeplan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (billingDate > new Date()) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
@@ -1055,12 +1066,14 @@ function Login() {
<>
<div aria-labelledby="loginHeading" role="region">
<div className="md:m-10 lg:m-16 md:p-4 lg:p-10 p-4 bg-[#ffffff] md:border-[1px] md:border-gray-400 ">
<div className="w-[250px] h-[66px] inline-block">
<img
src={image}
width="100%"
alt="The image displays the OpenSign logo with a stylized blue square with an open corner, accompanied by the tagline Seal the Deal, Openly."
/>
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
{image && (
<img
src={image}
className="object-contain h-full"
alt="The image displays the OpenSign logo with a stylized blue square with an open corner, accompanied by the tagline Seal the Deal, Openly."
/>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
<div>
@@ -1164,25 +1177,12 @@ function Login() {
</form>
<br />
{(appInfo.fbAppId || appInfo.googleClietId) && (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
className="text-sm"
>
<hr
className={"border-[1px] border-gray-300 w-full"}
style={{ color: "grey" }}
/>
<span style={{ color: "grey" }} className="px-2 ">
<div className="text-sm flex justify-center items-center">
<hr className="border-[1px] border-gray-300 w-full" />
<span className="px-2 text-gray-500 cursor-default">
OR
</span>
<hr
className={"border-[1px] border-gray-300 w-full"}
style={{ color: "grey" }}
/>
<hr className="border-[1px] border-gray-300 w-full" />
</div>
)}
<br />
+228 -22
View File
@@ -7,6 +7,10 @@ import { useNavigate } from "react-router-dom";
import Title from "../components/Title";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Tour from "reactour";
import axios from "axios";
const DriveBody = React.lazy(
() => import("../components/opensigndrive/DriveBody")
);
@@ -42,6 +46,8 @@ function Opensigndrive() {
const [error, setError] = useState();
const [folderLoader, setIsFolderLoader] = useState(false);
const [isShowSort, setIsShowSort] = useState(false);
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
const [isLoading, setIsLoading] = useState({
isLoad: true,
message: "This might take some time"
@@ -56,6 +62,8 @@ function Opensigndrive() {
const [loading, setLoading] = useState(false);
const sortOrder = ["Ascending", "Descending"];
const sortingValue = ["Name", "Date"];
const [isDontShow, setIsDontShow] = useState(false);
const [tourData, setTourData] = useState();
const orderName = {
Ascending: "Ascending",
Descending: "Descending",
@@ -73,12 +81,60 @@ function Opensigndrive() {
useEffect(() => {
getPdfDocumentList();
// eslint-disable-next-line
}, [docId]);
const tourConfigs = [
{
selector: '[data-tut="reactourFirst"]',
content: () => (
<TourContentWithBtn
message={`Click on the breadcrumb links to easily navigate through the folder hierarchy and view the documents within each folder.`}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
},
{
selector: '[data-tut="reactourSecond"]',
content: () => (
<TourContentWithBtn
message={`Click the add button to create a new folder or document.`}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
},
{
selector: '[data-tut="reactourThird"]',
content: () => (
<TourContentWithBtn
message={`Sort your documents by Date or Name using this menu.`}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
},
{
selector: '[data-tut="reactourForth"]',
content: () => (
<TourContentWithBtn
message={`Click on this menu to display the documents in list view.`}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
}
];
//function for get all pdf document list
const getPdfDocumentList = async (disbaleLoading) => {
setLoading(true);
checkTourStatus();
if (!disbaleLoading) {
setIsLoading({ isLoad: true, message: "This might take some time" });
}
@@ -87,8 +143,61 @@ function Opensigndrive() {
if (driveDetails && driveDetails === "Error: Something went wrong!") {
setHandleError("Error: Something went wrong!");
} else if (driveDetails && driveDetails.length > 0) {
const addMoreTour = [
{
selector: '[data-tut="reactourFifth"]',
content: () => (
<TourContentWithBtn
message={`The document list is displayed according to the selected sorting option. Icons next to each document indicate its current status.`}
isChecked={handleDontShow}
/>
),
position: "bottom",
style: { fontSize: "13px" }
},
{
selector: '[data-tut="reactourSixth"]',
content: () => (
<TourContentWithBtn
message={`Right-click on a document to see options such as Download, Rename, Move, and Delete. Click on the document to open it.`}
isChecked={handleDontShow}
/>
),
position: "bottom",
style: { fontSize: "13px" }
},
{
selector: '[data-tut="reactourSeventh"]',
content: () => (
<TourContentWithBtn
message={`Right-click on any folder to see options. Choose Rename to change the folders name or click on the folder to navigate through its contents.`}
isChecked={handleDontShow}
/>
),
position: "bottom",
style: { fontSize: "13px" }
}
];
let newTour = [...tourConfigs, ...addMoreTour];
const isFolderExist = driveDetails.some(
(data) => data.Type === "Folder"
);
if (!isFolderExist) {
newTour = newTour.filter(
(data) => data.selector !== '[data-tut="reactourSeventh"]'
);
}
const isDocumentExist = driveDetails.some((data) => data.URL);
if (!isDocumentExist) {
newTour = newTour.filter(
(data) => data.selector !== '[data-tut="reactourSixth"]'
);
}
setTourData(newTour);
setSkip((prevSkip) => prevSkip + limit);
sortingData(null, null, driveDetails, true);
} else {
setTourData(tourConfigs);
}
if (!docId) {
setFolderName([{ name: "OpenSign™ Drive", objectId: "" }]);
@@ -203,6 +312,7 @@ function Opensigndrive() {
const result = JSON.parse(JSON.stringify(res));
setPdfData((prev) => [...prev, result]);
sortingData(null, null, [result], true);
setNewFolderName();
setIsFolderLoader(false);
setIsFolder(false);
@@ -223,9 +333,13 @@ function Opensigndrive() {
const sortedBy = (appInfo, type, order) => {
if (type === orderName.Name) {
if (order === orderName.Ascending) {
return appInfo.sort((a, b) => (a.Name > b.Name ? 1 : -1));
return appInfo.sort((a, b) =>
a.Name.toLowerCase() < b.Name.toLowerCase() ? -1 : 1
);
} else if (order === orderName.Descending) {
return appInfo.sort((a, b) => (a.Name > b.Name ? -1 : 1));
return appInfo.sort((a, b) =>
a.Name.toLowerCase() < b.Name.toLowerCase() ? 1 : -1
);
}
} else if (type === orderName.Date) {
if (order === orderName.Ascending) {
@@ -353,9 +467,76 @@ function Opensigndrive() {
setNewFolderName("");
setError("");
};
const handleDontShow = (isChecked) => {
setIsDontShow(isChecked);
};
const closeTour = async () => {
setIsTour(false);
if (isDontShow) {
const serverUrl = localStorage.getItem("baseUrl");
const appId = localStorage.getItem("parseAppId");
const extUserClass = localStorage.getItem("extended_class");
const json = JSON.parse(localStorage.getItem("Extand_Class"));
const extUserId = json && json.length > 0 && json[0].objectId;
let updatedTourStatus = [];
if (tourStatusArr.length > 0) {
updatedTourStatus = [...tourStatusArr];
const driveTourIndex = tourStatusArr.findIndex(
(obj) => obj["driveTour"] === false || obj["driveTour"] === true
);
if (driveTourIndex !== -1) {
updatedTourStatus[driveTourIndex] = { driveTour: true };
} else {
updatedTourStatus.push({ driveTour: true });
}
} else {
updatedTourStatus = [{ driveTour: true }];
}
await axios.put(
serverUrl + "classes/" + extUserClass + "/" + extUserId,
{
TourStatus: updatedTourStatus
},
{
headers: {
"X-Parse-Application-Id": appId
}
}
);
}
};
//function to use check tour status of open sign drive
async function checkTourStatus() {
const currentUser = Parse.User.current();
const cloudRes = await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
});
const res = { data: cloudRes.toJSON() };
if (res.data && res.data.TourStatus && res.data.TourStatus.length > 0) {
const tourStatus = res.data.TourStatus;
setTourStatusArr(tourStatus);
const filteredtourStatus = tourStatus.filter((obj) => obj["driveTour"]);
if (filteredtourStatus.length > 0) {
const driveTour = filteredtourStatus[0]["driveTour"];
// console.log("loginTour", loginTour);
if (driveTour) {
setIsTour(false);
} else {
setIsTour(true);
}
} else {
setIsTour(true);
}
} else {
setIsTour(true);
}
}
return (
<div style={{ backgroundColor: "white" }} className="folderComponent">
<Title title={"OpenSign™ Drive"} drive={true} />
<div>
<ModalUi
headerColor={"#dc3545"}
@@ -518,7 +699,19 @@ function Opensigndrive() {
) : (
<>
<div className="folderContainer">
{tourData && (
<Tour
onRequestClose={closeTour}
steps={tourData}
isOpen={isTour}
closeWithMask={false}
scrollOffset={-100}
rounded={5}
/>
)}
<div
data-tut="reactourFirst"
onMouseEnter={(e) => handleMouseEnter(e)}
ref={scrollRef}
style={{
@@ -536,7 +729,7 @@ function Opensigndrive() {
}
onClick={() => setIsNewFol(!isNewFol)}
>
<div className="sort">
<div className="sort" data-tut="reactourSecond">
<i
className="fa fa-plus-square"
aria-hidden="true"
@@ -597,7 +790,11 @@ function Opensigndrive() {
className={isShowSort ? "dropdown show" : "dropdown"}
onClick={() => setIsShowSort(!isShowSort)}
>
<div className=" sort " data-toggle="dropdown">
<div
data-tut="reactourThird"
className=" sort "
data-toggle="dropdown"
>
<i
className="fa fa-sort-amount-asc"
aria-hidden="true"
@@ -687,7 +884,11 @@ function Opensigndrive() {
></i>
</div>
) : (
<div className="sort" onClick={() => setIsList(!isList)}>
<div
data-tut="reactourForth"
className="sort"
onClick={() => setIsList(!isList)}
>
<i
className="fa fa-list"
aria-hidden="true"
@@ -712,23 +913,28 @@ function Opensigndrive() {
<span style={{ fontWeight: "bold" }}>No Data Found!</span>
</div>
) : (
<React.Suspense fallback={<Loader />}>
<DriveBody
pdfData={pdfData}
setFolderName={setFolderName}
setIsLoading={setIsLoading}
setDocId={setDocId}
getPdfDocumentList={getPdfDocumentList}
isDocId={docId}
setPdfData={setPdfData}
isList={isList}
setIsAlert={setIsAlert}
setSkip={setSkip}
/>
{loading && (
<div style={{ textAlign: "center" }}>Loading...</div>
)}
</React.Suspense>
<div data-tut="reactourFifth">
<React.Suspense fallback={<Loader />}>
<DriveBody
dataTutSixth="reactourSixth"
dataTutSeventh="reactourSeventh"
pdfData={pdfData}
setFolderName={setFolderName}
setIsLoading={setIsLoading}
setDocId={setDocId}
getPdfDocumentList={getPdfDocumentList}
isDocId={docId}
setPdfData={setPdfData}
isList={isList}
setIsAlert={setIsAlert}
setSkip={setSkip}
sortingData={sortingData}
/>
{loading && (
<div style={{ textAlign: "center" }}>Loading...</div>
)}
</React.Suspense>
</div>
)}
</>
)}
+332 -273
View File
@@ -21,7 +21,10 @@ import {
onSaveSign,
onSaveImage,
addDefaultSignatureImg,
radioButtonWidget
radioButtonWidget,
replaceMailVaribles,
fetchSubscription,
convertPdfArrayBuffer
} from "../constant/Utils";
import Loader from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
@@ -31,7 +34,6 @@ import PdfDeclineModal from "../primitives/PdfDeclineModal";
import Title from "../components/Title";
import DefaultSignature from "../components/pdf/DefaultSignature";
import ModalUi from "../primitives/ModalUi";
import Parse from "parse";
function PdfRequestFiles() {
const { docId } = useParams();
@@ -74,6 +76,7 @@ function PdfRequestFiles() {
isShow: false,
alertMessage: ""
});
const [isSubscribed, setIsSubscribed] = useState(false);
const [isCompleted, setIsCompleted] = useState({
isCertificate: false,
isModal: false
@@ -93,8 +96,9 @@ function PdfRequestFiles() {
const [minRequiredCount, setminRequiredCount] = useState();
const [sendInOrder, setSendInOrder] = useState(false);
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
const [isSubscribed, setIsSubscribed] = useState(false);
const [isSubscriptionExpired, setIsSubscriptionExpired] = useState(false);
const [extUserId, setExtUserId] = useState("");
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
const divRef = useRef(null);
const isMobile = window.innerWidth < 767;
const rowLevel =
@@ -134,28 +138,26 @@ function PdfRequestFiles() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current]);
async function checkIsSubscribed(email) {
const user = await Parse.Cloud.run("getUserDetails", {
email: email
});
const freeplan = user?.get("Plan") && user?.get("Plan").plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
async function checkIsSubscribed(extUserId) {
const res = await fetchSubscription(extUserId);
const plan = res.plan;
const billingDate = res.billingDate;
if (plan === "freeplan") {
return true;
} else if (billingDate) {
if (billingDate > new Date()) {
if (new Date(billingDate) > new Date()) {
setIsSubscribed(true);
return true;
} else {
if (location.pathname.includes("/load/")) {
setIsSubscribed(true);
setIsSubscriptionExpired(true);
} else {
navigate(`/subscription`);
}
}
} else {
if (location.pathname.includes("/load/")) {
setIsSubscribed(true);
setIsSubscriptionExpired(true);
} else {
navigate(`/subscription`);
}
@@ -167,8 +169,17 @@ function PdfRequestFiles() {
//getting document details
const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) {
const url =
documentData[0] && (documentData[0]?.SignedUrl || documentData[0]?.URL);
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
const arrayBuffer = await convertPdfArrayBuffer(url);
if (arrayBuffer === "Error") {
setHandleError("Error: Something went wrong!");
} else {
setPdfArrayBuffer(arrayBuffer);
}
if (isEnableSubscription) {
await checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
await checkIsSubscribed(documentData[0]?.ExtUserPtr?.objectId);
}
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const isCompleted =
@@ -384,144 +395,142 @@ function PdfRequestFiles() {
});
};
const checkSendInOrder = () => {
if (sendInOrder) {
const index = pdfDetails?.[0].Signers.findIndex(
(x) => x.Email === jsonSender.email
);
const newIndex = index - 1;
if (newIndex !== -1) {
const user = pdfDetails?.[0].Signers[newIndex];
const isPrevUserSigned =
pdfDetails?.[0].AuditTrail &&
pdfDetails?.[0].AuditTrail.some(
(x) =>
x.UserPtr.objectId === user.objectId && x.Activity === "Signed"
);
if (isPrevUserSigned) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
return true;
}
};
// const checkSendInOrder = () => {
// if (sendInOrder) {
// const index = pdfDetails?.[0].Signers.findIndex(
// (x) => x.Email === jsonSender.email
// );
// const newIndex = index - 1;
// if (newIndex !== -1) {
// const user = pdfDetails?.[0].Signers[newIndex];
// const isPrevUserSigned =
// pdfDetails?.[0].AuditTrail &&
// pdfDetails?.[0].AuditTrail.some(
// (x) =>
// x.UserPtr.objectId === user.objectId && x.Activity === "Signed"
// );
// if (isPrevUserSigned) {
// return true;
// } else {
// return false;
// }
// } else {
// return true;
// }
// } else {
// return true;
// }
// };
//function for embed signature or image url in pdf
async function embedWidgetsData() {
const validateSigning = checkSendInOrder();
if (validateSigning) {
const checkUser = signerPos.filter(
(data) => data.signerObjId === signerObjectId
);
if (checkUser && checkUser.length > 0) {
let checkboxExist,
requiredRadio,
showAlert = false,
widgetKey,
radioExist,
requiredCheckbox;
// const validateSigning = checkSendInOrder();
// if (validateSigning) {
const checkUser = signerPos.filter(
(data) => data.signerObjId === signerObjectId
);
if (checkUser && checkUser.length > 0) {
let checkboxExist,
requiredRadio,
showAlert = false,
widgetKey,
radioExist,
requiredCheckbox;
for (let i = 0; i < checkUser[0].placeHolder.length; i++) {
for (let j = 0; j < checkUser[0].placeHolder[i].pos.length; j++) {
checkboxExist =
checkUser[0].placeHolder[i].pos[j].type === "checkbox";
radioExist =
checkUser[0].placeHolder[i].pos[j].type === radioButtonWidget;
if (checkboxExist) {
requiredCheckbox = checkUser[0].placeHolder[i].pos.filter(
(position) =>
!position.options?.isReadOnly && position.type === "checkbox"
);
for (let i = 0; i < checkUser[0].placeHolder.length; i++) {
for (let j = 0; j < checkUser[0].placeHolder[i].pos.length; j++) {
checkboxExist =
checkUser[0].placeHolder[i].pos[j].type === "checkbox";
radioExist =
checkUser[0].placeHolder[i].pos[j].type === radioButtonWidget;
if (checkboxExist) {
requiredCheckbox = checkUser[0].placeHolder[i].pos.filter(
(position) =>
!position.options?.isReadOnly && position.type === "checkbox"
);
if (requiredCheckbox && requiredCheckbox.length > 0) {
for (let i = 0; i < requiredCheckbox.length; i++) {
const minCount =
requiredCheckbox[i].options?.validation?.minRequiredCount;
const parseMin = minCount && parseInt(minCount);
const maxCount =
requiredCheckbox[i].options?.validation?.maxRequiredCount;
const parseMax = maxCount && parseInt(maxCount);
const response =
requiredCheckbox[i].options?.response?.length;
const defaultValue =
requiredCheckbox[i].options?.defaultValue?.length;
if (parseMin === 0 && parseMax === 0) {
if (!showAlert) {
showAlert = false;
setminRequiredCount(null);
}
} else if (parseMin === 0 && parseMax > 0) {
if (!showAlert) {
showAlert = false;
setminRequiredCount(null);
}
} else if (!response) {
if (!defaultValue) {
if (!showAlert) {
showAlert = true;
widgetKey = requiredCheckbox[i].key;
setminRequiredCount(parseMin);
}
}
} else if (parseMin > 0 && parseMin > response) {
if (requiredCheckbox && requiredCheckbox.length > 0) {
for (let i = 0; i < requiredCheckbox.length; i++) {
const minCount =
requiredCheckbox[i].options?.validation?.minRequiredCount;
const parseMin = minCount && parseInt(minCount);
const maxCount =
requiredCheckbox[i].options?.validation?.maxRequiredCount;
const parseMax = maxCount && parseInt(maxCount);
const response = requiredCheckbox[i].options?.response?.length;
const defaultValue =
requiredCheckbox[i].options?.defaultValue?.length;
if (parseMin === 0 && parseMax === 0) {
if (!showAlert) {
showAlert = false;
setminRequiredCount(null);
}
} else if (parseMin === 0 && parseMax > 0) {
if (!showAlert) {
showAlert = false;
setminRequiredCount(null);
}
} else if (!response) {
if (!defaultValue) {
if (!showAlert) {
showAlert = true;
widgetKey = requiredCheckbox[i].key;
setminRequiredCount(parseMin);
}
}
} else if (parseMin > 0 && parseMin > response) {
if (!showAlert) {
showAlert = true;
widgetKey = requiredCheckbox[i].key;
setminRequiredCount(parseMin);
}
}
}
} else if (radioExist) {
requiredRadio = checkUser[0].placeHolder[i].pos.filter(
(position) =>
!position.options?.isReadOnly &&
position.type === radioButtonWidget
);
if (requiredRadio && requiredRadio?.length > 0) {
let checkSigned;
for (let i = 0; i < requiredRadio?.length; i++) {
checkSigned = requiredRadio[i]?.options.response;
if (!checkSigned) {
let checkDefaultSigned =
requiredRadio[i]?.options.defaultValue;
if (!checkDefaultSigned) {
if (!showAlert) {
showAlert = true;
widgetKey = requiredRadio[i].key;
setminRequiredCount(null);
}
}
} else if (radioExist) {
requiredRadio = checkUser[0].placeHolder[i].pos.filter(
(position) =>
!position.options?.isReadOnly &&
position.type === radioButtonWidget
);
if (requiredRadio && requiredRadio?.length > 0) {
let checkSigned;
for (let i = 0; i < requiredRadio?.length; i++) {
checkSigned = requiredRadio[i]?.options.response;
if (!checkSigned) {
let checkDefaultSigned =
requiredRadio[i]?.options.defaultValue;
if (!checkDefaultSigned) {
if (!showAlert) {
showAlert = true;
widgetKey = requiredRadio[i].key;
setminRequiredCount(null);
}
}
}
}
} else {
const requiredWidgets = checkUser[0].placeHolder[i].pos.filter(
(position) =>
position.options?.status === "required" &&
position.type !== radioButtonWidget &&
position.type !== "checkbox"
);
if (requiredWidgets && requiredWidgets?.length > 0) {
let checkSigned;
for (let i = 0; i < requiredWidgets?.length; i++) {
checkSigned = requiredWidgets[i]?.options?.response;
if (!checkSigned) {
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
}
} else {
const requiredWidgets = checkUser[0].placeHolder[i].pos.filter(
(position) =>
position.options?.status === "required" &&
position.type !== radioButtonWidget &&
position.type !== "checkbox"
);
if (requiredWidgets && requiredWidgets?.length > 0) {
let checkSigned;
for (let i = 0; i < requiredWidgets?.length; i++) {
checkSigned = requiredWidgets[i]?.options?.response;
if (!checkSigned) {
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
let checkDefaultSigned =
requiredWidgets[i]?.options?.defaultValue;
if (!checkSignUrl) {
if (!checkDefaultSigned) {
if (!showAlert) {
showAlert = true;
widgetKey = requiredWidgets[i].key;
setminRequiredCount(null);
}
let checkDefaultSigned =
requiredWidgets[i]?.options?.defaultValue;
if (!checkSignUrl) {
if (!checkDefaultSigned) {
if (!showAlert) {
showAlert = true;
widgetKey = requiredWidgets[i].key;
setminRequiredCount(null);
}
}
}
@@ -530,108 +539,153 @@ function PdfRequestFiles() {
}
}
}
}
if (checkboxExist && requiredCheckbox && showAlert) {
setUnSignedWidgetId(widgetKey);
setWidgetsTour(true);
} else if (radioExist && showAlert) {
setUnSignedWidgetId(widgetKey);
setWidgetsTour(true);
} else if (showAlert) {
setUnSignedWidgetId(widgetKey);
setWidgetsTour(true);
} else {
setIsUiLoading(true);
const pngUrl = checkUser[0].placeHolder;
// Load a PDFDocument from the existing PDF bytes
const existingPdfBytes = await fetch(pdfUrl).then((res) =>
res.arrayBuffer()
);
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
ignoreEncryption: true
});
const flag = false;
const extUserPtr = pdfDetails[0].ExtUserPtr;
const HeaderDocId = extUserPtr?.HeaderDocId;
//embed document's object id to all pages in pdf document
if (!HeaderDocId) {
if (!isDocId) {
await embedDocId(pdfDoc, documentId, allPages);
}
if (checkboxExist && requiredCheckbox && showAlert) {
setUnSignedWidgetId(widgetKey);
setWidgetsTour(true);
} else if (radioExist && showAlert) {
setUnSignedWidgetId(widgetKey);
setWidgetsTour(true);
} else if (showAlert) {
setUnSignedWidgetId(widgetKey);
setWidgetsTour(true);
} else {
setIsUiLoading(true);
const pngUrl = checkUser[0].placeHolder;
// Load a PDFDocument from the existing PDF bytes
const existingPdfBytes = pdfArrayBuffer;
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
ignoreEncryption: true
});
const isSignYourSelfFlow = false;
const extUserPtr = pdfDetails[0].ExtUserPtr;
const HeaderDocId = extUserPtr?.HeaderDocId;
//embed document's object id to all pages in pdf document
if (!HeaderDocId) {
if (!isDocId) {
await embedDocId(pdfDoc, documentId, allPages);
}
//embed multi signature in pdf
const pdfBytes = await multiSignEmbed(
pngUrl,
pdfDoc,
pdfOriginalWidth,
flag,
containerWH
}
//embed multi signature in pdf
const pdfBytes = await multiSignEmbed(
pngUrl,
pdfDoc,
pdfOriginalWidth,
isSignYourSelfFlow,
containerWH
);
//get ExistUserPtr object id of user class to get tenantDetails
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
//function for call to embed signature in pdf and get digital signature pdf
try {
const res = await signPdfFun(
pdfBytes,
documentId,
signerObjectId,
setIsAlert,
objectId,
isSubscribed
);
//function for call to embed signature in pdf and get digital signature pdf
try {
const res = await signPdfFun(
pdfBytes,
documentId,
signerObjectId,
setIsAlert
if (res && res.status === "success") {
setPdfUrl(res.data);
setIsSigned(true);
setSignedSigners([]);
setUnSignedSigners([]);
getDocumentDetails();
const index = pdfDetails?.[0].Signers.findIndex(
(x) => x.Email === jsonSender.email
);
if (res && res.status === "success") {
setPdfUrl(res.data);
setIsSigned(true);
setSignedSigners([]);
setUnSignedSigners([]);
getDocumentDetails();
if (sendInOrder) {
const index = pdfDetails?.[0].Signers.findIndex(
(x) => x.Email === jsonSender.email
);
const newIndex = index + 1;
const user = pdfDetails?.[0].Signers[newIndex];
if (user) {
// console.log("pdfDetails", pdfDetails);
const expireDate = pdfDetails?.[0].ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric"
});
let sender = pdfDetails?.[0].ExtUserPtr.Email;
const newIndex = index + 1;
const user = pdfDetails?.[0].Signers[newIndex];
if (user) {
setIsCompleted({
isModal: true,
message:
"You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document will be sent to the owner over email once it is signed by all signers."
});
}
if (sendInOrder) {
const requestBody = pdfDetails?.[0]?.RequestBody;
const requestSubject = pdfDetails?.[0]?.RequestSubject;
if (user) {
const expireDate = pdfDetails?.[0].ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString("en-US", {
day: "numeric",
month: "long",
year: "numeric"
});
let senderEmail = pdfDetails?.[0].ExtUserPtr.Email;
let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone;
const senderName = `${pdfDetails?.[0].ExtUserPtr.Name}`;
try {
const imgPng =
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem(
"baseUrl"
)}functions/sendmailv3/`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id":
localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
try {
const imgPng =
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png";
let url = `${localStorage.getItem(
"baseUrl"
)}functions/sendmailv3/`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id":
localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const serverUrl = localStorage.getItem("baseUrl");
const newServer = serverUrl.replaceAll("/", "%2F");
const objectId = user.objectId;
const serverParams = `${newServer}&${localStorage.getItem(
"parseAppId"
)}&${localStorage.getItem("_appName")}`;
const hostUrl = window.location.origin;
let signPdf = `${hostUrl}/login/${pdfDetails?.[0].objectId}/${user.Email}/${objectId}/${serverParams}`;
const openSignUrl = "https://www.opensignlabs.com/contact-us";
const orgName = pdfDetails[0]?.ExtUserPtr.Company
? pdfDetails[0].ExtUserPtr.Company
: "";
const themeBGcolor = themeColor;
let replaceVar;
if (
requestBody &&
requestSubject &&
(!isEnableSubscription || isSubscribed)
) {
const replacedRequestBody = requestBody.replace(/"/g, "'");
const htmlReqBody =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
replacedRequestBody +
"</body> </html>";
const variables = {
document_title: pdfDetails?.[0].Name,
sender_name: senderName,
sender_mail: senderEmail,
sender_phone: senderPhone,
receiver_name: user.Name,
receiver_email: user.Email,
receiver_phone: user.Phone,
expiry_date: localExpireDate,
company_name: orgName,
signing_url: `<a href=${signPdf}>Sign here</a>`
};
const serverUrl = localStorage.getItem("baseUrl");
const newServer = serverUrl.replaceAll("/", "%2F");
const objectId = user.objectId;
const serverParams = `${newServer}&${localStorage.getItem(
"parseAppId"
)}&${localStorage.getItem("_appName")}`;
const hostUrl = window.location.origin;
let signPdf = `${hostUrl}/login/${pdfDetails?.[0].objectId}/${user.Email}/${objectId}/${serverParams}`;
const openSignUrl =
"https://www.opensignlabs.com/contact-us";
const orgName = pdfDetails[0]?.ExtUserPtr.Company
? pdfDetails[0].ExtUserPtr.Company
: "";
const themeBGcolor = themeColor;
let params = {
extUserId: extUserId,
recipient: user.Email,
subject: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign ${pdfDetails?.[0].Name}`,
from: sender,
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'=> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
replaceVar = replaceMailVaribles(
requestSubject,
htmlReqBody,
variables
);
}
let params = {
extUserId: extUserId,
recipient: user.Email,
subject: requestSubject
? replaceVar?.subject
: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign ${pdfDetails?.[0].Name}`,
from: senderEmail,
html: requestBody
? replaceVar?.body
: "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'=> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
imgPng +
" height='50' style='padding: 20px,width:170px,height:40px' /></div> <div style=' padding: 2px;font-family: system-ui;background-color:" +
themeBGcolor +
@@ -640,7 +694,7 @@ function PdfRequestFiles() {
" has requested you to review and sign <strong> " +
pdfDetails?.[0].Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
sender +
senderEmail +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
@@ -648,47 +702,50 @@ function PdfRequestFiles() {
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender +
senderEmail +
" directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
openSignUrl +
" target=_blank>here</a>.</p> </div></div></body> </html>"
};
await axios.post(url, params, {
headers: headers
});
} catch (error) {
console.log("error", error);
}
};
await axios.post(url, params, {
headers: headers
});
} catch (error) {
console.log("error", error);
}
}
} else {
setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
}
} catch (err) {
} else {
setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
}
} catch (err) {
setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
}
setIsSignPad(false);
} else {
setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
}
setIsSignPad(false);
} else {
setIsAlert({
isShow: true,
alertMessage:
"Please wait for your turn to sign this document, as it has been set up by the creator to be signed in a specific order; you'll be notified when it's your turn."
alertMessage: "something went wrong"
});
}
// }
// else {
// setIsAlert({
// isShow: true,
// alertMessage:
// "Please wait for your turn to sign this document, as it has been set up by the creator to be signed in a specific order; you'll be notified when it's your turn."
// });
// }
}
//function for update TourStatus
const closeTour = async () => {
setWidgetsTour(false);
@@ -937,10 +994,10 @@ function PdfRequestFiles() {
return (
<DndProvider backend={HTML5Backend}>
<Title title={"Request Sign"} />
{isSubscribed ? (
{isSubscriptionExpired ? (
<ModalUi
title={"Subscription Expired"}
isOpen={isSubscribed}
isOpen={isSubscriptionExpired}
showClose={false}
>
<div className="flex flex-col justify-center items-center py-4 md:py-5 gap-5">
@@ -984,7 +1041,7 @@ function PdfRequestFiles() {
)}
<div
className="signatureContainer"
className="relative flex flex-col md:flex-row justify-between bg-[#ebebeb]"
style={{
pointerEvents:
isExpired ||
@@ -1046,16 +1103,15 @@ function PdfRequestFiles() {
{/* this modal is used to show decline alert */}
<PdfDeclineModal
containerWH={containerWH}
show={isDecline.isDeclined}
headMsg="Document Declined"
bodyMssg={
isDecline.currnt === "Sure"
? "Are you sure want to decline this document ?"
: isDecline.currnt === "YouDeclined"
? "You have declined this document!"
: isDecline.currnt === "another" &&
"You cannot sign this document as it has been declined by one or more recipient(s)."
? "You have declined this document!"
: isDecline.currnt === "another" &&
"You can not sign this document as it has been declined/revoked."
}
footerMessage={isDecline.currnt === "Sure"}
declineDoc={declineDoc}
@@ -1063,7 +1119,6 @@ function PdfRequestFiles() {
/>
{/* this modal is used for show expired alert */}
<PdfDeclineModal
containerWH={containerWH}
show={isExpired}
headMsg="Document Expired!"
bodyMssg={`This document expired on ${expiredDate} and is no longer available to sign.`}
@@ -1135,7 +1190,7 @@ function PdfRequestFiles() {
</ModalUi>
{/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage
signPdfUrl={pdfDetails[0].URL}
signPdfUrl={pdfDetails[0] && pdfDetails[0].SignedUrl}
allPages={allPages}
setAllPages={setAllPages}
setPageNumber={setPageNumber}
@@ -1154,11 +1209,15 @@ function PdfRequestFiles() {
isOpen={isCompleted.isModal}
title={"Sign Documents"}
handleClose={() => {
setIsCompleted({ isModal: false, isCertificate: true });
setIsCompleted((prev) => ({ ...prev, isModal: false }));
}}
>
<div style={{ height: "100%", padding: 20 }}>
<p>This document has been signed by all Signers.</p>
<p>
{" "}
{isCompleted?.message ||
"This document has been signed by all Signers."}
</p>
<div
style={{
@@ -1172,12 +1231,12 @@ function PdfRequestFiles() {
<button
type="button"
className="finishBtn cancelBtn"
onClick={() =>
setIsCompleted({
isModal: false,
isCertificate: true
})
}
onClick={() => {
setIsCompleted((prev) => ({
...prev,
isModal: false
}));
}}
>
Close
</button>
@@ -1215,7 +1274,7 @@ function PdfRequestFiles() {
pdfDetails={pdfDetails}
signerPos={signerPos}
isSigned={isSigned}
isCompleted={isCompleted}
isCompleted={isCompleted.isCertificate}
embedWidgetsData={embedWidgetsData}
isShowHeader={true}
setIsDecline={setIsDecline}
+15 -41
View File
@@ -41,12 +41,8 @@ const PgSignUp = () => {
const url = window.location.href;
let paramString = url.split("?")[1];
let queryString = new URLSearchParams(paramString);
// console.log("url ", queryString);
let obj = { ...formData };
for (let pair of queryString.entries()) {
// console.log("Key is: " + pair[0]);
// console.log("Value is: " + pair[1]);
obj = { ...obj, [pair[0]]: pair[1] };
}
const zohoRes = await axios.post(
@@ -61,34 +57,27 @@ const PgSignUp = () => {
}
}
);
// console.log("zohoRes ", zohoRes);
const userSettings = JSON.parse(localStorage.getItem("userSettings"));
const extClass = userSettings[0].extended_class;
// console.log("extClass ", extClass);
const params = { email: userDetails.email };
const res = await Parse.Cloud.run("getUserDetails", params);
// console.log("res", res);
if (res) {
const updateQuery = new Parse.Object(extClass);
updateQuery.id = res.id;
updateQuery.set(
"Next_billing_date",
new Date(zohoRes.data.result.nextBillingDate)
);
updateQuery.set("Plan", zohoRes.data.result.plan);
updateQuery.set("Customer_id", zohoRes.data.result.customer_id);
updateQuery.set(
"Subscription_id",
zohoRes.data.result.subscription_id
);
const updateRes = await updateQuery.save();
if (updateRes) {
const params = { subscription: zohoRes.data.result.subscription };
const baseurl = `${parseBaseUrl}functions/savesubscription`;
try {
const res = await axios.post(baseurl, params, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessiontoken: localStorage.getItem("accesstoken")
}
});
if (res) {
localStorage.removeItem("userDetails");
navigate(
"/" + userSettings[0].pageType + "/" + userSettings[0].pageId
);
setIsLoader(false);
}
} catch (err) {
console.log("err ", err.message);
setIsLoader(false);
}
} catch (err) {
console.log("err ", err);
@@ -108,12 +97,8 @@ const PgSignUp = () => {
const url = window.location.href;
let paramString = url.split("?")[1];
let queryString = new URLSearchParams(paramString);
// console.log("url ", queryString);
let obj = { ...formData };
for (let pair of queryString.entries()) {
// console.log("Key is: " + pair[0]);
// console.log("Value is: " + pair[1]);
obj = { ...obj, [pair[0]]: pair[1] };
}
saveUser(obj);
@@ -157,19 +142,8 @@ const PgSignUp = () => {
email: zohoRes.data.result.email,
phone: zohoRes.data.result.phone,
role: obj.role
// "pincode": "",
// "address": "",
// "country": "",
// "state": "",
// "city": ""
},
planDetails: {
plan: zohoRes.data.result.plan || {},
nextBillingDate: zohoRes.data.result.nextBillingDate || "",
customer_id: zohoRes.data.result.customer_id || "",
subscription_id: zohoRes.data.result.subscription_id || ""
}
subscription: zohoRes.data.result.subscription
};
const usersignup = await Parse.Cloud.run("usersignup", params);
// console.log("usersignup ", usersignup);
File diff suppressed because it is too large Load Diff
+23 -37
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { NavLink, useNavigate } from "react-router-dom";
import checkmark from "../assets/images/checkmark.png";
import plansArr from "../json/plansArr.json";
import plansArr from "../json/plansArr";
import Title from "../components/Title";
import Parse from "parse";
const listItemStyle = {
@@ -19,15 +19,22 @@ const PlanSubscriptions = () => {
const userDetails = JSON.parse(localStorage.getItem("userDetails"));
const name =
userDetails && userDetails.name ? "first_name=" + userDetails.name : "";
userDetails && userDetails.name
? "first_name=" + encodeURIComponent(userDetails.name)
: "";
const email =
userDetails && userDetails.email ? "&email=" + userDetails.email : "";
userDetails && userDetails.email
? "&email=" + encodeURIComponent(userDetails.email)
: "";
const company =
userDetails && userDetails.company
? "&company_name=" + userDetails.company
? "&company_name=" + encodeURIComponent(userDetails.company)
: "";
const phone =
userDetails && userDetails.phone ? "&mobile=" + userDetails.phone : "";
userDetails && userDetails.phone
? "&mobile=" + encodeURIComponent(userDetails.phone)
: "";
const details =
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
name +
@@ -92,7 +99,7 @@ const PlanSubscriptions = () => {
maxHeight: "100%"
}}
>
<div id="monthlyPlans" className="block my-2">
<div className="block my-2">
<div className="flex flex-col justify-center items-center w-full">
<div className="mb-6 mt-2 flex flex-row border-[1px] p-2 border-gray-300 rounded text-sm">
<span
@@ -119,10 +126,10 @@ const PlanSubscriptions = () => {
<ul className="flex flex-col md:flex-row h-full bg-white justify-center">
{plansArr.map((item) => (
<li
className="flex flex-col md:my-0 text-center border-collapse border-[1px] border-gray-300 w-[260px]"
className="flex flex-col text-center border-collapse border-[1px] border-gray-300 max-w-[250px]"
key={item.planName}
>
<div className="p-2 flex flex-col justify-center items-center min-h-[320px]">
<div className="p-2 flex flex-col justify-center items-center max-h-[310px]">
<h3 className="text-[#002862] uppercase">
{item.planName}
</h3>
@@ -133,7 +140,7 @@ const PlanSubscriptions = () => {
alt="freeimg"
/>
</div>
<div className="">
<div>
<span className="text-3xl">
{item.currency && <small>{item.currency}</small>}
{yearlyVisible
@@ -143,13 +150,7 @@ const PlanSubscriptions = () => {
<p className="font-semibold pt-2 text-sm">
{yearlyVisible ? "Billed Yearly" : "Billed Monthly"}
</p>
<div
className={`${
item.subtitle.length <= 32
? "w-[150px] h-[40px] text-center"
: ""
} text-sm text-center my-2`}
>
<div className="max-w-[250px] h-[40px] text-center text-sm my-2">
<div
style={{
textAlign: "center",
@@ -179,14 +180,14 @@ const PlanSubscriptions = () => {
: item.url + details
: item.url
}
className="bg-[#002862] w-full mt-1 text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
target={item.target}
>
{item.btnText}
</NavLink>
) : (
<button
className="bg-[#002862] w-full mt-1 text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:text-white cursor-pointer"
onClick={() => handleFreePlan()}
>
{item.btnText}
@@ -207,25 +208,10 @@ const PlanSubscriptions = () => {
))}
</ul>
</div>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
className="text-sm"
>
<hr
className={"border-[1px] border-gray-300 w-[20%]"}
style={{ color: "grey" }}
/>
<span style={{ color: "grey" }} className="px-2 ">
or
</span>
<hr
className={"border-[1px] border-gray-300 w-[20%]"}
style={{ color: "grey" }}
/>
<div className="text-sm flex justify-center items-center">
<hr className="border-[1px] border-gray-300 w-[20%]" />
<span className="px-2 text-gray-500 cursor-default">or</span>
<hr className="border-[1px] border-gray-300 w-[20%]" />
</div>
<div className="flex flex-col justify-center w-full items-center">
<h3 className="text-[#002862] mt-1 mb-2">
+53 -7
View File
@@ -6,6 +6,7 @@ import reportJson from "../json/ReportJson";
import { useParams } from "react-router-dom";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
const Report = () => {
const { id } = useParams();
@@ -18,6 +19,8 @@ const Report = () => {
const [isNextRecord, setIsNextRecord] = useState(false);
const [isMoreDocs, setIsMoreDocs] = useState(true);
const [form, setForm] = useState("");
const [tourData, setTourData] = useState([]);
const [isDontShow, setIsDontShow] = useState(false);
const abortController = new AbortController();
const docPerPage = 10;
@@ -46,6 +49,9 @@ const Report = () => {
// eslint-disable-next-line
}, [isNextRecord]);
const handleDontShow = (isChecked) => {
setIsDontShow(isChecked);
};
const getReportData = async (skipUserRecord = 0, limit = 200) => {
// setIsLoader(true);
const json = reportJson(id);
@@ -54,7 +60,7 @@ const Report = () => {
setHeading(json.heading);
setReportName(json.reportName);
setForm(json.form);
setReportHelp(json?.helpMsg)
setReportHelp(json?.helpMsg);
const currentUser = Parse.User.current().id;
const headers = {
@@ -69,6 +75,42 @@ const Report = () => {
headers: headers,
signal: abortController.signal // is used to cancel fetch query
});
if (id === "6TeaPr321t") {
const tourConfig = [
{
selector: "[data-tut=reactourFirst]",
content: () => (
<TourContentWithBtn
message={
"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. "
}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
}
];
if (res.data.result && res.data.result?.length > 0) {
json.actions.map((data) => {
const newConfig = {
selector: `[data-tut="${data?.selector}"]`,
content: () => (
<TourContentWithBtn
message={data?.message}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
};
tourConfig.push(newConfig);
});
}
setTourData(tourConfig);
}
if (id === "4Hhwbp482K") {
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
let arr = [];
@@ -106,12 +148,14 @@ const Report = () => {
} else {
setIsMoreDocs(false);
}
setIsNextRecord(false);
setList((prevRecord) =>
prevRecord.length > 0
? [...prevRecord, ...res.data.result]
: res.data.result
);
if (!res.data.result.error) {
setIsNextRecord(false);
setList((prevRecord) =>
prevRecord.length > 0
? [...prevRecord, ...res.data.result]
: res.data.result
);
}
}
setIsLoader(false);
} catch (err) {
@@ -159,6 +203,8 @@ const Report = () => {
docPerPage={docPerPage}
form={form}
report_help={reporthelp}
tourData={tourData}
isDontShow={isDontShow}
/>
) : (
<PageNotFound prefix={"Report"} />
+20 -3
View File
@@ -11,6 +11,7 @@ import { useDispatch } from "react-redux";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import { isEnableSubscription } from "../constant/const";
import { getAppLogo } from "../constant/Utils";
const Signup = () => {
const { width } = useWindowSize();
const navigate = useNavigate();
@@ -22,6 +23,7 @@ const Signup = () => {
const [email, setEmail] = useState("");
const [company, setCompany] = useState("");
const [jobTitle, setJobTitle] = useState("");
const [image, setImage] = useState();
const [state, setState] = useState({
loading: false,
alertType: "success",
@@ -33,7 +35,6 @@ const Signup = () => {
const [specialCharValid, setSpecialCharValid] = useState(false);
const togglePasswordVisibility = () => setShowPassword(!showPassword);
const image = appInfo.applogo;
const clearStorage = async () => {
if (Parse.User.current()) {
@@ -469,9 +470,23 @@ const Signup = () => {
};
useEffect(() => {
dispatch(fetchAppInfo());
saveLogo();
// eslint-disable-next-line
}, []);
const saveLogo = async () => {
if (isEnableSubscription) {
const logo = await getAppLogo();
if (logo) {
setImage(logo);
} else {
setImage(appInfo?.applogo || undefined);
}
} else {
setImage(appInfo?.applogo || undefined);
}
};
const handlePasswordChange = (e) => {
const newPassword = e.target.value;
setPassword(newPassword);
@@ -517,8 +532,10 @@ const Signup = () => {
{appInfo && appInfo.applogo ? (
<div>
<div className="md:m-10 lg:m-16 md:p-4 lg:p-10 p-5 bg-[#ffffff] md:border-[1px] md:border-gray-400 ">
<div className="w-[250px] h-[66px] inline-block">
<img src={image} width="100%" alt="" />
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
{image && (
<img src={image} className="object-contain h-full" alt="logo" />
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
<div className="">
+45 -29
View File
@@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from "react";
import { PDFDocument } from "pdf-lib";
import "../styles/signature.css";
import { themeColor } from "../constant/const";
import { isEnableSubscription, themeColor } from "../constant/const";
import axios from "axios";
import Loader from "../primitives/LoaderWithMsg";
import loader from "../assets/images/loader2.gif";
@@ -26,7 +26,10 @@ import {
contactBook,
randomId,
getDate,
textWidget
textWidget,
getTenantDetails,
checkIsSubscribed,
convertPdfArrayBuffer
} from "../constant/Utils";
import { useParams } from "react-router-dom";
import Tour from "reactour";
@@ -67,7 +70,6 @@ function SignYourSelf() {
const [pdfOriginalWidth, setPdfOriginalWidth] = useState();
const [successEmail, setSuccessEmail] = useState(false);
const imageRef = useRef(null);
const [documentStatus, setDocumentStatus] = useState({ isCompleted: false });
const [myInitial, setMyInitial] = useState("");
const [isInitial, setIsInitial] = useState(false);
const [isUiLoading, setIsUiLoading] = useState(false);
@@ -100,6 +102,8 @@ function SignYourSelf() {
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isDontShow, setIsDontShow] = useState(false);
const [extUserId, setExtUserId] = useState("");
const [isCompleted, setIsCompleted] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
const divRef = useRef(null);
const nodeRef = useRef(null);
const [, drop] = useDrop({
@@ -188,12 +192,17 @@ function SignYourSelf() {
if (documentData && documentData.length > 0) {
setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const url = documentData[0] && documentData[0]?.URL;
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
const arrayBuffer = await convertPdfArrayBuffer(url);
if (arrayBuffer === "Error") {
setHandleError("Error: Something went wrong!");
} else {
setPdfArrayBuffer(arrayBuffer);
}
isCompleted = documentData[0].IsCompleted && documentData[0].IsCompleted;
if (isCompleted) {
const docStatus = {
isCompleted: isCompleted
};
setDocumentStatus(docStatus);
setIsCompleted(true);
setPdfUrl(documentData[0].SignedUrl);
const alreadySign = {
status: true,
@@ -256,9 +265,7 @@ function SignYourSelf() {
setHandleError("Error: Something went wrong!");
setIsLoading(loadObj);
});
const contractUsersRes = await contractUsers(jsonSender.email);
if (contractUsersRes[0] && contractUsersRes.length > 0) {
setContractName("_Users");
setSignerUserId(contractUsersRes[0].objectId);
@@ -434,13 +441,13 @@ function SignYourSelf() {
Width: widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth
: dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).width
: "",
? defaultWidthHeight(dragTypeValue).width
: "",
Height: widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getHeight
: dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).height
: "",
? defaultWidthHeight(dragTypeValue).height
: "",
options: addWidgetOptions(dragTypeValue)
};
@@ -562,22 +569,14 @@ function SignYourSelf() {
setIsCeleb(false);
}, 3000);
setIsUiLoading(true);
const url = pdfDetails[0] && pdfDetails[0].URL;
const existingPdfBytes = await fetch(url).then((res) =>
res.arrayBuffer()
);
const existingPdfBytes = pdfArrayBuffer;
// Load a PDFDocument from the existing PDF bytes
const pdfDoc = await PDFDocument.load(existingPdfBytes, {
ignoreEncryption: true
});
const flag = true;
const isSignYourSelfFlow = true;
const extUserPtr = pdfDetails[0].ExtUserPtr;
const HeaderDocId = extUserPtr?.HeaderDocId;
//embed document's object id to all pages in pdf document
if (!HeaderDocId) {
await embedDocId(pdfDoc, documentId, allPages);
@@ -587,7 +586,7 @@ function SignYourSelf() {
xyPostion,
pdfDoc,
pdfOriginalWidth,
flag,
isSignYourSelfFlow,
containerWH
);
// console.log("pdf", pdfBytes);
@@ -598,9 +597,25 @@ function SignYourSelf() {
// console.log("signyourself", xyPostion);
//function for get digital signature
const signPdfFun = async (base64Url, documentId) => {
let isCustomCompletionMail = false;
const getIsSubscribe = await checkIsSubscribed();
const tenantDetails = await getTenantDetails(jsonSender.objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert("User does not exist");
} else {
if (
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject &&
(!isEnableSubscription || getIsSubscribe)
) {
isCustomCompletionMail = true;
}
}
let singleSign = {
pdfFile: base64Url,
docId: documentId
docId: documentId,
isCustomCompletionMail: isCustomCompletionMail
};
await axios
@@ -986,8 +1001,9 @@ function SignYourSelf() {
{/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage
pdfUrl={pdfUrl}
signPdfUrl={pdfDetails[0] && pdfDetails[0].URL}
signPdfUrl={
pdfDetails[0] && (pdfDetails[0].SignedUrl || pdfDetails[0].URL)
}
allPages={allPages}
setAllPages={setAllPages}
setPageNumber={setPageNumber}
@@ -1129,7 +1145,6 @@ function SignYourSelf() {
allPages={allPages}
changePage={changePage}
pdfUrl={pdfUrl}
documentStatus={documentStatus}
embedWidgetsData={embedWidgetsData}
pdfDetails={pdfDetails}
isShowHeader={true}
@@ -1137,6 +1152,7 @@ function SignYourSelf() {
alreadySign={pdfUrl ? true : false}
isSignYourself={true}
setIsEmail={setIsEmail}
isCompleted={isCompleted}
/>
<div data-tut="reactourSecond">
@@ -1189,7 +1205,7 @@ function SignYourSelf() {
}}
className="autoSignScroll"
>
{!documentStatus.isCompleted ? (
{!isCompleted ? (
<div>
<WidgetComponent
dataTut="reactourFirst"
+47 -26
View File
@@ -23,7 +23,8 @@ import {
defaultWidthHeight,
addWidgetOptions,
textInputWidget,
radioButtonWidget
radioButtonWidget,
fetchSubscription
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css";
@@ -35,7 +36,6 @@ import AddRoleModal from "../components/pdf/AddRoleModal";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import Parse from "parse";
const TemplatePlaceholder = () => {
const navigate = useNavigate();
const { templateId } = useParams();
@@ -145,6 +145,7 @@ const TemplatePlaceholder = () => {
const [currWidgetsDetails, setCurrWidgetsDetails] = useState([]);
const [isCheckbox, setIsCheckbox] = useState(false);
const [widgetName, setWidgetName] = useState(false);
const [isAddRole, setIsAddRole] = useState(false);
const senderUser =
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
@@ -170,17 +171,14 @@ const TemplatePlaceholder = () => {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current]);
async function checkIsSubscribed(email) {
const user = await Parse.Cloud.run("getUserDetails", {
email: email
});
const freeplan = user?.get("Plan") && user?.get("Plan").plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
async function checkIsSubscribed() {
const res = await fetchSubscription();
const freeplan = res.plan;
const billingDate = res.billingDate;
if (freeplan === "freeplan") {
return true;
} else if (billingDate) {
if (billingDate > new Date()) {
if (new Date(billingDate) > new Date()) {
setIsSubscribe(true);
return true;
} else {
@@ -311,10 +309,10 @@ const TemplatePlaceholder = () => {
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRecipients = tourstatus.filter(
(data) => data.templatetour
(data) => data.templateTour
);
if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].templatetour);
setCheckTourStatus(checkTourRecipients[0].templateTour);
}
}
const loadObj = {
@@ -484,9 +482,20 @@ const TemplatePlaceholder = () => {
} else {
setIsReceipent(false);
}
} else {
setIsAddRole(true);
}
};
const tourAddRole = [
{
selector: '[data-tut="reactourAddbtn"]',
content: "You need to add a role before you can add fields for it. ",
position: "top",
style: { fontSize: "13px" }
}
];
//function for get pdf page details
const pageDetails = async (pdf) => {
const load = {
@@ -765,7 +774,7 @@ const TemplatePlaceholder = () => {
selector: '[data-tut="reactourSecond"]',
content: () => (
<TourContentWithBtn
message={`Drag a widget placeholder onto the PDF to choose your desired signing location.`}
message={`Drag or click on a field to add it to the document.`}
isChecked={handleDontShow}
/>
),
@@ -787,7 +796,7 @@ const TemplatePlaceholder = () => {
selector: '[data-tut="reactourFour"]',
content: () => (
<TourContentWithBtn
message={`Clicking "Save" button will save the template and will ask you if you want to create a new document using this template.`}
message={`Clicking Save will store the current template. After saving, youll be prompted to create a new document from this template if you wish.`}
isChecked={handleDontShow}
/>
),
@@ -805,15 +814,15 @@ const TemplatePlaceholder = () => {
if (tourStatus.length > 0) {
updatedTourStatus = [...tourStatus];
const templatetourIndex = tourStatus.findIndex(
(obj) => obj["templatetour"] === false || obj["templatetour"] === true
(obj) => obj["templateTour"] === false || obj["templateTour"] === true
);
if (templatetourIndex !== -1) {
updatedTourStatus[templatetourIndex] = { templatetour: true };
updatedTourStatus[templatetourIndex] = { templateTour: true };
} else {
updatedTourStatus.push({ templatetour: true });
updatedTourStatus.push({ templateTour: true });
}
} else {
updatedTourStatus = [{ templatetour: true }];
updatedTourStatus = [{ templateTour: true }];
}
await axios
.put(
@@ -1123,8 +1132,6 @@ const TemplatePlaceholder = () => {
inputype = options.includes(defaultdata.textvalidate)
? defaultdata.textvalidate
: "regex";
} else {
inputype = "text";
}
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
@@ -1149,11 +1156,14 @@ const TemplatePlaceholder = () => {
status: defaultdata?.status || "required",
hint: defaultdata?.hint || "",
defaultValue: defaultdata?.defaultValue || "",
validation: {
type: inputype,
pattern:
inputype === "regex" ? defaultdata.textvalidate : ""
}
validation:
(isSubscribe || !isEnableSubscription) && inputype
? {
type: inputype,
pattern:
inputype === "regex" ? defaultdata.textvalidate : ""
}
: {}
}
};
} else {
@@ -1197,6 +1207,7 @@ const TemplatePlaceholder = () => {
setIsCheckbox(false);
};
console.log("signerpos", signerPos);
return (
<div>
<Title title={"Template"} />
@@ -1221,6 +1232,15 @@ const TemplatePlaceholder = () => {
closeWithMask={false}
/>
)}
{isAddRole && (
<Tour
onRequestClose={() => setIsAddRole(false)}
steps={tourAddRole}
isOpen={isAddRole}
rounded={5}
closeWithMask={false}
/>
)}
{/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage
signPdfUrl={pdfDetails[0].URL}
@@ -1246,7 +1266,7 @@ const TemplatePlaceholder = () => {
handleClose={() => setIsSendAlert(false)}
>
<div style={{ height: "100%", padding: 20 }}>
<p>Please add field for all recipients.</p>
<p>Please add at least one signature field for all roles.</p>
</div>
</ModalUi>
<ModalUi
@@ -1551,6 +1571,7 @@ const TemplatePlaceholder = () => {
isAddUser={isAddUser}
uniqueId={uniqueId}
closePopup={closePopup}
signersData={signersdata}
/>
</div>
<ModalUi
+45 -2
View File
@@ -15,6 +15,7 @@ import Upgrade from "../primitives/Upgrade";
function UserProfile() {
const navigate = useNavigate();
let UserProfile = JSON.parse(localStorage.getItem("UserInformation"));
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [editmode, setEditMode] = useState(false);
@@ -25,6 +26,12 @@ function UserProfile() {
const [percentage, setpercentage] = useState(0);
const [isDisableDocId, setIsDisableDocId] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [company, setCompany] = useState(
extendUser && extendUser?.[0]?.Company
);
const [jobTitle, setJobTitle] = useState(
extendUser && extendUser?.[0]?.JobTitle
);
useEffect(() => {
getUserDetail();
@@ -66,6 +73,7 @@ function UserProfile() {
SetPhone(res.phone);
setImage(res.ProfilePic);
localStorage.setItem("username", res.name);
localStorage.setItem("profileImg", res.ProfilePic);
await updateExtUser({ Name: res.name, Phone: res.phone });
alert("Profile updated successfully.");
setEditMode(false);
@@ -93,7 +101,9 @@ function UserProfile() {
const body = {
Phone: obj.Phone,
Name: obj.Name,
HeaderDocId: isDisableDocId
HeaderDocId: isDisableDocId,
JobTitle: jobTitle,
Company: company
};
await axios.put(
parseBaseUrl + "classes/" + extClass + "/" + ExtUserId,
@@ -147,7 +157,6 @@ function UserProfile() {
// console.log("File URL:", response.url());
if (response.url()) {
setImage(response.url());
localStorage.setItem("profileImg", response.url());
setpercentage(0);
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
@@ -264,6 +273,40 @@ function UserProfile() {
<span className="font-semibold">Email:</span>{" "}
<span>{UserProfile && UserProfile.email}</span>
</li>
<li
className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${
editmode ? "py-1" : "py-2"
}`}
>
<span className="font-semibold">Company:</span>{" "}
{editmode ? (
<input
type="text"
value={company}
className="py-1 px-2 text-sm border-[1px] border-[#15b4e9] text-black rounded"
onChange={(e) => setCompany(e.target.value)}
/>
) : (
<span>{extendUser?.[0].Company}</span>
)}
</li>
<li
className={`flex justify-between items-center border-t-[1px] border-gray-300 break-all ${
editmode ? "py-1" : "py-2"
}`}
>
<span className="font-semibold">Job title:</span>{" "}
{editmode ? (
<input
type="text"
value={jobTitle}
className="py-1 px-2 text-sm border-[1px] border-[#15b4e9] text-black rounded"
onChange={(e) => setJobTitle(e.target.value)}
/>
) : (
<span>{extendUser?.[0]?.JobTitle}</span>
)}
</li>
<li className="flex justify-between items-center border-t-[1px] border-gray-300 py-2 break-all">
<span className="font-semibold">Is Email verified:</span>{" "}
<span>
File diff suppressed because it is too large Load Diff
@@ -7,12 +7,16 @@ const LinkUserModal = (props) => {
return (
<ModalUi
title={"Add/Choose Signer"}
isOpen={props.isAddUser[props.uniqueId]}
isOpen={
props?.isAddSigner ||
(props?.isAddUser && props?.isAddUser[props?.uniqueId])
}
handleClose={props.closePopup}
>
<SelectSigners
details={props.handleAddUser}
closePopup={props.closePopup}
signersData={props?.signersData}
/>
<div
style={{
+12 -2
View File
@@ -1,4 +1,6 @@
import React from "react";
import { isEnableSubscription } from "../constant/const";
import PremiumAlertHeader from "./PremiumAlertHeader";
const ModalUi = ({
children,
@@ -8,12 +10,13 @@ const ModalUi = ({
handleClose,
showHeader = true,
showClose = true,
styleClass
styleClass,
showHeaderMessage
}) => {
return (
<>
{isOpen && (
<div className="fixed z-[999] top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]">
<div className="fixed z-[999] 2 top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]">
<div
className={
styleClass
@@ -37,6 +40,13 @@ const ModalUi = ({
)}
</div>
)}
{!isEnableSubscription && showHeaderMessage && (
<PremiumAlertHeader
message={
"Cutomize Email is free in beta, this feature will incur a fee later."
}
/>
)}
<div>{children}</div>
</div>
</div>
@@ -8,19 +8,11 @@ function CustomModal({
bodyMssg,
footerMessage,
declineDoc,
setIsDecline,
containerWH
setIsDecline
}) {
const isMobile = window.innerWidth < 767;
return (
show && (
<div
className="bg-black bg-opacity-[75%] absolute z-[999] flex flex-col items-center justify-center"
style={{
width: containerWH && containerWH.width,
height: isMobile ? "100%" : containerWH && containerWH.height
}}
>
<div className="bg-black bg-opacity-[75%] absolute z-[999] flex flex-col items-center justify-center w-full h-full rounded">
<div className="bg-white rounded outline-none md:w-[40%] w-[80%]">
<div className="bg-[#de4337] text-white p-[10px] rounded-t">
{headMsg && headMsg}
+12 -4
View File
@@ -1,16 +1,24 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { openInNewTab } from "../constant/Utils";
function Upgrade() {
function Upgrade({ message, newWindow }) {
const navigation = useNavigate();
return (
<sup>
<span
onClick={() => navigation("/subscription")}
className="text-blue-800 text-sm cursor-pointer hover:underline"
onClick={() => {
if (newWindow) {
const url = window.location.origin + "/subscription";
openInNewTab(url);
} else {
navigation("/subscription");
}
}}
className="text-blue-800 text-sm cursor-pointer underline"
>
Upgrade now
{message ? message : "Upgrade now"}
</span>
</sup>
);
+8 -1
View File
@@ -9,9 +9,15 @@ const Validate = () => {
useEffect(() => {
(async () => {
try {
const userDetails = JSON.parse(
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
)
);
// Use the session token to validate the user
const userQuery = new Parse.Query(Parse.User);
const user = await userQuery.get(Parse.User.current().id, {
const user = await userQuery.get(userDetails?.objectId, {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
@@ -24,6 +30,7 @@ const Validate = () => {
setIsUserValid(false);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+10 -53
View File
@@ -395,14 +395,6 @@
margin-left: 10px;
}
.backBtn {
background-color: white;
}
.backBtn:hover {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
}
.defaultBtn {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
padding: 3px 26px;
@@ -420,42 +412,6 @@
outline: none;
}
.downloadBtn {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
padding: 3px 15px !important;
background-color: rgb(241 79 79);
border: none !important;
color: white !important;
}
.downloadBtn:hover {
box-shadow: 0 2px 4px rgba(154, 36, 36, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
}
.printBtn {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
padding: 3px 15px !important;
background: #188ae2;
border: none !important;
color: white !important;
}
.printBtn:hover {
box-shadow: 0 2px 4px rgba(23, 48, 137, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
}
.certificateBtn {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
padding: 3px 15px !important;
background: #08bc66;
border: none !important;
color: white !important;
text-decoration: none !important;
}
.certificateBtn:hover {
box-shadow: 0 2px 4px rgba(15, 150, 87, 0.1), 0 2px 4px rgba(0, 0, 0, 0.18);
}
.signerList {
overflow-y: scroll;
@@ -496,11 +452,15 @@
overflow: hidden !important;
text-overflow: ellipsis;
}
.disabled {
opacity: 0.5; /* Example: reduce opacity to visually indicate disabled state */
pointer-events: none; /* Prevents mouse clicks and other events */
opacity: 0.5;
/* Example: reduce opacity to visually indicate disabled state */
pointer-events: none;
/* Prevents mouse clicks and other events */
/* background-color: #888; */
}
.useEmail {
font-size: 10px;
color: #424242;
@@ -557,13 +517,6 @@
position: "relative";
}
.signatureHeader {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 5px;
}
.signerComponent {
/* box-shadow: rgba(17, 12, 46, 0.15) 0px 48px 100px 0px; */
@@ -851,6 +804,7 @@ option {
z-index: 999;
border-radius: 2px;
resize: none;
overflow: hidden;
}
.labelTextArea:focus {
@@ -1014,6 +968,9 @@ option {
}
}
.scroll-hide::-webkit-scrollbar {
display: none;
}
@media screen and (max-width: 350px) and (min-width: 311px) {
.signatureCanvas {
width: 280px;
+93
View File
@@ -150,3 +150,96 @@ export const updateMailCount = async extUserId => {
console.log('Error updating EmailCount in contracts_users: ' + error.message);
}
};
export function formatWidgetOptions(type, options) {
const status = options?.required === true ? 'required' : 'optional' || 'required';
const defaultValue = options?.default || '';
const values = options?.values || [];
switch (type) {
case 'signature':
return { name: 'signature', status: 'required' };
case 'stamp':
return { status: status, name: 'stamp' };
case 'initials':
return { status: status, name: options.name || 'initials' };
case 'image':
return { status: status, name: options.name || 'image' };
case 'email':
return { status: status, name: options.name || 'email', validation: { type: 'email' } };
case 'name':
return { status: status, name: options.name || 'name' };
case 'job title':
return { status: status, name: options.name || 'job title' };
case 'company':
return { status: status, name: options.name || 'company' };
case 'date': {
let today = new Date();
let dd = String(today.getDate()).padStart(2, '0');
let mm = String(today.getMonth() + 1).padStart(2, '0'); // January is 0!
let yyyy = today.getFullYear();
today = dd + '-' + mm + '-' + yyyy;
let dateFormat = options?.format;
dateFormat = dateFormat.replace(/m/g, 'M');
return {
status: status,
name: options.name || 'date',
response: defaultValue || today,
validation: { format: dateFormat || 'dd-MM-yyyy', type: 'date-format' },
};
}
case 'textbox':
return {
status: status,
name: 'textbox',
defaultValue: defaultValue,
hint: options.hint,
validation: { type: 'regex', pattern: options?.regularexpression || '/^[a-zA-Z0-9s]+$/' },
};
case 'checkbox': {
const arr = options?.values;
let selectedvalues = [];
for (const obj of options.selectedvalues) {
const index = arr.indexOf(obj);
selectedvalues.push(index);
}
return {
status: status,
name: options.name || 'checkbox',
values: values,
isReadOnly: options?.readonly || false,
isHideLabel: options?.hidelabel || false,
validation: {
minRequiredCount: options?.validation?.minselections || 0,
maxRequiredCount: options?.validation?.maxselections || 0,
},
defaultValue: selectedvalues || [],
};
}
case 'radio button': {
return {
status: status,
name: options.name || 'radio',
values: values,
isReadOnly: options?.readonly || false,
isHideLabel: options?.hidelabel || false,
defaultValue: defaultValue,
};
}
case 'dropdown':
return {
status: status,
name: options.name || 'dropdown',
values: values,
defaultValue: defaultValue,
};
default:
break;
}
}
export function sanitizeFileName(fileName) {
// Remove spaces and invalid characters
const file = fileName.replace(/[^a-zA-Z0-9._-]/g, '');
const removedot = file.replace(/\.(?=.*\.)/g, '');
return removedot.replace(/[^a-zA-Z0-9._-]/g, '');
}
@@ -1,12 +1,22 @@
//--npm modules
import express from 'express';
import cors from 'cors';
export const app = express();
import dotenv from 'dotenv';
import uploadFile from './uploadFile.js';
import saveSubscription from './saveSubscription.js';
import saveInvoice from './saveInvoice.js';
import savePayments from './savePayments.js';
export const app = express();
dotenv.config();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
import uploadFile from './uploadFile.js';
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ limit: '50mb', extended: true }));
app.post('/file_upload', uploadFile);
app.post('/savesubscription', saveSubscription)
app.post('/saveinvoice', saveInvoice)
app.post('/savepayment', savePayments)
@@ -0,0 +1,51 @@
export default async function saveInvoice(request, response) {
const InvoiceId = request.body.data.invoice.invoice_id;
const body = request.body;
const Email = request.body.data.invoice.email;
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('Email', Email);
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const invoiceCls = new Parse.Query('contracts_Invoices');
invoiceCls.equalTo('InvoiceId', InvoiceId);
const invoice = await invoiceCls.first({ useMasterKey: true });
if (invoice) {
const updateInvoice = new Parse.Object('contracts_Invoices');
updateInvoice.id = invoice.id;
updateInvoice.set('InvoiceDetails', body);
await updateInvoice.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'update invoice!' });
} else {
const createInvoice = new Parse.Object('contracts_Invoices');
createInvoice.set('InvoiceId', InvoiceId);
createInvoice.set('InvoiceDetails', body);
createInvoice.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
});
createInvoice.set('CreatedBy', {
__type: 'Pointer',
className: '_User',
objectId: extUser.get('UserId').id,
});
if (extUser?.get('TenantId')?.id) {
createInvoice.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
}
await createInvoice.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'create invoice!' });
}
} else {
return response.status(404).json({ status: 'user not found!' });
}
} catch (err) {
console.log('Err in save invoice', err);
return response.status(400).json({ status: 'error:' + err.message });
}
}
@@ -0,0 +1,50 @@
export default async function savePayments(request, response) {
const PaymentId = request.body.data.payment.payment_id;
const body = request.body;
const Email = request.body.data.payment.email;
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('Email', Email);
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const paymentCls = new Parse.Query('contracts_Payments');
paymentCls.equalTo('PaymentId', PaymentId);
const payment = await paymentCls.first({ useMasterKey: true });
if (payment) {
const updatePayment = new Parse.Object('contracts_Payments');
updatePayment.id = payment.id;
updatePayment.set('PaymentDetails', body);
await updatePayment.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'update Payment!' });
} else {
const createPayment = new Parse.Object('contracts_Payments');
createPayment.set('PaymentId', PaymentId);
createPayment.set('PaymentDetails', body);
createPayment.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
});
createPayment.set('CreatedBy', {
__type: 'Pointer',
className: '_User',
objectId: extUser.get('UserId').id,
});
if (extUser?.get('TenantId')?.id) {
createPayment.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
}
await createPayment.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'create payments!' });
}
} else {
return response.status(404).json({ status: 'user not found!' });
}
} catch (err) {
console.log('Err in save payment', err);
return response.status(400).json({ status: 'error:' + err.message });
}
}
@@ -0,0 +1,60 @@
export default async function saveSubscription(request, response) {
const SubscriptionId = request.body.data.subscription.subscription_id;
const body = request.body;
const Email = request.body.data.subscription.customer.email;
const Next_billing_date = request.body.data.subscription.next_billing_at;
const planCode = request.body.data.subscription.plan.plan_code
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('Email', Email);
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const subcriptionCls = new Parse.Query('contracts_Subscriptions');
subcriptionCls.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
const subscription = await subcriptionCls.first({ useMasterKey: true });
if (subscription) {
const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subscription.id;
updateSubscription.set('SubscriptionId', SubscriptionId);
updateSubscription.set('SubscriptionDetails', body);
updateSubscription.set('Next_billing_date', new Date(Next_billing_date));
updateSubscription.set('PlanCode', planCode);
await updateSubscription.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'update subscription!' });
} else {
const createSubscription = new Parse.Object('contracts_Subscriptions');
createSubscription.set('SubscriptionId', SubscriptionId);
createSubscription.set('SubscriptionDetails', body);
createSubscription.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
});
createSubscription.set('CreatedBy', {
__type: 'Pointer',
className: '_User',
objectId: extUser.get('UserId').id,
});
createSubscription.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
createSubscription.set('Next_billing_date', new Date(Next_billing_date));
createSubscription.set('PlanCode', planCode);
await createSubscription.save(null, { useMasterKey: true });
return response.status(200).json({ status: 'create subscription!' });
}
} else {
return response.status(404).json({ status: 'user not found!' });
}
} catch (err) {
console.log('Err in save subscription', err);
return response.status(400).json({ status: 'error:' + err.message });
}
}
@@ -1,5 +1,12 @@
import axios from 'axios';
import { color, customAPIurl, replaceMailVaribles, saveFileUsage } from '../../../../Utils.js';
import {
color,
customAPIurl,
replaceMailVaribles,
saveFileUsage,
formatWidgetOptions,
sanitizeFileName,
} from '../../../../Utils.js';
// `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) {
@@ -92,9 +99,8 @@ export default async function createDocumentwithCoordinate(request, response) {
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const file = new Parse.File(`${name}.pdf`, {
base64: base64File,
});
const filename = sanitizeFileName(`${name}.pdf`);
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
@@ -177,17 +183,17 @@ export default async function createDocumentwithCoordinate(request, response) {
for (const widget of signer.widgets) {
const pageNumber = widget.page;
const options = formatWidgetOptions(widget.type, widget.options);
const page = placeHolder.find(page => page.pageNumber === pageNumber);
const signOpt = { name: 'signature', status: 'required' };
const widgetData = {
isStamp: widget.type === 'stamp',
isStamp: widget.type === 'stamp' || widget.type === 'image',
key: randomId(),
isDrag: false,
scale: 1,
isMobile: false,
zIndex: 1,
type: widget.type,
options: widget.type === 'signature' ? signOpt : widget.options,
type: widget.type === 'textbox' ? 'text input' : widget.type,
options: options,
Width: widget.w,
Height: widget.h,
xPosition: widget.x,
@@ -1,5 +1,11 @@
import axios from 'axios';
import { color, customAPIurl, saveFileUsage } from '../../../../Utils.js';
import {
color,
customAPIurl,
saveFileUsage,
formatWidgetOptions,
sanitizeFileName,
} from '../../../../Utils.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function createTemplatewithCoordinate(request, response) {
@@ -43,9 +49,8 @@ export default async function createTemplatewithCoordinate(request, response) {
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const file = new Parse.File(`${name}.pdf`, {
base64: base64File,
});
const filename = sanitizeFileName(`${name}.pdf`);
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
@@ -134,16 +139,16 @@ export default async function createTemplatewithCoordinate(request, response) {
for (const widget of signer.widgets) {
const pageNumber = widget.page;
const page = placeHolder.find(page => page.pageNumber === pageNumber);
const signOpt = { name: 'signature', status: 'required' };
const options = formatWidgetOptions(widget.type, widget.options);
const widgetData = {
isStamp: widget.type === 'stamp',
isStamp: widget.type === 'stamp' || widget.type === 'image',
key: randomId(),
isDrag: false,
scale: 1,
isMobile: false,
zIndex: 1,
type: widget.type,
options: widget.type === 'signature' ? signOpt : widget.options,
type: widget.type === 'textbox' ? 'text input' : widget.type,
options: options,
Width: widget.w,
Height: widget.h,
xPosition: widget.x,
+18
View File
@@ -25,6 +25,15 @@ import callWebhook from './parsefunction/callWebhook.js';
import SubscribeFree from './parsefunction/SubscribeFree.js';
import DocumentBeforesave from './parsefunction/DocumentBeforesave.js';
import TemplateBeforeSave from './parsefunction/TemplateBeforesave.js';
import DocumentBeforeFind from './parsefunction/DocumentAfterFind.js';
import TemplateAfterFind from './parsefunction/TemplateAfterFind.js';
import UserAfterFind from './parsefunction/UserAfterFInd.js';
import SignatureAfterFind from './parsefunction/SignatureAfterFind.js';
import getInvoices from './parsefunction/getInvoices.js';
import getPayments from './parsefunction/getPayments.js';
import getSubscriptions from './parsefunction/getSubscriptions.js';
import TenantAterFind from './parsefunction/TenantAfterFind.js';
import saveSubscriptio from './parsefunction/saveSubscription.js';
Parse.Cloud.define('AddUserToRole', addUserToGroups);
Parse.Cloud.define('UserGroups', getUserGroups);
@@ -47,9 +56,18 @@ Parse.Cloud.define('getTemplate', GetTemplate);
Parse.Cloud.define('savewebhook', savewebhook);
Parse.Cloud.define('callwebhook', callWebhook);
Parse.Cloud.define('freesubscription', SubscribeFree);
Parse.Cloud.define('getinvoices', getInvoices);
Parse.Cloud.define('getpayments', getPayments);
Parse.Cloud.define('getsubscriptions', getSubscriptions);
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave);
Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave);
Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave);
Parse.Cloud.beforeSave('contracts_Document', DocumentBeforesave);
Parse.Cloud.beforeSave('contracts_Template', TemplateBeforeSave);
Parse.Cloud.afterFind(Parse.User, UserAfterFind);
Parse.Cloud.afterFind('contracts_Document', DocumentBeforeFind);
Parse.Cloud.afterFind('contracts_Template', TemplateAfterFind);
Parse.Cloud.afterFind('contracts_Signature', SignatureAfterFind);
Parse.Cloud.afterFind('partners_Tenant', TenantAterFind);
Parse.Cloud.define('savesubscription', saveSubscriptio);
@@ -0,0 +1,24 @@
import getPresignedUrl from './getSignedUrl.js';
async function DocumentAfterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
const Url = obj?.get('URL') && obj?.get('URL');
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
}
if (Url) {
obj.set('URL', getPresignedUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
}
return [obj];
}
}
}
export default DocumentAfterFind;
@@ -5,9 +5,7 @@ async function DocumentBeforesave(request) {
const oldDocument = request.original;
// Check if SignedUrl field has been added (transition from undefined to defined)
if (!oldDocument.get('SignedUrl') && document.get('SignedUrl')) {
const SignedUrl = document.get('SignedUrl');
if (!oldDocument?.get('SignedUrl') && document?.get('SignedUrl')) {
// Update count in contracts_Users class
const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', oldDocument.get('ExtUserPtr').id);
@@ -0,0 +1,22 @@
import getPresignedUrl from './getSignedUrl.js';
async function SignatureAfterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ImageURL = obj?.get('ImageURL') && obj?.get('ImageURL');
const Initials = obj?.get('Initials') && obj?.get('Initials');
if (ImageURL) {
obj.set('ImageURL', getPresignedUrl(ImageURL));
}
if (Initials) {
obj.set('Initials', getPresignedUrl(Initials));
}
return [obj];
}
}
}
export default SignatureAfterFind;
@@ -6,27 +6,51 @@ export default async function SubscribeFree(request) {
extQuery.equalTo('UserId', userPtr);
const extUser = await extQuery.first({ useMasterKey: true });
if (extUser) {
if (extUser?.get('Plan')?.plan_code === 'freeplan') {
const subscriptionCls = new Parse.Query('contracts_Subscriptions');
subscriptionCls.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
subscriptionCls.descending('createdAt');
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
if (subcripitions?.get('PlanCode') === 'freeplan') {
return { status: 'success', result: 'already subscribed!' };
} else if (extUser?.get('Next_billing_date') < new Date()) {
} else if (subcripitions?.get('Next_billing_date') < new Date()) {
try {
const extUpdate = new Parse.Object('contracts_Users');
extUpdate.id = extUser.id;
extUpdate.set('Plan', { plan_code: 'freeplan' });
await extUpdate.save(null, { useMasterKey: true });
const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subcripitions.id;
updateSubscription.set('PlanCode', 'freeplan');
await updateSubscription.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
}
} else if (extUser?.get('Next_billing_date') > new Date()) {
} else if (subcripitions?.get('Next_billing_date') > new Date()) {
return { status: 'success', result: 'already subscribed!' };
} else {
try {
const extUpdate = new Parse.Object('contracts_Users');
extUpdate.id = extUser.id;
extUpdate.set('Plan', { plan_code: 'freeplan' });
await extUpdate.save(null, { useMasterKey: true });
const createSubscription = new Parse.Object('contracts_Subscriptions');
createSubscription.set('PlanCode', 'freeplan');
createSubscription.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
});
createSubscription.set('CreatedBy', {
__type: 'Pointer',
className: '_User',
objectId: extUser.get('UserId').id,
});
if (extUser?.get('TenantId')) {
createSubscription.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
}
await createSubscription.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
console.log('err ', err);
@@ -0,0 +1,24 @@
import getPresignedUrl from './getSignedUrl.js';
async function TemplateAfterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const SignedUrl = obj?.get('SignedUrl') && obj?.get('SignedUrl');
const Url = obj?.get('URL') && obj?.get('URL');
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
}
if (Url) {
obj.set('URL', getPresignedUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
}
return [obj];
}
}
}
export default TemplateAfterFind;
@@ -0,0 +1,15 @@
import getPresignedUrl from './getSignedUrl.js';
async function TenantAterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const Logo = obj?.get('Logo') && obj?.get('Logo');
if (Logo) {
obj.set('Logo', getPresignedUrl(Logo));
}
return [obj];
}
}
}
export default TenantAterFind;
@@ -0,0 +1,17 @@
import getPresignedUrl from './getSignedUrl.js';
async function UserAfterFind(request) {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ProfilePic = obj?.get('ProfilePic') && obj?.get('ProfilePic');
if (ProfilePic) {
obj.set('ProfilePic', getPresignedUrl(ProfilePic));
}
return [obj];
}
}
}
export default UserAfterFind;
@@ -23,23 +23,16 @@ export default async function ZohoDetails(request) {
};
// Make the POST request using Axios
const res = await axios.post(url, formData, { headers });
// console.log("Access Token:", res.data);
if (res.data.access_token) {
const hostedpages = request.params.hostedpagesId;
const userData = await axios.get('https://billing.zoho.in/api/v1/hostedpages/' + hostedpages, {
headers: {
Authorization: 'Zoho-oauthtoken ' + res.data.access_token,
'X-com-zoho-billing-organizationid': process.env.ZOHO_BILLING_ORG_ID,
'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID,
},
});
// console.log(
// "userData.data.data.subscription.contactpersons ",
// userData.data.data.subscription.contactpersons
// );
// console.log("userData.data.expiring_time ", userData.data.expiring_time);
const first_name = userData.data.data.subscription.contactpersons[0].first_name || '';
const last_name = userData.data.data.subscription.contactpersons[0].last_name || '';
const company_name =
@@ -60,6 +53,7 @@ export default async function ZohoDetails(request) {
customer_id: userData.data.data.subscription.customer_id,
subscription_id: userData.data.data.subscription.subscription_id,
jobTitle: jobTitle,
subscription: userData.data,
};
return resData;
}
@@ -21,7 +21,7 @@ export default async function getDocument(request) {
query.include('Signers');
query.include('AuditTrail.UserPtr');
query.include('Placeholders');
query.notEqualTo('IsArchive', true)
query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true });
if (res) {
const acl = res.getACL();
@@ -0,0 +1,43 @@
import axios from 'axios';
const serverUrl = process.env.SERVER_URL;
const appId = process.env.APP_ID;
export default async function getInvoices(request) {
const limit = request.params.limit || 100;
const skip = request.params.skip || 0;
const extUserId = request.params.extUserId;
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) {
if (!extUserId) {
return { status: 'error', result: 'Please provide parameter!' };
}
const invoiceCls = new Parse.Query('contracts_Invoices');
invoiceCls.equalTo('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUserId,
});
invoiceCls.limit(limit);
invoiceCls.skip(skip);
invoiceCls.descending('createdAt');
const invoices = await invoiceCls.find({ useMasterKey: true });
if (invoices?.length > 0) {
const _invoices = JSON.parse(JSON.stringify(invoices));
return { status: 'success', result: _invoices };
} else {
return { status: 'success', result: [] };
}
} else {
return { status: 'error', result: 'Invalid session token!' };
}
} catch (err) {
console.log('Err in get invoices', err.message);
return { status: 'error', result: err.message };
}
}
@@ -0,0 +1,40 @@
import axios from 'axios';
const serverUrl = process.env.SERVER_URL;
const appId = process.env.APP_ID;
export default async function getPayments(request) {
const limit = request.params.limit || 100;
const skip = request.params.skip || 0;
const extUserId = request.params.extUserId;
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 paymentsCls = new Parse.Query('contracts_Payments');
paymentsCls.equalTo('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_User',
objectId: extUserId,
});
paymentsCls.limit(limit);
paymentsCls.skip(skip);
paymentsCls.descending('createdAt');
const payments = await paymentsCls.find({ useMasterKey: true });
if (payments?.length > 0) {
const _payments = JSON.parse(JSON.stringify(payments));
return { status: 'success', result: _payments };
} else {
return { status: 'success', result: [] };
}
} else {
return { status: 'error', result: 'Invalid session token!' };
}
} catch (err) {
console.log('Err in get Payments', err.message);
return { status: 'error', result: err.message };
}
}
@@ -42,7 +42,7 @@ export default async function getReport(request) {
}
}
} catch (err) {
console.log('err', err);
console.log('err', err.message);
if (err.code == 209) {
return { error: 'Invalid session token' };
} else {
@@ -0,0 +1,26 @@
import AWS from 'aws-sdk';
const credentials = {
accessKeyId: process.env.DO_ACCESS_KEY_ID,
secretAccessKey: process.env.DO_SECRET_ACCESS_KEY,
};
AWS.config.update({ credentials: credentials, region: process.env.DO_REGION });
const spacesEndpoint = new AWS.Endpoint(process.env.DO_ENDPOINT);
const s3 = new AWS.S3({ endpoint: spacesEndpoint });
export default function getPresignedUrl(url) {
// Create a new URL object
const parsedUrl = new URL(url);
// Get the pathname of the URL
const pathname = parsedUrl.pathname;
// Extract the filename from the pathname
const filename = pathname.substring(pathname.lastIndexOf('/') + 1);
// presignedGETURL return presignedUrl with expires time
const presignedGETURL = s3.getSignedUrl('getObject', {
Bucket: process.env.DO_SPACE,
Key: filename, //filename
Expires: 160, //time to expire in seconds
});
return presignedGETURL;
}
@@ -0,0 +1,42 @@
import axios from 'axios';
const serverUrl = process.env.SERVER_URL;
const appId = process.env.APP_ID;
export default async function getSubscription(request) {
const extUserId = request.params.extUserId;
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 subcriptionCls = new Parse.Query('contracts_Users');
const exUser = await subcriptionCls.get(extUserId, { useMasterKey: true });
if (exUser) {
const subscriptionCls = new Parse.Query('contracts_Subscriptions');
subscriptionCls.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: exUser.get('TenantId').id,
});
subscriptionCls.descending('createdAt');
const subcripitions = await subscriptionCls.first({ useMasterKey: true });
if (subcripitions) {
const _subcripitions = JSON.parse(JSON.stringify(subcripitions));
return { status: 'success', result: _subcripitions };
} else {
return { status: 'success', result: {} };
}
} else {
return { status: 'error', result: 'User not found!' };
}
} else {
return { status: 'error', result: 'Invalid session token!' };
}
} catch (err) {
console.log('Err in get subscription', err.message);
return { status: 'error', result: err.message };
}
}
@@ -0,0 +1,244 @@
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
import fs from 'node:fs';
export default async function GenerateCertificate(docDetails) {
const pdfDoc = await PDFDocument.create();
const timesRomanFont = await pdfDoc.embedFont(StandardFonts.TimesRoman);
const pngUrl = fs.readFileSync('./logo.png').buffer;
const pngImage = await pdfDoc.embedPng(pngUrl);
const page = pdfDoc.addPage();
const { width, height } = page.getSize();
const startX = 15;
const startY = 15;
const borderColor = rgb(0.12, 0.12, 0.12);
const titleColor = rgb(0, 0.2, 0.4); //rgb(0, 0.53, 0.71);
const titleUnderline = rgb(0, 0.2, 0.4); // rgb(0.12, 0.12, 0.12);
const title = 25;
const subtitle = 20;
const text = 14;
const textKeyColor = rgb(0.12, 0.12, 0.12);
const textValueColor = rgb(0.3, 0.3, 0.3);
const completedAt = new Date(docDetails.updatedAt);
const completedUTCtime = completedAt.toUTCString();
const signersCount = docDetails?.Signers?.length || 1;
const createdAt = new Date();
const createdUTCTime = createdAt.toUTCString();
const createDate = 'Generated On ' + createdUTCTime;
const company = docDetails?.ExtUserPtr?.Company || '';
const auditTrail =
docDetails.AuditTrail?.length > 1
? docDetails.AuditTrail.map(x => {
const data = docDetails.Signers.find(y => y.objectId === x.UserPtr.objectId);
return { ...data, ipAddress: x.ipAddress };
})
: [{ ...docDetails.ExtUserPtr, ipAddress: docDetails?.AuditTrail[0].ipAddress }];
// Draw a border
page.drawRectangle({
x: startX,
y: startY,
width: width - 2 * startX,
height: height - 2 * startY,
borderColor: borderColor,
borderWidth: 1,
});
page.drawImage(pngImage, {
x: 30,
y: 790,
width: 100,
height: 25,
});
page.drawText(createDate, {
x: 320,
y: 810,
size: 12,
font: timesRomanFont,
color: rgb(0.12, 0.12, 0.12),
});
page.drawText('Certificate of Completion', {
x: 160,
y: 750,
size: title,
font: timesRomanFont,
color: titleColor,
});
const underlineY = 740;
page.drawLine({
start: { x: 30, y: underlineY },
end: { x: width - 30, y: underlineY },
color: titleUnderline,
thickness: 1,
});
page.drawText('Summary', {
x: 30,
y: 710,
size: subtitle,
font: timesRomanFont,
color: titleColor,
});
page.drawText('Document Id :', {
x: 30,
y: 685,
size: text,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(docDetails.objectId, {
x: 115,
y: 685,
size: text,
font: timesRomanFont,
color: textValueColor,
});
page.drawText('Document Name :', {
x: 30,
y: 665,
size: text,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(docDetails.Name, {
x: 140,
y: 665,
size: text,
font: timesRomanFont,
color: textValueColor,
});
page.drawText('Organization :', {
x: 30,
y: 645,
size: text,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(company, {
x: 115,
y: 645,
size: text,
font: timesRomanFont,
color: textValueColor,
});
page.drawText('Completed on :', {
x: 30,
y: 625,
size: text,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(`${completedUTCtime}`, {
x: 120,
y: 625,
size: text,
font: timesRomanFont,
color: textValueColor,
});
page.drawText('Signers :', {
x: 30,
y: 605,
size: text,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(`${signersCount}`, {
x: 80,
y: 605,
size: text,
font: timesRomanFont,
color: textValueColor,
});
page.drawLine({
start: { x: 30, y: 565 },
end: { x: width - 30, y: 565 },
color: rgb(0.12, 0.12, 0.12),
thickness: 0.5,
});
page.drawText('Recipients', {
x: 30,
y: 575,
size: subtitle,
font: timesRomanFont,
color: titleColor,
});
let yPosition1 = 550;
let yPosition2 = 530;
let yPosition3 = 510;
let yPosition4 = 500;
auditTrail.forEach(x => {
page.drawText('Name :', {
x: 30,
y: yPosition1,
size: text,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(x?.Name, {
x: 75,
y: yPosition1,
size: text,
font: timesRomanFont,
color: textValueColor,
});
page.drawText('Email :', {
x: 30,
y: yPosition2,
size: text,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(x?.Email, {
x: 75,
y: yPosition2,
size: text,
font: timesRomanFont,
color: textValueColor,
});
page.drawText('Accessed from :', {
x: 30,
y: yPosition3,
size: text,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(x?.ipAddress, {
x: 125,
y: yPosition3,
size: text,
font: timesRomanFont,
color: textValueColor,
});
page.drawLine({
start: { x: 30, y: yPosition4 },
end: { x: width - 30, y: yPosition4 },
color: rgb(0.12, 0.12, 0.12),
thickness: 0.5,
});
yPosition1 = yPosition4 - 20;
yPosition2 = yPosition1 - 20;
yPosition3 = yPosition2 - 20;
yPosition4 = yPosition4 - 70;
});
const pdfBytes = await pdfDoc.save();
return pdfBytes;
}
+183 -115
View File
@@ -3,34 +3,35 @@ import fs from 'node:fs';
import axios from 'axios';
import { pdflibAddPlaceholder } from './customSignPdf/pdflibplaceholder.min.js';
import { PDFDocument } from 'pdf-lib';
import { saveFileUsage } from '../../../Utils.js';
import { replaceMailVaribles, saveFileUsage } from '../../../Utils.js';
import GenerateCertificate from './GenerateCertificate.js';
const serverUrl = process.env.SERVER_URL,
APPID = process.env.APP_ID,
masterKEY = process.env.MASTER_KEY;
async function uploadFile(e, a) {
try {
var t = fs.readFileSync(a),
s = new Parse.File(e, [...t]),
s = new Parse.File(e, [...t], 'application/pdf'),
r = (await s.save({ useMasterKey: !0 }), s.url());
return { imageUrl: r };
} catch (e) {
console.log('Err ', e), fs.unlinkSync(a);
}
}
async function updateDoc(t, s, r, o, i, n) {
async function updateDoc(t, s, r, i, o, n) {
try {
var d = {
var l = {
UserPtr: { __type: 'Pointer', className: n, objectId: r },
SignedUrl: s,
Activity: 'Signed',
ipAddress: o,
ipAddress: i,
};
let e;
var l = (e = i.AuditTrail && 0 < i.AuditTrail.length ? [...i.AuditTrail, d] : [d]).filter(
var d = (e = o.AuditTrail && 0 < o.AuditTrail.length ? [...o.AuditTrail, l] : [l]).filter(
e => 'Signed' === e.Activity
);
let a = !1;
!((i.Signers && 0 < i.Signers.length && l.length !== i.Signers.length) || !(a = !0));
!((o.Signers && 0 < o.Signers.length && d.length !== o.Signers.length) || !(a = !0));
var c = { SignedUrl: s, AuditTrail: e, IsCompleted: a };
await axios.put(serverUrl + '/classes/contracts_Document/' + t, c, {
headers: {
@@ -39,30 +40,69 @@ async function updateDoc(t, s, r, o, i, n) {
'X-Parse-Master-Key': masterKEY,
},
});
return { isCompleted: a, message: 'success' };
return { isCompleted: a, message: 'success', AuditTrail: e };
} catch (e) {
return console.log('update doc err ', e), 'err';
}
}
async function sendCompletedMail(e) {
var a = e.url,
t = e.sender,
s = e.pdfName,
r = e.receiver,
e = {
extUserId: e.extUserId,
url: a,
from: 'OpenSign™',
recipient: r,
subject: `Document ${s} has been signed by all parties`,
pdfName: s,
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><img src=https://qikinnovation.ams3.digitaloceanspaces.com/logo.png height='50' style='padding:20px'/> </div><div style='padding:2px;font-family:system-ui; background-color: #47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document sign successfully</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document" +
s +
'. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' +
t.Mail +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>',
};
t = e.doc,
s = e.doc.ExtUserPtr,
r = t.Name,
i = s.Email;
let o = `Document "${r}" has been signed by all parties`,
n =
"<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><img src=https://qikinnovation.ams3.digitaloceanspaces.com/logo.png height='50' style='padding:20px'/> </div><div style='padding:2px;font-family:system-ui; background-color: #47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document signed successfully</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document " +
`<b>"${r}"</b>` +
'. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' +
s.Email +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>';
if (e?.isCustomMail)
try {
var l,
d,
c,
p,
m,
g = new Parse.Query('partners_Tenant');
g.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: s.UserId.objectId });
const u = await g.first();
u &&
((l = JSON.parse(JSON.stringify(u)))?.CompletionSubject && (o = l?.CompletionSubject),
l?.CompletionBody && (n = l?.CompletionBody),
(d = t.ExpiryDate.iso),
(c = new Date(d).toLocaleDateString('en-US', {
day: 'numeric',
month: 'long',
year: 'numeric',
})),
(p = {
document_title: r,
sender_name: s.Name,
sender_mail: s.Email,
sender_phone: s.Phone,
receiver_name: s.Name,
receiver_email: s.Email,
receiver_phone: s.Phone,
expiry_date: c,
company_name: s.Company,
}),
(m = replaceMailVaribles(o, n, p)),
(o = m.subject),
(n = m.body));
} catch (e) {
console.log('error in fetch tenant in signpdf', e.message);
}
e = {
extUserId: s.objectId,
url: a,
from: 'OpenSign™',
recipient: i,
subject: o,
pdfName: r,
html: n,
};
await axios.post(serverUrl + '/functions/sendmailv3', e, {
headers: {
'Content-Type': 'application/json',
@@ -110,11 +150,11 @@ async function sendDoctoWebhook(t, e, a, s) {
}),
a.save(null, { useMasterKey: !0 });
} catch (e) {
console.log('err save in contracts_Webhook', e);
console.log('err save in contracts_Webhook', e.message);
}
})
.catch(e => {
console.log('Err send data to webhook', e);
console.log('Err send data to webhook', e.message);
try {
var a = new Parse.Object('contracts_Webhook');
a.set('Log', e?.status),
@@ -125,16 +165,17 @@ async function sendDoctoWebhook(t, e, a, s) {
}),
a.save(null, { useMasterKey: !0 });
} catch (e) {
console.log('err save in contracts_Webhook', e);
console.log('err save in contracts_Webhook', e.message);
}
}));
}
async function PDF(i) {
async function PDF(o) {
try {
var e = i.params.docId,
a = i.params.userId,
n = await axios.get(
serverUrl + '/classes/contracts_Document/' + e + '?include=ExtUserPtr,Signers',
var n = o.params.docId,
e = o.params.userId,
l = o.params.isCustomCompletionMail,
d = await axios.get(
serverUrl + '/classes/contracts_Document/' + n + '?include=ExtUserPtr,Signers',
{
headers: {
'Content-Type': 'application/json',
@@ -143,138 +184,165 @@ async function PDF(i) {
},
}
),
d = await axios.get(serverUrl + '/users/me', {
c = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken,
'X-Parse-Session-Token': o.headers.sessiontoken,
},
});
if (!d.data || !d.data.objectId) return { status: 'error', message: 'This user not allowed!' };
if (!c.data || !c.data.objectId) return { status: 'error', message: 'This user not allowed!' };
{
var t,
var a,
t,
s,
l,
c = JSON.stringify({ objectId: a });
let r, o;
o = a
? (t = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + c, {
p = JSON.stringify({ objectId: e });
let r, i;
i = e
? (a = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + p, {
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken,
'X-Parse-Session-Token': o.headers.sessiontoken,
},
})).data && 0 < t.data.results.length
? ((r = t), 'contracts_Contactbook')
: ((r = await axios.get(serverUrl + '/classes/contracts_Users?where=' + c, {
})).data && 0 < a.data.results.length
? ((r = a), 'contracts_Contactbook')
: ((r = await axios.get(serverUrl + '/classes/contracts_Users?where=' + p, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
})),
'contracts_Users')
: ((s = JSON.stringify({
UserId: { __type: 'Pointer', className: '_User', objectId: d.data.objectId },
: ((t = JSON.stringify({
UserId: { __type: 'Pointer', className: '_User', objectId: c.data.objectId },
})),
(l = await axios.get(serverUrl + '/classes/contracts_Users?where=' + s, {
(s = await axios.get(serverUrl + '/classes/contracts_Users?where=' + t, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
})).data && 0 < l.data.results.length
? ((r = l), 'contracts_Users')
: ((r = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + s, {
})).data && 0 < s.data.results.length
? ((r = s), 'contracts_Users')
: ((r = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + t, {
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken,
'X-Parse-Session-Token': o.headers.sessiontoken,
},
})),
'contracts_Contactbook'));
var p = r.data.results[0].Name,
m = r.data.results[0].Email;
if (!i.params.pdfFile) return { status: 'error', message: 'pdf file not present!' };
var m = r.data.results[0].Name,
g = r.data.results[0].Email;
if (!o.params.pdfFile) return { status: 'error', message: 'Pdf file not present!' };
{
let e = Buffer.from(i.params.pdfFile, 'base64');
var g = process.env.PFX_BASE64,
u = Buffer.from(g, 'base64'),
h = {
UserPtr: { __type: 'Pointer', className: o, objectId: r.data.results[0].objectId },
let e = Buffer.from(o.params.pdfFile, 'base64');
var u = process.env.PFX_BASE64,
h = Buffer.from(u, 'base64'),
f = {
UserPtr: { __type: 'Pointer', className: i, objectId: r.data.results[0].objectId },
SignedUrl: '',
Activity: 'Signed',
ipAddress: i.headers['x-real-ip'],
ipAddress: o.headers['x-real-ip'],
};
let a;
var f = (a =
n.data.AuditTrail && 0 < n.data.AuditTrail.length
? [...n.data.AuditTrail, h]
: [h]).filter(e => 'Signed' === e.Activity);
var P = (a =
d.data.AuditTrail && 0 < d.data.AuditTrail.length
? [...d.data.AuditTrail, f]
: [f]).filter(e => 'Signed' === e.Activity);
let t = !1;
!(
(n.data.Signers && 0 < n.data.Signers.length && f.length !== n.data.Signers.length) ||
(d.data.Signers && 0 < d.data.Signers.length && P.length !== d.data.Signers.length) ||
!(t = !0)
);
var P,
y,
var y,
b,
v,
U,
b,
I,
S = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
x = './exports/' + S;
w,
D = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
S = './exports/' + D;
let s = e.length;
s = (
t
? ((P = n.data.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
? ((y = d.data.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
(e =
P && 0 < P.length
? ((y = await PDFDocument.load(e)),
y && 0 < y.length
? ((b = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: y,
reason: 'Digitally signed by OpenSign for ' + P?.join(', '),
location: 'location',
pdfDoc: b,
reason: 'Digitally signed by OpenSign for ' + y?.join(', '),
location: 'n/a',
signatureLength: 15e3,
}),
(v = await y.save()),
(v = await b.save()),
Buffer.from(v))
: ((U = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: U,
reason: 'Digitally signed by OpenSign for ' + p + ' <' + m + '>',
location: 'location',
reason: 'Digitally signed by OpenSign for ' + m + ' <' + g + '>',
location: 'n/a',
signatureLength: 15e3,
}),
(b = await U.save()),
Buffer.from(b))),
(I = await new SignPDF(e, u).signPDF()),
fs.writeFileSync(x, I),
I)
: (fs.writeFileSync(x, e), e)
(I = await U.save()),
Buffer.from(I))),
(w = await new SignPDF(e, h).signPDF()),
fs.writeFileSync(S, w),
w)
: (fs.writeFileSync(S, e), e)
).length;
var A,
w,
D = await uploadFile(S, x);
if (D && D.imageUrl)
var _,
A,
x,
E,
j,
k,
F,
C,
T,
N = await uploadFile(D, S);
if (N && N.imageUrl)
return (
(A = await updateDoc(
i.params.docId,
D.imageUrl,
(_ = await updateDoc(
o.params.docId,
N.imageUrl,
r.data.results[0].objectId,
i.headers['x-real-ip'],
n.data,
o
o.headers['x-real-ip'],
d.data,
i
)),
sendDoctoWebhook(n, D.imageUrl, 'signed', r?.data.results?.[0]),
saveFileUsage(s, D.imageUrl, d.data.objectId),
A &&
A.isCompleted &&
((w = {
url: D.imageUrl,
sender: { Mail: n.data.ExtUserPtr.Email, Name: 'OpenSign™' },
pdfName: n.data.Name,
receiver: n.data.ExtUserPtr.Email,
extUserId: n.data.ExtUserPtr.objectId,
sendDoctoWebhook(d, N.imageUrl, 'signed', r?.data.results?.[0]),
saveFileUsage(s, N.imageUrl, c.data.objectId),
_ &&
_.isCompleted &&
((A = { ...d.data, AuditTrail: _.AuditTrail }),
(x = await GenerateCertificate(A)),
(E = await PDFDocument.load(x)),
pdflibAddPlaceholder({
pdfDoc: E,
reason: 'Digitally signed by OpenSign.',
location: 'n/a',
signatureLength: 15e3,
}),
n.data.IsSendMail && !1 === n.data.IsSendMail
(j = await E.save()),
(k = Buffer.from(j)),
(F = await new SignPDF(k, h).signPDF()),
fs.writeFileSync('./exports/certificate.pdf', F),
(T = {
CertificateUrl: (C = await uploadFile(
'certificate.pdf',
'./exports/certificate.pdf'
)).imageUrl,
}),
await axios.put(serverUrl + '/classes/contracts_Document/' + n, T, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
}),
d.data.IsSendMail && !1 === d.data.IsSendMail
? console.log("don't send mail")
: sendCompletedMail(w),
sendDoctoWebhook(n, D.imageUrl, 'completed')),
fs.unlinkSync(x),
console.log('New Signed PDF created called: ' + x),
'success' === A.message
? { status: 'success', data: D.imageUrl }
: { status: 'error', message: 'please provide required parameters!' }
: sendCompletedMail({ url: N.imageUrl, isCustomMail: l, doc: d.data }),
saveFileUsage(k.length, C.imageUrl, c.data.objectId),
sendDoctoWebhook(d, N.imageUrl, 'completed')),
fs.unlinkSync(S),
console.log('New Signed PDF created called: ' + S),
'success' === _.message
? { status: 'success', data: N.imageUrl }
: { status: 'error', message: 'Please provide required parameters!' }
);
}
}
@@ -164,19 +164,10 @@ export default function reportJson(id, userId) {
IsDeclined: { $ne: true },
IsArchive: { $ne: true },
Type: { $ne: 'Folder' },
$and: [
{
$or: [
{ Signers: { $ne: null }, SignedUrl: { $ne: null } },
{ Placeholders: { $ne: null } },
],
},
{
ExpiryDate: {
$lt: { __type: 'Date', iso: new Date().toISOString() },
},
},
],
SignedUrl: { $ne: null },
ExpiryDate: {
$lt: { __type: 'Date', iso: new Date().toISOString() },
},
CreatedBy: {
__type: 'Pointer',
className: '_User',
@@ -0,0 +1,77 @@
import axios from 'axios';
export default async function saveSubscriptio(request) {
const serverUrl = process.env.SERVER_URL;
const appId = process.env.APP_ID;
const subscription = request.params.subscription;
const SubscriptionId = subscription.data.subscription.subscription_id;
const body = subscription;
const Next_billing_date = subscription.data.subscription.next_billing_at;
const planCode = subscription.data.subscription.plan.plan_code;
try {
const userRes = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': appId,
'X-Parse-Session-Token': request.headers['sessiontoken'],
},
});
if (userRes.data && userRes.data.objectId) {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userRes.data.objectId,
});
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const subcriptionCls = new Parse.Query('contracts_Subscriptions');
subcriptionCls.equalTo('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
const subscription = await subcriptionCls.first({ useMasterKey: true });
if (subscription) {
const updateSubscription = new Parse.Object('contracts_Subscriptions');
updateSubscription.id = subscription.id;
updateSubscription.set('SubscriptionId', SubscriptionId);
updateSubscription.set('SubscriptionDetails', body);
updateSubscription.set('Next_billing_date', new Date(Next_billing_date));
updateSubscription.set('PlanCode', planCode);
await updateSubscription.save(null, { useMasterKey: true });
return { status: 'update subscription!' };
} else {
const createSubscription = new Parse.Object('contracts_Subscriptions');
createSubscription.set('SubscriptionId', SubscriptionId);
createSubscription.set('SubscriptionDetails', body);
createSubscription.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUser.id,
});
createSubscription.set('CreatedBy', {
__type: 'Pointer',
className: '_User',
objectId: extUser.get('UserId').id,
});
createSubscription.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: extUser.get('TenantId').id,
});
createSubscription.set('Next_billing_date', new Date(Next_billing_date));
createSubscription.set('PlanCode', planCode);
await createSubscription.save(null, { useMasterKey: true });
return { status: 'create subscription!' };
}
} else {
return { status: 'user not found!' };
}
} else {
return { status: 'user not found!' };
}
} catch (err) {
console.log('Err in save subscription', err);
throw new Error(err.message);
}
}
@@ -58,8 +58,20 @@ async function sendmailv3(req) {
data: process.env.SMTP_ENABLE ? undefined : PdfBuffer,
};
// const html = "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body style='text-align: center;'> <p style='font-weight: bolder; font-size: large;'>Hello!</p> <p>This is a html checking mail</p><p><button 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</button></p></body></html>"
let attachment;
// `certificateBuffer` used to create buffer from pdf file
try {
const certificateBuffer = fs.readFileSync('./exports/certificate.pdf');
const certificate = {
filename: 'certificate.pdf',
content: process.env.SMTP_ENABLE ? certificateBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
data: process.env.SMTP_ENABLE ? undefined : certificateBuffer,
};
attachment = [file, certificate];
} catch (err) {
attachment = [file];
console.log('Err in read certificate sendmailv3', err);
}
const from = req.params.from || '';
const mailsender = process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
@@ -71,8 +83,8 @@ async function sendmailv3(req) {
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
attachments: process.env.SMTP_ENABLE ? [file] : undefined,
attachment: process.env.SMTP_ENABLE ? undefined : file,
attachments: process.env.SMTP_ENABLE ? attachment : undefined,
attachment: process.env.SMTP_ENABLE ? undefined : attachment,
};
if (transporterSMTP) {
const res = await transporterSMTP.sendMail(messageParams);
@@ -81,6 +93,11 @@ async function sendmailv3(req) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
try {
fs.unlinkSync('./exports/certificate.pdf');
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
return {
status: 'success',
};
@@ -92,6 +109,11 @@ async function sendmailv3(req) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
try {
fs.unlinkSync('./exports/certificate.pdf');
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
return {
status: 'success',
};
@@ -1,22 +1,22 @@
import axios from "axios";
import axios from 'axios';
const serverUrl = process.env.SERVER_URL;
const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY;
async function saveUser(userDetails) {
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo("username", userDetails.email);
userQuery.equalTo('username', userDetails.email);
const userRes = await userQuery.first({ useMasterKey: true });
if (userRes) {
const url = `${serverUrl}/loginAs`;
const axiosRes = await axios({
method: "POST",
method: 'POST',
url: url,
headers: {
"Content-Type": "application/json;charset=utf-8",
"X-Parse-Application-Id": APPID,
"X-Parse-Master-Key": masterKEY,
'Content-Type': 'application/json;charset=utf-8',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
params: {
userId: userRes.id,
@@ -27,39 +27,39 @@ async function saveUser(userDetails) {
return { id: login.objectId, sessionToken: login.sessionToken };
} else {
const user = new Parse.User();
user.set("username", userDetails.email);
user.set("password", userDetails.password);
user.set("email", userDetails.email);
user.set("phone", userDetails.phone);
user.set("name", userDetails.name);
user.set('username', userDetails.email);
user.set('password', userDetails.password);
user.set('email', userDetails.email);
user.set('phone', userDetails.phone);
user.set('name', userDetails.name);
const res = await user.signUp();
// console.log("res ", res);
return { id: res.id, sessionToken: res.getSessionToken() };
}
}
export default async function (request) {
export default async function usersignup(request) {
const userDetails = request.params.userDetails;
const planDetails = request.params.planDetails;
const subscription = request.params.subscription;
const user = await saveUser(userDetails);
try {
const extClass = userDetails.role.split("_")[0];
const extClass = userDetails.role.split('_')[0];
const extQuery = new Parse.Query(extClass + "_Users");
extQuery.equalTo("UserId", {
__type: "Pointer",
className: "_User",
const extQuery = new Parse.Query(extClass + '_Users');
extQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: user.id,
});
const extUser = await extQuery.first({ useMasterKey: true });
if (extUser) {
return { message: "User already exist" };
return { message: 'User already exist' };
} else {
const roleurl = `${serverUrl}/functions/AddUserToRole`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": APPID,
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
sessionToken: user.sessionToken, //localStorage.getItem("accesstoken"),
};
let body = {
@@ -69,74 +69,98 @@ export default async function (request) {
};
let role = await axios.post(roleurl, body, { headers: headers });
// console.log("role ", role);
const partnerCls = Parse.Object.extend("partners_Tenant");
const partnerCls = Parse.Object.extend('partners_Tenant');
const partnerQuery = new partnerCls();
partnerQuery.set("UserId", {
__type: "Pointer",
className: "_User",
partnerQuery.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: user.id,
});
partnerQuery.set("ContactNumber", userDetails.phone);
partnerQuery.set("TenantName", userDetails.name);
partnerQuery.set("EmailAddress", userDetails.email);
partnerQuery.set("IsActive", true);
partnerQuery.set("CreatedBy", {
__type: "Pointer",
className: "_User",
partnerQuery.set('ContactNumber', userDetails.phone);
partnerQuery.set('TenantName', userDetails.name);
partnerQuery.set('EmailAddress', userDetails.email);
partnerQuery.set('IsActive', true);
partnerQuery.set('CreatedBy', {
__type: 'Pointer',
className: '_User',
objectId: user.id,
});
if (userDetails && userDetails.pincode) {
partnerQuery.set("PinCode", userDetails.pincode);
partnerQuery.set('PinCode', userDetails.pincode);
}
if (userDetails && userDetails.country) {
partnerQuery.set("Country", userDetails.country);
partnerQuery.set('Country', userDetails.country);
}
if (userDetails && userDetails.state) {
partnerQuery.set("State", userDetails.state);
partnerQuery.set('State', userDetails.state);
}
if (userDetails && userDetails.city) {
partnerQuery.set("City", userDetails.city);
partnerQuery.set('City', userDetails.city);
}
if (userDetails && userDetails.address) {
partnerQuery.set("Address", userDetails.address);
partnerQuery.set('Address', userDetails.address);
}
const tenantRes = await partnerQuery.save(null, { useMasterKey: true });
// console.log("tenantRes ", tenantRes);
const extCls = Parse.Object.extend(extClass + "_Users");
const extCls = Parse.Object.extend(extClass + '_Users');
const newObj = new extCls();
newObj.set("UserId", {
__type: "Pointer",
className: "_User",
newObj.set('UserId', {
__type: 'Pointer',
className: '_User',
objectId: user.id,
});
newObj.set("UserRole", userDetails.role);
newObj.set("Email", userDetails.email);
newObj.set("Name", userDetails.name);
newObj.set("Phone", userDetails.phone);
newObj.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
newObj.set('UserRole', userDetails.role);
newObj.set('Email', userDetails.email);
newObj.set('Name', userDetails.name);
newObj.set('Phone', userDetails.phone);
newObj.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: tenantRes.id,
});
if (userDetails && userDetails.company) {
newObj.set("Company", userDetails.company);
newObj.set('Company', userDetails.company);
}
if (userDetails && userDetails.jobTitle) {
newObj.set("JobTitle", userDetails.jobTitle);
newObj.set('JobTitle', userDetails.jobTitle);
}
if (planDetails && planDetails.customer_id) {
newObj.set("Next_billing_date", new Date(planDetails.nextBillingDate));
newObj.set("Plan", planDetails.plan);
newObj.set("Customer_id", planDetails.customer_id);
newObj.set("Subscription_id", planDetails.subscription_id);
}
const extRes = await newObj.save(null, { useMasterKey: true });
return { message: "User sign up", sessionToken: user.sessionToken };
await saveSubscription(extRes.id, user.id, tenantRes.id, subscription);
return { message: 'User sign up', sessionToken: user.sessionToken };
}
} catch (err) {
console.log("Err ", err);
console.log('Err ', err);
}
}
async function saveSubscription(extUserId, UserId, tenantId, subscription) {
const SubscriptionId = subscription?.data?.subscription?.subscription_id || '';
const Next_billing_date = subscription?.data?.subscription?.next_billing_at || '';
const planCode = subscription?.data?.subscription?.plan?.plan_code || '';
try {
const createSubscription = new Parse.Object('contracts_Subscriptions');
createSubscription.set('SubscriptionId', SubscriptionId);
createSubscription.set('SubscriptionDetails', subscription);
createSubscription.set('ExtUserPtr', {
__type: 'Pointer',
className: 'contracts_Users',
objectId: extUserId,
});
createSubscription.set('CreatedBy', {
__type: 'Pointer',
className: '_User',
objectId: UserId,
});
createSubscription.set('TenantId', {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: tenantId,
});
createSubscription.set('Next_billing_date', new Date(Next_billing_date));
createSubscription.set('PlanCode', planCode);
await createSubscription.save(null, { useMasterKey: true });
} catch (err) {
console.log('err in save subscription pgsignup', err);
}
}
@@ -0,0 +1,33 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
// TODO: set className here
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.addString('RequestBody');
schema.addString('RequestSubject');
// TODO: Set the schema here
// Example:
// schema.addString('name').addNumber('cash');
return schema.update();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
// TODO: set className here
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.deleteField('RequestBody');
schema.deleteField('RequestSubject');
// TODO: Set the schema here
// Example:
// schema.deleteField('name').deleteField('cash');
return schema.update();
};
+3 -1
View File
@@ -13,7 +13,7 @@ import http from 'http';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import { ApiPayloadConverter } from 'parse-server-api-mail-adapter';
import S3Adapter from 'parse-server-s3-adapter';
import S3Adapter from '@parse/s3-files-adapter';
import FSFilesAdapter from 'parse-server-fs-adapter';
import AWS from 'aws-sdk';
import { app as customRoute } from './cloud/customRoute/customApp.js';
@@ -32,6 +32,8 @@ if (process.env.USE_LOCAL !== 'TRUE') {
region: process.env.DO_REGION,
directAccess: true,
preserveFileName: true,
presignedUrl: true,
presignedUrlExpires: 900,
s3overrides: {
accessKeyId: process.env.DO_ACCESS_KEY_ID,
secretAccessKey: process.env.DO_SECRET_ACCESS_KEY,
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

+64
View File
@@ -9,6 +9,7 @@
"version": "1.4.0",
"license": "MIT",
"dependencies": {
"@parse/s3-files-adapter": "^2.2.0",
"aws-sdk": "^2.1581.0",
"axios": "^1.6.8",
"cors": "^2.8.5",
@@ -2560,6 +2561,69 @@
}
}
},
"node_modules/@parse/s3-files-adapter": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@parse/s3-files-adapter/-/s3-files-adapter-2.2.0.tgz",
"integrity": "sha512-y+d7+AvmRUrROe4kJOF8128yUSxieM53vXQsSaG2gvY9VgPScfpfho0pE7VZ5mLumzxJjmwW6oufiGdgcjgcTA==",
"dependencies": {
"aws-sdk": "2.1363.0"
}
},
"node_modules/@parse/s3-files-adapter/node_modules/aws-sdk": {
"version": "2.1363.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1363.0.tgz",
"integrity": "sha512-M2MZZXehgi/EMQv5GlzRkn3TlhoOYHg2cYdSAAqhjv67WaEG50MjaQy5vRvfN1i8XvB24aJFJ5pCrx69TaCaIg==",
"dependencies": {
"buffer": "4.9.2",
"events": "1.1.1",
"ieee754": "1.1.13",
"jmespath": "0.16.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"util": "^0.12.4",
"uuid": "8.0.0",
"xml2js": "0.5.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/@parse/s3-files-adapter/node_modules/buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"dependencies": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"node_modules/@parse/s3-files-adapter/node_modules/ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"node_modules/@parse/s3-files-adapter/node_modules/uuid": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@parse/s3-files-adapter/node_modules/xml2js": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/@pdf-lib/standard-fonts": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
+1
View File
@@ -18,6 +18,7 @@
"watch": "nodemon index.js"
},
"dependencies": {
"@parse/s3-files-adapter": "^2.2.0",
"aws-sdk": "^2.1581.0",
"axios": "^1.6.8",
"cors": "^2.8.5",
Binary file not shown.