Compare commits

...
253 Commits
Author SHA1 Message Date
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
AmolandGitHub 97bd375de3 Merge pull request #550 from OpenSignLabs/patch-1
Patch 1
2024-04-01 20:15:21 +05:30
prafull-opensignlabs e2d450f9aa fix: correct grammatical mistake in report help button 2024-04-01 20:13:56 +05:30
prafull-opensignlabs add01cb02a feat: auto assign free plan in google signup flow 2024-04-01 20:08:02 +05:30
prafull-opensignlabs cea1653805 feat: auto assign free plan to new signup 2024-04-01 19:51:11 +05:30
RaktimaNXG 9d81fce956 fix: input text width 2024-04-01 19:26:04 +05:30
prafull-opensignlabs 79e3762b36 feat: change subscription plans 2024-04-01 18:47:03 +05:30
RaktimaNXG aa04a957ed feat: change webhook & API token UI 2024-04-01 17:48:12 +05:30
prafull-opensignlabs d31910aefa fix: visibility of upgrade now button in header 2024-04-01 17:25:23 +05:30
prafull-opensignlabs 96a4a2e7fa feat: add help button for Role in template 2024-04-01 17:17:58 +05:30
prafull-opensignlabs e8aaab99f9 fix: change message of already subscribed 2024-04-01 16:54:26 +05:30
RaktimaNXG c61be7c35b fix: all text type fields similar to text widget 2024-04-01 15:38:06 +05:30
prafull-opensignlabs 0ee85f53da refactor: handle updgrade now visibililty in header 2024-04-01 14:31:14 +05:30
AmolandGitHub 748e2196da fix: typo in help text 2024-04-01 14:02:46 +05:30
RaktimaNXG 58199a934a fix: change upgrade button redirect ul, disable documentId issue 2024-04-01 13:27:43 +05:30
prafull-opensignlabs 7eec822a65 refactor: correct grammatical mistake in report help message 2024-03-31 14:16:23 +05:30
prafull-opensignlabs 6c21028523 feat: add upgrade now button in header 2024-03-31 14:11:26 +05:30
prafull-opensignlabs 099a3cc46d refactor: update Emailcount in OTP mail 2024-03-31 13:21:04 +05:30
prafull-opensignlabs 5adaffc684 feat: add emailcount of user 2024-03-31 12:42:08 +05:30
prafull-opensignlabs d588caf6d4 fix: not able to subscribed free plan & stop sending individual mail 2024-03-31 09:28:35 +05:30
prafull-opensignlabs 5930fd940d refactor: add loader in for free subscription 2024-03-31 09:08:27 +05:30
prafull-opensignlabs 741f66b019 feat: add documentCount and templateCount for user 2024-03-31 08:28:17 +05:30
AndrewandGitHub 0bf07c2703 Update README.md 2024-03-30 13:26:13 +05:30
RaktimaNXG de26c2d719 fix: handle condition of free validation warning message 2024-03-29 17:51:59 +05:30
prafull-opensignlabs 881ac12b03 feat: savefileusage in server side 2024-03-29 17:26:33 +05:30
RaktimaNXG 1932dda6d6 feat: disbale validation feature, documentId,webhook,API token for free users and add upgrade button to subscribe 2024-03-29 17:06:09 +05:30
prafull-opensignlabs 46159b7214 fix: freeplan navigation 2024-03-29 13:11:13 +05:30
prafull-opensignlabs c57c15a4cb feat: save tenant usage 2024-03-28 18:57:43 +05:30
prafull-opensignlabs 724fa7c27e Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into patch-1 2024-03-28 16:51:42 +05:30
prafull-opensignlabs 2762c3e170 feat: add validation of free plan 2024-03-28 13:57:37 +05:30
prafull-opensignlabsandGitHub 10a0c60056 Merge pull request #540 from OpenSignLabs/opensign_drive_move
fix: document list sorting issue on scroll down in opensign-drive issue, disable recipient's list and field after send request
2024-03-28 13:55:06 +05:30
RaktimaNXG 036c9b8d4a fix: open-sign drive sorting issue by name 2024-03-28 12:46:15 +05:30
RaktimaNXG 1f50368259 fix: open-sign drive sorting issue by date and name 2024-03-28 12:30:28 +05:30
RaktimaNXG 6d5e07dac7 fix: sorting issue in open-sign drive 2024-03-28 11:03:54 +05:30
RaktimaNXG 3656175f11 fix: drive details sorting, signature and intial widgets signature url isuue 2024-03-27 20:31:30 +05:30
RaktimaNXG febb50c3cd fix: expired document message 2024-03-27 18:09:06 +05:30
RaktimaNXG f8ba0d357b fix: mismatch between signature URL in the signature widget and the initial widget on the signature pad. 2024-03-27 17:31:11 +05:30
prafull-opensignlabs 74bfd39abc feat: restrict guest route 2024-03-27 16:07:26 +05:30
RaktimaNXG 5a25b2d874 fix: disable tour message for completed document 2024-03-27 13:54:12 +05:30
prafull-opensignlabs 3115ff3e2a fix: restrict user from logging in when next renewal date is expired even when route is entered directly 2024-03-27 13:11:20 +05:30
RaktimaNXG d1e7b9a2ab fix: document list sorting issue on scroll down in opensign-drive issue, disable recipient's list and field after send request 2024-03-27 12:19:17 +05:30
prafull-opensignlabsandGitHub adbf1fe1e0 Merge pull request #539 from OpenSignLabs/staging
sync branch
2024-03-27 11:35:13 +05:30
AmolandGitHub 60140885e0 Merge pull request #537 from OpenSignLabs/tooltip_pos 2024-03-27 11:12:18 +05:30
prafull-opensignlabsandGitHub fe2f3c5f7c Merge pull request #538 from OpenSignLabs/staging
sync branch
2024-03-27 11:09:58 +05:30
prafull-opensignlabs a06723ff3f refactor: reduce size of tooltip and adjust it's position 2024-03-27 11:05:24 +05:30
AmolandGitHub 12a6a84450 Merge pull request #535 from OpenSignLabs/premium_alert 2024-03-26 20:49:51 +05:30
prafull-opensignlabs 1f350fc459 fix: move document to folder in opensigndrive not working 2024-03-26 20:48:41 +05:30
AmolandGitHub d9fd2a5531 Merge pull request #533 from OpenSignLabs/premium_alert 2024-03-26 20:14:41 +05:30
prafull-opensignlabs cd4fd6e3de refactor: change help message of contact report 2024-03-26 19:12:38 +05:30
prafull-opensignlabs 49cd30de96 refactor: update help message 2024-03-26 19:00:51 +05:30
prafull-opensignlabsandGitHub a24ebe25a8 Merge pull request #534 from OpenSignLabs/widget_copy
feat: implement feature of copy widget to next of that widget for any widgets except `signature,text,stamp,initial` widgets.
2024-03-26 18:36:11 +05:30
RaktimaNXG 3e664bf677 fix: checkbox position issue 2024-03-26 17:29:26 +05:30
prafull-opensignlabs 6161e975fb refactor: add help buttons for all sections 2024-03-26 17:03:08 +05:30
RaktimaNXG c2a45c230b fix: widget copy issue in sign-yourself flow 2024-03-26 16:02:03 +05:30
RaktimaNXG a629aa0bff Merge branch 'widget_copy' of https://github.com/OpenSignLabs/OpenSign into widget_copy 2024-03-26 14:42:54 +05:30
RaktimaNXG 899678d98a feat: implement feature of copy widget to next of that widget for any widgets except signature,text,stamp,initial widgets 2024-03-26 14:31:48 +05:30
prafull-opensignlabsandGitHub 076a0f54c5 Merge pull request #522 from OpenSignLabs/signature_pad
fix: change dashboard icon of signyour-self & request sign, add pen color for text signature
2024-03-26 10:48:22 +05:30
RaktimaNXG a94e67cd7c fix:signature pad pen color issue 2024-03-26 10:12:07 +05:30
RaktimaNXG e78a3991f0 fix: remoove unnecessary code 2024-03-26 09:50:46 +05:30
prafull-opensignlabs 593a8499f4 refactor: add premium features warning 2024-03-25 22:12:58 +05:30
AndrewandGitHub 70d643a5e2 Merge pull request #524 from OpenSignLabs/prafull-opensignlabs-patch-5 2024-03-23 19:35:07 +05:30
prafull-opensignlabsandGitHub f2b1a54695 change mongdb url in .env.local_dev 2024-03-23 19:33:14 +05:30
AndrewandGitHub 4353a4f732 Merge pull request #523 from OpenSignLabs/fix_localdocker 2024-03-23 17:38:39 +05:30
RaktimaNXG 34c99edf1a fix: increase text area height on press enter and fix text widget embeding issue on finished button 2024-03-23 17:08:09 +05:30
prafull-opensignlabs 91f3bdb299 fix: issue of cannot post /login in local docker setup 2024-03-23 17:06:40 +05:30
RaktimaNXG 276fc974fa fix: change dashboard icon of signyour-self & request sign,other minor issue fixed 2024-03-22 21:20:49 +05:30
prafull-opensignlabsandGitHub 081e11f9af Merge pull request #518 from OpenSignLabs/send_In_Order
feat: implement of draggable recipient's list to modify recipient's sequence when enable send-in-order during edit document
2024-03-22 18:08:34 +05:30
RaktimaandGitHub 4f056cc9d3 Update RecipientList.js 2024-03-22 18:06:18 +05:30
RaktimaNXG d9da25e908 fix: increase gap between up and down arrow icon 2024-03-22 16:42:51 +05:30
RaktimaNXG 99b3436c8c feat: add up and down key to change sequence of recipient's list 2024-03-22 16:29:08 +05:30
prafull-opensignlabsandGitHub 452d4d7a1d Merge pull request #519 from OpenSignLabs/signyourself-textinput
fix: change text-input widget to text in signyour-self flow
2024-03-22 12:46:52 +05:30
RaktimaNXG ed613c3424 fix: set default option field value oftextInput widget issue in signyourself 2024-03-22 12:30:53 +05:30
RaktimaNXG bf70fa0b11 fix: drag icon image select issue in ios 2024-03-22 12:01:27 +05:30
RaktimaNXG 8684197d49 fix: drag icon image select issue in ios 2024-03-22 11:55:55 +05:30
RaktimaNXG 648d1224b3 fix: disable default menu open on press on drag icon in mobile view 2024-03-21 18:30:46 +05:30
RaktimaNXG 126b6ad355 fix: change text-input widget to text in signyour-self flow 2024-03-21 18:01:02 +05:30
RaktimaNXG 0512f27e2e change drag icon 2024-03-21 17:41:48 +05:30
prafull-opensignlabsandGitHub 2c61630017 Update signature.css 2024-03-21 17:19:25 +05:30
prafull-opensignlabsandGitHub 72c2b2dde6 change size of custom cursor icon 2024-03-21 17:11:56 +05:30
prafull-opensignlabsandGitHub 29eb78c960 Update signature.css 2024-03-21 17:01:18 +05:30
prafull-opensignlabsandGitHub aa9ba05dde Update move cursor icon 2024-03-21 16:45:28 +05:30
RaktimaNXG 365b074f62 fix: change default value of send in order,dragging functionality to change sequence of signers list in mobile 2024-03-21 16:08:07 +05:30
RaktimaNXG af6cb4c17d feat: implement dragging functionality on recipient's list to modify sequence of send-in-order during edit document 2024-03-21 12:39:00 +05:30
prafull-opensignlabsandGitHub 6bcad91b0f Merge pull request #517 from OpenSignLabs/openSign_drive
feat: updated draft document query
2024-03-20 19:16:46 +05:30
RaktimaNXG 1aa2b003b2 feat: updated draft document query 2024-03-20 19:14:20 +05:30
prafull-opensignlabsandGitHub 1a27e9addf Merge pull request #515 from OpenSignLabs/openSign_drive
fix: data fetching issue in openSign_drive
2024-03-20 18:21:20 +05:30
RaktimaNXG c2891a0858 refactor code 2024-03-20 16:43:56 +05:30
RaktimaNXG c1b226db96 fix: handle condition of fetching opensign details 2024-03-20 15:59:06 +05:30
prafull-opensignlabsandGitHub 8a01efee4c Merge pull request #516 from OpenSignLabs/staging
sync branch
2024-03-20 15:27:47 +05:30
prafull-opensignlabs eafb26d462 feat: restrict route if user subscription is expired 2024-03-20 15:26:38 +05:30
RaktimaNXG 62e1a1bd9b fix: refactor code 2024-03-20 15:02:08 +05:30
RaktimaNXG 58eb71c52d fix: refactor code 2024-03-20 14:50:48 +05:30
RaktimaNXG c1e6f2b5ab add comment 2024-03-20 12:49:07 +05:30
RaktimaNXG ba0c18b591 fix: data fetching issue in openSign_drive 2024-03-20 12:38:34 +05:30
prafull-opensignlabsandGitHub c3a902dcc7 Merge pull request #514 from OpenSignLabs/unuse_package
Refactor: remove warning
2024-03-20 11:40:46 +05:30
prafull-opensignlabs 5ece3c0694 Refactor: remov warning 2024-03-20 11:38:15 +05:30
AmolandGitHub 1053540410 Merge pull request #513 from OpenSignLabs/unuse_package 2024-03-20 11:14:09 +05:30
prafull-opensignlabs 24773759c2 Refactor: Add LazyPage Component for Lazy Loading of Pages & update dependcies 2024-03-20 11:11:37 +05:30
AmolandGitHub 7a6de70169 Merge pull request #510 from OpenSignLabs/unuse_package 2024-03-19 19:52:34 +05:30
prafull-opensignlabs 04b2c7c4da build(deps): update dependencies 2024-03-19 19:51:20 +05:30
AmolandGitHub d11171e91c Merge pull request #508 from OpenSignLabs/migrate_redux 2024-03-19 18:52:44 +05:30
prafull-opensignlabs a4a78cb96c fix: stuck on loader in login screen after guest login 2024-03-19 18:51:04 +05:30
prafull-opensignlabs dfdcde8fcc Refactor: Implement Lazy Loading for OpenSign Drive 2024-03-19 18:03:13 +05:30
prafull-opensignlabs 5e1b84d2e6 refactor: migrate redux to redux-toolkit 2024-03-19 17:29:48 +05:30
126 changed files with 11862 additions and 5062 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ appName=open_sign_server
# A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database. # A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database.
MASTER_KEY=XnAadwKxxByMr MASTER_KEY=XnAadwKxxByMr
# Mongodb URI to connect to # Mongodb URI to connect to
MONGODB_URI=mongodb://localhost:27017/OpenSignDB MONGODB_URI=mongodb://mongo:27017/OpenSignDB
# Path on which APIs should be mounted. Do not change this. This variable shall be removed & value hardcoded in the source code in coming versions. # Path on which APIs should be mounted. Do not change this. This variable shall be removed & value hardcoded in the source code in coming versions.
PARSE_MOUNT=/app PARSE_MOUNT=/app
# Set it to the URL from where APIs will be accessible to the NodeJS functions, for local development it should be localhost:3000/api/app (use your local port number instead) # Set it to the URL from where APIs will be accessible to the NodeJS functions, for local development it should be localhost:3000/api/app (use your local port number instead)
+1 -1
View File
@@ -1,6 +1,6 @@
build: build:
cp .env.local_dev .env cp .env.local_dev .env
cd apps/OpenSign && npm install && npm run build cd apps/OpenSign && cp ../../.env.local_dev .env && npm install && npm run build
docker compose up --build --force-recreate docker compose up --build --force-recreate
run: run:
+11 -11
View File
@@ -42,24 +42,24 @@ Please star ⭐ the repo to support us! 😀
### Introduction ### Introduction
Welcome to OpenSign, the premier open-source docusign alternative - document e-signing solution designed to provide a secure, reliable, and free alternative to commercial platforms like DocuSign, PandaDoc, SignNow, Adobe Sign, Smartwaiver, SignRequest, HelloSign & Zoho sign. Developed under the OpenSignLabs organization, our mission is to democratize the e-signing process, making it accessible and straightforward for everyone. Welcome to OpenSign, the premier open source docusign alternative - document e-signing solution designed to provide a secure, reliable and free alternative to commercial esign platforms like DocuSign, PandaDoc, SignNow, Adobe Sign, Smartwaiver, SignRequest, HelloSign & Zoho sign. Our mission is to democratize the e-signing process, making it accessible and straightforward for everyone.
--- ---
### Features ### Features
- **Secure PDF E-Signing:** With the help of Robust encryption algorithms, OpenSign™ ensures maximum security, privacy & compatibility. - **Secure PDF E-Signing:** With the help of robust encryption algorithms, OpenSign™ ensures maximum security, privacy & compatibility.
- **Annotate Documents:** OpenSign™ allows you to annotate PDF documents with an advanced signing pad that comes with hand drawn signatures support as well as uploaded images & saved signatures for the simplest signing experience. - **Annotate Documents:** OpenSign™ allows you to annotate PDF documents with an advanced signing pad that allows hand drawn signatures, uploaded images, typed signatures & saved signatures for the simplest signing experience ever.
- **User-Friendly Interface:** OpenSign™ was built while keeping Intuitive design in mind for ease of use. Features like "Sign yourself", "One click signatures" and "OpenSign Drive" makes it stand out of the crowd and even makes it better than a lot of so-called industry leaders. - **User-Friendly Interface:** OpenSign™ was built while keeping Intuitive design in mind for ease of use. Features like "Sign yourself", "Templates", "One click signatures" and "OpenSign Drive" makes it stand out of the crowd and even makes it better than a lot of so-called industry leaders.
- **Multi-signer Support:** OpenSign's ability to invite multiple signers for signing along with the ability to invite witnesses & being able to enforce signing in a sequence makes it the only open source solution that is fully loaded and allows it to compete head-to-head with established players. - **Multi-signer Support:** OpenSign's ability to invite multiple signers for signing along with the ability to invite witnesses & being able to enforce signing in a sequence makes it the only open source solution that is fully loaded and allows it to compete head-to-head with established players in e-signature space.
- **Email Unique Code(OTP) verification support for guest signers:** With OpenSign™, your documents are fully secure even when being signed by guest users. Guest signers can only sign the document after entering a unique code sent to their email address.  - **Email Unique Code(OTP) verification support for guest signers:** With OpenSign™, your documents are fully secure even when being signed by guest users. Guest signers can only sign the document after entering a unique code sent to their email address. 
- **"Expiring Docs" & "Rejection":** You can set documents to expire after certain number of days after which nobody will be able to sign it. Not just this, OpenSign also allows signers to reject signing a document. - **"Expiring Docs" & "Rejection":** You can set documents to expire after certain number of days after which nobody will be able to sign. Not just this, OpenSign also allows signers to reject signing a document.
- **Beautiful email templates:** All document signing invitations, completion notifications & reminders are formatted using great looking email templates. - **Beautiful email templates:** All document signing invitations, completion notifications & reminders are formatted using great looking email templates.
- **PDF Template Creation(coming soon):** OpenSign™ allows you to create and store PDF document templates for repeated use thereby saving you a lot of time. - **PDF Template Creation:** OpenSign™ allows you to create and store PDF document templates for repeated use thereby saving you a lot of time & collect e-signatures seamlessly.
- **OpenSign™ Drive:** It is a centralised secure vault for your signed documents that makes storing, signing, organizing, sharing & achieving your docs a breeze. - **OpenSign™ Drive:** It is a centralised secure vault for your digital documents that makes storing, signing, organizing, sharing & achieving your docs a breeze.
- **Audit Trails & completion certificate:** Being a security focused solution, OpenSign™ makes it a top priority to save detailed logs for tracking document activities along with time-stamps, IP addresses, email IDs & phone numbers. A completion certificate is generated as soon as document is completed which contains all the document related logs for added safety. - **Audit Trails & completion certificate:** Being a security focused solution, OpenSign™ makes it a top priority to save detailed logs for tracking document activities along with time-stamps, IP addresses, email IDs & phone numbers. A completion certificate is generated as soon as document is completed which contains all the document related logs for added safety.
- **API Support(coming soon):** OpenSign™ API allows seamless integration into existing systems and software. APIs will soon be available as a cloud hosted solution. - **API Support:** OpenSign™ API allows seamless integration into existing systems and software. You can generate an API key from the app and refer the [docs](https://docs.opensignlabs.com) to start integrating it in your existing applications.
- **Integrations:** Seamless integrations with various Cloud storage systems, CRMs & enterprise platforms is coming soon. - **Integrations:** Seamless integrations with various Cloud storage systems, CRMs & enterprise platforms is available. We also have a Zapier integration that allows you to integrate it with virtually any application.
<img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/b37ff443-7099-4273-9aeb-21c46d7154cf height='200'> <img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/b37ff443-7099-4273-9aeb-21c46d7154cf height='200'>
<img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/86db91b7-6c2f-4885-a33c-58f4fd35ec89 height='200'> <img src=https://github.com/OpenSignLabs/OpenSign/assets/5486116/86db91b7-6c2f-4885-a33c-58f4fd35ec89 height='200'>
@@ -76,7 +76,7 @@ Please refer to the [Installation Guide](INSTALLATION.md) for detailed instructi
### Usage ### Usage
For comprehensive guidelines on how to use OpenSign, please consult our [User Manual](USAGE.md). For comprehensive guidelines on how to use OpenSign, please consult our [User Manual](USAGE.md).
--- ---
+1 -1
View File
@@ -21,5 +21,5 @@ EXPOSE 3000
# ENV NODE_ENV production # ENV NODE_ENV production
# Run the application # Run the application
ENTRYPOINT npm run start-dev ENTRYPOINT npm run start
+804 -712
View File
File diff suppressed because it is too large Load Diff
+19 -19
View File
@@ -3,35 +3,34 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@formkit/auto-animate": "^0.8.1",
"@radix-ui/themes": "^2.0.3", "@radix-ui/themes": "^2.0.3",
"@react-pdf/renderer": "^3.3.8", "@react-pdf/renderer": "^3.4.0",
"@reduxjs/toolkit": "^2.2.1", "@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.0", "axios": "^1.6.8",
"daisyui": "^3.9.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"html-react-parser": "^4.2.2",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"moment": "^2.29.4", "moment": "^2.30.1",
"parse": "^4.3.1", "parse": "^4.3.1",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"print-js": "^1.6.0", "print-js": "^1.6.0",
"radix-ui": "^1.0.1", "radix-ui": "^1.0.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-bootstrap": "^2.10.1", "react-bootstrap": "^2.10.2",
"react-datepicker": "^6.2.0", "react-cookie": "^7.1.4",
"react-datepicker": "^6.4.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^8.0.3", "react-dnd-multi-backend": "^8.0.3",
"react-dnd-touch-backend": "^16.0.1", "react-dnd-touch-backend": "^16.0.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-drag-listview": "^2.0.0",
"react-draggable": "^4.4.6",
"react-gtm-module": "^2.0.11", "react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-konva": "^18.2.10", "react-konva": "^18.2.10",
"react-modal": "^3.16.1", "react-modal": "^3.16.1",
"react-pdf": "^7.7.1", "react-pdf": "^7.7.1",
"react-redux": "^8.1.3", "react-quill": "^2.0.0",
"react-redux": "^9.1.0",
"react-rnd": "^10.4.1", "react-rnd": "^10.4.1",
"react-router-dom": "^6.22.3", "react-router-dom": "^6.22.3",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
@@ -39,9 +38,10 @@
"react-select": "^5.8.0", "react-select": "^5.8.0",
"react-signature-canvas": "^1.0.6", "react-signature-canvas": "^1.0.6",
"react-tooltip": "^5.26.3", "react-tooltip": "^5.26.3",
"react-web-share": "^2.0.2",
"reactour": "^1.19.2", "reactour": "^1.19.2",
"redux": "^4.2.1", "redux": "^5.0.1",
"redux-thunk": "^2.4.2", "redux-thunk": "^3.1.0",
"regex-parser": "^2.3.0", "regex-parser": "^2.3.0",
"serve": "^14.2.1", "serve": "^14.2.1",
"styled-components": "^4.4.1", "styled-components": "^4.4.1",
@@ -83,16 +83,16 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@babel/runtime-corejs2": "^7.23.9", "@babel/runtime-corejs2": "^7.24.1",
"autoprefixer": "^10.4.18", "autoprefixer": "^10.4.18",
"commitizen": "^4.3.0", "commitizen": "^4.3.0",
"eslint": "^8.51.0", "eslint": "^8.57.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.34.1",
"lint-staged": "^15.2.2", "lint-staged": "^15.2.2",
"postcss": "^8.4.35", "postcss": "^8.4.37",
"prettier": "^2.8.0", "prettier": "^2.8.8",
"pretty-quick": "^3.1.3", "pretty-quick": "^3.3.1",
"tailwindcss": "^3.3.3" "tailwindcss": "^3.4.1"
} }
} }
+2 -2
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<!-- <link rel="icon" type="image/png" <!-- <link rel="icon" type="image/png"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAALlJREFUaEPtmN0NwjAMBpNxYDKYiM1Yp90g93CKStH1NbIdfz921Dker2Pc+Js1cDF7MXAxASMGYkAigBI6vh9ZwoXP53uZoAYcvhwdA3mAVbI2aSb+9zFKU4IURB6j/HoPUIEa2G3iGIAhQQDlAUIoE2diabIklISS0NoFPebo77RF6OenEF3QntOm128he0GKrwHyACFoz2MgBqSGkpAEcHs47oHtN5AFakACqMNjQEMoE8SABFCHn4HE2zGHSLeEAAAAAElFTkSuQmCC" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAALlJREFUaEPtmN0NwjAMBpNxYDKYiM1Yp90g93CKStH1NbIdfz921Dker2Pc+Js1cDF7MXAxASMGYkAigBI6vh9ZwoXP53uZoAYcvhwdA3mAVbI2aSb+9zFKU4IURB6j/HoPUIEa2G3iGIAhQQDlAUIoE2diabIklISS0NoFPebo77RF6OenEF3QntOm128he0GKrwHyACFoz2MgBqSGkpAEcHs47oHtN5AFakACqMNjQEMoE8SABFCHn4HE2zGHSLeEAAAAAElFTkSuQmCC"
sizes="40x40" /> --> sizes="40x40" /> -->
@@ -15,7 +15,7 @@
manifest.json provides metadata used when your web app is installed on a 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/ 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. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.
+96 -72
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect, Suspense, lazy } from "react"; import React, { useState, useEffect, lazy } from "react";
import { Routes, Route, BrowserRouter } from "react-router-dom"; import { Routes, Route, BrowserRouter } from "react-router-dom";
import { pdfjs } from "react-pdf"; import { pdfjs } from "react-pdf";
import Login from "./pages/Login"; import Login from "./pages/Login";
@@ -9,12 +9,13 @@ import HomeLayout from "./layout/HomeLayout";
import PageNotFound from "./pages/PageNotFound"; import PageNotFound from "./pages/PageNotFound";
import ValidateRoute from "./primitives/ValidateRoute"; import ValidateRoute from "./primitives/ValidateRoute";
import Validate from "./primitives/Validate"; import Validate from "./primitives/Validate";
import ManageSign from "./pages/Managesign";
import TemplatePlaceholder from "./pages/TemplatePlaceholder"; import TemplatePlaceholder from "./pages/TemplatePlaceholder";
import SignYourSelf from "./pages/SignyourselfPdf"; import SignYourSelf from "./pages/SignyourselfPdf";
import DraftDocument from "./components/pdf/DraftDocument"; import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign"; import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles"; import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage";
import { isEnableSubscription } from "./constant/const";
const DebugPdf = lazy(() => import("./pages/DebugPdf")); const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword")); const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin")); const GuestLogin = lazy(() => import("./pages/GuestLogin"));
@@ -23,9 +24,10 @@ const Subscriptions = lazy(() => import("./pages/PlanSubscriptions"));
const ChangePassword = lazy(() => import("./pages/ChangePassword")); const ChangePassword = lazy(() => import("./pages/ChangePassword"));
const UserProfile = lazy(() => import("./pages/UserProfile")); const UserProfile = lazy(() => import("./pages/UserProfile"));
const Signup = lazy(() => import("./pages/Signup")); const Signup = lazy(() => import("./pages/Signup"));
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
const ManageSign = lazy(() => import("./pages/Managesign"));
const GenerateToken = lazy(() => import("./pages/GenerateToken")); const GenerateToken = lazy(() => import("./pages/GenerateToken"));
const Webhook = lazy(() => import("./pages/Webhook")); const Webhook = lazy(() => import("./pages/Webhook"));
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
const Loader = () => { const Loader = () => {
@@ -78,83 +80,105 @@ function App() {
<Loader /> <Loader />
) : ( ) : (
<BrowserRouter> <BrowserRouter>
<Suspense fallback={<Loader />}> <Routes>
<Routes> <Route element={<ValidateRoute />}>
<Route element={<ValidateRoute />}> <Route exact path="/" element={<Login />} />
<Route exact path="/" element={<Login />} /> <Route path="/signup" element={<LazyPage Page={Signup} />} />
<Route exact path="/signup" element={<Signup />} /> </Route>
</Route> <Route element={<Validate />}>
<Route element={<Validate />}>
<Route
path="/load/template/:templateId"
element={<TemplatePlaceholder />}
/>
<Route
exact
path="/load/placeholdersign/:docId"
element={<PlaceHolderSign />}
/>
<Route
exact
path="/load/recipientSignPdf/:docId/:contactBookId"
element={<PdfRequestFiles />}
/>
</Route>
<Route <Route
path="/loadmf/signmicroapp/login/:id/:userMail/:contactBookId/:serverUrl" path="/load/template/:templateId"
element={<GuestLogin />} element={<TemplatePlaceholder />}
/> />
<Route <Route
path="/login/:id/:userMail/:contactBookId/:serverUrl" exact
element={<GuestLogin />} path="/load/placeholdersign/:docId"
element={<PlaceHolderSign />}
/> />
<Route path="/debugpdf" element={<DebugPdf />} /> <Route
<Route path="/forgetpassword" element={<ForgetPassword />} /> exact
{process.env.REACT_APP_ENABLE_SUBSCRIPTION && ( path="/load/recipientSignPdf/:docId/:contactBookId"
<> element={<PdfRequestFiles />}
<Route exact path="/pgsignup" element={<Pgsignup />} /> />
<Route path="/subscription" element={<Subscriptions />} /> </Route>
</> <Route
)} path="/loadmf/signmicroapp/login/:id/:userMail/:contactBookId/:serverUrl"
<Route element={<HomeLayout />}> element={<LazyPage Page={GuestLogin} />}
<Route path="/changepassword" element={<ChangePassword />} /> />
<Route path="/form/:id" element={<Form />} /> <Route
<Route path="/report/:id" element={<Report />} /> path="/login/:id/:userMail/:contactBookId/:serverUrl"
<Route path="/dashboard/:id" element={<Dashboard />} /> element={<LazyPage Page={GuestLogin} />}
<Route path="/profile" element={<UserProfile />} /> />
<Route path="/generatetoken" element={<GenerateToken />} /> <Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
<Route path="/webhook" element={<Webhook />} /> <Route
<Route path="/managesign" element={<ManageSign />} /> path="/forgetpassword"
<Route path="/opensigndrive" element={<Opensigndrive />} /> element={<LazyPage Page={ForgetPassword} />}
/>
{isEnableSubscription && (
<>
<Route <Route
path="/template/:templateId" path="/pgsignup"
element={<TemplatePlaceholder />} element={<LazyPage Page={Pgsignup} />}
/> />
{/* signyouself route with no rowlevel data using docId from url */}
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
{/* draft document route to handle and navigate route page accordiing to document status */}
<Route path="/draftDocument" element={<DraftDocument />} />
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
<Route <Route
path="/placeHolderSign/:docId" path="/subscription"
element={<PlaceHolderSign />} element={<LazyPage Page={Subscriptions} />}
/> />
{/* for user signature (need your sign route) with row level data */} </>
<Route path="/pdfRequestFiles" element={<PdfRequestFiles />} /> )}
{/* for user signature (need your sign route) with no row level data */} <Route element={<HomeLayout />}>
<Route <Route
path="/pdfRequestFiles/:docId" path="/changepassword"
element={<PdfRequestFiles />} element={<LazyPage Page={ChangePassword} />}
/> />
{/* recipient signature route with no rowlevel data using docId from url */} <Route path="/form/:id" element={<Form />} />
<Route <Route path="/report/:id" element={<Report />} />
path="/recipientSignPdf/:docId/:contactBookId" <Route path="/dashboard/:id" element={<Dashboard />} />
element={<PdfRequestFiles />} <Route
/> path="/profile"
</Route> element={<LazyPage Page={UserProfile} />}
<Route path="*" element={<PageNotFound />} /> />
</Routes> <Route
</Suspense> path="/opensigndrive"
element={<LazyPage Page={Opensigndrive} />}
/>
<Route
path="/managesign"
element={<LazyPage Page={ManageSign} />}
/>
<Route
path="/generatetoken"
element={<LazyPage Page={GenerateToken} />}
/>
<Route path="/webhook" element={<LazyPage Page={Webhook} />} />
<Route
path="/template/:templateId"
element={<TemplatePlaceholder />}
/>
{/* signyouself route with no rowlevel data using docId from url */}
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
{/* draft document route to handle and navigate route page accordiing to document status */}
<Route path="/draftDocument" element={<DraftDocument />} />
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
<Route
path="/placeHolderSign/:docId"
element={<PlaceHolderSign />}
/>
{/* for user signature (need your sign route) with row level data */}
<Route path="/pdfRequestFiles" element={<PdfRequestFiles />} />
{/* for user signature (need your sign route) with no row level data */}
<Route
path="/pdfRequestFiles/:docId"
element={<PdfRequestFiles />}
/>
{/* recipient signature route with no rowlevel data using docId from url */}
<Route
path="/recipientSignPdf/:docId/:contactBookId"
element={<PdfRequestFiles />}
/>
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter> </BrowserRouter>
)} )}
</div> </div>
+2 -2
View File
@@ -56,11 +56,11 @@ const AddSigner = (props) => {
contactQuery.set("Email", email); contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest"); contactQuery.set("UserRole", "contracts_Guest");
if (localStorage.getItem("TenetId")) { if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", { contactQuery.set("TenantId", {
__type: "Pointer", __type: "Pointer",
className: "partners_Tenant", className: "partners_Tenant",
objectId: localStorage.getItem("TenetId") objectId: localStorage.getItem("TenantId")
}); });
} }
+2 -2
View File
@@ -48,11 +48,11 @@ const AddUser = (props) => {
contactQuery.set("Email", email); contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_User"); contactQuery.set("UserRole", "contracts_User");
if (localStorage.getItem("TenetId")) { if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", { contactQuery.set("TenantId", {
__type: "Pointer", __type: "Pointer",
className: "partners_Tenant", className: "partners_Tenant",
objectId: localStorage.getItem("TenetId") objectId: localStorage.getItem("TenantId")
}); });
} }
+54 -7
View File
@@ -4,19 +4,43 @@ import FullScreenButton from "./FullScreenButton";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Parse from "parse"; import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize"; import { useWindowSize } from "../hook/useWindowSize";
import { openInNewTab } from "../constant/Utils"; import { checkIsSubscribed, getAppLogo, openInNewTab } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const";
const Header = ({ showSidebar }) => { const Header = ({ showSidebar }) => {
const navigation = useNavigate(); const navigate = useNavigate();
const { width } = useWindowSize(); const { width } = useWindowSize();
let applogo = localStorage.getItem("appLogo") || "";
let username = localStorage.getItem("username"); let username = localStorage.getItem("username");
const image = localStorage.getItem("profileImg") || dp; const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true);
const [isPro, setIsPro] = useState(false);
const [applogo, setAppLogo] = useState(
localStorage.getItem("appLogo") || " "
);
const toggleDropdown = () => { const toggleDropdown = () => {
setIsOpen(!isOpen); setIsOpen(!isOpen);
}; };
useEffect(() => {
checkSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
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 = () => { const closeDropdown = () => {
setIsOpen(false); setIsOpen(false);
@@ -43,7 +67,7 @@ const Header = ({ showSidebar }) => {
localStorage.setItem("baseUrl", baseUrl); localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid); localStorage.setItem("parseAppId", appid);
navigation("/"); navigate("/");
}; };
//handle to close profile drop down menu onclick screen //handle to close profile drop down menu onclick screen
@@ -83,6 +107,21 @@ const Header = ({ showSidebar }) => {
id="profile-menu" id="profile-menu"
className="flex justify-between items-center gap-x-3" className="flex justify-between items-center gap-x-3"
> >
{!isSubscribe && (
<div>
<button
className="text-xs bg-[#002864] p-2 text-white rounded shadow"
onClick={() => navigate("/subscription")}
>
Upgrade Now
</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> <div>
<FullScreenButton /> <FullScreenButton />
</div> </div>
@@ -123,7 +162,7 @@ const Header = ({ showSidebar }) => {
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal" className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
onClick={() => { onClick={() => {
setIsOpen(false); setIsOpen(false);
navigation("/profile"); navigate("/profile");
}} }}
> >
<i className="fa-regular fa-user"></i> Profile <i className="fa-regular fa-user"></i> Profile
@@ -132,11 +171,19 @@ const Header = ({ showSidebar }) => {
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal" className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
onClick={() => { onClick={() => {
setIsOpen(false); setIsOpen(false);
navigation("/changepassword"); navigate("/changepassword");
}} }}
> >
<i className="fa-solid fa-lock"></i> Change Password <i className="fa-solid fa-lock"></i> Change Password
</li> </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 <li
className="hover:bg-gray-100 rounded-b-lg py-1 px-2 cursor-pointer font-normal" className="hover:bg-gray-100 rounded-b-lg py-1 px-2 cursor-pointer font-normal"
onClick={closeDropdown} onClick={closeDropdown}
@@ -4,6 +4,7 @@ import Parse from "parse";
import getReplacedHashQuery from "../../constant/getReplacedHashQuery"; import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
import "../../styles/loader.css"; import "../../styles/loader.css";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Tooltip from "../../primitives/Tooltip";
const DashboardCard = (props) => { const DashboardCard = (props) => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -148,7 +149,7 @@ const DashboardCard = (props) => {
} }
}) })
.then((res) => { .then((res) => {
const listData = res.data?.result.filter( const listData = res.data?.result?.filter(
(x) => x.Signers.length > 0 (x) => x.Signers.length > 0
); );
let arr = []; let arr = [];
@@ -335,7 +336,7 @@ const DashboardCard = (props) => {
props.Data && props.Data.Redirect_type props.Data && props.Data.Redirect_type
? "cursor-pointer" ? "cursor-pointer"
: "cursor-default" : "cursor-default"
} w-full h-[140px] px-3 pt-4 pb-10 text-white rounded-md shadow overflow-hidden`} } w-full h-[140px] px-3 pt-4 pb-10 text-white rounded-md shadow overflow-hidden relative`}
> >
<div className="flex items-center justify-start gap-5"> <div className="flex items-center justify-start gap-5">
<span className="rounded-full bg-black bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center"> <span className="rounded-full bg-black bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
@@ -352,6 +353,9 @@ const DashboardCard = (props) => {
</div> </div>
</div> </div>
</div> </div>
<div className="text-xs absolute top-2 right-1">
<Tooltip id={props.Label} iconColor={"white"} message={props?.Data?.tourMessage} />
</div>
</div> </div>
); );
}; };
@@ -97,11 +97,13 @@ function DashboardReport(props) {
setIsMoreDocs(false); setIsMoreDocs(false);
} }
setIsNextRecord(false); setIsNextRecord(false);
setList((prevRecord) => if (!res.data.result.error) {
prevRecord.length > 0 setList((prevRecord) =>
? [...prevRecord, ...res.data.result] prevRecord.length > 0
: res.data.result ? [...prevRecord, ...res.data.result]
); : res.data.result
);
}
} }
setIsLoader(false); setIsLoader(false);
} catch (err) { } catch (err) {
@@ -110,6 +112,7 @@ function DashboardReport(props) {
console.log("err ", err); console.log("err ", err);
setIsLoader(false); setIsLoader(false);
} }
setIsLoader(false);
} }
} else { } else {
setIsLoader(false); setIsLoader(false);
@@ -6,16 +6,18 @@ const buttonList = [
{ {
label: "Sign yourself", label: "Sign yourself",
redirectId: "sHAnZphf69", redirectId: "sHAnZphf69",
redirectType: "Form" redirectType: "Form",
icon: "fas fa-pen-nib"
}, },
{ {
label: "Request signature", label: "Request signature",
redirectId: "8mZzFxbG1z", redirectId: "8mZzFxbG1z",
redirectType: "Form" redirectType: "Form",
icon: "fa-solid fa-paper-plane"
} }
]; ];
const GetDashboard = (props) => { const GetDashboard = (props) => {
const Button = ({ label, redirectId, redirectType }) => ( const Button = ({ label, redirectId, redirectType, icon }) => (
<div className={"bg-white rounded-md shadow w-full"}> <div className={"bg-white rounded-md shadow w-full"}>
<Suspense <Suspense
fallback={ fallback={
@@ -33,7 +35,7 @@ const GetDashboard = (props) => {
} }
> >
<DashboardButton <DashboardButton
Icon={"fa-solid fa-plus"} Icon={icon}
Label={label} Label={label}
Data={{ Redirect_type: redirectType, Redirect_id: redirectId }} Data={{ Redirect_type: redirectType, Redirect_id: redirectId }}
/> />
@@ -136,6 +138,7 @@ const GetDashboard = (props) => {
label={btn.label} label={btn.label}
redirectType={btn.redirectType} redirectType={btn.redirectType}
redirectId={btn.redirectId} redirectId={btn.redirectId}
icon={btn.icon}
/> />
))} ))}
</div> </div>
@@ -8,19 +8,11 @@ import { saveAs } from "file-saver";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Table from "react-bootstrap/Table"; import Table from "react-bootstrap/Table";
import * as HoverCard from "@radix-ui/react-hover-card"; import * as HoverCard from "@radix-ui/react-hover-card";
import SelectFolder from "../shared/fields/SelectFolder"; //check this one
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
import { themeColor } from "../../constant/const"; import { themeColor } from "../../constant/const";
import FolderModal from "../shared/fields/FolderModal";
function DriveBody({ function DriveBody(props) {
pdfData,
setFolderName,
setDocId,
setIsLoading,
setPdfData,
isList,
setIsAlert
}) {
const [rename, setRename] = useState(""); const [rename, setRename] = useState("");
const [renameValue, setRenameValue] = useState(""); const [renameValue, setRenameValue] = useState("");
const inputRef = useRef(null); const inputRef = useRef(null);
@@ -50,28 +42,25 @@ function DriveBody({
name: data.Name, name: data.Name,
objectId: data.objectId objectId: data.objectId
}; };
setFolderName((prev) => [...prev, folderData]); props.setFolderName((prev) => [...prev, folderData]);
const loadObj = { props.setIsLoading({
isLoad: true, isLoad: true,
message: "This might take some time" message: "This might take some time"
}; });
props.setDocId(data.objectId);
setIsLoading(loadObj); props.setPdfData([]);
setDocId(data.objectId); props.setSkip(0);
}; };
//function for change doc name and update doc name in _document class //function for change doc name and update doc name in _document class
const handledRenameDoc = async (data) => { const handledRenameDoc = async (data) => {
setRename(""); setRename("");
const trimmedValue = renameValue.trim(); const trimmedValue = renameValue.trim();
if (trimmedValue.length > 0) { if (trimmedValue.length > 0) {
const updateName = { const updateName = {
Name: renameValue Name: renameValue
}; };
const docId = data.objectId; const docId = data.objectId;
const docData = props.pdfData;
const docData = pdfData;
const updatedData = docData.map((item) => { const updatedData = docData.map((item) => {
if (item.objectId === docId) { if (item.objectId === docId) {
// If the item's ID matches the target ID, update the name // If the item's ID matches the target ID, update the name
@@ -80,8 +69,8 @@ function DriveBody({
// If the item's ID doesn't match, keep it unchanged // If the item's ID doesn't match, keep it unchanged
return item; return item;
}); });
props.setPdfData(updatedData);
setPdfData(updatedData); props.sortingData(null, null, updatedData);
await axios await axios
.put( .put(
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem( `${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
@@ -102,7 +91,7 @@ function DriveBody({
}) })
.catch((err) => { .catch((err) => {
console.log("Err ", err); console.log("Err ", err);
setIsAlert({ props.setIsAlert({
isShow: true, isShow: true,
alertMessage: "something went wrong" alertMessage: "something went wrong"
}); });
@@ -199,13 +188,13 @@ function DriveBody({
.then((result) => { .then((result) => {
const res = result.data; const res = result.data;
if (res) { if (res) {
const updatedData = pdfData.filter((x) => x.objectId !== docId); const updatedData = props.pdfData.filter((x) => x.objectId !== docId);
setPdfData(updatedData); props.setPdfData(updatedData);
} }
}) })
.catch((err) => { .catch((err) => {
console.log("Err ", err); console.log("Err ", err);
setIsAlert({ props.setIsAlert({
isShow: true, isShow: true,
alertMessage: "something went wrong" alertMessage: "something went wrong"
}); });
@@ -262,10 +251,10 @@ function DriveBody({
// console.log("Listdata ", Listdata); // console.log("Listdata ", Listdata);
const res = Listdata.data; const res = Listdata.data;
if (res) { if (res) {
const updatedData = pdfData.filter( const updatedData = props.pdfData.filter(
(x) => x.objectId !== updateDocId (x) => x.objectId !== updateDocId
); );
setPdfData(updatedData); props.setPdfData(updatedData);
} }
}) })
.catch((err) => { .catch((err) => {
@@ -292,14 +281,13 @@ function DriveBody({
//component to handle type of document and render according to type //component to handle type of document and render according to type
const handleFolderData = (data, ind, listType) => { const handleFolderData = (data, ind, listType) => {
let createddate, status, isDecline, signerExist, isComplete, isPlaceholder; let createddate, status, isDecline, signerExist, isComplete;
if (data.Type !== "Folder") { if (data.Type !== "Folder") {
const expireDate = data.ExpiryDate && data.ExpiryDate.iso; const expireDate = data.ExpiryDate && data.ExpiryDate.iso;
const createdDate = data.createdAt && data.createdAt; const createdDate = data.createdAt && data.createdAt;
createddate = new Date(createdDate).toLocaleDateString(); createddate = new Date(createdDate).toLocaleDateString();
isComplete = data.IsCompleted && data.IsCompleted ? true : false; isComplete = data.IsCompleted && data.IsCompleted ? true : false;
isDecline = data.IsDeclined && data.IsDeclined; isDecline = data.IsDeclined && data.IsDeclined;
isPlaceholder = data.Placeholders && data.Placeholders;
signerExist = data.Signers && data.Signers; signerExist = data.Signers && data.Signers;
const signedUrl = data.SignedUrl; const signedUrl = data.SignedUrl;
@@ -314,18 +302,7 @@ function DriveBody({
status = "Completed"; status = "Completed";
} else if (isDecline) { } else if (isDecline) {
status = "Declined"; status = "Declined";
} else if (!signerExist || signerExist?.length === 0) { } else if (!signedUrl) {
status = "Draft";
} else if (
signerExist?.length > 0 &&
(!isPlaceholder || isPlaceholder?.length === 0)
) {
status = "Draft";
} else if (
signerExist?.length > 0 &&
isPlaceholder?.length > 0 &&
!signedUrl
) {
status = "Draft"; status = "Draft";
} else if (isExpire) { } else if (isExpire) {
status = "Expired"; status = "Expired";
@@ -408,6 +385,7 @@ function DriveBody({
flexDirection: "column", flexDirection: "column",
cursor: "pointer" cursor: "pointer"
}} }}
data-tut={props.dataTutSeventh}
> >
<img <img
alt="no img" alt="no img"
@@ -475,6 +453,7 @@ function DriveBody({
className="pdf-icon" className="pdf-icon"
src={pdfLogo} src={pdfLogo}
onClick={() => checkPdfStatus(data)} onClick={() => checkPdfStatus(data)}
data-tut={props.dataTutSixth}
/> />
{rename === data.objectId ? ( {rename === data.objectId ? (
<input <input
@@ -584,7 +563,7 @@ function DriveBody({
//component to handle type of document and render according to type //component to handle type of document and render according to type
return ( return (
<> <>
{isList ? ( {props.isList ? (
<div className="container" style={{ overflowX: "auto" }}> <div className="container" style={{ overflowX: "auto" }}>
<Table striped bordered hover> <Table striped bordered hover>
<thead> <thead>
@@ -597,7 +576,7 @@ function DriveBody({
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{pdfData.map((data, ind) => { {props.pdfData.map((data, ind) => {
return ( return (
<React.Fragment key={ind}> <React.Fragment key={ind}>
{handleFolderData(data, ind, "table")} {handleFolderData(data, ind, "table")}
@@ -609,7 +588,7 @@ function DriveBody({
</div> </div>
) : ( ) : (
<div className="pdfContainer"> <div className="pdfContainer">
{pdfData.map((data, ind) => { {props.pdfData.map((data, ind) => {
return ( return (
<div className="box" key={ind}> <div className="box" key={ind}>
{handleFolderData(data, ind, "list")} {handleFolderData(data, ind, "list")}
@@ -618,13 +597,14 @@ function DriveBody({
})} })}
</div> </div>
)} )}
{isOpenMoveModal && ( {isOpenMoveModal && (
<SelectFolder <FolderModal
onSuccess={handleMoveFolder} onSuccess={handleMoveFolder}
isOpenModal={isOpenMoveModal} isOpenModal={isOpenMoveModal}
folderCls={"contracts_Document"} folderCls={"contracts_Document"}
setIsOpenMoveModal={setIsOpenMoveModal} setIsOpenMoveModal={setIsOpenMoveModal}
setPdfData={setPdfData} setPdfData={props.setPdfData}
/> />
)} )}
<ModalUi <ModalUi
@@ -30,7 +30,7 @@ const AddRoleModal = (props) => {
margin: "10px 0 10px 5px" margin: "10px 0 10px 5px"
}} }}
> >
e.g: Hr, Director, Manager, New joinee, Accountant, etc... e.g: Customer, Hr, Director, Manager, Student, etc...
</p> </p>
<div> <div>
<div <div
@@ -1,7 +1,9 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { themeColor } from "../../constant/const"; import { isEnableSubscription, themeColor } from "../../constant/const";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
import { radioButtonWidget } from "../../constant/Utils"; import { radioButtonWidget } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
function DropdownWidgetOption(props) { function DropdownWidgetOption(props) {
const [dropdownOptionList, setDropdownOptionList] = useState([ const [dropdownOptionList, setDropdownOptionList] = useState([
"option-1", "option-1",
@@ -144,21 +146,15 @@ function DropdownWidgetOption(props) {
}; };
return ( return (
//props.showDropdown <ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
<ModalUi <div className="h-full p-[15px]">
// styleClass={"dropdownModal"}
isOpen={props.showDropdown}
title={props.title}
showClose={false}
>
<div style={{ height: "100%", padding: 20 }}>
<form <form
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
handleSaveOption(); handleSaveOption();
}} }}
> >
<div className="dropdownContainer"> <div>
<label style={{ fontSize: "13px", fontWeight: "600" }}> <label style={{ fontSize: "13px", fontWeight: "600" }}>
Name<span style={{ color: "red", fontSize: 13 }}> *</span> Name<span style={{ color: "red", fontSize: 13 }}> *</span>
</label> </label>
@@ -246,36 +242,59 @@ function DropdownWidgetOption(props) {
}} }}
className="fa-solid fa-square-plus" className="fa-solid fa-square-plus"
></i> ></i>
{props.type === "checkbox" && !props.isSignYourself && ( <div>
<> {props.type === "checkbox" && !props.isSignYourself && (
<label style={{ fontSize: "13px", fontWeight: "600" }}> <>
Minimun check <label
</label> style={{
<input fontSize: "13px",
required fontWeight: "600",
defaultValue={0} color: !props.isSubscribe && "gray"
value={minCount} }}
onChange={(e) => { >
const count = handleSetMinMax(e); Minimun check
setMinCount(count); </label>
}} {!props.isSubscribe && isEnableSubscription && <Upgrade />}
className="drodown-input" <input
/> required
<label style={{ fontSize: "13px", fontWeight: "600" }}> defaultValue={0}
Maximum check value={minCount}
</label> onChange={(e) => {
<input const count = handleSetMinMax(e);
required setMinCount(count);
defaultValue={0} }}
value={maxCount} className={
onChange={(e) => { props.isSubscribe || !isEnableSubscription
const count = handleSetMinMax(e); ? "drodown-input"
setMaxCount(count); : "disabled drodown-input"
}} }
className="drodown-input" />
/> <label
</> style={{
)} fontSize: "13px",
fontWeight: "600",
color: !props.isSubscribe && "gray"
}}
>
Maximum check
</label>
<input
required
defaultValue={0}
value={maxCount}
onChange={(e) => {
const count = handleSetMinMax(e);
setMaxCount(count);
}}
className={
props.isSubscribe || !isEnableSubscription
? "drodown-input"
: "disabled drodown-input"
}
/>
</>
)}
</div>
</div> </div>
{["dropdown", radioButtonWidget].includes(props.type) && ( {["dropdown", radioButtonWidget].includes(props.type) && (
<> <>
@@ -383,17 +402,24 @@ function DropdownWidgetOption(props) {
</div> </div>
)} )}
</div> </div>
{props.type === "checkbox" &&
!props.isSignYourself &&
!isEnableSubscription && (
<PremiumAlertHeader
message={
"Field validations are free in beta, this feature will incur a fee later."
}
/>
)}
<div <div
style={{ className={`${
height: "1px", props.type === "checkbox" && !props.isSignYourself
backgroundColor: "#9f9f9f", ? "mb-[15px]"
width: "100%", : "my-[15px]"
marginTop: "15px", } w-full h-[1px] bg-[#9f9f9f]`}
marginBottom: "15px"
}}
></div> ></div>
<button <button
// onClick={() => handleSaveOption()}
disabled={dropdownOptionList.length === 0 && true} disabled={dropdownOptionList.length === 0 && true}
style={{ style={{
background: themeColor, background: themeColor,
@@ -1,5 +1,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import "../../styles/AddUser.css"; import "../../styles/AddUser.css";
import { getFileName } from "../../constant/Utils";
// import SelectFolder from "../../premitives/SelectFolder"; // import SelectFolder from "../../premitives/SelectFolder";
const EditTemplate = ({ template, onSuccess }) => { const EditTemplate = ({ template, onSuccess }) => {
@@ -45,7 +46,7 @@ const EditTemplate = ({ template, onSuccess }) => {
fontWeight: "700" fontWeight: "700"
}} }}
> >
{template.URL?.split("/")?.pop()?.split("_")[1]} {getFileName(template.URL)}
</div> </div>
</div> </div>
<div className="form-section"> <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>
);
}
@@ -15,7 +15,8 @@ function EmailComponent({
setSuccessEmail, setSuccessEmail,
pdfName, pdfName,
sender, sender,
setIsAlert setIsAlert,
extUserId
}) { }) {
const [emailList, setEmailList] = useState([]); const [emailList, setEmailList] = useState([]);
const [emailValue, setEmailValue] = useState(); const [emailValue, setEmailValue] = useState();
@@ -38,6 +39,7 @@ function EmailComponent({
const openSignUrl = "https://www.opensignlabs.com/contact-us"; const openSignUrl = "https://www.opensignlabs.com/contact-us";
const themeBGcolor = themeColor; const themeBGcolor = themeColor;
let params = { let params = {
extUserId: extUserId,
pdfName: pdfName, pdfName: pdfName,
url: pdfUrl, url: pdfUrl,
recipient: emailList[i], recipient: emailList[i],
+161 -317
View File
@@ -1,7 +1,5 @@
import React from "react"; import React from "react";
import PrevNext from "./PrevNext"; import PrevNext from "./PrevNext";
import { PDFDownloadLink } from "@react-pdf/renderer";
import Certificate from "./Certificate";
import printModule from "print-js"; import printModule from "print-js";
import { getBase64FromUrl } from "../../constant/Utils"; import { getBase64FromUrl } from "../../constant/Utils";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
@@ -9,18 +7,17 @@ import "../../styles/signature.css";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { themeColor } from "../../constant/const"; import { themeColor } from "../../constant/const";
import Certificate from "./Certificate";
import { PDFDownloadLink } from "@react-pdf/renderer";
function Header({ function Header({
isPdfRequestFiles, isPdfRequestFiles,
isPlaceholder, isPlaceholder,
recipient,
setIsDecline, setIsDecline,
pageNumber, pageNumber,
isAlreadySign,
allPages, allPages,
changePage, changePage,
pdfUrl, pdfUrl,
documentStatus,
embedWidgetsData, embedWidgetsData,
pdfDetails, pdfDetails,
signerPos, signerPos,
@@ -94,8 +91,21 @@ function Header({
// Replace spaces with underscore // Replace spaces with underscore
return pdfName.replace(/ /g, "_"); 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 //after generate download certifcate pdf
const handleDownload = (pdfBlob, fileName) => { const handleDownload = (pdfBlob, fileName) => {
if (pdfBlob) { if (pdfBlob) {
@@ -122,76 +132,20 @@ function Header({
document={<Certificate pdfData={pdfDetails} />} document={<Certificate pdfData={pdfDetails} />}
> >
{({ blob, loading }) => ( {({ 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 <button
type="button" onClick={() =>
className="defaultBtn certificateBtn" handleDownload(
blob,
`completion certificate-${
pdfDetails[0] && pdfDetails[0].Name
}.pdf`
)
}
disabled={loading} disabled={loading}
style={{ 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]"
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
> >
<i <i className="fa-solid fa-award py-[3px]" aria-hidden="true"></i>
className="fa-solid fa-award" <span className="md:hidden lg:block ml-1">Certificate</span>
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Certificate
</button> </button>
)} )}
</PDFDownloadLink> </PDFDownloadLink>
@@ -210,11 +164,7 @@ function Header({
}} }}
> >
<div className="preBtn2"> <div className="preBtn2">
<div <div onClick={() => navigate(-1)}>
onClick={() => {
navigate(-1);
}}
>
<i <i
className="fa fa-arrow-left" className="fa fa-arrow-left"
aria-hidden="true" aria-hidden="true"
@@ -266,7 +216,6 @@ function Header({
<i className="fa fa-ellipsis-v" aria-hidden="true"></i> <i className="fa fa-ellipsis-v" aria-hidden="true"></i>
</div> </div>
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Portal> <DropdownMenu.Portal>
<DropdownMenu.Content <DropdownMenu.Content
className="DropdownMenuContent" className="DropdownMenuContent"
@@ -290,42 +239,53 @@ function Header({
Download Download
</div> </div>
</DropdownMenu.Item> </DropdownMenu.Item>
{recipient && pdfDetails[0] && pdfDetails.length > 0 ? ( {isCompleted && (
<DropdownMenu.Item className="DropdownMenuItem"> <>
<CertificateDropDown /> {pdfDetails[0] && pdfDetails[0]?.CertificateUrl ? (
</DropdownMenu.Item>
) : isPdfRequestFiles &&
alreadySign &&
isCompleted.isCertificate ? (
<DropdownMenu.Item className="DropdownMenuItem">
<CertificateDropDown />
</DropdownMenu.Item>
) : (
isSignYourself && (
<>
<DropdownMenu.Item className="DropdownMenuItem">
<CertificateDropDown />
</DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
className="DropdownMenuItem" className="DropdownMenuItem"
onClick={() => setIsEmail(true)} onClick={() => handleDownloadCertificate()}
> >
<div <div
style={{ style={{
display: "flex", border: "none",
flexDirection: "row" backgroundColor: "#fff"
}} }}
> >
<i <i
className="fa fa-envelope" className="fa-solid fa-award"
style={{ marginRight: "2px" }} style={{ marginRight: "2px" }}
aria-hidden="true" aria-hidden="true"
></i> ></i>
Mail Certificate
</div> </div>
</DropdownMenu.Item> </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 <DropdownMenu.Item
className="DropdownMenuItem" className="DropdownMenuItem"
@@ -337,7 +297,6 @@ function Header({
flexDirection: "row" flexDirection: "row"
}} }}
> >
{" "}
<i <i
className="fa fa-print" className="fa fa-print"
aria-hidden="true" aria-hidden="true"
@@ -416,217 +375,126 @@ function Header({
</div> </div>
</div> </div>
) : ( ) : (
<div className="signatureHeader headerBtn"> <div className="flex flex-wrap justify-between items-center mt-[5px]">
<PrevNext <PrevNext
pageNumber={pageNumber} pageNumber={pageNumber}
allPages={allPages} allPages={allPages}
changePage={changePage} changePage={changePage}
/> />
{isPlaceholder ? (
{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 ? (
<> <>
{!isMailSend && <div className="flex mx-[100px] lg:mx-0 order-last lg:order-none">
signersdata.length > 0 && {!isMailSend &&
signersdata.length !== filterPrefill.length && ( signersdata.length > 0 &&
<div> signersdata.length !== filterPrefill.length && (
{filterPrefill.length === 0 ? ( <div>
<span style={{ fontSize: "13px", color: "#f5405e" }}> {filterPrefill.length === 0 ? (
Add {signersdata.length - filterPrefill.length}{" "} <span style={{ fontSize: "13px", color: "#f5405e" }}>
recipients signature Add {signersdata.length - filterPrefill.length}{" "}
</span> recipients signature
) : ( </span>
<span style={{ fontSize: "13px", color: "#f5405e" }}> ) : (
Add {signersdata.length - filterPrefill.length} more <span style={{ fontSize: "13px", color: "#f5405e" }}>
recipients signature Add {signersdata.length - filterPrefill.length} more
</span> recipients signature
)} </span>
</div> )}
)} </div>
)}
<div> </div>
<div className="flex">
{setIsEditTemplate && ( {setIsEditTemplate && (
<button <button
onClick={() => setIsEditTemplate(true)} onClick={() => setIsEditTemplate(true)}
style={{ className="outline-none border-none text-center mr-[5px]"
border: "none",
outline: "none",
textAlign: "center"
}}
> >
<i className="fa-solid fa-gear fa-lg"></i> <i className="fa-solid fa-gear fa-lg"></i>
</button> </button>
)} )}
<button <button
onClick={() => { onClick={() => navigate(-1)}
navigate(-1);
}}
type="button" 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 Back
</button> </button>
<button <button
disabled={isMailSend && true} disabled={isMailSend && true}
data-tut="reactourFour" data-tut="reactourFour"
className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[18px] font-[500] text-sm mr-[5px]"
style={{ style={{
background: isMailSend ? "rgb(203, 203, 203)" : "#188ae2", background: isMailSend ? "rgb(203, 203, 203)" : "#188ae2",
color: isMailSend && "rgb(77, 75, 75)" color: isMailSend ? "rgb(77, 75, 75)" : "white"
}} }}
onClick={() => { onClick={() => {
alertSendEmail(); alertSendEmail();
}} }}
className={isMailSend ? "sendMail" : "sendMail sendHover"}
> >
{completeBtnTitle ? completeBtnTitle : "Send"} {completeBtnTitle
? completeBtnTitle
: isMailSend
? "Sent"
: "Send"}
</button> </button>
</div> </div>
</> </>
) : isPdfRequestFiles ? ( ) : isPdfRequestFiles ? (
alreadySign ? ( alreadySign ? (
<div style={{ display: "flex", flexDirection: "row" }}> <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 <button
onClick={handleToPrint} onClick={handleToPrint}
type="button" type="button"
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]"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
> >
<i <i className="fa fa-print py-[3px]" aria-hidden="true"></i>
className="fa fa-print" <span className="hidden lg:block ml-1">Print</span>
style={{
fontSize: "15px",
marginRight: "3px",
color: "white"
}}
aria-hidden="true"
></i>
Print
</button> </button>
<button <button
type="button" type="button"
className="defaultBtn downloadBtn" className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
onClick={() => handleDownloadPdf()} onClick={() => handleDownloadPdf()}
> >
<i <i className="fa fa-download py-[3px]" aria-hidden="true"></i>
className="fa fa-download" <span className="hidden lg:block ml-1">Download</span>
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Download
</button> </button>
</div> </div>
) : ( ) : (
<div> <div className="flex">
<button <button
onClick={() => { onClick={() => navigate(-1)}
navigate(-1);
}}
type="button" 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 Back
</button> </button>
{currentSigner && ( {currentSigner && (
<> <>
<button <button
style={{ style={{ background: "#de4337" }}
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]"
}}
className="finishBtn hoverCss"
onClick={() => { onClick={() => {
handleDeclinePdfAlert(); handleDeclinePdfAlert();
}} }}
@@ -638,7 +506,7 @@ function Header({
background: "#188ae2" background: "#188ae2"
}} }}
type="button" 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={() => { onClick={() => {
embedWidgetsData(); embedWidgetsData();
}} }}
@@ -649,93 +517,69 @@ function Header({
)} )}
</div> </div>
) )
) : pdfUrl || (documentStatus && documentStatus.isCompleted) ? ( ) : isCompleted ? (
<div style={{ display: "flex", flexDirection: "row" }}> <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 <button
onClick={handleToPrint} onClick={handleToPrint}
type="button" type="button"
style={{ className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#188ae2]"
display: "flex",
flexDirection: "row",
alignItems: "center"
}}
className="defaultBtn printBtn"
> >
<i <i className="fa fa-print py-[3px]" aria-hidden="true"></i>
className="fa fa-print" <span className="hidden lg:block ml-1">Print</span>
style={{
fontSize: "15px",
marginRight: "3px",
color: "white"
}}
aria-hidden="true"
></i>
Print
</button> </button>
<button <button
type="button" type="button"
className="defaultBtn downloadBtn" className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#f14343]"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
marginLeft: "10px"
}}
onClick={() => handleDownloadPdf()} onClick={() => handleDownloadPdf()}
> >
<i <i className="fa fa-download py-[3px]" aria-hidden="true"></i>
className="fa fa-download" <span className="hidden lg:block ml-1">Download</span>
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Download
</button> </button>
<button <button
type="button" type="button"
className="defaultBtn mailBtn" className="flex flex-row items-center shadow rounded-[3px] py-[3px] px-[11px] text-white font-[500] text-[13px] mr-[5px] bg-[#3ba7e5]"
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
marginLeft: "10px"
}}
onClick={() => setIsEmail(true)} onClick={() => setIsEmail(true)}
> >
<i <i className="fa fa-envelope py-[3px]" aria-hidden="true"></i>
className="fa fa-envelope" <span className="hidden lg:block ml-1">Mail</span>
style={{
color: "white",
fontSize: "15px",
marginRight: "3px"
}}
aria-hidden="true"
></i>
Mail
</button> </button>
</div> </div>
) : ( ) : (
<div> <div className="flex">
<button <button
onClick={() => { onClick={() => {
navigate(-1); navigate(-1);
}} }}
type="button" 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 Back
</button> </button>
<button <button
style={{ style={{
background: "#188ae2" background: "#188ae2"
}} }}
type="button" 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={() => { onClick={() => {
if (!pdfUrl) { if (!pdfUrl) {
embedWidgetsData(); embedWidgetsData();
@@ -4,6 +4,7 @@ import PlaceholderBorder from "./PlaceholderBorder";
import { Rnd } from "react-rnd"; import { Rnd } from "react-rnd";
import { import {
defaultWidthHeight, defaultWidthHeight,
handleCopyNextToWidget,
isMobile, isMobile,
onChangeInput, onChangeInput,
radioButtonWidget, radioButtonWidget,
@@ -274,6 +275,7 @@ function Placeholder(props) {
} }
}; };
const handleOnClickPlaceholder = () => { const handleOnClickPlaceholder = () => {
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
if (!props.isNeedSign) { if (!props.isNeedSign) {
props.setWidgetType(props.pos.type); props.setWidgetType(props.pos.type);
} }
@@ -302,8 +304,14 @@ function Placeholder(props) {
props.setWidgetType(props.pos.type); props.setWidgetType(props.pos.type);
props.setCurrWidgetsDetails(props.pos); props.setCurrWidgetsDetails(props.pos);
}; };
//function ro set state value of onclick on widget's copy icon //function to set required state value onclick on widget's copy icon
const handleCopyPlaceholder = (e) => { const handleCopyPlaceholder = (e) => {
e.stopPropagation();
//condition to handle text widget signer obj id and unique id
//when user click on copy icon of text widget in that condition text widget does not have any signerObjId
//in that case i have to save in tempSignerId as a unique id of previous select signer's unique id
//and on save or cancel button of copy all page popup i have set this temp signer Id in unique id
if (props.data && props?.pos?.type !== textWidget) { if (props.data && props?.pos?.type !== textWidget) {
props.setSignerObjId(props?.data?.signerObjId); props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id); props.setUniqueId(props?.data?.Id);
@@ -312,9 +320,24 @@ function Placeholder(props) {
props.setSignerObjId(props?.data?.signerObjId); props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id); props.setUniqueId(props?.data?.Id);
} }
e.stopPropagation();
props.setIsPageCopy(true); //checking widget's type and open widget copy modal for required widgets
props.setSignKey(props.pos.key); if (
["signature", textWidget, "stamp", "initial"].includes(props.pos.type)
) {
props.setIsPageCopy(true);
props.setSignKey(props.pos.key);
} else {
//function to create new widget next to just widget
handleCopyNextToWidget(
props.pos,
props.pos.type,
props.xyPostion,
props.index,
props.setXyPostion,
props.data && props.data?.Id
);
}
}; };
//function to save date and format on local array onchange date and onclick format //function to save date and format on local array onchange date and onclick format
@@ -393,7 +416,7 @@ function Placeholder(props) {
{props.pos.type !== textWidget && !props.isSignYourself && ( {props.pos.type !== textWidget && !props.isSignYourself && (
<i <i
data-tut="reactourLinkUser" data-tut="assignSigner"
className="fa-regular fa-user signUserIcon" className="fa-regular fa-user signUserIcon"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@@ -519,7 +542,15 @@ function Placeholder(props) {
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""} data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key} key={props.pos.key}
lockAspectRatio={ 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.Width / props.pos.Height ? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width / : defaultWidthHeight(props.pos.type).width /
@@ -3,6 +3,7 @@ import {
getMonth, getMonth,
getYear, getYear,
isMobile, isMobile,
onChangeHeightOfTextArea,
onChangeInput, onChangeInput,
radioButtonWidget, radioButtonWidget,
range, range,
@@ -37,15 +38,17 @@ function PlaceholderType(props) {
"December" "December"
]; ];
const validateExpression = (regexValidation) => { const validateExpression = (regexValidation) => {
let regexObject = regexValidation; if (textValue) {
if (props.pos?.options.validation.type === "regex") { let regexObject = regexValidation;
regexObject = RegexParser(regexValidation); if (props.pos?.options.validation.type === "regex") {
} regexObject = RegexParser(regexValidation);
// new RegExp(regexValidation); }
let isValidate = regexObject.test(textValue); // new RegExp(regexValidation);
if (!isValidate) { let isValidate = regexObject.test(textValue);
props?.setValidateAlert(true); if (!isValidate) {
inputRef.current.focus(); props?.setValidateAlert(true);
inputRef.current.focus();
}
} }
}; };
@@ -60,7 +63,7 @@ function PlaceholderType(props) {
validateExpression(regexValidation); validateExpression(regexValidation);
break; break;
case "number": case "number":
regexValidation = /^\d+$/; regexValidation = /^[0-9\s]*$/;
validateExpression(regexValidation); validateExpression(regexValidation);
break; break;
case "text": case "text":
@@ -134,7 +137,7 @@ function PlaceholderType(props) {
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos]); }, [props.pos?.options?.defaultValue]);
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => ( const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div <div
@@ -285,11 +288,28 @@ function PlaceholderType(props) {
}; };
props.handleSaveDate(dateObj, isDateChange); props.handleSaveDate(dateObj, isDateChange);
}; };
//handle height on enter press in text area
const handleEnterPress = (e) => {
const height = 18;
if (e.key === "Enter") {
//function to save height of text area
onChangeHeightOfTextArea(
height,
props.pos.type,
props.pos.key,
props.xyPostion,
props.index,
props.setXyPostion,
props.data && props.data?.Id
);
}
};
switch (type) { switch (type) {
case "signature": case "signature":
return props.pos.SignUrl ? ( return props.pos.SignUrl ? (
<img <img
alt="signimg" alt="signimg"
draggable="false"
src={props.pos.SignUrl} src={props.pos.SignUrl}
style={{ style={{
width: "99%", width: "99%",
@@ -311,6 +331,7 @@ function PlaceholderType(props) {
return props.pos.SignUrl ? ( return props.pos.SignUrl ? (
<img <img
alt="signimg" alt="signimg"
draggable="false"
src={props.pos.SignUrl} src={props.pos.SignUrl}
style={{ style={{
width: "99%", width: "99%",
@@ -336,7 +357,7 @@ function PlaceholderType(props) {
<div key={ind} className="flex items-center text-center gap-0.5"> <div key={ind} className="flex items-center text-center gap-0.5">
<input <input
style={{ style={{
width: props.pos.Width, // width: props.pos.Width,
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
marginTop: ind === 0 ? 0 : "5px" marginTop: ind === 0 ? 0 : "5px"
@@ -384,19 +405,12 @@ function PlaceholderType(props) {
case textInputWidget: case textInputWidget:
return props.isSignYourself || return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? ( (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input <textarea
className="inputPlaceholder"
ref={inputRef} ref={inputRef}
placeholder={validatePlaceholder} placeholder={validatePlaceholder}
style={{ fontSize: calculateFontSize() }} rows={1}
onKeyDown={handleEnterPress}
value={textValue} value={textValue}
type="text"
tabIndex="0"
disabled={
props.isNeedSign && props.data?.signerObjId !== props.signerObjId
? true
: props.isPlaceholder
}
onBlur={handleInputBlur} onBlur={handleInputBlur}
onChange={(e) => { onChange={(e) => {
setTextValue(e.target.value); setTextValue(e.target.value);
@@ -410,6 +424,13 @@ function PlaceholderType(props) {
false false
); );
}} }}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/> />
) : ( ) : (
<div <div
@@ -443,7 +464,7 @@ function PlaceholderType(props) {
> >
{/* Default/Title option */} {/* Default/Title option */}
<option value="" disabled hidden> <option value="" disabled hidden>
{props.pos.options.name} {props?.pos?.options?.name}
</option> </option>
{props.pos?.options?.values.map((data, ind) => { {props.pos?.options?.values.map((data, ind) => {
@@ -466,6 +487,7 @@ function PlaceholderType(props) {
return props.pos.SignUrl ? ( return props.pos.SignUrl ? (
<img <img
alt="signimg" alt="signimg"
draggable="false"
src={props.pos.SignUrl} src={props.pos.SignUrl}
style={{ style={{
width: "99%", width: "99%",
@@ -486,15 +508,12 @@ function PlaceholderType(props) {
case "name": case "name":
return props.isSignYourself || return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? ( (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input <textarea
tabIndex="0"
ref={inputRef} ref={inputRef}
placeholder={"name"} placeholder={"name"}
style={{ fontSize: calculateFontSize() }} rows={1}
className="inputPlaceholder" onKeyDown={handleEnterPress}
type="text"
value={textValue} value={textValue}
onBlur={handleInputBlur}
onChange={(e) => { onChange={(e) => {
const isDefault = false; const isDefault = false;
handleTextValid(e); handleTextValid(e);
@@ -508,6 +527,13 @@ function PlaceholderType(props) {
isDefault isDefault
); );
}} }}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/> />
) : ( ) : (
<div <div
@@ -522,14 +548,12 @@ function PlaceholderType(props) {
case "company": case "company":
return props.isSignYourself || return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? ( (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input <textarea
className="inputPlaceholder"
type="text"
ref={inputRef} ref={inputRef}
placeholder={"company"} placeholder="Enter text"
style={{ fontSize: calculateFontSize() }} rows={1}
onKeyDown={handleEnterPress}
value={textValue} value={textValue}
onBlur={handleInputBlur}
onChange={(e) => { onChange={(e) => {
handleTextValid(e); handleTextValid(e);
onChangeInput( onChangeInput(
@@ -542,6 +566,13 @@ function PlaceholderType(props) {
false false
); );
}} }}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/> />
) : ( ) : (
<div <div
@@ -556,14 +587,12 @@ function PlaceholderType(props) {
case "job title": case "job title":
return props.isSignYourself || return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? ( (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input <textarea
className="inputPlaceholder"
type="text"
ref={inputRef} ref={inputRef}
placeholder={"job title"} placeholder={"job title"}
style={{ fontSize: calculateFontSize() }} rows={1}
onKeyDown={handleEnterPress}
value={textValue} value={textValue}
onBlur={handleInputBlur}
onChange={(e) => { onChange={(e) => {
handleTextValid(e); handleTextValid(e);
onChangeInput( onChangeInput(
@@ -576,6 +605,13 @@ function PlaceholderType(props) {
false false
); );
}} }}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/> />
) : ( ) : (
<div <div
@@ -651,6 +687,7 @@ function PlaceholderType(props) {
return props.pos.SignUrl ? ( return props.pos.SignUrl ? (
<img <img
alt="signimg" alt="signimg"
draggable="false"
src={props.pos.SignUrl} src={props.pos.SignUrl}
style={{ style={{
width: "99%", width: "99%",
@@ -671,12 +708,11 @@ function PlaceholderType(props) {
case "email": case "email":
return props.isSignYourself || return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? ( (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input <textarea
className="inputPlaceholder"
type="text"
ref={inputRef} ref={inputRef}
placeholder={"email"} placeholder={"email"}
style={{ fontSize: calculateFontSize() }} rows={1}
onKeyDown={handleEnterPress}
value={textValue} value={textValue}
onBlur={handleInputBlur} onBlur={handleInputBlur}
onChange={(e) => { onChange={(e) => {
@@ -691,6 +727,13 @@ function PlaceholderType(props) {
false false
); );
}} }}
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/> />
) : ( ) : (
<div <div
@@ -741,6 +784,7 @@ function PlaceholderType(props) {
<textarea <textarea
placeholder="Enter label" placeholder="Enter label"
rows={1} rows={1}
onKeyDown={handleEnterPress}
value={textValue} value={textValue}
onBlur={handleInputBlur} onBlur={handleInputBlur}
onChange={(e) => { onChange={(e) => {
@@ -760,7 +804,7 @@ function PlaceholderType(props) {
? "labelTextArea labelWidthMobile" ? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop" : "labelTextArea labelWidthDesktop"
} }
style={{ whiteSpace: "pre-wrap" }} style={{ whiteSpace: "pre-wrap", overflow: "hidden" }}
cols="50" cols="50"
/> />
); );
@@ -769,6 +813,7 @@ function PlaceholderType(props) {
<div style={{ pointerEvents: "none" }}> <div style={{ pointerEvents: "none" }}>
<img <img
alt="signimg" alt="signimg"
draggable="false"
src={props.pos.SignUrl} src={props.pos.SignUrl}
style={{ style={{
width: "99%", width: "99%",
+20 -25
View File
@@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import { themeColor } from "../../constant/const";
function PrevNext({ pageNumber, allPages, changePage }) { function PrevNext({ pageNumber, allPages, changePage }) {
//for go to previous page //for go to previous page
@@ -14,41 +15,35 @@ function PrevNext({ pageNumber, allPages, changePage }) {
<div> <div>
<div className="preBtn1"> <div className="preBtn1">
<button <button
style={{ className="py-[3px] px-[10px] text-xs bg-[#d3edeb] font-[600] mr-[5px]"
padding: "3px 20px ",
fontSize: "10px",
background: "#d3edeb",
fontWeight: "600",
border: "0px",
marginRight: "5px"
}}
disabled={pageNumber <= 1} disabled={pageNumber <= 1}
onClick={previousPage} 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> </button>
<span <span className="text-xs font-[500]">
style={{
fontSize: "13px",
fontWeight: "600"
}}
>
{pageNumber || (allPages ? 1 : "--")} of {allPages || "--"} {pageNumber || (allPages ? 1 : "--")} of {allPages || "--"}
</span> </span>
<button <button
style={{ className="py-[3px] px-[10px] text-xs bg-[#d3edeb] font-[600] ml-[5px]"
padding: "3px 20px ",
fontSize: "10px",
background: "#d3edeb",
fontWeight: "600",
marginLeft: "5px",
border: "0px"
}}
disabled={pageNumber >= allPages} disabled={pageNumber >= allPages}
onClick={nextPage} 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> </button>
</div> </div>
</div> </div>
+133 -45
View File
@@ -1,36 +1,15 @@
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { darkenColor, getFirstLetter } from "../../constant/Utils"; import { useAutoAnimate } from "@formkit/auto-animate/react";
import {
color,
darkenColor,
getFirstLetter,
isMobile,
nameColor
} from "../../constant/Utils";
const RecipientList = (props) => { const RecipientList = (props) => {
const color = [ const [animationParent] = useAutoAnimate();
"#93a3db",
"#e6c3db",
"#c0e3bc",
"#bce3db",
"#b8ccdb",
"#ceb8db",
"#ffccff",
"#99ffcc",
"#cc99ff",
"#ffcc99",
"#66ccff",
"#ffffcc"
];
const nameColor = [
"#304fbf",
"#7d5270",
"#5f825b",
"#578077",
"#576e80",
"#6d527d",
"#cc00cc",
"#006666",
"#cc00ff",
"#ff9900",
"#336699",
"#cc9900"
];
const [isHover, setIsHover] = useState(); const [isHover, setIsHover] = useState();
const [isEdit, setIsEdit] = useState(false); const [isEdit, setIsEdit] = useState(false);
//function for onhover signer name change background color //function for onhover signer name change background color
@@ -63,27 +42,100 @@ const RecipientList = (props) => {
return props.signerPos.some((x) => x.Id === Id); return props.signerPos.some((x) => x.Id === Id);
}; };
//handle drag start
const handleDragStart = (e, id) => {
// `e.dataTransfer.setData('text/plain')`is used to set the data to be transferred during a drag operation.
// The first argument specifies the type of data being set, and the second argument is the actual data you want to transfer.
e.dataTransfer.setData("text/plain", id);
};
//handleDragOver prevents the default behavior of the dragover event, which is necessary for the drop event to be triggered.
const handleDragOver = (e) => {
e.preventDefault();
};
//handle draggable element drop and also used in mobile view on up and down key to chnage sequence of recipient's list
const handleChangeSequence = (e, ind, isUp, isDown) => {
e.preventDefault();
let draggedItemId;
let index = ind;
//if isUp true then set item's id and index in mobile view
if (isUp) {
draggedItemId = index;
index = index - 1;
}
//if isDown true then set item's id and index in mobile view
else if (isDown) {
draggedItemId = index;
index = index + 1;
} //else set id on drag element in desktop viiew
else {
//`e.dataTransfer.getData('text/plain')` is used to get data that you have saved.
draggedItemId = e.dataTransfer.getData("text/plain");
}
//convert string to number
const intDragId = parseInt(draggedItemId);
//get that item to change position
const draggedItem = props.signersdata.filter((_, ind) => ind === intDragId);
const remainingItems = props.signersdata.filter(
(_, ind) => ind !== intDragId
);
//splice method is used to replace or add new value in array at specific index
remainingItems.splice(index, 0, ...draggedItem);
//set current draggable recipient details,objectId,index,contract_className ... after replace recipient list
props.setSignersData(remainingItems);
props.setSignerObjId(remainingItems[index]?.objectId || "");
props.setIsSelectId(index);
props.setContractName(remainingItems[index]?.className || "");
props.setUniqueId(remainingItems[index]?.Id);
props.setRoleName(remainingItems[index]?.Role);
props.setBlockColor(remainingItems[index]?.blockColor);
};
return ( return (
<> <>
{props.signersdata.length > 0 && {props.signersdata.length > 0 &&
props.signersdata.map((obj, ind) => { props.signersdata.map((obj, ind) => {
return ( return (
<div <div
ref={animationParent}
key={ind}
draggable={props.sendInOrder && !isMobile ? true : false}
onDragStart={(e) =>
props.sendInOrder && !isMobile && handleDragStart(e, ind)
}
onDragOver={(e) =>
props.sendInOrder && !isMobile && handleDragOver(e)
}
onDrop={(e) =>
props.sendInOrder && !isMobile && handleChangeSequence(e, ind)
}
data-tut="reactourFirst" data-tut="reactourFirst"
onMouseEnter={() => setIsHover(ind)} onMouseEnter={() => setIsHover(ind)}
onMouseLeave={() => setIsHover(null)} onMouseLeave={() => setIsHover(null)}
key={ind} className={
props.sendInOrder
? props.isMailSend
? "disabled"
: "dragCursor"
: props.isMailSend && "disabled"
}
style={ style={
isHover === ind || props.isSelectListId === ind (!isMobile && isHover === ind) || props.isSelectListId === ind
? onHoverStyle(ind, obj.blockColor) ? onHoverStyle(ind, obj?.blockColor)
: nonHoverStyle(ind) : nonHoverStyle(ind)
} }
onClick={() => { onClick={(e) => {
e.preventDefault();
props.setSignerObjId(obj?.objectId || ""); props.setSignerObjId(obj?.objectId || "");
props.setIsSelectId(ind); props.setIsSelectId(ind);
props.setContractName(obj?.className || ""); props.setContractName(obj?.className || "");
props.setUniqueId(obj.Id); props.setUniqueId(obj.Id);
props.setRoleName(obj.Role); props.setRoleName(obj.Role);
props.setBlockColor(obj?.blockColor);
if (props.handleModal) { if (props.handleModal) {
props.handleModal(); props.handleModal();
} }
@@ -98,10 +150,9 @@ const RecipientList = (props) => {
}} }}
> >
<div <div
className="signerStyle"
style={{ style={{
background: obj.blockColor background: obj?.blockColor
? darkenColor(obj.blockColor, 0.4) ? darkenColor(obj?.blockColor, 0.4)
: nameColor[ind % nameColor.length], : nameColor[ind % nameColor.length],
width: 30, width: 30,
height: 30, height: 30,
@@ -113,6 +164,7 @@ const RecipientList = (props) => {
}} }}
> >
<span <span
className={props.sendInOrder && "dragCursor"}
style={{ style={{
fontSize: "12px", fontSize: "12px",
textAlign: "center", textAlign: "center",
@@ -140,13 +192,12 @@ const RecipientList = (props) => {
}} }}
> >
{obj.Name ? ( {obj.Name ? (
<span className="userName" style={{ cursor: "default" }}> <span className={"userName"}>{obj.Name}</span>
{obj.Name}
</span>
) : ( ) : (
<> <>
<span <span
className="userName" className="userName"
style={{ cursor: "pointer" }}
onClick={() => { onClick={() => {
setIsEdit({ [obj.Id]: true }); setIsEdit({ [obj.Id]: true });
props.setRoleName(obj.Role); props.setRoleName(obj.Role);
@@ -157,7 +208,8 @@ const RecipientList = (props) => {
ref={inputRef} ref={inputRef}
style={{ style={{
backgroundColor: "transparent", backgroundColor: "transparent",
width: "inherit" width: "inherit",
padding: "3px"
}} }}
value={obj.Role} value={obj.Role}
onChange={(e) => props.handleRoleChange(e, obj.Id)} onChange={(e) => props.handleRoleChange(e, obj.Id)}
@@ -173,25 +225,61 @@ const RecipientList = (props) => {
}} }}
/> />
) : ( ) : (
obj.Role <span style={{ padding: "3px" }}>{obj.Role}</span>
)} )}
</span> </span>
</> </>
)} )}
{obj.Name && ( {obj.Name && (
<span className="useEmail" style={{ cursor: "default" }}> <span className={"useEmail"}>
{obj.Role} {obj?.Role || obj?.Email}
</span> </span>
)} )}
</div> </div>
</div> </div>
{isMobile && props.sendInOrder && (
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 5
}}
>
<div
onClick={(e) => {
if (ind !== 0) {
e.stopPropagation();
handleChangeSequence(e, ind, "up");
}
}}
style={{ color: ind === 0 ? "gray" : "black" }}
>
</div>
<div
onClick={(e) => {
if (ind !== props.signersdata.length - 1) {
e.stopPropagation();
handleChangeSequence(e, ind, null, "down");
}
}}
style={{
color:
ind === props.signersdata.length - 1 ? "gray" : "black"
}}
>
</div>
</div>
)}
{props.handleDeleteUser && ( {props.handleDeleteUser && (
<div <div
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
props.handleDeleteUser(obj.Id); props.handleDeleteUser(obj.Id);
}} }}
style={{ cursor: "pointer" }} style={{ cursor: "pointer", marginLeft: "5px" }}
> >
<i className="fa-regular fa-trash-can"></i> <i className="fa-regular fa-trash-can"></i>
</div> </div>
@@ -4,7 +4,6 @@ import { Document, Page } from "react-pdf";
import { themeColor } from "../../constant/const"; import { themeColor } from "../../constant/const";
function RenderAllPdfPage({ function RenderAllPdfPage({
pdfUrl,
signPdfUrl, signPdfUrl,
allPages, allPages,
setAllPages, setAllPages,
@@ -50,8 +49,7 @@ function RenderAllPdfPage({
<Document <Document
loading={"Loading Document.."} loading={"Loading Document.."}
onLoadSuccess={onDocumentLoad} onLoadSuccess={onDocumentLoad}
file={pdfUrl ? pdfUrl : signPdfUrl} file={signPdfUrl}
// file="https://api.printnode.com/static/test/pdf/multipage.pdf"
> >
{Array.from(new Array(allPages), (el, index) => ( {Array.from(new Array(allPages), (el, index) => (
<div <div
@@ -272,6 +272,7 @@ function RenderPdf({
unSignedWidgetId={unSignedWidgetId} unSignedWidgetId={unSignedWidgetId}
setSelectWidgetId={setSelectWidgetId} setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId} selectWidgetId={selectWidgetId}
setCurrWidgetsDetails={setCurrWidgetsDetails}
/> />
</React.Fragment> </React.Fragment>
) )
@@ -295,9 +296,11 @@ function RenderPdf({
{type && ( {type && (
<div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div> <div style={{ fontWeight: "700", fontSize: 11 }}>{type}</div>
)} )}
<div style={{ color: "black", fontSize: 8, fontWeight: "500" }}> {Role && (
{`(${Role})`} <div style={{ color: "black", fontSize: 8, fontWeight: "500" }}>
</div> {`(${Role})`}
</div>
)}
</> </>
); );
} else { } else {
+102 -60
View File
@@ -20,7 +20,9 @@ function SignPad({
isInitial, isInitial,
setIsInitial, setIsInitial,
setIsStamp, setIsStamp,
widgetType widgetType,
currWidgetsDetails,
setCurrWidgetsDetails
}) { }) {
const [penColor, setPenColor] = useState("blue"); const [penColor, setPenColor] = useState("blue");
const allColor = ["blue", "red", "black"]; const allColor = ["blue", "red", "black"];
@@ -31,6 +33,7 @@ function SignPad({
const [signValue, setSignValue] = useState(""); const [signValue, setSignValue] = useState("");
const [textWidth, setTextWidth] = useState(null); const [textWidth, setTextWidth] = useState(null);
const [textHeight, setTextHeight] = useState(null); const [textHeight, setTextHeight] = useState(null);
const [signatureType, setSignatureType] = useState("draw");
const fontOptions = [ const fontOptions = [
{ value: "Fasthand" }, { value: "Fasthand" },
{ value: "Dancing Script" }, { value: "Dancing Script" },
@@ -48,19 +51,11 @@ function SignPad({
`Parse/${localStorage.getItem("parseAppId")}/currentUser` `Parse/${localStorage.getItem("parseAppId")}/currentUser`
); );
const jsonSender = JSON.parse(senderUser); const jsonSender = JSON.parse(senderUser);
const currentUserName = jsonSender && jsonSender.name; const currentUserName = jsonSender && jsonSender.name;
useEffect(() => {
const trimmedName = currentUserName.trim();
const firstCharacter = trimmedName.charAt(0);
const userName = isInitial ? firstCharacter : currentUserName;
setSignValue(userName);
setFontSelect("Fasthand");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function for clear signature image //function for clear signature image
const handleClear = () => { const handleClear = () => {
setCurrWidgetsDetails({});
if (isTab === "draw") { if (isTab === "draw") {
if (canvasRef.current) { if (canvasRef.current) {
canvasRef.current.clear(); canvasRef.current.clear();
@@ -72,11 +67,10 @@ function SignPad({
} else if (isTab === "uploadImage") { } else if (isTab === "uploadImage") {
setImage(""); setImage("");
} }
setIsInitial(false); // setIsInitial(false);
}; };
//function for set signature url //function for set signature url
const handleSignatureChange = () => { const handleSignatureChange = () => {
// canvasRef.current.backgroundColor = 'rgb(165, 26, 26)'
setSignature(canvasRef.current.toDataURL()); setSignature(canvasRef.current.toDataURL());
setIsSignImg(canvasRef.current.toDataURL()); setIsSignImg(canvasRef.current.toDataURL());
}; };
@@ -101,28 +95,28 @@ function SignPad({
<button <button
onClick={() => { onClick={() => {
setCurrWidgetsDetails({});
if (!image) { if (!image) {
if (isTab === "mysignature") { if (isTab === "mysignature") {
setIsSignImg(""); setIsSignImg("");
if (isInitial) { if (isInitial) {
onSaveSign("initials"); onSaveSign(signatureType, "initials");
} else { } else {
onSaveSign("default"); onSaveSign(null, "default");
} }
} else { } else {
if (isTab === "type") { if (isTab === "type") {
setIsSignImg(""); setIsSignImg("");
onSaveSign(false, textWidth, textHeight); onSaveSign(null, false, textWidth, textHeight);
} else { } else {
setIsSignImg(""); onSaveSign(signatureType);
onSaveSign();
} }
} }
setPenColor("blue"); setPenColor("blue");
} else { } else {
setIsSignImg(""); setIsSignImg("");
onSaveImage(); onSaveImage(signatureType);
} }
setIsSignPad(false); setIsSignPad(false);
setIsInitial(false); setIsInitial(false);
@@ -152,7 +146,35 @@ function SignPad({
</div> </div>
); );
}; };
//useEffect for set already draw or save signature url/text url of signature text type and draw type for initial type and signature type widgets
useEffect(() => {
if (currWidgetsDetails && canvasRef.current) {
const isWidgetType = currWidgetsDetails?.type;
const signatureType = currWidgetsDetails?.signatureType;
const url = currWidgetsDetails?.SignUrl;
//checking widget type and draw type signature url
if (isInitial) {
if (isWidgetType === "initials" && signatureType === "draw" && url) {
canvasRef.current.fromDataURL(url);
}
} else if (
isWidgetType === "signature" &&
signatureType === "draw" &&
url
) {
canvasRef.current.fromDataURL(url);
}
}
const trimmedName = currentUserName.trim();
const firstCharacter = trimmedName.charAt(0);
const userName = isInitial ? firstCharacter : currentUserName;
setSignValue(userName);
setFontSelect("Fasthand");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSignPad]);
useEffect(() => { useEffect(() => {
const loadFont = async () => { const loadFont = async () => {
try { try {
@@ -186,7 +208,7 @@ function SignPad({
}, [isTab]); }, [isTab]);
//function for convert input text value in image //function for convert input text value in image
const convertToImg = async (fontStyle, text) => { const convertToImg = async (fontStyle, text, color) => {
//get text content to convert in image //get text content to convert in image
const textContent = text; const textContent = text;
const fontfamily = fontStyle const fontfamily = fontStyle
@@ -199,6 +221,7 @@ function SignPad({
const span = document.createElement("span"); const span = document.createElement("span");
span.textContent = textContent; span.textContent = textContent;
span.style.font = `20px ${fontfamily}`; // here put your text size and font family span.style.font = `20px ${fontfamily}`; // here put your text size and font family
span.style.color = color ? color : penColor;
span.style.display = "hidden"; span.style.display = "hidden";
document.body.appendChild(span); // Replace 'container' with the ID of the container element document.body.appendChild(span); // Replace 'container' with the ID of the container element
@@ -216,13 +239,9 @@ function SignPad({
canvasElement.width = width * pixelRatio; canvasElement.width = width * pixelRatio;
canvasElement.height = height * pixelRatio; canvasElement.height = height * pixelRatio;
// Render the content of the span onto the canvas
// ctx.fillStyle = "white"; // Set the background color of the canvas
// ctx.fillRect(0, 0, width*pixelRatio, height*pixelRatio);
// You can customize text styles if needed // You can customize text styles if needed
ctx.font = font; ctx.font = font;
ctx.fillStyle = "black"; // Set the text color ctx.fillStyle = color ? color : penColor; // Set the text color
ctx.textAlign = "center"; ctx.textAlign = "center";
ctx.textBaseline = "middle"; ctx.textBaseline = "middle";
ctx.scale(pixelRatio, pixelRatio); ctx.scale(pixelRatio, pixelRatio);
@@ -235,6 +254,45 @@ function SignPad({
setSignature(dataUrl); setSignature(dataUrl);
}; };
const PenColorComponent = (props) => {
return (
<div style={{ display: "flex", flexDirection: "row" }}>
{allColor.map((data, key) => {
return (
<i
style={{
margin: "5px",
color: data,
borderBottom:
key === 0 && penColor === "blue"
? "2px solid blue"
: key === 1 && penColor === "red"
? "2px solid red"
: key === 2 && penColor === "black"
? "2px solid black"
: "2px solid white"
}}
onClick={() => {
props?.convertToImg &&
props?.convertToImg(fontSelect, signValue, data);
if (key === 0) {
setPenColor("blue");
} else if (key === 1) {
setPenColor("red");
} else if (key === 2) {
setPenColor("black");
}
}}
key={key}
className="fa solid fa-pen-nib"
width={20}
height={20}
></i>
);
})}
</div>
);
};
return ( return (
<div> <div>
{isSignPad && ( {isSignPad && (
@@ -303,6 +361,7 @@ function SignPad({
setIsDefaultSign(false); setIsDefaultSign(false);
setIsImageSelect(true); setIsImageSelect(true);
setIsTab("uploadImage"); setIsTab("uploadImage");
setSignatureType("");
}} }}
style={{ style={{
color: color:
@@ -327,6 +386,7 @@ function SignPad({
setIsDefaultSign(false); setIsDefaultSign(false);
setIsImageSelect(false); setIsImageSelect(false);
setIsTab("type"); setIsTab("type");
setSignatureType("");
setImage(); setImage();
}} }}
style={{ style={{
@@ -354,6 +414,7 @@ function SignPad({
setIsDefaultSign(true); setIsDefaultSign(true);
setIsImageSelect(true); setIsImageSelect(true);
setIsTab("mysignature"); setIsTab("mysignature");
setSignatureType("");
setImage(); setImage();
}} }}
style={{ style={{
@@ -384,6 +445,7 @@ function SignPad({
setIsDefaultSign(true); setIsDefaultSign(true);
setIsImageSelect(true); setIsImageSelect(true);
setIsTab("mysignature"); setIsTab("mysignature");
setSignatureType("");
setImage(); setImage();
}} }}
style={{ style={{
@@ -423,6 +485,7 @@ function SignPad({
setIsDefaultSign(false); setIsDefaultSign(false);
setImage(); setImage();
setIsTab("draw"); setIsTab("draw");
setSignatureType("draw");
setSignValue(""); setSignValue("");
setIsStamp(false); setIsStamp(false);
}} }}
@@ -457,6 +520,7 @@ function SignPad({
background: "rgb(255, 255, 255)", background: "rgb(255, 255, 255)",
objectFit: "contain" objectFit: "contain"
}} }}
draggable="false"
src={isInitial ? myInitial : defaultSign} src={isInitial ? myInitial : defaultSign}
/> />
</div> </div>
@@ -517,6 +581,7 @@ function SignPad({
alt="print img" alt="print img"
ref={imageRef} ref={imageRef}
src={image.src} src={image.src}
draggable="false"
style={{ style={{
objectFit: "contain", objectFit: "contain",
height: "100%", height: "100%",
@@ -547,7 +612,7 @@ function SignPad({
<input <input
maxLength={isInitial ? 3 : 30} maxLength={isInitial ? 3 : 30}
style={{ fontFamily: fontSelect }} style={{ fontFamily: fontSelect, color: penColor }}
type="text" type="text"
className="signatureInput" className="signatureInput"
placeholder="Your signature" placeholder="Your signature"
@@ -578,7 +643,8 @@ function SignPad({
<div <div
style={{ style={{
padding: "5px 10px 5px 10px", padding: "5px 10px 5px 10px",
fontSize: "20px" fontSize: "20px",
color: penColor
}} }}
> >
{signValue ? signValue : "Your signature"} {signValue ? signValue : "Your signature"}
@@ -588,7 +654,15 @@ function SignPad({
})} })}
</div> </div>
<div style={{ display: "flex", justifyContent: "flex-end" }}> <div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
marginTop: "10px"
}}
>
<PenColorComponent convertToImg={convertToImg} />
<SaveBtn /> <SaveBtn />
</div> </div>
</div> </div>
@@ -622,39 +696,7 @@ function SignPad({
marginTop: "10px" marginTop: "10px"
}} }}
> >
<div style={{ display: "flex", flexDirection: "row" }}> <PenColorComponent />
{allColor.map((data, key) => {
return (
<i
style={{
margin: "5px",
color: data,
borderBottom:
key === 0 && penColor === "blue"
? "2px solid blue"
: key === 1 && penColor === "red"
? "2px solid red"
: key === 2 && penColor === "black"
? "2px solid black"
: "2px solid white"
}}
onClick={() => {
if (key === 0) {
setPenColor("blue");
} else if (key === 1) {
setPenColor("red");
} else if (key === 2) {
setPenColor("black");
}
}}
key={key}
className="fa solid fa-pen-nib"
width={20}
height={20}
></i>
);
})}
</div>
<SaveBtn /> <SaveBtn />
</div> </div>
</> </>
@@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { themeColor } from "../../constant/const"; import { themeColor } from "../../constant/const";
import RecipientList from "./RecipientList"; import RecipientList from "./RecipientList";
import { Tooltip } from "react-tooltip";
function SignerListPlace(props) { function SignerListPlace(props) {
return ( return (
@@ -13,20 +14,89 @@ function SignerListPlace(props) {
> >
<span className="signedStyle"> <span className="signedStyle">
{props.title ? props.title : "Recipients"} {props.title ? props.title : "Recipients"}
<span className="absolute text-xs z-[30] mt-1 ml-0.5">
{props?.title === "Roles" && (
<>
<a data-tooltip-id="my-tooltip">
<sup>
<i
className="fa-solid fa-question rounded-full"
style={{
borderColor: "white",
color: "white",
fontSize: 11,
borderWidth: 1.5,
padding: "1px 3px"
}}
></i>
</sup>
</a>
<Tooltip id="my-tooltip">
<div className="max-w-[450px]">
<p className="font-bold">What are template roles?</p>
<p>
Begin by specifying each role needed for the completion of
the document. Think about the parties involved in the
signing process and what their responsibilities are.
Common roles include HR for internal documents, Customer
for agreements or Vendor for business agreements.{" "}
</p>
<p className="font-bold">
Why pre-attach users to some roles?
</p>
<p>
For roles that consistently involve the same individual
(e.g., the CEO&apos;s signature on employee offer
letters), you can pre-attach a user to a role within the
template. This step is optional but recommended for
efficiency and consistency across documents.
</p>
<p className="font-bold">
When do i specify the user attached to each role?
</p>
<p>
When you create a document from your template, you&apos;ll
be prompted to attach users to each defined role. If a
role already has a user attached, this will be pre-filled,
but you can modify it as needed before sending out the
document.
</p>
</div>
</Tooltip>
</>
)}
</span>
</span> </span>
</div> </div>
<div className="signerList"> <div className="signerList">
<RecipientList {...props} /> <RecipientList {...props} />
</div> </div>
{props.handleAddSigner && ( {props.handleAddSigner ? (
<div <div
data-tut="reactourAddbtn" 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" 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.handleAddSigner()} onClick={() => props.handleAddSigner()}
style={{
opacity: props.isMailSend && "0.5",
pointerEvents: props.isMailSend && "none"
}}
> >
<i className="fa-solid fa-plus"></i> <i className="fa-solid fa-plus"></i>
<span style={{ marginLeft: 2 }}>Add role</span> <span style={{ marginLeft: 2 }}>Add role</span>
</div> </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> </div>
); );
@@ -34,8 +34,12 @@ function WidgetComponent({
handleRoleChange, handleRoleChange,
handleOnBlur, handleOnBlur,
title, title,
setSignersData,
isTemplateFlow sendInOrder,
isTemplateFlow,
setBlockColor,
blockColor,
setIsAddSigner
}) { }) {
const [isSignersModal, setIsSignersModal] = useState(false); const [isSignersModal, setIsSignersModal] = useState(false);
@@ -233,14 +237,14 @@ function WidgetComponent({
(data) => (data) =>
data.type !== "dropdown" && data.type !== "dropdown" &&
data.type !== radioButtonWidget && data.type !== radioButtonWidget &&
data.type !== textWidget data.type !== textInputWidget
); );
const textWidgetData = widget.filter((data) => data.type !== textWidget); const textWidgetData = widget.filter((data) => data.type !== textWidget);
const updateWidgets = isSignYourself const updateWidgets = isSignYourself
? filterWidgets ? filterWidgets
: isTemplateFlow : isTemplateFlow
? textWidgetData ? textWidgetData
: widget; : widget;
return ( return (
<> <>
@@ -256,9 +260,11 @@ function WidgetComponent({
<div <div
data-tut={dataTut} data-tut={dataTut}
style={{ style={{
background: isSelectListId background: blockColor
? color[isSelectListId % color.length] ? blockColor
: color[0], : isSelectListId
? color[isSelectListId % color.length]
: color[0],
padding: "10px 20px", padding: "10px 20px",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
@@ -314,7 +320,7 @@ function WidgetComponent({
</span> </span>
</div> </div>
)} )}
{handleAddSigner && ( {handleAddSigner ? (
<div <div
data-tut="reactourAddbtn" data-tut="reactourAddbtn"
style={{ style={{
@@ -328,6 +334,22 @@ function WidgetComponent({
<i className="fa-solid fa-plus"></i> <i className="fa-solid fa-plus"></i>
<span style={{ marginLeft: 2 }}>Add role</span> <span style={{ marginLeft: 2 }}>Add role</span>
</div> </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 <div
@@ -359,7 +381,12 @@ function WidgetComponent({
</div> </div>
) )
) : ( ) : (
<div data-tut={dataTut} className="signerComponent"> <div
data-tut={dataTut}
className={
isMailSend ? "disabled signerComponent " : "signerComponent"
}
>
<div <div
style={{ style={{
background: themeColor, background: themeColor,
@@ -381,25 +408,30 @@ function WidgetComponent({
)} )}
{isSignersModal && ( {isSignersModal && (
<ModalUi <ModalUi
title={"Roles"} title={title ? title : "Recipients"}
isOpen={isSignersModal} isOpen={isSignersModal}
handleClose={handleModal} handleClose={handleModal}
> >
{signersdata.length > 0 ? ( {signersdata.length > 0 ? (
<RecipientList <div style={{ maxHeight: "600px", overflow: "auto" }}>
signerPos={signerPos} <RecipientList
signersdata={signersdata} signerPos={signerPos}
isSelectListId={isSelectListId} signersdata={signersdata}
setSignerObjId={setSignerObjId} isSelectListId={isSelectListId}
setIsSelectId={setIsSelectId} setSignerObjId={setSignerObjId}
setContractName={setContractName} setIsSelectId={setIsSelectId}
setUniqueId={setUniqueId} setContractName={setContractName}
setRoleName={setRoleName} setUniqueId={setUniqueId}
handleDeleteUser={handleDeleteUser} setRoleName={setRoleName}
handleRoleChange={handleRoleChange} handleDeleteUser={handleDeleteUser}
handleOnBlur={handleOnBlur} handleRoleChange={handleRoleChange}
handleModal={handleModal} handleOnBlur={handleOnBlur}
/> handleModal={handleModal}
sendInOrder={sendInOrder}
setSignersData={setSignersData}
setBlockColor={setBlockColor}
/>
</div>
) : ( ) : (
<div <div
style={{ style={{
@@ -409,7 +441,7 @@ function WidgetComponent({
textAlign: "center" textAlign: "center"
}} }}
> >
Please add a role Please add a {title ? title : "recipients"}
</div> </div>
)} )}
</ModalUi> </ModalUi>
@@ -3,6 +3,9 @@ import ModalUi from "../../primitives/ModalUi";
import "../../styles/AddUser.css"; import "../../styles/AddUser.css";
import RegexParser from "regex-parser"; import RegexParser from "regex-parser";
import { textInputWidget } from "../../constant/Utils"; import { textInputWidget } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
const WidgetNameModal = (props) => { const WidgetNameModal = (props) => {
const [formdata, setFormdata] = useState({ const [formdata, setFormdata] = useState({
@@ -14,7 +17,7 @@ const WidgetNameModal = (props) => {
}); });
const [isValid, setIsValid] = useState(true); const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"]; const statusArr = ["Required", "Optional"];
const inputOpt = ["email", "number"]; const inputOpt = ["text", "email", "number"];
useEffect(() => { useEffect(() => {
if (props.defaultdata) { if (props.defaultdata) {
@@ -70,7 +73,7 @@ const WidgetNameModal = (props) => {
return "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/"; return "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/";
case "number": case "number":
return "/^\\d+$/"; return "/^\\d+$/";
case textInputWidget: case "text":
return "/^[a-zA-Zs]+$/"; return "/^[a-zA-Zs]+$/";
default: default:
return type; return type;
@@ -98,7 +101,24 @@ const WidgetNameModal = (props) => {
handleClose={props.handleClose && props.handleClose} handleClose={props.handleClose && props.handleClose}
title={"Widget info"} title={"Widget info"}
> >
<form onSubmit={handleSubmit} style={{ padding: 20 }}> {(props.defaultdata?.type === textInputWidget ||
props.widgetName === textInputWidget) &&
!isEnableSubscription && (
<PremiumAlertHeader
message={
"Field validations are free in beta, this feature will incur a fee later."
}
/>
)}
<form
onSubmit={handleSubmit}
className={`${
props.defaultdata?.type === textInputWidget ||
props.widgetName === textInputWidget
? "pt-0"
: ""
} p-[20px]`}
>
<div className="form-section"> <div className="form-section">
<label htmlFor="name" style={{ fontSize: 13 }}> <label htmlFor="name" style={{ fontSize: 13 }}>
Name Name
@@ -116,10 +136,20 @@ const WidgetNameModal = (props) => {
props.widgetName === textInputWidget) && ( props.widgetName === textInputWidget) && (
<> <>
<div className="form-section"> <div className="form-section">
<label htmlFor="textvalidate" style={{ fontSize: 13 }}> <label
htmlFor="textvalidate"
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{ fontSize: 13 }}
>
Validation Validation
</label> </label>
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
<div <div
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{ style={{
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
@@ -161,6 +191,7 @@ const WidgetNameModal = (props) => {
</div> </div>
</div> </div>
</div> </div>
<div className="form-section"> <div className="form-section">
<label htmlFor="name" style={{ fontSize: 13 }}> <label htmlFor="name" style={{ fontSize: 13 }}>
Default value Default value
@@ -170,25 +201,25 @@ const WidgetNameModal = (props) => {
name="defaultValue" name="defaultValue"
value={formdata.defaultValue} value={formdata.defaultValue}
onChange={(e) => handledefaultChange(e)} onChange={(e) => handledefaultChange(e)}
onBlur={() => autoComplete="off"
isValid === false && onBlur={() => {
setFormdata({ ...formdata, defaultValue: "" }) if (isValid === false) {
} setFormdata({ ...formdata, defaultValue: "" });
setIsValid(true);
}
}}
/> />
{isValid === false ? ( {isValid === false && (
<p style={{ color: "Red", fontSize: 8 }}> <div
invalid default value className="warning defaultvalueWarning"
</p> style={{ fontSize: 12 }}
) : (
<p
style={{
color: "transparent",
fontSize: 10,
margin: "3px 8px"
}}
> >
. <i
</p> className="fas fa-exclamation-circle"
style={{ color: "#fab005", fontSize: 15 }}
></i>{" "}
invalid default value
</div>
)} )}
</div> </div>
</> </>
@@ -0,0 +1,234 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import CreateFolder from "./CreateFolder";
import ModalUi from "../../../primitives/ModalUi";
const FolderModal = (props) => {
const [clickFolder, setClickFolder] = useState("");
const [folderList, setFolderList] = useState([]);
const [tabList, setTabList] = useState([]);
const [isLoader, setIsLoader] = useState(false);
const [isAdd, setIsAdd] = useState(false);
// below useEffect is called when user open popup
useEffect(() => {
if (props.isOpenModal) {
fetchFolder();
}
// eslint-disable-next-line
}, [props.isOpenModal]);
// `fetchFolder` is used to fetch of folder list created by user on basis of folderPtr or without folderPtr
const fetchFolder = async (folderPtr) => {
setIsLoader(true);
try {
const FolderQuery = new Parse.Query(props.folderCls);
if (folderPtr) {
FolderQuery.equalTo("Folder", folderPtr);
FolderQuery.equalTo("Type", "Folder");
} else {
FolderQuery.doesNotExist("Folder");
FolderQuery.equalTo("Type", "Folder");
}
const res = await FolderQuery.find();
if (res) {
const result = JSON.parse(JSON.stringify(res));
if (result) {
setFolderList(result);
setIsLoader(false);
}
setIsLoader(false);
}
} catch (error) {
setIsLoader(false);
}
};
// `handleSelect` is used to save pointer of folder selected by user and it's path in state
const handleSelect = (item) => {
setFolderList([]);
setClickFolder({ ObjectId: item.objectId, Name: item.Name });
if (tabList.length > 0) {
const tab = tabList.some((x) => x.objectId === item.objectId);
if (!tab) {
setTabList((tabs) => [...tabs, item]);
const folderPtr = {
__type: "Pointer",
className: props.folderCls,
objectId: item.objectId
};
fetchFolder(folderPtr);
}
} else {
setTabList((tabs) => [...tabs, item]);
const folderPtr = {
__type: "Pointer",
className: props.folderCls,
objectId: item.objectId
};
fetchFolder(folderPtr);
}
};
// `handleSubmit` is used to pass folderPtr to parent component
const handleSubmit = () => {
let url = "OpenSign™ Drive";
tabList.forEach((t) => {
url = url + " / " + t.Name;
});
if (props.onSuccess) {
props.onSuccess(clickFolder);
}
// SetIsOpen(false);
props.setIsOpenMoveModal(false);
};
// `handleCancel` is used to clear list of folder, close popup and folderUrl
const handleCancel = () => {
// SetIsOpen(false);
props.setIsOpenMoveModal(false);
setClickFolder({});
setFolderList([]);
setTabList([]);
};
// `handleCancel` is call when user click on folder name from path/tab in popup
const removeTabListItem = async (e, i) => {
e.preventDefault();
// setEditable(false);
if (!isAdd) {
setIsLoader(true);
let folderPtr;
if (i) {
setFolderList([]);
let list = tabList.filter((itm, j) => {
if (j <= i) {
return itm;
}
});
let _len = list.length - 1;
folderPtr = {
__type: "Pointer",
className: props.folderCls,
objectId: list[_len].objectId
};
setTabList(list);
} else {
setClickFolder({});
setFolderList([]);
setTabList([]);
}
fetchFolder(folderPtr);
}
};
// `handleCreate` is used to open folder creation form in popup
const handleCreate = () => {
setIsAdd(!isAdd);
};
// `handleAddFolder` is call when user folder created successfully and it fetch folder list on the basis of folderPtr or without folderPtr
const handleAddFolder = (newFolder) => {
props.setPdfData((prev) => [...prev, newFolder?.toJSON()]);
if (clickFolder && clickFolder.ObjectId) {
fetchFolder({
__type: "Pointer",
className: props.folderCls,
objectId: clickFolder.ObjectId
});
} else {
fetchFolder();
}
handleCreate();
};
return (
<div className="text-xs mt-2 ">
<ModalUi
title={"Select Folder"}
isOpen={props.isOpenModal}
handleClose={handleCancel}
>
<div className="w-full min-w-[300px] md:min-w-[500px] max-w-[500px] px-3">
<div className="py-2 text-[#ac4848] text-[14px] font-[500]">
<span
className="cursor-pointer"
title="OpenSign™ Drive"
onClick={(e) => removeTabListItem(e)}
>
OpenSign Drive /{" "}
</span>
{tabList &&
tabList.map((tab, i) => (
<React.Fragment key={`${tab.objectId}-${i}`}>
<span
className="cursor-pointer"
title={tab.Name}
onClick={(e) => removeTabListItem(e, i)}
>
{tab.Name}
</span>
{" / "}
</React.Fragment>
))}
<hr />
</div>
<div className="mt-2 mb-3">
<div className="max-h-[210px] overflow-auto">
{!isAdd &&
folderList.length > 0 &&
folderList.map((folder) => (
<div
key={folder.Name}
className="border-[1px] border-[#8a8a8a] px-2 py-2 mb-2 cursor-pointer "
onClick={() => handleSelect(folder)}
>
<div className="flex items-center gap-2">
<i
className="fa fa-folder text-[#33bbff] text-[1.4rem]"
aria-hidden="true"
></i>
<span className="font-semibold">{folder.Name}</span>
</div>
</div>
))}
</div>
{isAdd && (
<CreateFolder
parentFolderId={clickFolder && clickFolder.ObjectId}
folderCls={props.folderCls}
onSuccess={handleAddFolder}
/>
)}
{isLoader && (
<div className="flex justify-center">
<i className="fa-solid fa-spinner fa-spin-pulse text-[30px]"></i>
</div>
)}
</div>
</div>
<hr />
<div className="flex justify-between items-center py-[.75rem] px-[1.25rem]">
<div
className="text-[30px] cursor-pointer text-[#32a3ac]"
title="Save Here"
onClick={handleCreate}
>
{isAdd ? (
<i className="fa-solid fa-arrow-left" aria-hidden="true"></i>
) : (
<i className="fa-solid fa-square-plus" aria-hidden="true"></i>
)}
</div>
<div
className="text-[30px] cursor-pointer"
title="Save Here"
onClick={handleSubmit}
>
<i className="fas fa-save" aria-hidden="true"></i>
</div>
</div>
</ModalUi>
</div>
);
};
export default FolderModal;
@@ -196,7 +196,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
{selectFolder && selectFolder.Name ? `(${folderPath})` : ""} {selectFolder && selectFolder.Name ? `(${folderPath})` : ""}
</p> </p>
</div> </div>
<div className="absolute top-1 right-1 cursor-pointer"> <div className="absolute top-2 right-1 cursor-pointer">
<Tooltip <Tooltip
message={ message={
"If you do not select a folder, your signed document will be saved in the Main OpenSign drive folder." "If you do not select a folder, your signed document will be saved in the Main OpenSign drive folder."
@@ -53,9 +53,22 @@ const SelectSigners = (props) => {
const contactRes = await contactbook.find(); const contactRes = await contactbook.find();
if (contactRes) { if (contactRes) {
const res = JSON.parse(JSON.stringify(contactRes)); const res = JSON.parse(JSON.stringify(contactRes));
// console.log("userList ", res); //compareArrays is a function where compare between two array (total signersList and dcument signers list)
setUserList(res); //and filter signers from total signer's list which already present in document's signers list
return await res.map((item) => ({ 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, label: item.Email,
value: item.objectId value: item.objectId
})); }));
@@ -3,6 +3,7 @@ import Select from "react-select";
import AddSigner from "../../AddSigner"; import AddSigner from "../../AddSigner";
import Modal from "react-modal"; import Modal from "react-modal";
import Parse from "parse"; import Parse from "parse";
import Tooltip from "../../../primitives/Tooltip";
function arrayMove(array, from, to) { function arrayMove(array, from, to) {
array = array.slice(); array = array.slice();
array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]); array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
@@ -104,9 +105,15 @@ const SignersInput = (props) => {
return ( return (
<div className="text-xs mt-2 "> <div className="text-xs mt-2 ">
<label className="block"> <label className="block relative">
Signers Signers
{props.required && <span className="text-red-500 text-[13px]">*</span>} {props.required && <span className="text-red-500 text-[13px]">*</span>}
<span className="absolute ml-1 text-xs z-50">
<Tooltip
id={"signer-tooltip"}
message={"Begin typing a contact's name to see suggested signers from your saved contacts or add new ones. Arrange the signing order by adding signers in the desired sequence. Use the '+' button to include signers and the 'x' to remove them. Each signer will receive an email prompt to sign the document in the order listed."}
/>
</span>
</label> </label>
<div style={{ display: "flex", gap: 5 }}> <div style={{ display: "flex", gap: 5 }}>
<div style={{ flexWrap: "wrap", width: "100%" }}> <div style={{ flexWrap: "wrap", width: "100%" }}>
+1 -1
View File
@@ -10,7 +10,7 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
? `/${item.pageType}/${item.objectId}` ? `/${item.pageType}/${item.objectId}`
: `/${item.objectId}` : `/${item.objectId}`
} }
className="mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4 cursor-pointer" className="mx-auto flex items-center hover:bg-[#eef1f5] p-3 lg:p-4 cursor-pointer focus:text-[#0056b3] focus:bg-[#eef1f5]"
onClick={closeSidebar} onClick={closeSidebar}
tabIndex={isOpen ? 0 : -1} tabIndex={isOpen ? 0 : -1}
role="menuitem" role="menuitem"
@@ -41,7 +41,7 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
<div className="flex px-2 py-3 gap-2 items-center shadow-md"> <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"> <div className="w-[75px] h-[75px] rounded-full ring-[2px] ring-offset-2 ring-gray-400 overflow-hidden">
<img <img
className="w-full h-full object-cover" className="w-full h-full object-contain"
src={image} src={image}
alt="Profile" alt="Profile"
/> />
@@ -13,7 +13,7 @@ const Submenu = ({ item, closeSidebar }) => {
<li role="none"> <li role="none">
<button <button
onClick={toggleSubmenu} onClick={toggleSubmenu}
className="w-full flex items-center text-left text-[#444] p-3 lg:p-4 hover:bg-[#eef1f5] focus:ring-2 focus:ring-blue-600" className="w-full flex items-center text-left text-[#444] p-3 lg:p-4 hover:bg-[#eef1f5] focus:outline-none focus:text-[#0056b3] focus:bg-[#eef1f5]"
aria-expanded={submenuOpen} aria-expanded={submenuOpen}
aria-haspopup="true" aria-haspopup="true"
aria-controls={`submenu-${title}`} aria-controls={`submenu-${title}`}
@@ -39,7 +39,7 @@ const Submenu = ({ item, closeSidebar }) => {
? `/${childItem.pageType}/${childItem.objectId}` ? `/${childItem.pageType}/${childItem.objectId}`
: `/${childItem.objectId}` : `/${childItem.objectId}`
} }
className="block pl-6 md:pl-8 py-2 text-sm text-gray-700 hover:bg-blue-500 hover:text-white cursor-pointer" className="block pl-6 md:pl-8 py-2 text-sm text-gray-700 hover:bg-blue-500 hover:text-white focus:text-[#0056b3] focus:bg-[#eef1f5] cursor-pointer"
onClick={closeSidebar} onClick={closeSidebar}
role="menuitem" role="menuitem"
tabIndex={submenuOpen ? 0 : -1} tabIndex={submenuOpen ? 0 : -1}
+390 -42
View File
@@ -1,8 +1,9 @@
import axios from "axios"; import axios from "axios";
import moment from "moment"; import moment from "moment";
import { themeColor } from "./const"; import { isEnableSubscription, themeColor } from "./const";
import React from "react"; import React from "react";
import { rgb } from "pdf-lib"; import { rgb } from "pdf-lib";
import Parse from "parse";
export const isMobile = window.innerWidth < 767; export const isMobile = window.innerWidth < 767;
export const textInputWidget = "text input"; export const textInputWidget = "text input";
@@ -12,6 +13,83 @@ export const openInNewTab = (url) => {
window.open(url, "_blank", "noopener,noreferrer"); 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() {
try {
const res = await fetchSubscription();
if (res.plan === "freeplan") {
return false;
} else if (res.billingDate) {
if (new Date(res.billingDate) > new Date()) {
return true;
} else {
return false;
}
} else {
return false;
}
} catch (err) {
console.log("Err in fetch subscription", err);
return false;
}
}
export const color = [
"#93a3db",
"#e6c3db",
"#c0e3bc",
"#bce3db",
"#b8ccdb",
"#ceb8db",
"#ffccff",
"#99ffcc",
"#cc99ff",
"#ffcc99",
"#66ccff",
"#ffffcc"
];
export const nameColor = [
"#304fbf",
"#7d5270",
"#5f825b",
"#578077",
"#576e80",
"#6d527d",
"#cc00cc",
"#006666",
"#cc00ff",
"#ff9900",
"#336699",
"#cc9900"
];
export const toDataUrl = (file) => { export const toDataUrl = (file) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const reader = new FileReader(); const reader = new FileReader();
@@ -22,10 +100,12 @@ export const toDataUrl = (file) => {
}); });
}; };
//function for getting document details for getDrive //function for getting document details for getDrive cloud function
export const getDrive = async (documentId) => { export const getDrive = async (documentId, skip = 0, limit = 100) => {
const data = { const data = {
docId: documentId && documentId docId: documentId && documentId,
limit: limit,
skip: skip
}; };
const driveDeatils = await axios const driveDeatils = await axios
.post(`${localStorage.getItem("baseUrl")}functions/getDrive`, data, { .post(`${localStorage.getItem("baseUrl")}functions/getDrive`, data, {
@@ -446,16 +526,33 @@ export const signPdfFun = async (
base64Url, base64Url,
documentId, documentId,
signerObjectId, 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 = { singleSign = {
pdfFile: base64Url, pdfFile: base64Url,
docId: documentId, docId: documentId,
userId: signerObjectId userId: signerObjectId,
isCustomCompletionMail: isCustomCompletionMail
}; };
const response = await axios const response = await axios
.post(`${localStorage.getItem("baseUrl")}functions/signPdf`, singleSign, { .post(`${localStorage.getItem("baseUrl")}functions/signPdf`, singleSign, {
headers: { headers: {
@@ -729,6 +826,82 @@ export const onChangeInput = (
} }
}; };
//function to increase height of text area on press enter
export const onChangeHeightOfTextArea = (
height,
widgetType,
signKey,
xyPostion,
index,
setXyPostion,
userId
) => {
const isSigners = xyPostion.some((data) => data.signerPtr);
let filterSignerPos;
if (isSigners) {
if (userId) {
filterSignerPos = xyPostion.filter((data) => data.Id === userId);
}
const getPlaceHolder = filterSignerPos[0]?.placeHolder;
const getPageNumer = getPlaceHolder.filter(
(data) => data.pageNumber === index
);
if (getPageNumer.length > 0) {
const getXYdata = getPageNumer[0].pos;
const getPosData = getXYdata;
const addSignPos = getPosData.map((position) => {
if (position.key === signKey) {
return {
...position,
Height: position.Height
? position.Height + height
: defaultWidthHeight(widgetType).height + height
};
}
return position;
});
const newUpdateSignPos = getPlaceHolder.map((obj) => {
if (obj.pageNumber === index) {
return { ...obj, pos: addSignPos };
}
return obj;
});
const newUpdateSigner = xyPostion.map((obj) => {
if (obj.Id === userId) {
return { ...obj, placeHolder: newUpdateSignPos };
}
return obj;
});
setXyPostion(newUpdateSigner);
}
} else {
let getXYdata = xyPostion[index].pos;
const updatePosition = getXYdata.map((position) => {
if (position.key === signKey) {
return {
...position,
Height: position.Height
? position.Height + height
: defaultWidthHeight(widgetType).height + height
};
}
return position;
});
const updatePlaceholder = xyPostion.map((obj, ind) => {
if (ind === index) {
return { ...obj, pos: updatePosition };
}
return obj;
});
setXyPostion(updatePlaceholder);
}
};
export const addInitialData = (signerPos, setXyPostion, value, userId) => { export const addInitialData = (signerPos, setXyPostion, value, userId) => {
function widgetDataValue(type) { function widgetDataValue(type) {
switch (type) { switch (type) {
@@ -828,6 +1001,7 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
//function for save button to save signature or image url //function for save button to save signature or image url
export function onSaveSign( export function onSaveSign(
type,
xyPostion, xyPostion,
index, index,
signKey, signKey,
@@ -868,6 +1042,7 @@ export function onSaveSign(
Width: posWidth, Width: posWidth,
Height: posHeight, Height: posHeight,
SignUrl: signatureImg, SignUrl: signatureImg,
signatureType: type && type,
options: { options: {
...position.options, ...position.options,
response: signatureImg response: signatureImg
@@ -1002,10 +1177,10 @@ export const multiSignEmbed = async (
const newUrl = await convertPNGtoJPEG(signUrl); const newUrl = await convertPNGtoJPEG(signUrl);
signUrl = newUrl; signUrl = newUrl;
} }
const checkUrl = urlValidator(signUrl); // const checkUrl = urlValidator(signUrl);
if (checkUrl) { // if (checkUrl) {
signUrl = signUrl + "?get"; // signUrl = signUrl + "?get";
} // }
const res = await fetch(signUrl); const res = await fetch(signUrl);
return res.arrayBuffer(); return res.arrayBuffer();
} }
@@ -1078,8 +1253,8 @@ export const multiSignEmbed = async (
position.type === radioButtonWidget position.type === radioButtonWidget
? 10 ? 10
: position.type === "checkbox" : position.type === "checkbox"
? 10 ? 10
: newUpdateHeight; : newUpdateHeight;
const newHeight = ind ? (ind > 0 ? widgetHeight : 0) : widgetHeight; const newHeight = ind ? (ind > 0 ? widgetHeight : 0) : widgetHeight;
if (signyourself) { if (signyourself) {
@@ -1140,6 +1315,7 @@ export const multiSignEmbed = async (
} }
}; };
const widgetTypeExist = [ const widgetTypeExist = [
textWidget,
textInputWidget, textInputWidget,
"name", "name",
"company", "company",
@@ -1148,13 +1324,14 @@ export const multiSignEmbed = async (
"email" "email"
].includes(position.type); ].includes(position.type);
if (position.type === "checkbox") { if (position.type === "checkbox") {
let addYPosition, isCheck; let addYPosition = 0,
isCheck;
if (position?.options?.values.length > 0) { if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => { position?.options?.values.forEach((item, ind) => {
const checkboxRandomId = "checkbox" + randomId(); const checkboxRandomId = "checkbox" + randomId();
let yPosition; let yPosition;
const height = 10; const height = 13;
if ( if (
position?.options?.response && position?.options?.response &&
position?.options?.response?.length > 0 position?.options?.response?.length > 0
@@ -1175,14 +1352,14 @@ export const multiSignEmbed = async (
if (!position?.options?.isHideLabel) { if (!position?.options?.isHideLabel) {
// below line of code is used to embed label with radio button in pdf // below line of code is used to embed label with radio button in pdf
page.drawText(item, { page.drawText(item, {
x: xPos(position) + 15, x: xPos(position) + 17,
y: yPosition + 2, y: yPosition + 2,
size: height size: height
}); });
} }
checkbox.addToPage(page, { checkbox.addToPage(page, {
x: xPos(position), x: xPos(position),
y: yPosition, y: yPosition - 3,
width: height, width: height,
height: height height: height
}); });
@@ -1194,7 +1371,7 @@ export const multiSignEmbed = async (
checkbox.enableReadOnly(); checkbox.enableReadOnly();
}); });
} }
} else if (position.type === textWidget) { } else if (widgetTypeExist) {
const font = await pdfDoc.embedFont("Helvetica"); const font = await pdfDoc.embedFont("Helvetica");
const fontSize = 12; const fontSize = 12;
let textContent; let textContent;
@@ -1203,19 +1380,21 @@ export const multiSignEmbed = async (
} else if (position?.options?.defaultValue) { } else if (position?.options?.defaultValue) {
textContent = position?.options?.defaultValue; textContent = position?.options?.defaultValue;
} }
const fixedWidth = scaleWidth; // Set your fixed width const fixedWidth = scaleWidth; // Set your fixed width
const isNewOnEnterLineExist = textContent.includes("\n");
// Function to break text into lines based on the fixed width // Function to break text into lines based on the fixed width
const breakTextIntoLines = (textContent, width) => { const NewbreakTextIntoLines = (textContent, width) => {
const lines = []; const lines = [];
let currentLine = ""; let currentLine = "";
for (const word of textContent.split(" ")) { for (const word of textContent.split(" ")) {
//get text line width
const lineWidth = font.widthOfTextAtSize( const lineWidth = font.widthOfTextAtSize(
`${currentLine} ${word}`, `${currentLine} ${word}`,
fontSize fontSize
); );
//check text content line width is less or equal to container width
if (lineWidth <= width) { if (lineWidth <= width) {
currentLine += ` ${word}`; currentLine += ` ${word}`;
} else { } else {
@@ -1226,7 +1405,33 @@ export const multiSignEmbed = async (
lines.push(currentLine.trim()); lines.push(currentLine.trim());
return lines; return lines;
}; };
const lines = breakTextIntoLines(textContent, fixedWidth);
// Function to break text into lines based on when user go next line on press enter button
const breakTextIntoLines = (textContent, width) => {
const lines = [];
for (const word of textContent.split("\n")) {
const lineWidth = font.widthOfTextAtSize(`${word}`, fontSize);
//checking string length to container width
//if string length is less then container width it means user press enter button
if (lineWidth <= width) {
lines.push(word);
}
//else adjust text content according to width and send it in new line
else {
const newLine = NewbreakTextIntoLines(word, width);
lines.push(...newLine);
}
}
return lines;
};
//check if text content have `\n` string it means user press enter to go next line and handle condition
//else auto adjust text content according to container width
const lines = isNewOnEnterLineExist
? breakTextIntoLines(textContent, fixedWidth)
: NewbreakTextIntoLines(textContent, fixedWidth);
// Set initial y-coordinate for the first line // Set initial y-coordinate for the first line
const labelDefaultHeight = defaultWidthHeight(position.type).height; const labelDefaultHeight = defaultWidthHeight(position.type).height;
@@ -1242,24 +1447,8 @@ export const multiSignEmbed = async (
color: rgb(0, 0, 0), color: rgb(0, 0, 0),
size: fontSize 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") { } else if (position.type === "dropdown") {
const dropdownRandomId = "dropdown" + randomId(); const dropdownRandomId = "dropdown" + randomId();
const dropdown = form.createDropdown(dropdownRandomId); const dropdown = form.createDropdown(dropdownRandomId);
@@ -1327,9 +1516,7 @@ export const multiSignEmbed = async (
} }
}); });
} }
const pdfBytes = await pdfDoc.saveAsBase64({ useObjectStreams: false }); const pdfBytes = await pdfDoc.saveAsBase64({ useObjectStreams: false });
return pdfBytes; return pdfBytes;
}; };
@@ -1569,3 +1756,164 @@ export const getYear = (date) => {
const newYear = new Date(date).getFullYear(); const newYear = new Date(date).getFullYear();
return newYear; return newYear;
}; };
//function to create/copy widget next to already dropped widget
export const handleCopyNextToWidget = (
position,
widgetType,
xyPostion,
index,
setXyPostion,
userId
) => {
const isSigners = xyPostion.some((data) => data.signerPtr);
let filterSignerPos;
//get position of previous widget and create new widget next to that widget on same data except
// xPosition and key
let newpos = position;
const calculateXPosition =
parseInt(position.xPosition) +
defaultWidthHeight(widgetType).width +
resizeBorderExtraWidth();
const newId = randomId();
newpos = { ...newpos, xPosition: calculateXPosition, key: newId };
//if condition to create widget in request-sign flow
if (isSigners) {
if (userId) {
filterSignerPos = xyPostion.filter((data) => data.Id === userId);
}
const getPlaceHolder = filterSignerPos[0]?.placeHolder;
const getPageNumer = getPlaceHolder.filter(
(data) => data.pageNumber === index
);
const getXYdata = getPageNumer[0].pos;
getXYdata.push(newpos);
if (getPageNumer.length > 0) {
const newUpdateSignPos = getPlaceHolder.map((obj) => {
if (obj.pageNumber === index) {
return { ...obj, pos: getXYdata };
}
return obj;
});
const newUpdateSigner = xyPostion.map((obj) => {
if (obj.Id === userId) {
return { ...obj, placeHolder: newUpdateSignPos };
}
return obj;
});
setXyPostion(newUpdateSigner);
}
} else {
// else condition to create widget in sign-yourself flow
let getXYdata = xyPostion[index].pos;
getXYdata.push(newpos);
const updatePlaceholder = xyPostion.map((obj, ind) => {
if (ind === index) {
return { ...obj, pos: getXYdata };
}
return obj;
});
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";
}
};
+1
View File
@@ -9,3 +9,4 @@ export const modalSubmitBtnColor = "#17a2b8";
export const modalCancelBtnColor = "white"; export const modalCancelBtnColor = "white";
export const themeColor = "#47a3ad"; export const themeColor = "#47a3ad";
export const iconColor = "#686968"; export const iconColor = "#686968";
export const isEnableSubscription = process.env.REACT_APP_ENABLE_SUBSCRIPTION;
+39 -60
View File
@@ -2,26 +2,25 @@ import axios from "axios";
const parseAppId = localStorage.getItem("parseAppId"); const parseAppId = localStorage.getItem("parseAppId");
const serverUrl = localStorage.getItem("baseUrl"); const serverUrl = localStorage.getItem("baseUrl");
export const SaveFileSize = async (size, imageUrl) => { export const SaveFileSize = async (size, imageUrl, tenantId) => {
//checking server url and save file's size //checking server url and save file's size
const tenantPtr = {
__type: "Pointer",
className: "partners_Tenant",
objectId: tenantId
};
const _tenantPtr = JSON.stringify(tenantPtr);
try { try {
const response = await axios const res = await axios.get(
.get(`${serverUrl}classes/partners_TenantCredits`, { `${serverUrl}classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
{
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId "X-Parse-Application-Id": parseAppId
} }
}) }
.then((result) => { );
const res = result.data; const response = res.data.results;
// console.log("res", res);
return res.results;
})
.catch((err) => {
console.log("axois err ", err);
});
let data; let data;
// console.log("response", response); // console.log("response", response);
if (response && response.length > 0) { if (response && response.length > 0) {
@@ -30,69 +29,49 @@ export const SaveFileSize = async (size, imageUrl) => {
? response[0].usedStorage + size ? response[0].usedStorage + size
: size : size
}; };
await axios await axios.put(
.put( `${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`,
`${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`, data,
data, {
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
}
)
.then(() => {
// const res = result.data;
// console.log("save res", res);
})
.catch((err) => {
console.log("axois err ", err);
});
} else {
data = {
usedStorage: size
};
await axios
.post(`${serverUrl}classes/partners_TenantCredits`, data, {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId "X-Parse-Application-Id": parseAppId
} }
}) }
.then(() => { );
// const res = result.data; } else {
// console.log("res", res); data = { usedStorage: size, PartnersTenant: tenantPtr };
}) await axios.post(`${serverUrl}classes/partners_TenantCredits`, data, {
.catch((err) => { headers: {
console.log("axois err ", err); "Content-Type": "application/json",
}); "X-Parse-Application-Id": parseAppId
}
});
} }
} catch (e) { } catch (err) {
console.log("org app error", e); console.log("err in save usage", err);
} }
saveDataFile(size, imageUrl); saveDataFile(size, imageUrl, tenantPtr);
}; };
//function for save fileUrl and file size in particular client db class partners_DataFiles //function for save fileUrl and file size in particular client db class partners_DataFiles
const saveDataFile = async (size, imageUrl) => { const saveDataFile = async (size, imageUrl, tenantPtr) => {
const data = { const data = {
FileUrl: imageUrl, FileUrl: imageUrl,
FileSize: size FileSize: size,
TenantPtr: tenantPtr
}; };
// console.log("data save",file, data) // console.log("data save",file, data)
await axios try {
.post(`${serverUrl}classes/partners_DataFiles`, data, { await axios.post(`${serverUrl}classes/partners_DataFiles`, data, {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId "X-Parse-Application-Id": parseAppId
} }
})
.then(() => {
// const res = result.data;
// console.log("res", res);
})
.catch((err) => {
console.log("axois err ", err);
}); });
} catch (err) {
console.log("err in save usage ", err);
}
}; };
+9 -6
View File
@@ -4,6 +4,7 @@ import "./index.css";
import App from "./App"; import App from "./App";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import { store } from "./redux/store"; import { store } from "./redux/store";
import { CookiesProvider } from "react-cookie";
import { HTML5Backend } from "react-dnd-html5-backend"; import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend"; import { TouchBackend } from "react-dnd-touch-backend";
import { import {
@@ -62,10 +63,12 @@ if (process.env.REACT_APP_GTM) {
const root = ReactDOM.createRoot(document.getElementById("root")); const root = ReactDOM.createRoot(document.getElementById("root"));
root.render( root.render(
<Provider store={store}> <CookiesProvider defaultSetOptions={{ path: "/" }}>
<DndProvider options={HTML5toTouch}> <Provider store={store}>
<Preview>{generatePreview}</Preview> <DndProvider options={HTML5toTouch}>
<App /> <Preview>{generatePreview}</Preview>
</DndProvider> <App />
</Provider> </DndProvider>
</Provider>
</CookiesProvider>
); );
+219 -38
View File
@@ -12,13 +12,26 @@ export default function reportJson(id) {
heading: head, heading: head,
actions: [ actions: [
{ {
btnLabel: "sign", btnId: "1231",
hoverLabel: "Edit",
btnColor: "#4bd396", btnColor: "#4bd396",
textColor: "white", textColor: "white",
btnIcon: "fa fa-plus", btnIcon: "fa-solid fa-pen",
redirectUrl: "draftDocument" 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."
}; };
// Need your sign report // Need your sign report
case "4Hhwbp482K": case "4Hhwbp482K":
@@ -27,13 +40,18 @@ export default function reportJson(id) {
heading: head, heading: head,
actions: [ actions: [
{ {
btnId: "4536",
btnLabel: "sign", btnLabel: "sign",
hoverLabel: "Sign",
btnColor: "#3ac9d6", btnColor: "#3ac9d6",
textColor: "white", textColor: "white",
btnIcon: "fa fa-eye", btnIcon: "fa-solid fa-signature",
redirectUrl: "pdfRequestFiles" redirectUrl: "pdfRequestFiles",
action: "redirect"
} }
] ],
helpMsg:
"This is a list of documents that are waiting for your signature"
}; };
// In progess report // In progess report
case "1MwEuxLEkF": case "1MwEuxLEkF":
@@ -42,13 +60,52 @@ export default function reportJson(id) {
heading: head, heading: head,
actions: [ 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", btnColor: "#3ac9d6",
textColor: "white", textColor: "white",
btnIcon: "fa fa-eye", 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:
"This is a list of documents you've sent to other parties for signature."
}; };
// completed documents report // completed documents report
case "kQUoW4hUXz": case "kQUoW4hUXz":
@@ -57,13 +114,26 @@ export default function reportJson(id) {
heading: head, heading: head,
actions: [ actions: [
{ {
btnLabel: "View", btnId: "1378",
hoverLabel: "Edit",
btnColor: "#4bd396", btnColor: "#4bd396",
textColor: "white", textColor: "white",
btnIcon: "fa fa-eye", 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."
}; };
// declined documents report // declined documents report
case "UPr2Fm5WY3": case "UPr2Fm5WY3":
@@ -72,13 +142,26 @@ export default function reportJson(id) {
heading: head, heading: head,
actions: [ actions: [
{ {
btnLabel: "View", btnId: "1458",
hoverLabel: "View",
btnColor: "#4bd396", btnColor: "#4bd396",
textColor: "white", textColor: "white",
btnIcon: "fa fa-eye", 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."
}; };
// Expired Documents report // Expired Documents report
case "zNqBHXHsYH": case "zNqBHXHsYH":
@@ -87,13 +170,26 @@ export default function reportJson(id) {
heading: head, heading: head,
actions: [ actions: [
{ {
btnLabel: "View", btnId: "1898",
hoverLabel: "View",
btnColor: "#4bd396", btnColor: "#4bd396",
textColor: "white", textColor: "white",
btnIcon: "fa fa-eye", 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."
}; };
// Recently sent for signatures report show on dashboard // Recently sent for signatures report show on dashboard
case "d9k3UfYHBc": case "d9k3UfYHBc":
@@ -102,11 +198,48 @@ export default function reportJson(id) {
heading: dashboardReportHead, heading: dashboardReportHead,
actions: [ actions: [
{ {
btnLabel: "View", btnId: "1999",
hoverLabel: "Share",
btnColor: "#4bd396", btnColor: "#4bd396",
textColor: "white", textColor: "white",
btnIcon: "fa-solid fa-share",
redirectUrl: "",
action: "share"
},
{
btnId: "1999",
hoverLabel: "View",
btnColor: "#3ac9d6",
textColor: "white",
btnIcon: "fa fa-eye", 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"
}
]
} }
] ]
}; };
@@ -117,11 +250,14 @@ export default function reportJson(id) {
heading: dashboardReportHead, heading: dashboardReportHead,
actions: [ actions: [
{ {
btnLabel: "Sign", btnId: "2001",
btnColor: "#4bd396", btnLabel: "sign",
hoverLabel: "Sign",
btnColor: "#3ac9d6",
textColor: "white", textColor: "white",
btnIcon: "fa fa-eye", btnIcon: "fa-solid fa-signature",
redirectUrl: "pdfRequestFiles" redirectUrl: "pdfRequestFiles",
action: "redirect"
} }
] ]
}; };
@@ -132,11 +268,22 @@ export default function reportJson(id) {
heading: ["Title", "Note", "Folder", "File", "Owner", "Signers"], heading: ["Title", "Note", "Folder", "File", "Owner", "Signers"],
actions: [ actions: [
{ {
btnLabel: "sign", btnId: "2003",
hoverLabel: "Edit",
btnColor: "#4bd396", btnColor: "#4bd396",
textColor: "white", textColor: "white",
btnIcon: "fa fa-plus", btnIcon: "fa-solid fa-pen",
redirectUrl: "draftDocument" redirectUrl: "draftDocument",
action: "redirect"
},
{
btnId: "2004",
hoverLabel: "Delete",
btnColor: "#ff4848",
textColor: "white",
btnIcon: "fa fa-trash",
redirectUrl: "",
action: "delete"
} }
] ]
}; };
@@ -147,13 +294,17 @@ export default function reportJson(id) {
heading: contactbook, heading: contactbook,
actions: [ actions: [
{ {
btnLabel: "", btnId: "2204",
hoverLabel: "Delete",
btnColor: "#f55a42", btnColor: "#f55a42",
textColor: "white", textColor: "white",
btnIcon: "fa-solid fa-trash" btnIcon: "fa-solid fa-trash",
action: "delete"
} }
], ],
form: "ContactBook" 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."
}; };
// template report // template report
case "6TeaPr321t": case "6TeaPr321t":
@@ -162,20 +313,50 @@ export default function reportJson(id) {
heading: head, heading: head,
actions: [ actions: [
{ {
btnId: "2234",
btnLabel: "Use", btnLabel: "Use",
hoverLabel: "Use",
btnColor: "#4bd396", btnColor: "#4bd396",
textColor: "white", textColor: "white",
btnIcon: "fa fa-plus", 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", btnId: "2234",
btnColor: "#00c9d5", hoverLabel: "option",
textColor: "white", btnColor: "transparent",
btnIcon: "fa fa-plus", textColor: "black",
redirectUrl: "template" 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:
"This is a list of templates that are available to you for creating documents. You can click the 'use' button to create a new document using a template, modify the document & add signers in the next step."
}; };
default: default:
return null; return null;
+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;
-65
View File
@@ -1,65 +0,0 @@
[
{
"planName": "SELF HOSTED",
"currency": "",
"price": "Free",
"subtitle": "Free for life One click install.",
"btnText": "Visit github",
"url": "https://github.com/opensignlabs/opensign",
"target": "_blank",
"benefits": [
"Sign Unlimited Documents",
"S3 Doc Storage",
"Unlimited Guest Signers",
"Unlimited completion certificates",
"Unique Code(OTP) verification for guest signers",
"Expiring docs & reject signature support",
"PDF Template Creation(coming soon)",
"Audit Trails",
"API support",
"Add-ons support(coming soon)",
"Self hosted cloud infrastructure",
"Free for lifetime",
"Community support"
]
},
{
"planName": "THANKS PLAN",
"currency": "$",
"price": "29.99",
"subtitle": "Use code <span style='background-color: yellow;'>'FREEBETA'</span> to get this for free(First 1000 users)",
"btnText": "Subscribe",
"url": "https://subscriptions.zoho.in/subscribe/ef798486e6a0a11ea65f2bae8f2af901dbadf3175d495584fd25b9af5d2f2b9e/thanks",
"target": "_self",
"benefits": [
"Sign Unlimited Documents",
"Unlimited Secure Doc Storage with OpenSign™ Drive",
"Unlimited Guest Signers",
"Unlimited completion certificates",
"Unique Code(OTP) verification for guest signers",
"Expiring docs & reject signature support",
"PDF Template Creation(coming soon)",
"Audit Trails",
"API support",
"Add-ons support(coming soon)",
"Self hosted cloud infrastructure"
]
},
{
"planName": "ENTERPRISE",
"currency": "",
"price": "Request Price",
"subtitle": "Customization available Priority support.",
"btnText": "Contact us",
"url": "https://www.opensignlabs.com/contact-us",
"target": "_blank",
"benefits": [
"All features",
"Custom domain",
"Whitelabeling",
"Uptime SLA",
"SSO support",
"24/7 support"
]
}
]
+58 -11
View File
@@ -9,6 +9,9 @@ import { useSelector } from "react-redux";
import Parse from "parse"; import Parse from "parse";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import { useNavigate, useLocation, Outlet } from "react-router-dom"; 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 HomeLayout = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -18,6 +21,13 @@ const HomeLayout = () => {
const arr = useSelector((state) => state.TourSteps); const arr = useSelector((state) => state.TourSteps);
const [isUserValid, setIsUserValid] = useState(true); const [isUserValid, setIsUserValid] = useState(true);
const [isLoader, setIsLoader] = useState(true); const [isLoader, setIsLoader] = useState(true);
// reactour state
const [isCloseBtn, setIsCloseBtn] = useState(true);
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
const [tourConfigs, setTourConfigs] = useState([]);
const [, setCookie] = useCookies(["accesstoken", "main_Domain"]);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
@@ -27,8 +37,8 @@ const HomeLayout = () => {
sessionToken: localStorage.getItem("accesstoken") sessionToken: localStorage.getItem("accesstoken")
}); });
if (user) { if (user) {
setIsUserValid(true); localStorage.setItem("profileImg", user.get("ProfilePic") || "");
setIsLoader(false); checkIsSubscribed();
} else { } else {
setIsUserValid(false); setIsUserValid(false);
} }
@@ -37,14 +47,49 @@ const HomeLayout = () => {
setIsUserValid(false); 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
});
};
// reactour state async function checkIsSubscribed() {
const [isCloseBtn, setIsCloseBtn] = useState(true); if (isEnableSubscription) {
const [isTour, setIsTour] = useState(false); const res = await fetchSubscription();
const [tourStatusArr, setTourStatusArr] = useState([]); if (res.plan === "freeplan") {
const [tourConfigs, setTourConfigs] = useState([]); setIsUserValid(true);
setIsLoader(false);
} else if (res.billingDate) {
if (new Date(res.billingDate) > new Date()) {
setIsUserValid(true);
setIsLoader(false);
} else {
navigate(`/subscription`);
}
} else {
navigate(`/subscription`);
}
} else {
setIsUserValid(true);
setIsLoader(false);
}
}
const showSidebar = () => { const showSidebar = () => {
setIsOpen((value) => !value); setIsOpen((value) => !value);
}; };
@@ -198,7 +243,7 @@ const HomeLayout = () => {
return ( return (
<div> <div>
<div className="sticky top-0 z-50"> <div className="sticky top-0 z-50">
<Header showSidebar={showSidebar} /> {!isLoader && <Header showSidebar={showSidebar} />}
</div> </div>
{isUserValid ? ( {isUserValid ? (
<> <>
@@ -223,8 +268,10 @@ const HomeLayout = () => {
<> <>
<div className="flex md:flex-row flex-col z-50"> <div className="flex md:flex-row flex-col z-50">
<Sidebar isOpen={isOpen} closeSidebar={closeSidebar} /> <Sidebar isOpen={isOpen} closeSidebar={closeSidebar} />
<div
<div className="relative h-screen flex flex-col justify-between w-full overflow-y-auto"> id="renderList"
className="relative h-screen flex flex-col justify-between w-full overflow-y-auto"
>
<div className="bg-[#eef1f5] p-3">{<Outlet />}</div> <div className="bg-[#eef1f5] p-3">{<Outlet />}</div>
<div className="z-30"> <div className="z-30">
<Footer /> <Footer />
+7 -7
View File
@@ -4,10 +4,12 @@ import "../styles/loader.css";
import GetDashboard from "../components/dashboard/GetDashboard"; import GetDashboard from "../components/dashboard/GetDashboard";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import Title from "../components/Title"; import Title from "../components/Title";
import { save_tourSteps } from "../redux/actions"; import { useDispatch } from "react-redux";
import { connect } from "react-redux"; import { saveTourSteps } from "../redux/reducers/TourStepsReducer";
const Dashboard = (props) => {
const Dashboard = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch();
const { id } = useParams(); const { id } = useParams();
const [dashboard, setdashboard] = useState([]); const [dashboard, setdashboard] = useState([]);
const [classnameArray, setclassnameArray] = useState([]); const [classnameArray, setclassnameArray] = useState([]);
@@ -88,7 +90,7 @@ const Dashboard = (props) => {
// style: { backgroundColor: "#abd4d2" }, // style: { backgroundColor: "#abd4d2" },
}; };
}); });
props.save_tourSteps(arr); dispatch(saveTourSteps(arr));
// console.log("arr ", arr); // console.log("arr ", arr);
setclassnameArray(classArray); setclassnameArray(classArray);
if (localStorage.getItem("DashboardDefaultFilter")) { if (localStorage.getItem("DashboardDefaultFilter")) {
@@ -133,6 +135,4 @@ const Dashboard = (props) => {
); );
}; };
export default connect(null, { export default Dashboard;
save_tourSteps
})(Dashboard);
+14 -20
View File
@@ -1,12 +1,15 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { fetchAppInfo, forgetPassword } from "../redux/actions";
import Title from "../components/Title"; import Title from "../components/Title";
import { NavLink } from "react-router-dom"; import { NavLink } from "react-router-dom";
import login_img from "../assets/images/login_img.svg"; import login_img from "../assets/images/login_img.svg";
import Parse from "parse"; import Parse from "parse";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
function ForgotPassword(props) { import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
function ForgotPassword() {
const dispatch = useDispatch();
const [state, setState] = useState({ const [state, setState] = useState({
email: "", email: "",
password: "", password: "",
@@ -27,14 +30,11 @@ function ForgotPassword(props) {
const handleSubmit = async (event) => { const handleSubmit = async (event) => {
event.preventDefault(); event.preventDefault();
localStorage.setItem("appLogo", props.appInfo.applogo); localStorage.setItem("appLogo", appInfo.applogo);
localStorage.setItem("appName", props.appInfo.appname); localStorage.setItem("appName", appInfo.appname);
localStorage.setItem("defaultmenuid", props.appInfo.defaultmenuid); // localStorage.setItem("defaultmenuid", props.appInfo.defaultmenuid);
localStorage.setItem("PageLanding", props.appInfo.LandingPageId); // localStorage.setItem("PageLanding", props.appInfo.LandingPageId);
localStorage.setItem( localStorage.setItem("userSettings", JSON.stringify(appInfo.settings));
"userSettings",
JSON.stringify(props.appInfo.settings)
);
if (state.email) { if (state.email) {
const username = state.email; const username = state.email;
try { try {
@@ -50,14 +50,14 @@ function ForgotPassword(props) {
}; };
useEffect(() => { useEffect(() => {
props.fetchAppInfo(); dispatch(fetchAppInfo());
resize(); resize();
window.addEventListener("resize", resize); window.addEventListener("resize", resize);
return () => window.removeEventListener("resize", resize); return () => window.removeEventListener("resize", resize);
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
let image = props.appInfo.applogo; let image = appInfo.applogo;
return ( return (
<div className="bg-white"> <div className="bg-white">
<Title title="Forgot password page" /> <Title title="Forgot password page" />
@@ -129,10 +129,4 @@ function ForgotPassword(props) {
); );
} }
const mapStateToProps = (state) => { export default ForgotPassword;
return { appInfo: state.appInfo, forgotPassword: state.forgotPassword };
};
export default connect(mapStateToProps, { fetchAppInfo, forgetPassword })(
ForgotPassword
);
+25 -10
View File
@@ -10,6 +10,8 @@ import SelectFolder from "../components/shared/fields/SelectFolder";
import SignersInput from "../components/shared/fields/SignersInput"; import SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title"; import Title from "../components/Title";
import PageNotFound from "./PageNotFound"; import PageNotFound from "./PageNotFound";
import { SaveFileSize } from "../constant/saveFileSize";
import { getFileName } from "../constant/Utils";
// `Form` render all type of Form on this basis of their provided in path // `Form` render all type of Form on this basis of their provided in path
function Form() { function Form() {
@@ -35,7 +37,7 @@ const Forms = (props) => {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
Name: "", Name: "",
Description: "", Description: "",
Note: "Please review and sign this document", Note: "",
TimeToCompleteDays: 15, TimeToCompleteDays: 15,
SendinOrder: "false" SendinOrder: "false"
}); });
@@ -51,6 +53,7 @@ const Forms = (props) => {
}; };
useEffect(() => { useEffect(() => {
handleReset(); handleReset();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.title]); }, [props.title]);
const handleFileInput = (e) => { const handleFileInput = (e) => {
setpercentage(0); setpercentage(0);
@@ -81,6 +84,7 @@ const Forms = (props) => {
const handleFileUpload = async (file) => { const handleFileUpload = async (file) => {
setfileload(true); setfileload(true);
const fileName = file.name; const fileName = file.name;
const size = file.size;
const name = sanitizeFileName(fileName); const name = sanitizeFileName(fileName);
const pdfFile = file; const pdfFile = file;
const parseFile = new Parse.File(name, pdfFile); const parseFile = new Parse.File(name, pdfFile);
@@ -100,6 +104,8 @@ const Forms = (props) => {
setFileUpload(response.url()); setFileUpload(response.url());
setfileload(false); setfileload(false);
if (response.url()) { if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url(); return response.url();
} }
} catch (error) { } catch (error) {
@@ -112,6 +118,7 @@ const Forms = (props) => {
const dropboxSuccess = async (files) => { const dropboxSuccess = async (files) => {
setfileload(true); setfileload(true);
const file = files[0]; const file = files[0];
const size = file.bytes;
const url = file.link; const url = file.link;
const mb = Math.round(file.bytes / Math.pow(1024, 2)); const mb = Math.round(file.bytes / Math.pow(1024, 2));
@@ -140,6 +147,8 @@ const Forms = (props) => {
setfileload(false); setfileload(false);
if (response.url()) { if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url(); return response.url();
} }
} catch (error) { } catch (error) {
@@ -165,7 +174,9 @@ const Forms = (props) => {
"TimeToCompleteDays", "TimeToCompleteDays",
parseInt(formData?.TimeToCompleteDays) parseInt(formData?.TimeToCompleteDays)
); );
const isChecked = formData.SendinOrder === "true" ? true : false; }
if (props.title !== "Sign Yourself") {
const isChecked = formData.SendinOrder === "false" ? false : true;
object.set("SendinOrder", isChecked); object.set("SendinOrder", isChecked);
} }
object.set("URL", fileupload); object.set("URL", fileupload);
@@ -233,14 +244,18 @@ const Forms = (props) => {
setFormData({ setFormData({
Name: "", Name: "",
Description: "", Description: "",
Note: "Please review and sign this document", Note:
props.title === "Sign Yourself"
? "Note to myself"
: "Please review and sign this document",
TimeToCompleteDays: 15, TimeToCompleteDays: 15,
SendinOrder: "false" SendinOrder: "true"
}); });
setFileUpload([]); setFileUpload([]);
setpercentage(0); setpercentage(0);
setTimeout(() => setIsReset(false), 50); setTimeout(() => setIsReset(false), 50);
}; };
return ( return (
<div className="shadow-md rounded my-2 p-3 bg-[#ffffff] md:border-[1px] md:border-gray-600/50"> <div className="shadow-md rounded my-2 p-3 bg-[#ffffff] md:border-[1px] md:border-gray-600/50">
<Title title={props?.title} /> <Title title={props?.title} />
@@ -285,13 +300,10 @@ const Forms = (props) => {
<div className="flex gap-2 justify-center items-center"> <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="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"> <div className="break-all">
file selected :{" "} file selected : {getFileName(fileupload)}
{fileupload?.split("/")?.pop()?.split("_")[1]}
</div> </div>
<div <div
onClick={() => { onClick={() => setFileUpload([])}
setFileUpload([]);
}}
className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500" className="cursor-pointer px-[10px] text-[20px] font-bold bg-white text-red-500"
> >
<i className="fa-solid fa-xmark"></i> <i className="fa-solid fa-xmark"></i>
@@ -324,7 +336,10 @@ const Forms = (props) => {
</div> </div>
<div className="text-xs mt-2"> <div className="text-xs mt-2">
<label className="block"> <label className="block">
Title<span className="text-red-500 text-[13px]">*</span> {props.title === "New Template"
? "Template Title"
: "Document Title"}
<span className="text-red-500 text-[13px]">*</span>
</label> </label>
<input <input
name="Name" name="Name"
+81 -22
View File
@@ -1,12 +1,16 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Title from "../components/Title"; import Title from "../components/Title";
import axios from "axios"; import axios from "axios";
import { useNavigate } from "react-router-dom";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import { rejectBtn, submitBtn } from "../constant/const"; import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { openInNewTab } from "../constant/Utils"; import { checkIsSubscribed, copytoData, openInNewTab } from "../constant/Utils";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
function GenerateToken() { function GenerateToken() {
const navigation = useNavigate();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl")); const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId")); const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [apiToken, SetApiToken] = useState(""); const [apiToken, SetApiToken] = useState("");
@@ -15,6 +19,7 @@ function GenerateToken() {
const [isGenerate, setIsGenerate] = useState(false); const [isGenerate, setIsGenerate] = useState(false);
const [isErr, setIsErr] = useState(false); const [isErr, setIsErr] = useState(false);
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => { useEffect(() => {
fetchToken(); fetchToken();
@@ -23,6 +28,11 @@ function GenerateToken() {
const fetchToken = async () => { const fetchToken = async () => {
try { try {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
const url = parseBaseUrl + "functions/getapitoken"; const url = parseBaseUrl + "functions/getapitoken";
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -33,6 +43,7 @@ function GenerateToken() {
if (res) { if (res) {
SetApiToken(res.data.result.result); SetApiToken(res.data.result.result);
} }
setIsLoader(false); setIsLoader(false);
} catch (err) { } catch (err) {
SetApiToken(); SetApiToken();
@@ -81,13 +92,14 @@ function GenerateToken() {
}; };
const copytoclipboard = (text) => { const copytoclipboard = (text) => {
navigator.clipboard.writeText(text); copytoData(text);
setCopied(true); setCopied(true);
setTimeout(() => { setTimeout(() => {
setCopied(false); setCopied(false);
}, 1500); // Reset copied state after 1.5 seconds }, 1500); // Reset copied state after 1.5 seconds
}; };
const handleModal = () => setIsModal(!isModal); const handleModal = () => setIsModal(!isModal);
return ( return (
<React.Fragment> <React.Fragment>
<Title title={"API Token"} /> <Title title={"API Token"} />
@@ -115,29 +127,47 @@ function GenerateToken() {
</div> </div>
) : ( ) : (
<div className="bg-white flex flex-col justify-center shadow rounded"> <div className="bg-white flex flex-col justify-center shadow rounded">
<h1 className="ml-4 mt-3 mb-2 font-semibold">API Token</h1> {!isEnableSubscription && <PremiumAlertHeader />}
<ul className="w-full flex flex-col p-2 text-sm"> <h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
OpenSign API{" "}
<Tooltip
url={
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
}
isSubscribe={true}
/>
</h1>
<ul
className={
isSubscribe || !isEnableSubscription
? "w-full flex flex-col p-2 text-sm "
: "w-full flex flex-col p-2 text-sm opacity-20 pointer-events-none select-none"
}
>
<li <li
className={`flex justify-between items-center border-y-[1px] border-gray-300 break-all py-2`} className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
> >
<span className="w-[40%]">Api Token:</span>{" "} <div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
<span <span className="">Api Token:</span>{" "}
id="token" <span
className="w-[60%] md:text-end cursor-pointer" id="token"
onClick={() => copytoclipboard(apiToken)} className=" md:text-end cursor-pointer"
onClick={() => copytoclipboard(apiToken)}
>
{apiToken ? apiToken : "_____"}
</span>
</div>
<button
type="button"
onClick={apiToken ? handleModal : handleSubmit}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
> >
{apiToken && apiToken} {apiToken ? "Regenerate Token" : "Generate Token"}
</span> </button>
</li> </li>
</ul> </ul>
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
<button <div className="flex items-center justify-center ">
type="button"
onClick={apiToken ? handleModal : handleSubmit}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
>
{apiToken ? "Regenerate Token" : "Generate Token"}
</button>
<button <button
type="button" type="button"
onClick={() => onClick={() =>
@@ -145,12 +175,41 @@ function GenerateToken() {
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1" "https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
) )
} }
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none" className="rounded hover:bg-[#15b4e9] border-[1px] my-2 border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
> >
View Docs View Docs
</button> </button>
</div> </div>
{!isSubscribe && isEnableSubscription && (
<>
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
Upgrade to PRO Plan
</h1>
<ul className={"w-full flex flex-col p-2 text-sm "}>
<li
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
>
<div className="w-[70%] flex-col md:flex-row flex items-center gap-3 ">
<span className="">$29.99/month:</span>{" "}
<span id="token" className=" md:text-end cursor-pointer">
Sign 100 docs using API. Only 0.15/docs after that.
</span>
</div>
{!isSubscribe && isEnableSubscription && (
<button
type="button"
onClick={() => navigation("/subscription")}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
>
Upgrade Now
</button>
)}
</li>
</ul>
</>
)}
<ModalUi <ModalUi
isOpen={isModal} isOpen={isModal}
title={"Regenerate Token"} title={"Regenerate Token"}
+27 -10
View File
@@ -3,8 +3,9 @@ import { useNavigate, useParams } from "react-router-dom";
import "../styles/loginPage.css"; import "../styles/loginPage.css";
import loader from "../assets/images/loader2.gif"; import loader from "../assets/images/loader2.gif";
import axios from "axios"; import axios from "axios";
import { themeColor } from "../constant/const"; import { isEnableSubscription, themeColor } from "../constant/const";
import { contractUsers } from "../constant/Utils"; import { contractUsers, getAppLogo } from "../constant/Utils";
import logo from "../assets/images/logo.png";
function GuestLogin() { function GuestLogin() {
const { id, userMail, contactBookId, serverUrl } = useParams(); const { id, userMail, contactBookId, serverUrl } = useParams();
@@ -14,14 +15,27 @@ function GuestLogin() {
const [EnterOTP, setEnterOtp] = useState(false); const [EnterOTP, setEnterOtp] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [appLogo, setAppLogo] = useState("");
useEffect(() => { useEffect(() => {
handleServerUrl(); handleServerUrl();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
//function generate serverUrl and parseAppId from url and save it in local storage //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 '&' //split url in array from '&'
localStorage.clear(); localStorage.clear();
const checkSplit = serverUrl.split("&"); const checkSplit = serverUrl.split("&");
@@ -59,7 +73,8 @@ function GuestLogin() {
"X-Parse-Application-Id": parseId "X-Parse-Application-Id": parseId
}; };
let body = { let body = {
email: email.toString() email: email.toString(),
docId: id
}; };
let Otp = await axios.post(url, body, { headers: headers }); let Otp = await axios.post(url, body, { headers: headers });
@@ -163,12 +178,14 @@ function GuestLogin() {
}} }}
> >
<div className="main_head"> <div className="main_head">
<div className="main-logo"> <div className="w-[250px] h-[66px] inline-block overflow-hidden">
<img {appLogo && (
alt="sign img" <img
src="https://qikinnovation.ams3.digitaloceanspaces.com/logo.png" src={appLogo}
width="100%" className="object-contain h-full"
/> alt="logo"
/>
)}
</div> </div>
</div> </div>
+263 -164
View File
@@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Parse from "parse"; import Parse from "parse";
import "../styles/loader.css"; import "../styles/loader.css";
import { connect } from "react-redux"; import { useDispatch } from "react-redux";
import { fetchAppInfo, showTenantName } from "../redux/actions";
import axios from "axios"; import axios from "axios";
import Title from "../components/Title"; import Title from "../components/Title";
import GoogleSignInBtn from "../components/LoginGoogle"; import GoogleSignInBtn from "../components/LoginGoogle";
@@ -11,12 +10,20 @@ import { NavLink, useNavigate, useLocation } from "react-router-dom";
import login_img from "../assets/images/login_img.svg"; import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize"; import { useWindowSize } from "../hook/useWindowSize";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import { modalCancelBtnColor, modalSubmitBtnColor } from "../constant/const"; import {
isEnableSubscription,
modalCancelBtnColor,
modalSubmitBtnColor
} from "../constant/const";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
function Login(props) { import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import { fetchSubscription, getAppLogo } from "../constant/Utils";
function Login() {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const dispatch = useDispatch();
const { width } = useWindowSize(); const { width } = useWindowSize();
const [state, setState] = useState({ const [state, setState] = useState({
email: "", email: "",
@@ -37,16 +44,31 @@ function Login(props) {
Destination: "" Destination: ""
}); });
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState(false);
const image = props?.appInfo?.applogo || undefined; const [image, setImage] = useState();
useEffect(() => { useEffect(() => {
if (localStorage.getItem("accesstoken")) { if (localStorage.getItem("accesstoken")) {
setState({ ...state, loading: true }); setState({ ...state, loading: true });
GetLoginData(); GetLoginData();
} }
props.fetchAppInfo(); dispatch(fetchAppInfo());
saveLogo();
// eslint-disable-next-line // 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 handleChange = (event) => {
const { name, value } = event.target; const { name, value } = event.target;
setState({ ...state, [name]: value }); setState({ ...state, [name]: value });
@@ -61,8 +83,8 @@ function Login(props) {
setState({ ...state, loading: true }); setState({ ...state, loading: true });
let baseUrl = localStorage.getItem("baseUrl"); let baseUrl = localStorage.getItem("baseUrl");
let parseAppId = localStorage.getItem("parseAppId"); let parseAppId = localStorage.getItem("parseAppId");
localStorage.setItem("appLogo", props.appInfo.applogo); localStorage.setItem("appLogo", appInfo.applogo);
localStorage.setItem("appName", props.appInfo.appname); localStorage.setItem("appName", appInfo.appname);
// Pass the username and password to logIn function // Pass the username and password to logIn function
await Parse.User.logIn(email, password) await Parse.User.logIn(email, password)
.then(async (user) => { .then(async (user) => {
@@ -81,8 +103,8 @@ function Login(props) {
// Check extended class user role and tenentId // Check extended class user role and tenentId
try { try {
let userRoles = []; let userRoles = [];
if (props.appInfo.settings) { if (appInfo.settings) {
let userSettings = props.appInfo.settings; let userSettings = appInfo.settings;
//Get Current user roles //Get Current user roles
let url = `${baseUrl}functions/UserGroups`; let url = `${baseUrl}functions/UserGroups`;
@@ -147,7 +169,7 @@ function Login(props) {
await Parse.Cloud.run("getUserDetails", { await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email") email: currentUser.get("email")
}).then( }).then(
(result) => { async (result) => {
let tenentInfo = []; let tenentInfo = [];
const results = [result]; const results = [result];
if (results) { if (results) {
@@ -172,8 +194,10 @@ function Login(props) {
} }
}); });
if (tenentInfo.length) { if (tenentInfo.length) {
props.showTenantName( dispatch(
tenentInfo[0].tenentName || "" showTenant(
tenentInfo[0].tenentName || ""
)
); );
localStorage.setItem( localStorage.setItem(
"TenantName", "TenantName",
@@ -205,15 +229,17 @@ function Login(props) {
x.TenantId.TenantName || "" x.TenantId.TenantName || ""
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "TenantId",
x.TenantId.objectId x.TenantId.objectId
); );
tenentInfo.push(obj); tenentInfo.push(obj);
} }
}); });
if (tenentInfo.length) { if (tenentInfo.length) {
props.showTenantName( dispatch(
tenentInfo[0].tenentName || "" showTenant(
tenentInfo[0].tenentName || ""
)
); );
localStorage.setItem( localStorage.setItem(
"TenantName", "TenantName",
@@ -233,10 +259,7 @@ function Login(props) {
element.pageType element.pageType
); );
setState({ ...state, loading: false }); setState({ ...state, loading: false });
if ( if (isEnableSubscription) {
process.env
.REACT_APP_ENABLE_SUBSCRIPTION
) {
const LocalUserDetails = { const LocalUserDetails = {
name: results[0].get("Name"), name: results[0].get("Name"),
email: results[0].get("Email"), email: results[0].get("Email"),
@@ -247,11 +270,15 @@ function Login(props) {
"userDetails", "userDetails",
JSON.stringify(LocalUserDetails) JSON.stringify(LocalUserDetails)
); );
const billingDate = const res = await fetchSubscription();
results[0].get("Next_billing_date") && const freeplan = res.plan;
results[0].get("Next_billing_date"); const billingDate = res.billingDate;
if (billingDate) { if (freeplan === "freeplan") {
if (billingDate > new Date()) { navigate(redirectUrl);
} else if (billingDate) {
if (
new Date(billingDate) > new Date()
) {
localStorage.removeItem( localStorage.removeItem(
"userDetails" "userDetails"
); );
@@ -286,9 +313,7 @@ function Login(props) {
element.pageType element.pageType
); );
setState({ ...state, loading: false }); setState({ ...state, loading: false });
if ( if (isEnableSubscription) {
process.env.REACT_APP_ENABLE_SUBSCRIPTION
) {
const LocalUserDetails = { const LocalUserDetails = {
name: _user.name, name: _user.name,
email: email, email: email,
@@ -385,6 +410,18 @@ function Login(props) {
const setThirdpartyLoader = (value) => { const setThirdpartyLoader = (value) => {
setState({ ...state, thirdpartyLoader: value }); setState({ ...state, thirdpartyLoader: value });
}; };
const handleFreePlan = async (id) => {
try {
const params = { userId: id };
const res = await Parse.Cloud.run("freesubscription", params);
if (res.status === "error") {
alert(res.result);
}
} catch (err) {
console.log("err in free subscribe", err.message);
alert("Somenthing went wrong, please try again later!");
}
};
const thirdpartyLoginfn = async (sessionToken, billingDate) => { const thirdpartyLoginfn = async (sessionToken, billingDate) => {
const baseUrl = localStorage.getItem("baseUrl"); const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId"); const parseAppId = localStorage.getItem("parseAppId");
@@ -394,6 +431,11 @@ function Login(props) {
"X-Parse-Application-Id": parseAppId "X-Parse-Application-Id": parseAppId
} }
}); });
const param = new URLSearchParams(location.search);
const isFreeplan = param?.get("subscription") === "freeplan";
if (isFreeplan) {
await handleFreePlan(res.data.objectId);
}
await Parse.User.become(sessionToken).then(() => { await Parse.User.become(sessionToken).then(() => {
window.localStorage.setItem("accesstoken", sessionToken); window.localStorage.setItem("accesstoken", sessionToken);
}); });
@@ -412,8 +454,8 @@ function Login(props) {
// Check extended class user role and tenentId // Check extended class user role and tenentId
try { try {
let userRoles = []; let userRoles = [];
if (props.appInfo.settings) { if (appInfo.settings) {
let userSettings = props.appInfo.settings; let userSettings = appInfo.settings;
//Get Current user roles //Get Current user roles
let url = `${baseUrl}functions/UserGroups`; let url = `${baseUrl}functions/UserGroups`;
@@ -503,8 +545,8 @@ function Login(props) {
} }
}); });
if (tenentInfo.length) { if (tenentInfo.length) {
props.showTenantName( dispatch(
tenentInfo[0].tenentName || "" showTenant(tenentInfo[0].tenentName || "")
); );
localStorage.setItem( localStorage.setItem(
"TenantName", "TenantName",
@@ -536,15 +578,15 @@ function Login(props) {
tenentName: x.TenantId.TenantName || "" tenentName: x.TenantId.TenantName || ""
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "TenantId",
x.TenantId.objectId x.TenantId.objectId
); );
tenentInfo.push(obj); tenentInfo.push(obj);
} }
}); });
if (tenentInfo.length) { if (tenentInfo.length) {
props.showTenantName( dispatch(
tenentInfo[0].tenentName || "" showTenant(tenentInfo[0].tenentName || "")
); );
localStorage.setItem( localStorage.setItem(
"TenantName", "TenantName",
@@ -565,18 +607,28 @@ function Login(props) {
); );
setThirdpartyLoader(false); setThirdpartyLoader(false);
setState({ ...state, loading: false }); setState({ ...state, loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) { if (isEnableSubscription) {
if (billingDate) { if (billingDate) {
if (billingDate > new Date()) { if (billingDate > new Date()) {
localStorage.removeItem("userDetails"); localStorage.removeItem("userDetails");
navigate(redirectUrl); navigate(redirectUrl);
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
}
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else { } else {
navigate(`/subscription`, { navigate(`/subscription`, {
replace: true replace: true
}); });
} }
} else {
navigate(`/subscription`, { replace: true });
} }
} else { } else {
navigate(redirectUrl); navigate(redirectUrl);
@@ -591,17 +643,29 @@ function Login(props) {
localStorage.setItem("pageType", element.pageType); localStorage.setItem("pageType", element.pageType);
setState({ ...state, loading: false }); setState({ ...state, loading: false });
setThirdpartyLoader(false); setThirdpartyLoader(false);
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) { if (isEnableSubscription) {
if (billingDate) { if (billingDate) {
if (billingDate > new Date()) { if (billingDate > new Date()) {
localStorage.removeItem("userDetails"); localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login // Redirect to the appropriate URL after successful login
navigate(redirectUrl); navigate(redirectUrl);
} else { } else {
navigate(`/subscription`, { replace: true }); if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
} }
} else { } else {
navigate(`/subscription`, { replace: true }); if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
} }
} else { } else {
navigate(redirectUrl); navigate(redirectUrl);
@@ -730,94 +794,149 @@ function Login(props) {
} else { } else {
_currentRole = userRoles[0]; _currentRole = userRoles[0];
} }
let SettingsUser = JSON.parse(userSettings); if (_currentRole && _currentRole !== "contracts_Guest") {
SettingsUser.forEach(async (element) => { let SettingsUser = JSON.parse(userSettings);
if (element.role === _currentRole) { SettingsUser.forEach(async (item) => {
let _role = _currentRole.replace( if (item.role === _currentRole) {
`${localStorage.getItem("_appName")}_`, let _role = _currentRole.replace(`${appInfo.appname}_`, "");
"" localStorage.setItem("_user_role", _role);
); // Get TenentID from Extendend Class
localStorage.setItem("_user_role", _role); localStorage.setItem("extended_class", item.extended_class);
const currentUser = Parse.User.current();
// Get TenentID from Extendend Class const userSettings = appInfo.settings;
localStorage.setItem( const setting = userSettings.find(
"extended_class", (x) => x.role === _currentRole
element.extended_class );
); const redirectUrl =
location?.state?.from ||
const currentUser = Parse.User.current(); `/${setting.pageType}/${setting.pageId}`;
await Parse.Cloud.run("getUserDetails", { await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email") email: currentUser.get("email")
}).then( }).then(
(result) => { async (result) => {
let tenentInfo = []; let tenentInfo = [];
const results = [result]; const results = [result];
if (results) { if (results) {
let extendedInfo_stringify = JSON.stringify(results); let extendedInfo_stringify = JSON.stringify(results);
let extendedInfo = JSON.parse(extendedInfo_stringify); let extendedInfo = JSON.parse(extendedInfo_stringify);
if (extendedInfo.length > 1) { if (extendedInfo.length > 1) {
extendedInfo.forEach((x) => { extendedInfo.forEach((x) => {
if (x.TenantId) { if (x.TenantId) {
let obj = { let obj = {
tenentId: x.TenantId.objectId, tenentId: x.TenantId.objectId,
tenentName: x.TenantId.TenantName || "" tenentName: x.TenantId.TenantName || ""
}; };
tenentInfo.push(obj); tenentInfo.push(obj);
} }
}); });
localStorage.setItem("showpopup", true);
localStorage.setItem("showpopup", true); localStorage.setItem("PageLanding", item.pageId);
localStorage.setItem("PageLanding", element.pageId); localStorage.setItem("defaultmenuid", item.menuId);
localStorage.setItem("defaultmenuid", element.menuId); localStorage.setItem("pageType", item.pageType);
localStorage.setItem("pageType", element.pageType); navigate("/");
navigate("/"); } else {
} else { extendedInfo.forEach((x) => {
extendedInfo.forEach((x) => { if (x.TenantId) {
if (x.TenantId) { let obj = {
let obj = { tenentId: x.TenantId.objectId,
tenentId: x.TenantId.objectId, tenentName: x.TenantId.TenantName || ""
tenentName: x.TenantId.TenantName || "" };
localStorage.setItem(
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
}
});
localStorage.setItem("PageLanding", setting.pageId);
localStorage.setItem(
"defaultmenuid",
setting.menuId
);
localStorage.setItem("pageType", setting.pageType);
if (isEnableSubscription) {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
phone: results[0].get("Phone"),
company: results[0].get("Company")
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "userDetails",
x.TenantId.objectId JSON.stringify(LocalUserDetails)
); );
tenentInfo.push(obj); const res = await fetchSubscription();
const billingDate = res.billingDate;
const freeplan = res.plan;
if (freeplan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
navigate(`/subscription`);
}
} else {
navigate(`/subscription`);
}
} else {
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} }
}); }
localStorage.setItem("PageLanding", element.pageId); } else {
localStorage.setItem("defaultmenuid", element.menuId); localStorage.setItem("PageLanding", setting.pageId);
localStorage.setItem("pageType", element.pageType); localStorage.setItem("defaultmenuid", setting.menuId);
navigate(`/${element.pageType}/${element.pageId}`); localStorage.setItem("pageType", setting.pageType);
setState({ ...state, loading: false });
if (isEnableSubscription) {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
phone: results[0].get("Phone")
// company: results.get("Company"),
};
localStorage.setItem(
"userDetails",
JSON.stringify(LocalUserDetails)
);
const billingDate = "";
if (billingDate) {
navigate(`/subscription`);
}
} else {
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
}
} }
} else { },
localStorage.setItem("PageLanding", element.pageId); (error) => {
localStorage.setItem("defaultmenuid", element.menuId);
localStorage.setItem("pageType", element.pageType);
navigate(`/${element.pageType}/${element.pageId}`);
}
},
(error) => {
setState({
...state,
loading: false,
alertType: "danger",
alertMsg: "You don`t have access to this application!"
});
setTimeout(function () {
setState({ setState({
...state, ...state,
loading: false, loading: false,
alertType: "danger", alertType: "danger",
alertMsg: "" alertMsg: "You don`t have access to this application!"
}); });
}, 2000); setTimeout(function () {
localStorage.setItem("accesstoken", null); setState({
console.error("Error while fetching Follow", error); ...state,
} loading: false,
); alertType: "danger",
} alertMsg: ""
}); });
}, 2000);
localStorage.setItem("accesstoken", null);
console.error("Error while fetching Follow", error);
}
);
}
});
} else {
setState({ ...state, loading: false });
handleCloseModal();
}
} else { } else {
console.log("User Role Not Found."); console.log("User Role Not Found.");
} }
@@ -943,16 +1062,18 @@ function Login(props) {
</div> </div>
</div> </div>
)} )}
{props.isloginVisible && props.isloginVisible ? ( {appInfo && appInfo.appId ? (
<> <>
<div aria-labelledby="loginHeading" role="region"> <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="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"> <div className="w-[250px] h-[66px] inline-block overflow-hidden">
<img {image && (
src={image} <img
width="100%" src={image}
alt="The image displays the OpenSign logo with a stylized blue square with an open corner, accompanied by the tagline Seal the Deal, Openly." 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>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2"> <div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
<div> <div>
@@ -1043,7 +1164,11 @@ function Login(props) {
</button> </button>
<NavLink <NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase" className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
to="/signup" to={
location.search
? "/signup" + location.search
: "/signup"
}
style={width < 768 ? { textAlign: "center" } : {}} style={width < 768 ? { textAlign: "center" } : {}}
> >
Create Account Create Account
@@ -1051,26 +1176,13 @@ function Login(props) {
</div> </div>
</form> </form>
<br /> <br />
{(props.appInfo.fbAppId || props.appInfo.googleClietId) && ( {(appInfo.fbAppId || appInfo.googleClietId) && (
<div <div className="text-sm flex justify-center items-center">
style={{ <hr className="border-[1px] border-gray-300 w-full" />
display: "flex", <span className="px-2 text-gray-500 cursor-default">
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 ">
OR OR
</span> </span>
<hr <hr className="border-[1px] border-gray-300 w-full" />
className={"border-[1px] border-gray-300 w-full"}
style={{ color: "grey" }}
/>
</div> </div>
)} )}
<br /> <br />
@@ -1082,9 +1194,9 @@ function Login(props) {
justifyContent: "center" justifyContent: "center"
}} }}
> >
{/* {props.appInfo.fbAppId && props.appInfo.fbAppId !== "" ? ( {/* {appInfo.fbAppId && appInfo.fbAppId !== "" ? (
<LoginFacebook <LoginFacebook
FBCred={props.appInfo.fbAppId} FBCred={appInfo.fbAppId}
thirdpartyLoginfn={thirdpartyLoginfn} thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader} thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader} setThirdpartyLoader={setThirdpartyLoader}
@@ -1100,10 +1212,9 @@ function Login(props) {
justifyContent: "center" justifyContent: "center"
}} }}
> >
{props.appInfo.googleClietId && {appInfo.googleClietId && appInfo.googleClietId !== "" ? (
props.appInfo.googleClietId !== "" ? (
<GoogleSignInBtn <GoogleSignInBtn
GoogleCred={props.appInfo.googleClietId} GoogleCred={appInfo.googleClietId}
thirdpartyLoginfn={thirdpartyLoginfn} thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader} thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader} setThirdpartyLoader={setThirdpartyLoader}
@@ -1213,16 +1324,4 @@ function Login(props) {
</div> </div>
); );
} }
export default Login;
const mapStateToProps = (state) => {
if (Object.keys(state.appInfo).length !== 0) {
return { appInfo: state.appInfo, isloginVisible: true };
} else {
return { appInfo: state.appInfo, isloginVisible: false };
}
};
export default connect(mapStateToProps, {
fetchAppInfo,
showTenantName
})(Login);
+23 -86
View File
@@ -5,6 +5,7 @@ import "../styles/signature.css";
import { toDataUrl } from "../constant/Utils"; import { toDataUrl } from "../constant/Utils";
import Parse from "parse"; import Parse from "parse";
import { appInfo } from "../constant/appinfo"; import { appInfo } from "../constant/appinfo";
import { SaveFileSize } from "../constant/saveFileSize";
const ManageSign = () => { const ManageSign = () => {
const appName = appInfo.appname; const appName = appInfo.appname;
const [penColor, setPenColor] = useState("blue"); const [penColor, setPenColor] = useState("blue");
@@ -13,7 +14,6 @@ const ManageSign = () => {
const [signName, setSignName] = useState(""); const [signName, setSignName] = useState("");
const [signature, setSignature] = useState(""); const [signature, setSignature] = useState("");
const [warning, setWarning] = useState(false); const [warning, setWarning] = useState(false);
const [nameWarning, setNameWarning] = useState(false);
const [isvalue, setIsValue] = useState(false); const [isvalue, setIsValue] = useState(false);
const allColor = ["blue", "red", "black"]; const allColor = ["blue", "red", "black"];
const [isLoader, setIsLoader] = useState(true); const [isLoader, setIsLoader] = useState(true);
@@ -105,9 +105,7 @@ const ManageSign = () => {
e.preventDefault(); e.preventDefault();
const isUrl = image.includes("https"); const isUrl = image.includes("https");
if (!signName) { if (!isvalue) {
setNameWarning(true);
} else if (!isvalue) {
setWarning(true); setWarning(true);
setTimeout(() => setWarning(false), 1000); setTimeout(() => setWarning(false), 1000);
} else { } else {
@@ -161,7 +159,11 @@ const ManageSign = () => {
try { try {
const parseFile = new Parse.File(file.name, file); const parseFile = new Parse.File(file.name, file);
const response = await parseFile.save(); const response = await parseFile.save();
return response?.url(); if (response?.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(file.size, response.url(), tenantId);
return response?.url();
}
} catch (err) { } catch (err) {
console.log("sign upload err", err); console.log("sign upload err", err);
setIsLoader(false); setIsLoader(false);
@@ -222,26 +224,13 @@ const ManageSign = () => {
setIsValue(true); setIsValue(true);
}; };
return ( return (
<div <div className="relative h-full bg-white flex shadow rounded overflow-auto">
style={{
backgroundColor: "white",
maxHeight: "500px",
overflow: "auto"
}}
>
{isLoader && ( {isLoader && (
<div className="absolute bg-black bg-opacity-75 inset-0 z-50 flex justify-center items-center"> <div className="absolute bg-black bg-opacity-75 z-50 w-full h-full flex justify-center items-center">
<div style={{ height: "300px" }}> <div
<div style={{ color: "#3dd3e0" }}
style={{ className="loader-37 text-[45px]"
marginLeft: "45%", ></div>
marginTop: "150px",
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37"
></div>
</div>
</div> </div>
)} )}
{isSuccess && ( {isSuccess && (
@@ -269,61 +258,9 @@ const ManageSign = () => {
paddingBottom: 8 paddingBottom: 8
}} }}
> >
Manage Signature My Signature
</div> </div>
<div style={{ display: "flex", flexDirection: "column" }}> <div className="signBlock mt-4">
<label
htmlFor="signName"
style={{
fontSize: 12,
position: "relative",
paddingBottom: 5
}}
>
Signer&apos;s Name
{nameWarning && (
<div
className="warning nameWarning"
style={{ fontSize: 12 }}
onAnimationEnd={() => setNameWarning(false)}
>
<i
className="fas fa-exclamation-circle"
style={{ color: "#fab005", fontSize: 15 }}
></i>{" "}
Please fill input field
</div>
)}
</label>
<input
id="signName"
className="signInputManage"
value={signName}
style={{
border: "1px solid #ccc",
borderRadius: 4,
height: 35,
padding: "6px 12px",
marginBottom: 10,
fontSize: 12
}}
required
onFocus={() => setNameWarning(false)}
onChange={(e) => setSignName(e.target.value)}
/>
<label
htmlFor="imgoverview"
style={{
fontSize: 12,
paddingBottom: 5
}}
>
Signature/Image
</label>
</div>
<div className="signBlock">
<div> <div>
<div style={{ position: "relative" }}> <div style={{ position: "relative" }}>
<div <div
@@ -429,10 +366,10 @@ const ManageSign = () => {
key === 0 && penColor === "blue" key === 0 && penColor === "blue"
? "2px solid blue" ? "2px solid blue"
: key === 1 && penColor === "red" : key === 1 && penColor === "red"
? "2px solid red" ? "2px solid red"
: key === 2 && penColor === "black" : key === 2 && penColor === "black"
? "2px solid black" ? "2px solid black"
: "2px solid white" : "2px solid white"
}} }}
onClick={() => { onClick={() => {
if (key === 0) { if (key === 0) {
@@ -537,10 +474,10 @@ const ManageSign = () => {
key === 0 && initialPen === "blue" key === 0 && initialPen === "blue"
? "2px solid blue" ? "2px solid blue"
: key === 1 && initialPen === "red" : key === 1 && initialPen === "red"
? "2px solid red" ? "2px solid red"
: key === 2 && initialPen === "black" : key === 2 && initialPen === "black"
? "2px solid black" ? "2px solid black"
: "2px solid white" : "2px solid white"
}} }}
onClick={() => { onClick={() => {
if (key === 0) { if (key === 0) {
+384 -195
View File
@@ -1,26 +1,53 @@
import React, { useEffect, useState, useRef } from "react"; import React, { useEffect, useState, useRef } from "react";
import "../styles/opensigndrive.css"; import "../styles/opensigndrive.css";
import loader from "../assets/images/loader2.gif"; import loader from "../assets/images/loader2.gif";
import DriveBody from "../components/opensigndrive/DriveBody";
import { themeColor, iconColor } from "../constant/const"; import { themeColor, iconColor } from "../constant/const";
import { getDrive } from "../constant/Utils"; import { getDrive } from "../constant/Utils";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import Title from "../components/Title"; import Title from "../components/Title";
import Parse from "parse"; import Parse from "parse";
import ModalUi from "../primitives/ModalUi"; 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")
);
const Loader = () => {
return (
<div
style={{
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
<div
style={{
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37"
></div>
</div>
);
};
function Opensigndrive() { function Opensigndrive() {
const navigate = useNavigate(); const navigate = useNavigate();
const scrollRef = useRef(null); const scrollRef = useRef(null);
const [isList, setIsList] = useState(false); const [isList, setIsList] = useState(false);
const [selectedSort, setSelectedSort] = useState("Date"); const [selectedSort, setSelectedSort] = useState("Date");
const [sortingOrder, setSortingOrder] = useState("Decending"); const [sortingOrder, setSortingOrder] = useState("Descending");
const [pdfData, setPdfData] = useState([]); const [pdfData, setPdfData] = useState([]);
const [isFolder, setIsFolder] = useState(false); const [isFolder, setIsFolder] = useState(false);
const [newFolderName, setNewFolderName] = useState(); const [newFolderName, setNewFolderName] = useState();
const [error, setError] = useState(); const [error, setError] = useState();
const [folderLoader, setIsFolderLoader] = useState(false); const [folderLoader, setIsFolderLoader] = useState(false);
const [isShowSort, setIsShowSort] = useState(false); const [isShowSort, setIsShowSort] = useState(false);
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
const [isLoading, setIsLoading] = useState({ const [isLoading, setIsLoading] = useState({
isLoad: true, isLoad: true,
message: "This might take some time" message: "This might take some time"
@@ -30,6 +57,19 @@ function Opensigndrive() {
const [folderName, setFolderName] = useState([]); const [folderName, setFolderName] = useState([]);
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" }); const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isNewFol, setIsNewFol] = useState(false); const [isNewFol, setIsNewFol] = useState(false);
const [skip, setSkip] = useState(0);
const limit = 100;
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",
Name: "Name",
Date: "Date"
};
const currentUser = const currentUser =
localStorage.getItem( localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser` `Parse/${localStorage.getItem("parseAppId")}/currentUser`
@@ -40,103 +80,184 @@ function Opensigndrive() {
const jsonCurrentUser = JSON.parse(currentUser); const jsonCurrentUser = JSON.parse(currentUser);
useEffect(() => { useEffect(() => {
if (docId) { getPdfDocumentList();
getPdfFolderDocumentList();
} else {
getPdfDocumentList();
}
// eslint-disable-next-line // eslint-disable-next-line
}, [docId]); }, [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 //function for get all pdf document list
const getPdfDocumentList = async () => { const getPdfDocumentList = async (disbaleLoading) => {
const load = { setLoading(true);
isLoad: true, checkTourStatus();
message: "This might take some time" if (!disbaleLoading) {
}; setIsLoading({ isLoad: true, message: "This might take some time" });
let driveDetails; }
setIsLoading(load);
try { try {
driveDetails = await getDrive(); const driveDetails = await getDrive(docId, skip, limit);
if (driveDetails) { if (driveDetails && driveDetails === "Error: Something went wrong!") {
if (driveDetails.length > 0) { setHandleError("Error: Something went wrong!");
setPdfData(driveDetails); } else if (driveDetails && driveDetails.length > 0) {
sortApps(null, null, driveDetails); const addMoreTour = [
}
const data = [
{ {
name: "OpenSign™ Drive", selector: '[data-tut="reactourFifth"]',
objectId: "" 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" }
} }
]; ];
setFolderName(data); let newTour = [...tourConfigs, ...addMoreTour];
const loadObj = { const isFolderExist = driveDetails.some(
isLoad: false (data) => data.Type === "Folder"
}; );
setIsLoading(loadObj); if (!isFolderExist) {
} else if (driveDetails === "Error: Something went wrong!") { newTour = newTour.filter(
const loadObj = { (data) => data.selector !== '[data-tut="reactourSeventh"]'
isLoad: false );
}; }
setHandleError("Error: Something went wrong!"); const isDocumentExist = driveDetails.some((data) => data.URL);
setIsLoading(loadObj); 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: "" }]);
} }
} catch (e) { } catch (e) {
setIsAlert({ setIsAlert({
isShow: true, isShow: true,
alertMessage: "something went wrong" alertMessage: "something went wrong"
}); });
} } finally {
}; setLoading(false);
//function for get parent folder document list setIsLoading({
const getPdfFolderDocumentList = async () => { isLoad: false
const load = {
isLoad: true,
message: "This might take some time"
};
setIsLoading(load);
let driveDetails;
try {
driveDetails = await getDrive(docId);
if (driveDetails) {
if (driveDetails.length > 0) {
setPdfData(driveDetails);
sortApps(null, null, driveDetails);
} else {
setPdfData([]);
}
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
} else if (driveDetails === "Error: Something went wrong!") {
const loadObj = {
isLoad: false
};
setHandleError("Error: Something went wrong!");
setIsLoading(loadObj);
}
} catch (e) {
setIsAlert({
isShow: true,
alertMessage: "something went wrong!"
}); });
} }
}; };
//function for get all pdf document list //function to fetch drive details list on scroll bottom
const getParentFolder = async () => { const handleScroll = () => {
setIsFolder(true); //get document of render openSign-drive component using id
const documentList = document.getElementById("renderList");
//documentList.clientHeight property returns the height of an element's content area, including padding but not including borders, margins, or scrollbars.
//documentList.scrollHeight property returns the entire height of an element,including the parts that are not visible due to overflow..
// documentList.scrollTop property show height of element, how much the content has been scrolled from the top.
// When the sum of scrollTop and clientHeight is equal to scrollHeight, it means that the user has scrolled to the bottom of the div.
if (
documentList &&
documentList.scrollTop + documentList.clientHeight >=
documentList.scrollHeight
) {
//disableLoading is used disable initial loader
const disableLoading = true;
// If the fetched data length is less than the limit, it means there's no more data to fetch
if (!loading && pdfData.length % 100 === 0) {
getPdfDocumentList(disableLoading);
}
}
}; };
//useEffect is used to call handleScroll function on scrolling event
useEffect(() => {
const documentList = document.getElementById("renderList");
if (documentList) {
documentList.addEventListener("scroll", handleScroll);
return () => {
documentList.removeEventListener("scroll", handleScroll);
};
}
// eslint-disable-next-line
}, [loading, sortingOrder, selectedSort]); // Add/remove scroll event listener when loading changes
//function for handle folder name path //function for handle folder name path
const handleRoute = (index) => { const handleRoute = (index) => {
setPdfData([]);
setSkip(0);
const updateFolderName = folderName.filter((x, i) => { const updateFolderName = folderName.filter((x, i) => {
if (i <= index) { if (i <= index) {
return x; return x;
} }
}); });
setFolderName(updateFolderName); setFolderName(updateFolderName);
const getLastId = updateFolderName[updateFolderName.length - 1]; const getLastId = updateFolderName[updateFolderName.length - 1];
setDocId(getLastId.objectId); setDocId(getLastId.objectId);
@@ -191,6 +312,7 @@ function Opensigndrive() {
const result = JSON.parse(JSON.stringify(res)); const result = JSON.parse(JSON.stringify(res));
setPdfData((prev) => [...prev, result]); setPdfData((prev) => [...prev, result]);
sortingData(null, null, [result], true);
setNewFolderName(); setNewFolderName();
setIsFolderLoader(false); setIsFolderLoader(false);
setIsFolder(false); setIsFolder(false);
@@ -207,34 +329,42 @@ function Opensigndrive() {
} }
}; };
const sortingApp = (appInfo, type, order) => { //function to use sorting document list according to type and order
if (type === "Name") { const sortedBy = (appInfo, type, order) => {
if (order === "Accending") { if (type === orderName.Name) {
return appInfo.sort((a, b) => (a.Name > b.Name ? 1 : -1)); if (order === orderName.Ascending) {
} else if (order === "Decending") { return appInfo.sort((a, b) =>
return appInfo.sort((a, b) => (a.Name > b.Name ? -1 : 1)); a.Name.toLowerCase() < b.Name.toLowerCase() ? -1 : 1
);
} else if (order === orderName.Descending) {
return appInfo.sort((a, b) =>
a.Name.toLowerCase() < b.Name.toLowerCase() ? 1 : -1
);
} }
} else if (type === "Date") { } else if (type === orderName.Date) {
if (order === "Accending") { if (order === orderName.Ascending) {
return appInfo.sort((a, b) => (a.createdAt > b.createdAt ? 1 : -1)); return appInfo.sort((a, b) => (a.createdAt > b.createdAt ? 1 : -1));
} else if (order === "Decending") { } else if (order === orderName.Descending) {
return appInfo.sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1)); return appInfo.sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1));
} }
} }
}; };
const sortApps = (type, order, driveDetails) => { //function to use get sorting type, order and document list to sort
const selectedSortType = type ? type : selectedSort ? selectedSort : "Date"; const sortingData = (type, order, driveDetails, isInitial) => {
const sortOrder = order ? order : sortingOrder ? sortingOrder : "Decending"; const selectedSortType = type ? type : selectedSort;
const sortOrder = order ? order : sortingOrder;
let sortingData = driveDetails; //check isInitial true it means sort previous 20 and get on scrolling 20 = 40 document list
if (selectedSortType === "Name") { const allPdfData = isInitial ? [...pdfData, ...driveDetails] : driveDetails;
sortingApp(sortingData, "Name", sortOrder); //call sortedBy function according to selected Type and order
} else if (selectedSortType === "Date") { if (selectedSortType === orderName.Name) {
sortingApp(sortingData, "Date", sortOrder); sortedBy(allPdfData, orderName.Name, sortOrder);
} else if (selectedSortType === orderName.Date) {
sortedBy(allPdfData, orderName.Date, sortOrder);
} }
setPdfData(sortingData); setPdfData(allPdfData);
}; };
//function for handle auto scroll on folder path //function for handle auto scroll on folder path
@@ -301,6 +431,7 @@ function Opensigndrive() {
// Cleanup the event listener when the component unmounts // Cleanup the event listener when the component unmounts
document.removeEventListener("click", closeMenuOnOutsideClick); document.removeEventListener("click", closeMenuOnOutsideClick);
}; };
// eslint-disable-next-line
}, [isShowSort, isNewFol]); }, [isShowSort, isNewFol]);
const handleFolderTab = (folderData) => { const handleFolderTab = (folderData) => {
@@ -336,9 +467,76 @@ function Opensigndrive() {
setNewFolderName(""); setNewFolderName("");
setError(""); 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 ( return (
<div style={{ backgroundColor: "white" }} className="folderComponent"> <div style={{ backgroundColor: "white" }} className="folderComponent">
<Title title={"OpenSign™ Drive"} drive={true} /> <Title title={"OpenSign™ Drive"} drive={true} />
<div> <div>
<ModalUi <ModalUi
headerColor={"#dc3545"} headerColor={"#dc3545"}
@@ -501,7 +699,19 @@ function Opensigndrive() {
) : ( ) : (
<> <>
<div className="folderContainer"> <div className="folderContainer">
{tourData && (
<Tour
onRequestClose={closeTour}
steps={tourData}
isOpen={isTour}
closeWithMask={false}
scrollOffset={-100}
rounded={5}
/>
)}
<div <div
data-tut="reactourFirst"
onMouseEnter={(e) => handleMouseEnter(e)} onMouseEnter={(e) => handleMouseEnter(e)}
ref={scrollRef} ref={scrollRef}
style={{ style={{
@@ -519,7 +729,7 @@ function Opensigndrive() {
} }
onClick={() => setIsNewFol(!isNewFol)} onClick={() => setIsNewFol(!isNewFol)}
> >
<div className="sort"> <div className="sort" data-tut="reactourSecond">
<i <i
className="fa fa-plus-square" className="fa fa-plus-square"
aria-hidden="true" aria-hidden="true"
@@ -542,7 +752,7 @@ function Opensigndrive() {
> >
<span <span
className="dropdown-item itemColor" className="dropdown-item itemColor"
onClick={() => getParentFolder()} onClick={() => setIsFolder(true)}
> >
<i <i
style={{ marginRight: "5px" }} style={{ marginRight: "5px" }}
@@ -580,7 +790,11 @@ function Opensigndrive() {
className={isShowSort ? "dropdown show" : "dropdown"} className={isShowSort ? "dropdown show" : "dropdown"}
onClick={() => setIsShowSort(!isShowSort)} onClick={() => setIsShowSort(!isShowSort)}
> >
<div className=" sort " data-toggle="dropdown"> <div
data-tut="reactourThird"
className=" sort "
data-toggle="dropdown"
>
<i <i
className="fa fa-sort-amount-asc" className="fa fa-sort-amount-asc"
aria-hidden="true" aria-hidden="true"
@@ -606,95 +820,56 @@ function Opensigndrive() {
aria-labelledby="dropdownMenuButton" aria-labelledby="dropdownMenuButton"
aria-expanded={isShowSort ? "true" : "false"} aria-expanded={isShowSort ? "true" : "false"}
> >
<span {sortingValue.map((value, ind) => {
onClick={() => { return (
setSelectedSort("Name"); <span
sortApps("Name", null, pdfData); key={ind}
}} onClick={() => {
className="dropdown-item itemColor" setSelectedSort(value);
> sortingData(value, null, pdfData);
{selectedSort !== "Name" ? ( }}
<i className="dropdown-item itemColor"
className="fa fa-arrow-up" style={{
aria-hidden="true" paddingLeft: selectedSort !== value && "33px"
style={{ marginRight: "5px" }} }}
></i> >
) : ( {selectedSort === value && (
<i <i
className="fa fa-check" className="fa fa-check"
aria-hidden="true" aria-hidden="true"
style={{ marginRight: "5px" }} style={{ marginRight: "5px" }}
></i> ></i>
)} )}
Name {value}
</span> </span>
<span );
onClick={() => { })}
setSelectedSort("Date");
sortApps("Date", null, pdfData);
}}
className="dropdown-item itemColor"
>
{selectedSort !== "Date" ? (
<i
className="fa fa-arrow-up"
aria-hidden="true"
style={{ marginRight: "5px" }}
></i>
) : (
<i
className="fa fa-check"
aria-hidden="true"
style={{ marginRight: "5px" }}
></i>
)}
Date
</span>
<hr className="hrStyle" /> <hr className="hrStyle" />
<span {sortOrder.map((order, ind) => {
onClick={() => { return (
setSortingOrder("Accending"); <span
sortApps(null, "Accending", pdfData); key={ind}
}} onClick={() => {
className="dropdown-item itemColor" setSortingOrder(order);
> sortingData(null, order, pdfData);
{sortingOrder !== "Accending" ? ( }}
<i className="dropdown-item itemColor"
className="fa fa-arrow-up" style={{
aria-hidden="true" paddingLeft: sortingOrder !== order && "33px"
style={{ marginRight: "5px" }} }}
></i> >
) : ( {sortingOrder === order && (
<i <i
className="fa fa-check" className="fa fa-check"
aria-hidden="true" aria-hidden="true"
style={{ marginRight: "5px" }} style={{ marginRight: "5px" }}
></i> ></i>
)} )}
Accending {order}
</span> </span>
<span );
onClick={() => { })}
setSortingOrder("Decending");
sortApps(null, "Decending", pdfData);
}}
className="dropdown-item itemColor"
>
{sortingOrder !== "Decending" ? (
<i
className="fa fa-arrow-up"
aria-hidden="true"
style={{ marginRight: "5px" }}
></i>
) : (
<i
className="fa fa-check"
aria-hidden="true"
style={{ marginRight: "5px" }}
></i>
)}
Decending
</span>
</div> </div>
</div> </div>
@@ -709,7 +884,11 @@ function Opensigndrive() {
></i> ></i>
</div> </div>
) : ( ) : (
<div className="sort" onClick={() => setIsList(!isList)}> <div
data-tut="reactourForth"
className="sort"
onClick={() => setIsList(!isList)}
>
<i <i
className="fa fa-list" className="fa fa-list"
aria-hidden="true" aria-hidden="true"
@@ -734,18 +913,28 @@ function Opensigndrive() {
<span style={{ fontWeight: "bold" }}>No Data Found!</span> <span style={{ fontWeight: "bold" }}>No Data Found!</span>
</div> </div>
) : ( ) : (
<DriveBody <div data-tut="reactourFifth">
pdfData={pdfData} <React.Suspense fallback={<Loader />}>
setFolderName={setFolderName} <DriveBody
setIsLoading={setIsLoading} dataTutSixth="reactourSixth"
setDocId={setDocId} dataTutSeventh="reactourSeventh"
getPdfFolderDocumentList={getPdfFolderDocumentList} pdfData={pdfData}
getPdfDocumentList={getPdfDocumentList} setFolderName={setFolderName}
isDocId={docId} setIsLoading={setIsLoading}
setPdfData={setPdfData} setDocId={setDocId}
isList={isList} getPdfDocumentList={getPdfDocumentList}
setIsAlert={setIsAlert} isDocId={docId}
/> setPdfData={setPdfData}
isList={isList}
setIsAlert={setIsAlert}
setSkip={setSkip}
sortingData={sortingData}
/>
{loading && (
<div style={{ textAlign: "center" }}>Loading...</div>
)}
</React.Suspense>
</div>
)} )}
</> </>
)} )}
File diff suppressed because it is too large Load Diff
+30 -65
View File
@@ -2,14 +2,17 @@ import React, { useState, useEffect } from "react";
import "../styles/pgsignup.css"; import "../styles/pgsignup.css";
import Parse from "parse"; import Parse from "parse";
import axios from "axios"; import axios from "axios";
import { fetchAppInfo, showTenantName } from "../redux/actions/index"; import { useDispatch } from "react-redux";
import { connect } from "react-redux";
import Title from "../components/Title"; import Title from "../components/Title";
import { useNavigate, useLocation } from "react-router-dom"; import { useNavigate, useLocation } from "react-router-dom";
import { appInfo } from "../constant/appinfo";
import { showTenant } from "../redux/reducers/ShowTenant";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
const PgSignUp = (props) => { const PgSignUp = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const dispatch = useDispatch();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl")); const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId")); const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@@ -24,8 +27,7 @@ const PgSignUp = (props) => {
// below useEffect is used to fetch App data and save to redux state // below useEffect is used to fetch App data and save to redux state
useEffect(() => { useEffect(() => {
// Parse.User.logOut() dispatch(fetchAppInfo());
props.fetchAppInfo();
handleSignedUpUser(); handleSignedUpUser();
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
@@ -39,12 +41,8 @@ const PgSignUp = (props) => {
const url = window.location.href; const url = window.location.href;
let paramString = url.split("?")[1]; let paramString = url.split("?")[1];
let queryString = new URLSearchParams(paramString); let queryString = new URLSearchParams(paramString);
// console.log("url ", queryString);
let obj = { ...formData }; let obj = { ...formData };
for (let pair of queryString.entries()) { for (let pair of queryString.entries()) {
// console.log("Key is: " + pair[0]);
// console.log("Value is: " + pair[1]);
obj = { ...obj, [pair[0]]: pair[1] }; obj = { ...obj, [pair[0]]: pair[1] };
} }
const zohoRes = await axios.post( const zohoRes = await axios.post(
@@ -59,34 +57,27 @@ const PgSignUp = (props) => {
} }
} }
); );
// console.log("zohoRes ", zohoRes);
const userSettings = JSON.parse(localStorage.getItem("userSettings")); const userSettings = JSON.parse(localStorage.getItem("userSettings"));
const extClass = userSettings[0].extended_class; const params = { subscription: zohoRes.data.result.subscription };
// console.log("extClass ", extClass); const baseurl = `${parseBaseUrl}functions/savesubscription`;
const params = { email: userDetails.email }; try {
const res = await Parse.Cloud.run("getUserDetails", params); const res = await axios.post(baseurl, params, {
// console.log("res", res); headers: {
if (res) { "Content-Type": "application/json",
const updateQuery = new Parse.Object(extClass); "X-Parse-Application-Id": parseAppId,
updateQuery.id = res.id; sessiontoken: localStorage.getItem("accesstoken")
updateQuery.set( }
"Next_billing_date", });
new Date(zohoRes.data.result.nextBillingDate) if (res) {
);
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) {
localStorage.removeItem("userDetails"); localStorage.removeItem("userDetails");
navigate( navigate(
"/" + userSettings[0].pageType + "/" + userSettings[0].pageId "/" + userSettings[0].pageType + "/" + userSettings[0].pageId
); );
setIsLoader(false); setIsLoader(false);
} }
} catch (err) {
console.log("err ", err.message);
setIsLoader(false);
} }
} catch (err) { } catch (err) {
console.log("err ", err); console.log("err ", err);
@@ -106,12 +97,8 @@ const PgSignUp = (props) => {
const url = window.location.href; const url = window.location.href;
let paramString = url.split("?")[1]; let paramString = url.split("?")[1];
let queryString = new URLSearchParams(paramString); let queryString = new URLSearchParams(paramString);
// console.log("url ", queryString);
let obj = { ...formData }; let obj = { ...formData };
for (let pair of queryString.entries()) { for (let pair of queryString.entries()) {
// console.log("Key is: " + pair[0]);
// console.log("Value is: " + pair[1]);
obj = { ...obj, [pair[0]]: pair[1] }; obj = { ...obj, [pair[0]]: pair[1] };
} }
saveUser(obj); saveUser(obj);
@@ -155,19 +142,8 @@ const PgSignUp = (props) => {
email: zohoRes.data.result.email, email: zohoRes.data.result.email,
phone: zohoRes.data.result.phone, phone: zohoRes.data.result.phone,
role: obj.role role: obj.role
// "pincode": "",
// "address": "",
// "country": "",
// "state": "",
// "city": ""
}, },
subscription: zohoRes.data.result.subscription
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 || ""
}
}; };
const usersignup = await Parse.Cloud.run("usersignup", params); const usersignup = await Parse.Cloud.run("usersignup", params);
// console.log("usersignup ", usersignup); // console.log("usersignup ", usersignup);
@@ -243,8 +219,8 @@ const PgSignUp = (props) => {
// Check extended class user role and tenentId // Check extended class user role and tenentId
try { try {
let userRoles = []; let userRoles = [];
if (props.appInfo.settings) { if (appInfo.settings) {
let userSettings = props.appInfo.settings; let userSettings = appInfo.settings;
//Get Current user roles //Get Current user roles
let url = `${baseUrl}functions/UserGroups`; let url = `${baseUrl}functions/UserGroups`;
@@ -319,8 +295,8 @@ const PgSignUp = (props) => {
} }
}); });
if (tenentInfo.length) { if (tenentInfo.length) {
props.showTenantName( dispatch(
tenentInfo[0].tenentName || "" showTenant(tenentInfo[0].tenentName || "")
); );
localStorage.setItem( localStorage.setItem(
"TenantName", "TenantName",
@@ -351,15 +327,15 @@ const PgSignUp = (props) => {
tenentName: x.TenantId.TenantName || "" tenentName: x.TenantId.TenantName || ""
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "TenantId",
x.TenantId.objectId x.TenantId.objectId
); );
tenentInfo.push(obj); tenentInfo.push(obj);
} }
}); });
if (tenentInfo.length) { if (tenentInfo.length) {
props.showTenantName( dispatch(
tenentInfo[0].tenentName || "" showTenant(tenentInfo[0].tenentName || "")
); );
localStorage.setItem( localStorage.setItem(
"TenantName", "TenantName",
@@ -537,15 +513,4 @@ const PgSignUp = (props) => {
); );
}; };
const mapStateToProps = (state) => { export default PgSignUp;
if (Object.keys(state.appInfo).length !== 0) {
return { appInfo: state.appInfo, isloginVisible: true };
} else {
return { appInfo: state.appInfo, isloginVisible: false };
}
};
export default connect(mapStateToProps, {
fetchAppInfo,
showTenantName
})(PgSignUp);
File diff suppressed because it is too large Load Diff
+119 -48
View File
@@ -1,9 +1,9 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router"; import { NavLink, useNavigate } from "react-router-dom";
import { NavLink } from "react-router-dom";
import checkmark from "../assets/images/checkmark.png"; import checkmark from "../assets/images/checkmark.png";
import plansArr from "../json/plansArr.json"; import plansArr from "../json/plansArr";
import Title from "../components/Title"; import Title from "../components/Title";
import Parse from "parse";
const listItemStyle = { const listItemStyle = {
paddingLeft: "20px", // Add padding to create space for the image paddingLeft: "20px", // Add padding to create space for the image
backgroundImage: `url(${checkmark})`, // Set your image as the list style image backgroundImage: `url(${checkmark})`, // Set your image as the list style image
@@ -19,15 +19,22 @@ const PlanSubscriptions = () => {
const userDetails = JSON.parse(localStorage.getItem("userDetails")); const userDetails = JSON.parse(localStorage.getItem("userDetails"));
const name = const name =
userDetails && userDetails.name ? "first_name=" + userDetails.name : ""; userDetails && userDetails.name
? "first_name=" + encodeURIComponent(userDetails.name)
: "";
const email = const email =
userDetails && userDetails.email ? "&email=" + userDetails.email : ""; userDetails && userDetails.email
? "&email=" + encodeURIComponent(userDetails.email)
: "";
const company = const company =
userDetails && userDetails.company userDetails && userDetails.company
? "&company_name=" + userDetails.company ? "&company_name=" + encodeURIComponent(userDetails.company)
: ""; : "";
const phone = const phone =
userDetails && userDetails.phone ? "&mobile=" + userDetails.phone : ""; userDetails && userDetails.phone
? "&mobile=" + encodeURIComponent(userDetails.phone)
: "";
const details = const details =
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" + "?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
name + name +
@@ -35,15 +42,35 @@ const PlanSubscriptions = () => {
company + company +
phone; phone;
useEffect(() => { useEffect(() => {
if (localStorage.getItem("accesstoken")) { // if (localStorage.getItem("accesstoken")) {
setIsLoader(false); setIsLoader(false);
setYearlyVisible(false); // } else {
} else { // navigate("/", { replace: true });
navigate("/", { replace: true }); // }
}
// eslint-disable-next-line // eslint-disable-next-line
}, []); }, []);
const handleFreePlan = async () => {
setIsLoader(true);
try {
const params = { userId: Parse.User.current().id };
const res = await Parse.Cloud.run("freesubscription", params);
if (res.status === "success" && res.result === "already subscribed!") {
setIsLoader(false);
alert("You have already subscribed to plan!");
} else if (res.status === "success") {
setIsLoader(false);
navigate("/");
} else if (res.status === "error") {
setIsLoader(false);
alert(res.result);
}
} catch (err) {
setIsLoader(false);
console.log("err in free subscribe", err.message);
alert("Somenthing went wrong, please try again later!");
}
};
return ( return (
<> <>
<Title title={"Subscriptions"} /> <Title title={"Subscriptions"} />
@@ -69,23 +96,40 @@ const PlanSubscriptions = () => {
style={{ style={{
backgroundColor: "white", backgroundColor: "white",
overflowY: "auto", overflowY: "auto",
maxHeight: "100%", maxHeight: "100%"
"--theme-color": "#7952b3",
"--plan-width": 30
}} }}
> >
<div <div className="block my-2">
id="monthlyPlans" <div className="flex flex-col justify-center items-center w-full">
className={`${yearlyVisible ? "none" : "block my-2"}`} <div className="mb-6 mt-2 flex flex-row border-[1px] p-2 border-gray-300 rounded text-sm">
> <span
<div className="flex justify-center w-full my-2"> onClick={() => setYearlyVisible(false)}
<ul className=" flex flex-col md:flex-row h-full bg-white justify-center border-collapse border-[1px] border-gray-300"> className={`${
!yearlyVisible
? "bg-[#002862] text-white"
: "bg-white text-black"
} px-4 py-1 rounded cursor-pointer`}
>
Monthly
</span>
<span
onClick={() => setYearlyVisible(true)}
className={`${
yearlyVisible
? "bg-[#002862] text-white"
: "bg-white text-black"
} px-4 py-1 rounded cursor-pointer`}
>
Yearly (10% off)
</span>
</div>
<ul className="flex flex-col md:flex-row h-full bg-white justify-center">
{plansArr.map((item) => ( {plansArr.map((item) => (
<li <li
className="flex flex-col md:my-0 text-center 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} key={item.planName}
> >
<div className="p-2 flex flex-col justify-center items-center"> <div className="p-2 flex flex-col justify-center items-center max-h-[310px]">
<h3 className="text-[#002862] uppercase"> <h3 className="text-[#002862] uppercase">
{item.planName} {item.planName}
</h3> </h3>
@@ -96,20 +140,20 @@ const PlanSubscriptions = () => {
alt="freeimg" alt="freeimg"
/> />
</div> </div>
<div className=""> <div>
<span className="text-3xl"> <span className="text-3xl">
{item.currency && <small>{item.currency}</small>} {item.currency && <small>{item.currency}</small>}
{item.price} {yearlyVisible
? item?.yearlyPrice
: item.monthlyPrice}
</span> </span>
<div <p className="font-semibold pt-2 text-sm">
className={`${ {yearlyVisible ? "Billed Yearly" : "Billed Monthly"}
item.subtitle.length <= 32 </p>
? "w-[150px] text-center" <div className="max-w-[250px] h-[40px] text-center text-sm my-2">
: "" <div
} text-sm text-center my-2`}
>
<p
style={{ style={{
textAlign: "center",
backgroundColor: item.subtitlecolor backgroundColor: item.subtitlecolor
? item.subtitlecolor ? item.subtitlecolor
: "white" : "white"
@@ -124,23 +168,33 @@ const PlanSubscriptions = () => {
) : ( ) : (
<span>{item.subtitle}</span> <span>{item.subtitle}</span>
)} )}
</p> </div>
</div> </div>
</div> </div>
{item.url ? (
<NavLink <NavLink
to={ to={
item.btnText === "Subscribe" item.btnText === "Subscribe"
? item.url + details ? yearlyVisible
: item.url ? item.yearlyUrl + details
} : item.url + details
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:no-underline hover:text-white" : item.url
target={item.target} }
> className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
{item.btnText} target={item.target}
</NavLink> >
{item.btnText}
</NavLink>
) : (
<button
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:text-white cursor-pointer"
onClick={() => handleFreePlan()}
>
{item.btnText}
</button>
)}
</div> </div>
<hr className="w-full bg-gray-300 p-[.5px]" /> <hr className="w-full bg-gray-300 h-[0.5px]" />
<ul className="mx-1 p-3 text-left break-words text-sm list-none"> <ul className="mx-1 p-3 text-left break-words text-sm list-none">
{item.benefits.map((subitem, index) => ( {item.benefits.map((subitem, index) => (
<li style={listItemStyle} key={index} className="m-1"> <li style={listItemStyle} key={index} className="m-1">
@@ -154,6 +208,23 @@ const PlanSubscriptions = () => {
))} ))}
</ul> </ul>
</div> </div>
<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">
Host it yourself for free
</h3>
<NavLink
to={"https://github.com/OpenSignLabs/OpenSign"}
className="bg-[#002862] w-[200px] text-center text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
target={"_blank"}
>
Visit Github
</NavLink>
</div>
</div> </div>
</div> </div>
)} )}
+55 -6
View File
@@ -6,17 +6,21 @@ import reportJson from "../json/ReportJson";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Title from "../components/Title"; import Title from "../components/Title";
import PageNotFound from "./PageNotFound"; import PageNotFound from "./PageNotFound";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
const Report = () => { const Report = () => {
const { id } = useParams(); const { id } = useParams();
const [List, setList] = useState([]); const [List, setList] = useState([]);
const [isLoader, setIsLoader] = useState(true); const [isLoader, setIsLoader] = useState(true);
const [reportName, setReportName] = useState(""); const [reportName, setReportName] = useState("");
const [reporthelp, setReportHelp] = useState("");
const [actions, setActions] = useState([]); const [actions, setActions] = useState([]);
const [heading, setHeading] = useState([]); const [heading, setHeading] = useState([]);
const [isNextRecord, setIsNextRecord] = useState(false); const [isNextRecord, setIsNextRecord] = useState(false);
const [isMoreDocs, setIsMoreDocs] = useState(true); const [isMoreDocs, setIsMoreDocs] = useState(true);
const [form, setForm] = useState(""); const [form, setForm] = useState("");
const [tourData, setTourData] = useState([]);
const [isDontShow, setIsDontShow] = useState(false);
const abortController = new AbortController(); const abortController = new AbortController();
const docPerPage = 10; const docPerPage = 10;
@@ -45,6 +49,9 @@ const Report = () => {
// eslint-disable-next-line // eslint-disable-next-line
}, [isNextRecord]); }, [isNextRecord]);
const handleDontShow = (isChecked) => {
setIsDontShow(isChecked);
};
const getReportData = async (skipUserRecord = 0, limit = 200) => { const getReportData = async (skipUserRecord = 0, limit = 200) => {
// setIsLoader(true); // setIsLoader(true);
const json = reportJson(id); const json = reportJson(id);
@@ -53,6 +60,7 @@ const Report = () => {
setHeading(json.heading); setHeading(json.heading);
setReportName(json.reportName); setReportName(json.reportName);
setForm(json.form); setForm(json.form);
setReportHelp(json?.helpMsg);
const currentUser = Parse.User.current().id; const currentUser = Parse.User.current().id;
const headers = { const headers = {
@@ -67,6 +75,42 @@ const Report = () => {
headers: headers, headers: headers,
signal: abortController.signal // is used to cancel fetch query 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") { if (id === "4Hhwbp482K") {
const listData = res.data?.result.filter((x) => x.Signers.length > 0); const listData = res.data?.result.filter((x) => x.Signers.length > 0);
let arr = []; let arr = [];
@@ -104,12 +148,14 @@ const Report = () => {
} else { } else {
setIsMoreDocs(false); setIsMoreDocs(false);
} }
setIsNextRecord(false); if (!res.data.result.error) {
setList((prevRecord) => setIsNextRecord(false);
prevRecord.length > 0 setList((prevRecord) =>
? [...prevRecord, ...res.data.result] prevRecord.length > 0
: res.data.result ? [...prevRecord, ...res.data.result]
); : res.data.result
);
}
} }
setIsLoader(false); setIsLoader(false);
} catch (err) { } catch (err) {
@@ -156,6 +202,9 @@ const Report = () => {
isMoreDocs={isMoreDocs} isMoreDocs={isMoreDocs}
docPerPage={docPerPage} docPerPage={docPerPage}
form={form} form={form}
report_help={reporthelp}
tourData={tourData}
isDontShow={isDontShow}
/> />
) : ( ) : (
<PageNotFound prefix={"Report"} /> <PageNotFound prefix={"Report"} />
+79 -38
View File
@@ -1,23 +1,29 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import Parse from "parse"; import Parse from "parse";
import axios from "axios"; import axios from "axios";
import Title from "../components/Title"; import Title from "../components/Title";
import { fetchAppInfo, showTenantName } from "../redux/actions"; import { useNavigate, NavLink, useLocation } from "react-router-dom";
import { useNavigate, NavLink } from "react-router-dom";
import login_img from "../assets/images/login_img.svg"; import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize"; import { useWindowSize } from "../hook/useWindowSize";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
const Signup = (props) => { 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 { width } = useWindowSize();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
const [name, setName] = useState(""); const [name, setName] = useState("");
const [phone, setPhone] = useState(""); const [phone, setPhone] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [company, setCompany] = useState(""); const [company, setCompany] = useState("");
const [jobTitle, setJobTitle] = useState(""); const [jobTitle, setJobTitle] = useState("");
const [image, setImage] = useState();
const [state, setState] = useState({ const [state, setState] = useState({
loading: false, loading: false,
alertType: "success", alertType: "success",
@@ -29,7 +35,6 @@ const Signup = (props) => {
const [specialCharValid, setSpecialCharValid] = useState(false); const [specialCharValid, setSpecialCharValid] = useState(false);
const togglePasswordVisibility = () => setShowPassword(!showPassword); const togglePasswordVisibility = () => setShowPassword(!showPassword);
const image = props.appInfo.applogo;
const clearStorage = async () => { const clearStorage = async () => {
if (Parse.User.current()) { if (Parse.User.current()) {
@@ -65,6 +70,19 @@ const Signup = (props) => {
localStorage.setItem("baseUrl", baseUrl); localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid); localStorage.setItem("parseAppId", appid);
}; };
const handleFreePlan = async (id) => {
try {
const params = { userId: id };
const res = await Parse.Cloud.run("freesubscription", params);
if (res.status === "error") {
alert(res.result);
}
} catch (err) {
console.log("err in free subscribe", err.message);
alert("Somenthing went wrong, please try again later!");
}
};
const handleSubmit = (event) => { const handleSubmit = (event) => {
event.preventDefault(); event.preventDefault();
if (lengthValid && caseDigitValid && specialCharValid) { if (lengthValid && caseDigitValid && specialCharValid) {
@@ -90,7 +108,7 @@ const Signup = (props) => {
res res
.then(async (r) => { .then(async (r) => {
if (r) { if (r) {
let roleData = props.appInfo.settings; let roleData = appInfo.settings;
if (roleData && roleData.length > 0) { if (roleData && roleData.length > 0) {
const params = { const params = {
userDetails: { userDetails: {
@@ -99,7 +117,7 @@ const Signup = (props) => {
name: name, name: name,
email: email, email: email,
phone: phone, phone: phone,
role: props.appInfo.defaultRole role: appInfo.defaultRole
} }
}; };
try { try {
@@ -108,7 +126,13 @@ const Signup = (props) => {
params params
); );
if (usersignup) { if (usersignup) {
handleNavigation(r.getSessionToken()); const param = new URLSearchParams(location.search);
const isFreeplan =
param?.get("subscription") === "freeplan";
if (isFreeplan) {
await handleFreePlan(r.id);
}
handleNavigation(r.getSessionToken(), isFreeplan);
} }
} catch (err) { } catch (err) {
alert(err.message); alert(err.message);
@@ -153,7 +177,7 @@ const Signup = (props) => {
} }
}; };
const handleNavigation = async (sessionToken) => { const handleNavigation = async (sessionToken, isFreeplan = false) => {
const baseUrl = localStorage.getItem("baseUrl"); const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId"); const parseAppId = localStorage.getItem("parseAppId");
const res = await axios.get(baseUrl + "users/me", { const res = await axios.get(baseUrl + "users/me", {
@@ -181,8 +205,8 @@ const Signup = (props) => {
// Check extended class user role and tenentId // Check extended class user role and tenentId
try { try {
let userRoles = []; let userRoles = [];
if (props.appInfo.settings) { if (appInfo.settings) {
let userSettings = props.appInfo.settings; let userSettings = appInfo.settings;
//Get Current user roles //Get Current user roles
let url = `${baseUrl}functions/UserGroups`; let url = `${baseUrl}functions/UserGroups`;
@@ -256,8 +280,8 @@ const Signup = (props) => {
} }
}); });
if (tenentInfo.length) { if (tenentInfo.length) {
props.showTenantName( dispatch(
tenentInfo[0].tenentName || "" showTenant(tenentInfo[0].tenentName || "")
); );
localStorage.setItem( localStorage.setItem(
"TenantName", "TenantName",
@@ -293,15 +317,15 @@ const Signup = (props) => {
tenentName: x.TenantId.TenantName || "" tenentName: x.TenantId.TenantName || ""
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "TenantId",
x.TenantId.objectId x.TenantId.objectId
); );
tenentInfo.push(obj); tenentInfo.push(obj);
} }
}); });
if (tenentInfo.length) { if (tenentInfo.length) {
props.showTenantName( dispatch(
tenentInfo[0].tenentName || "" showTenant(tenentInfo[0].tenentName || "")
); );
localStorage.setItem( localStorage.setItem(
"TenantName", "TenantName",
@@ -321,8 +345,14 @@ const Signup = (props) => {
element.pageType element.pageType
); );
setState({ loading: false }); setState({ loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) { if (isEnableSubscription) {
navigate(`/subscription`, { replace: true }); if (isFreeplan) {
navigate(
`/${element.pageType}/${element.pageId}`
);
} else {
navigate(`/subscription`, { replace: true });
}
} else { } else {
alert("Registered user successfully"); alert("Registered user successfully");
navigate( navigate(
@@ -339,8 +369,14 @@ const Signup = (props) => {
); );
localStorage.setItem("pageType", element.pageType); localStorage.setItem("pageType", element.pageType);
setState({ loading: false }); setState({ loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) { if (isEnableSubscription) {
navigate(`/subscription`, { replace: true }); if (isFreeplan) {
navigate(
`/${element.pageType}/${element.pageId}`
);
} else {
navigate(`/subscription`, { replace: true });
}
} else { } else {
navigate( navigate(
`/${element.pageType}/${element.pageId}` `/${element.pageType}/${element.pageId}`
@@ -433,10 +469,24 @@ const Signup = (props) => {
} }
}; };
useEffect(() => { useEffect(() => {
props.fetchAppInfo(); dispatch(fetchAppInfo());
saveLogo();
// eslint-disable-next-line // 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 handlePasswordChange = (e) => {
const newPassword = e.target.value; const newPassword = e.target.value;
setPassword(newPassword); setPassword(newPassword);
@@ -479,11 +529,13 @@ const Signup = (props) => {
<Title title={"Signup page"} /> <Title title={"Signup page"} />
{props.isloginVisible && props.isloginVisible ? ( {appInfo && appInfo.applogo ? (
<div> <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="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"> <div className="w-[250px] h-[66px] inline-block overflow-hidden">
<img src={image} width="100%" alt="" /> {image && (
<img src={image} className="object-contain h-full" alt="logo" />
)}
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2"> <div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
<div className=""> <div className="">
@@ -622,7 +674,7 @@ const Signup = (props) => {
</button> </button>
<NavLink <NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase" className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
to="/" to={location.search ? "/" + location.search : "/"}
style={width < 768 ? { textAlign: "center" } : {}} style={width < 768 ? { textAlign: "center" } : {}}
> >
Login Login
@@ -657,15 +709,4 @@ const Signup = (props) => {
); );
}; };
const mapStateToProps = (state) => { export default Signup;
if (Object.keys(state.appInfo).length !== 0) {
return { appInfo: state.appInfo, isloginVisible: true };
} else {
return { appInfo: state.appInfo, isloginVisible: false };
}
};
export default connect(mapStateToProps, {
fetchAppInfo,
showTenantName
})(Signup);
+62 -37
View File
@@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import { PDFDocument } from "pdf-lib"; import { PDFDocument } from "pdf-lib";
import "../styles/signature.css"; import "../styles/signature.css";
import { themeColor } from "../constant/const"; import { isEnableSubscription, themeColor } from "../constant/const";
import axios from "axios"; import axios from "axios";
import Loader from "../primitives/LoaderWithMsg"; import Loader from "../primitives/LoaderWithMsg";
import loader from "../assets/images/loader2.gif"; import loader from "../assets/images/loader2.gif";
@@ -26,7 +26,10 @@ import {
contactBook, contactBook,
randomId, randomId,
getDate, getDate,
textInputWidget textWidget,
getTenantDetails,
checkIsSubscribed,
convertPdfArrayBuffer
} from "../constant/Utils"; } from "../constant/Utils";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import Tour from "reactour"; import Tour from "reactour";
@@ -67,7 +70,6 @@ function SignYourSelf() {
const [pdfOriginalWidth, setPdfOriginalWidth] = useState(); const [pdfOriginalWidth, setPdfOriginalWidth] = useState();
const [successEmail, setSuccessEmail] = useState(false); const [successEmail, setSuccessEmail] = useState(false);
const imageRef = useRef(null); const imageRef = useRef(null);
const [documentStatus, setDocumentStatus] = useState({ isCompleted: false });
const [myInitial, setMyInitial] = useState(""); const [myInitial, setMyInitial] = useState("");
const [isInitial, setIsInitial] = useState(false); const [isInitial, setIsInitial] = useState(false);
const [isUiLoading, setIsUiLoading] = useState(false); const [isUiLoading, setIsUiLoading] = useState(false);
@@ -90,7 +92,7 @@ function SignYourSelf() {
const [showAlreadySignDoc, setShowAlreadySignDoc] = useState({ const [showAlreadySignDoc, setShowAlreadySignDoc] = useState({
status: false status: false
}); });
const [currWidgetsDetails, setCurrWidgetsDetails] = useState([]); const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
const [isCheckbox, setIsCheckbox] = useState(false); const [isCheckbox, setIsCheckbox] = useState(false);
const [widgetType, setWidgetType] = useState(""); const [widgetType, setWidgetType] = useState("");
const [pdfLoadFail, setPdfLoadFail] = useState({ const [pdfLoadFail, setPdfLoadFail] = useState({
@@ -99,6 +101,9 @@ function SignYourSelf() {
}); });
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" }); const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isDontShow, setIsDontShow] = useState(false); const [isDontShow, setIsDontShow] = useState(false);
const [extUserId, setExtUserId] = useState("");
const [isCompleted, setIsCompleted] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
const divRef = useRef(null); const divRef = useRef(null);
const nodeRef = useRef(null); const nodeRef = useRef(null);
const [, drop] = useDrop({ const [, drop] = useDrop({
@@ -180,19 +185,24 @@ function SignYourSelf() {
//function for get document details for perticular signer with signer'object id //function for get document details for perticular signer with signer'object id
const getDocumentDetails = async (showComplete) => { const getDocumentDetails = async (showComplete) => {
let isCompleted;
//getting document details //getting document details
const documentData = await contractDocument(documentId); const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) { if (documentData && documentData.length > 0) {
setPdfDetails(documentData); setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const isCompleted = const url = documentData[0] && documentData[0]?.URL;
documentData[0].IsCompleted && documentData[0].IsCompleted; //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) { if (isCompleted) {
const docStatus = { setIsCompleted(true);
isCompleted: isCompleted
};
setDocumentStatus(docStatus);
setPdfUrl(documentData[0].SignedUrl); setPdfUrl(documentData[0].SignedUrl);
const alreadySign = { const alreadySign = {
status: true, status: true,
@@ -255,16 +265,13 @@ function SignYourSelf() {
setHandleError("Error: Something went wrong!"); setHandleError("Error: Something went wrong!");
setIsLoading(loadObj); setIsLoading(loadObj);
}); });
const contractUsersRes = await contractUsers(jsonSender.email); const contractUsersRes = await contractUsers(jsonSender.email);
if (contractUsersRes[0] && contractUsersRes.length > 0) { if (contractUsersRes[0] && contractUsersRes.length > 0) {
setContractName("_Users"); setContractName("_Users");
setSignerUserId(contractUsersRes[0].objectId); setSignerUserId(contractUsersRes[0].objectId);
const tourstatuss = const tourstatuss =
contractUsersRes[0].TourStatus && contractUsersRes[0].TourStatus; contractUsersRes[0].TourStatus && contractUsersRes[0].TourStatus;
if (tourstatuss && tourstatuss.length > 0 && !isCompleted) {
if (tourstatuss && tourstatuss.length > 0) {
setTourStatus(tourstatuss); setTourStatus(tourstatuss);
const checkTourRecipients = tourstatuss.filter( const checkTourRecipients = tourstatuss.filter(
(data) => data.signyourself (data) => data.signyourself
@@ -272,6 +279,8 @@ function SignYourSelf() {
if (checkTourRecipients && checkTourRecipients.length > 0) { if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].signyourself); setCheckTourStatus(checkTourRecipients[0].signyourself);
} }
} else {
setCheckTourStatus(true);
} }
const loadObj = { const loadObj = {
isLoad: false isLoad: false
@@ -291,7 +300,8 @@ function SignYourSelf() {
const tourstatuss = const tourstatuss =
contractContactBook[0].TourStatus && contractContactBook[0].TourStatus &&
contractContactBook[0].TourStatus; contractContactBook[0].TourStatus;
if (tourstatuss && tourstatuss.length > 0) {
if (tourstatuss && tourstatuss.length > 0 && !isCompleted) {
setTourStatus(tourstatuss); setTourStatus(tourstatuss);
const checkTourRecipients = tourstatuss.filter( const checkTourRecipients = tourstatuss.filter(
(data) => data.signyourself (data) => data.signyourself
@@ -299,6 +309,8 @@ function SignYourSelf() {
if (checkTourRecipients && checkTourRecipients.length > 0) { if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].signyourself); setCheckTourStatus(checkTourRecipients[0].signyourself);
} }
} else {
setCheckTourStatus(true);
} }
} else { } else {
setHandleError("No Data Found!"); setHandleError("No Data Found!");
@@ -342,8 +354,7 @@ function SignYourSelf() {
return { return {
name: "checkbox" name: "checkbox"
}; };
case textWidget:
case textInputWidget:
return { return {
name: "text" name: "text"
}; };
@@ -528,7 +539,6 @@ function SignYourSelf() {
checkSigned = requiredWidgets[i]?.options?.response; checkSigned = requiredWidgets[i]?.options?.response;
if (!checkSigned) { if (!checkSigned) {
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl; const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
let checkDefaultSigned = requiredWidgets[i]?.options?.defaultValue; let checkDefaultSigned = requiredWidgets[i]?.options?.defaultValue;
if (!checkSignUrl) { if (!checkSignUrl) {
if (!checkDefaultSigned) { if (!checkDefaultSigned) {
@@ -559,22 +569,14 @@ function SignYourSelf() {
setIsCeleb(false); setIsCeleb(false);
}, 3000); }, 3000);
setIsUiLoading(true); setIsUiLoading(true);
const url = pdfDetails[0] && pdfDetails[0].URL; const existingPdfBytes = pdfArrayBuffer;
const existingPdfBytes = await fetch(url).then((res) =>
res.arrayBuffer()
);
// Load a PDFDocument from the existing PDF bytes // Load a PDFDocument from the existing PDF bytes
const pdfDoc = await PDFDocument.load(existingPdfBytes, { const pdfDoc = await PDFDocument.load(existingPdfBytes, {
ignoreEncryption: true ignoreEncryption: true
}); });
const isSignYourSelfFlow = true;
const flag = true;
const extUserPtr = pdfDetails[0].ExtUserPtr; const extUserPtr = pdfDetails[0].ExtUserPtr;
const HeaderDocId = extUserPtr?.HeaderDocId; const HeaderDocId = extUserPtr?.HeaderDocId;
//embed document's object id to all pages in pdf document //embed document's object id to all pages in pdf document
if (!HeaderDocId) { if (!HeaderDocId) {
await embedDocId(pdfDoc, documentId, allPages); await embedDocId(pdfDoc, documentId, allPages);
@@ -584,19 +586,36 @@ function SignYourSelf() {
xyPostion, xyPostion,
pdfDoc, pdfDoc,
pdfOriginalWidth, pdfOriginalWidth,
flag, isSignYourSelfFlow,
containerWH containerWH
); );
// console.log('pdf',pdfBytes) // console.log("pdf", pdfBytes);
//function for call to embed signature in pdf and get digital signature pdf //function for call to embed signature in pdf and get digital signature pdf
await signPdfFun(pdfBytes, documentId); await signPdfFun(pdfBytes, documentId);
} }
} }
// console.log("signyourself", xyPostion);
//function for get digital signature //function for get digital signature
const signPdfFun = async (base64Url, documentId) => { 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 = { let singleSign = {
pdfFile: base64Url, pdfFile: base64Url,
docId: documentId docId: documentId,
isCustomCompletionMail: isCustomCompletionMail
}; };
await axios await axios
@@ -705,7 +724,7 @@ function SignYourSelf() {
}; };
//function for save button to save signature or image url //function for save button to save signature or image url
const saveSign = (isDefaultSign, width, height) => { const saveSign = (type, isDefaultSign, width, height) => {
const isTypeText = width && height ? true : false; const isTypeText = width && height ? true : false;
const signatureImg = isDefaultSign const signatureImg = isDefaultSign
? isDefaultSign === "initials" ? isDefaultSign === "initials"
@@ -728,6 +747,7 @@ function SignYourSelf() {
} }
} }
const getUpdatePosition = onSaveSign( const getUpdatePosition = onSaveSign(
type,
xyPostion, xyPostion,
index, index,
signKey, signKey,
@@ -764,6 +784,7 @@ function SignYourSelf() {
//function for delete signature block //function for delete signature block
const handleDeleteSign = (key) => { const handleDeleteSign = (key) => {
setCurrWidgetsDetails({});
const updateResizeData = []; const updateResizeData = [];
let filterData = xyPostion[index].pos.filter((data) => data.key !== key); let filterData = xyPostion[index].pos.filter((data) => data.key !== key);
@@ -980,8 +1001,9 @@ function SignYourSelf() {
{/* this component used to render all pdf pages in left side */} {/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage <RenderAllPdfPage
pdfUrl={pdfUrl} signPdfUrl={
signPdfUrl={pdfDetails[0] && pdfDetails[0].URL} pdfDetails[0] && (pdfDetails[0].SignedUrl || pdfDetails[0].URL)
}
allPages={allPages} allPages={allPages}
setAllPages={setAllPages} setAllPages={setAllPages}
setPageNumber={setPageNumber} setPageNumber={setPageNumber}
@@ -1102,6 +1124,8 @@ function SignYourSelf() {
setIsInitial={setIsInitial} setIsInitial={setIsInitial}
setIsStamp={setIsStamp} setIsStamp={setIsStamp}
widgetType={widgetType} widgetType={widgetType}
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
/> />
{/*render email component to send email after finish signature on document */} {/*render email component to send email after finish signature on document */}
<EmailComponent <EmailComponent
@@ -1113,6 +1137,7 @@ function SignYourSelf() {
setSuccessEmail={setSuccessEmail} setSuccessEmail={setSuccessEmail}
sender={jsonSender} sender={jsonSender}
setIsAlert={setIsAlert} setIsAlert={setIsAlert}
extUserId={extUserId}
/> />
{/* pdf header which contain funish back button */} {/* pdf header which contain funish back button */}
<Header <Header
@@ -1120,7 +1145,6 @@ function SignYourSelf() {
allPages={allPages} allPages={allPages}
changePage={changePage} changePage={changePage}
pdfUrl={pdfUrl} pdfUrl={pdfUrl}
documentStatus={documentStatus}
embedWidgetsData={embedWidgetsData} embedWidgetsData={embedWidgetsData}
pdfDetails={pdfDetails} pdfDetails={pdfDetails}
isShowHeader={true} isShowHeader={true}
@@ -1128,6 +1152,7 @@ function SignYourSelf() {
alreadySign={pdfUrl ? true : false} alreadySign={pdfUrl ? true : false}
isSignYourself={true} isSignYourself={true}
setIsEmail={setIsEmail} setIsEmail={setIsEmail}
isCompleted={isCompleted}
/> />
<div data-tut="reactourSecond"> <div data-tut="reactourSecond">
@@ -1180,7 +1205,7 @@ function SignYourSelf() {
}} }}
className="autoSignScroll" className="autoSignScroll"
> >
{!documentStatus.isCompleted ? ( {!isCompleted ? (
<div> <div>
<WidgetComponent <WidgetComponent
dataTut="reactourFirst" dataTut="reactourFirst"
+76 -13
View File
@@ -3,7 +3,7 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate } from "react-router-dom";
import axios from "axios"; import axios from "axios";
import "../styles/signature.css"; import "../styles/signature.css";
import { themeColor } from "../constant/const"; import { isEnableSubscription, themeColor } from "../constant/const";
import { DndProvider } from "react-dnd"; import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend"; import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd"; import { useDrag, useDrop } from "react-dnd";
@@ -23,7 +23,8 @@ import {
defaultWidthHeight, defaultWidthHeight,
addWidgetOptions, addWidgetOptions,
textInputWidget, textInputWidget,
radioButtonWidget radioButtonWidget,
fetchSubscription
} from "../constant/Utils"; } from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf"; import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css"; import "../styles/AddUser.css";
@@ -52,6 +53,7 @@ const TemplatePlaceholder = () => {
const [isSelectListId, setIsSelectId] = useState(); const [isSelectListId, setIsSelectId] = useState();
const [isSendAlert, setIsSendAlert] = useState(false); const [isSendAlert, setIsSendAlert] = useState(false);
const [isCreateDocModal, setIsCreateDocModal] = useState(false); const [isCreateDocModal, setIsCreateDocModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isLoading, setIsLoading] = useState({ const [isLoading, setIsLoading] = useState({
isLoad: true, isLoad: true,
message: "This might take some time" message: "This might take some time"
@@ -77,6 +79,7 @@ const TemplatePlaceholder = () => {
const [showDropdown, setShowDropdown] = useState(false); const [showDropdown, setShowDropdown] = useState(false);
const [widgetType, setWidgetType] = useState(""); const [widgetType, setWidgetType] = useState("");
const [isRadio, setIsRadio] = useState(false); const [isRadio, setIsRadio] = useState(false);
const [blockColor, setBlockColor] = useState("");
const [selectWidgetId, setSelectWidgetId] = useState(""); const [selectWidgetId, setSelectWidgetId] = useState("");
const [isNameModal, setIsNameModal] = useState(false); const [isNameModal, setIsNameModal] = useState(false);
const [pdfLoadFail, setPdfLoadFail] = useState({ const [pdfLoadFail, setPdfLoadFail] = useState({
@@ -142,6 +145,7 @@ const TemplatePlaceholder = () => {
const [currWidgetsDetails, setCurrWidgetsDetails] = useState([]); const [currWidgetsDetails, setCurrWidgetsDetails] = useState([]);
const [isCheckbox, setIsCheckbox] = useState(false); const [isCheckbox, setIsCheckbox] = useState(false);
const [widgetName, setWidgetName] = useState(false); const [widgetName, setWidgetName] = useState(false);
const [isAddRole, setIsAddRole] = useState(false);
const senderUser = const senderUser =
localStorage.getItem( localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser` `Parse/${localStorage.getItem("parseAppId")}/currentUser`
@@ -167,7 +171,23 @@ const TemplatePlaceholder = () => {
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current]); }, [divRef.current]);
async function checkIsSubscribed() {
const res = await fetchSubscription();
const freeplan = res.plan;
const billingDate = res.billingDate;
if (freeplan === "freeplan") {
return true;
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
setIsSubscribe(true);
return true;
} else {
navigate(`/subscription`);
}
} else {
navigate(`/subscription`);
}
}
// `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present // `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
const fetchTemplate = async () => { const fetchTemplate = async () => {
try { try {
@@ -189,6 +209,9 @@ const TemplatePlaceholder = () => {
: []; : [];
if (documentData && documentData.length > 0) { if (documentData && documentData.length > 0) {
if (isEnableSubscription) {
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
}
setPdfDetails(documentData); setPdfDetails(documentData);
setIsSigners(true); setIsSigners(true);
if (documentData[0].Signers && documentData[0].Signers.length > 0) { if (documentData[0].Signers && documentData[0].Signers.length > 0) {
@@ -223,6 +246,7 @@ const TemplatePlaceholder = () => {
}); });
setSignersData(updatedSigners); setSignersData(updatedSigners);
setUniqueId(updatedSigners[0].Id); setUniqueId(updatedSigners[0].Id);
setBlockColor(updatedSigners[0].blockColor);
} else { } else {
const updatedSigners = documentData[0].Signers.map((x, index) => ({ const updatedSigners = documentData[0].Signers.map((x, index) => ({
...x, ...x,
@@ -231,6 +255,7 @@ const TemplatePlaceholder = () => {
})); }));
setSignersData(updatedSigners); setSignersData(updatedSigners);
setUniqueId(updatedSigners[0].Id); setUniqueId(updatedSigners[0].Id);
setBlockColor(updatedSigners[0].blockColor);
} }
} else { } else {
setRoleName("User 1"); setRoleName("User 1");
@@ -249,6 +274,7 @@ const TemplatePlaceholder = () => {
setUniqueId(updatedSigners[0].Id); setUniqueId(updatedSigners[0].Id);
setSignersData(updatedSigners); setSignersData(updatedSigners);
setIsSelectId(0); setIsSelectId(0);
setBlockColor(updatedSigners[0].blockColor);
} }
} }
} else if ( } else if (
@@ -283,10 +309,10 @@ const TemplatePlaceholder = () => {
if (tourstatus && tourstatus.length > 0) { if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus); setTourStatus(tourstatus);
const checkTourRecipients = tourstatus.filter( const checkTourRecipients = tourstatus.filter(
(data) => data.templatetour (data) => data.templateTour
); );
if (checkTourRecipients && checkTourRecipients.length > 0) { if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].templatetour); setCheckTourStatus(checkTourRecipients[0].templateTour);
} }
} }
const loadObj = { const loadObj = {
@@ -456,9 +482,20 @@ const TemplatePlaceholder = () => {
} else { } else {
setIsReceipent(false); 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 //function for get pdf page details
const pageDetails = async (pdf) => { const pageDetails = async (pdf) => {
const load = { const load = {
@@ -737,7 +774,7 @@ const TemplatePlaceholder = () => {
selector: '[data-tut="reactourSecond"]', selector: '[data-tut="reactourSecond"]',
content: () => ( content: () => (
<TourContentWithBtn <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} isChecked={handleDontShow}
/> />
), ),
@@ -759,7 +796,7 @@ const TemplatePlaceholder = () => {
selector: '[data-tut="reactourFour"]', selector: '[data-tut="reactourFour"]',
content: () => ( content: () => (
<TourContentWithBtn <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} isChecked={handleDontShow}
/> />
), ),
@@ -777,15 +814,15 @@ const TemplatePlaceholder = () => {
if (tourStatus.length > 0) { if (tourStatus.length > 0) {
updatedTourStatus = [...tourStatus]; updatedTourStatus = [...tourStatus];
const templatetourIndex = tourStatus.findIndex( const templatetourIndex = tourStatus.findIndex(
(obj) => obj["templatetour"] === false || obj["templatetour"] === true (obj) => obj["templateTour"] === false || obj["templateTour"] === true
); );
if (templatetourIndex !== -1) { if (templatetourIndex !== -1) {
updatedTourStatus[templatetourIndex] = { templatetour: true }; updatedTourStatus[templatetourIndex] = { templateTour: true };
} else { } else {
updatedTourStatus.push({ templatetour: true }); updatedTourStatus.push({ templateTour: true });
} }
} else { } else {
updatedTourStatus = [{ templatetour: true }]; updatedTourStatus = [{ templateTour: true }];
} }
await axios await axios
.put( .put(
@@ -854,6 +891,7 @@ const TemplatePlaceholder = () => {
setIsModalRole(false); setIsModalRole(false);
setRoleName(""); setRoleName("");
setUniqueId(Id); setUniqueId(Id);
setBlockColor(color[index]);
setIsSelectId(index); setIsSelectId(index);
setIsMailSend(false); setIsMailSend(false);
}; };
@@ -870,9 +908,11 @@ const TemplatePlaceholder = () => {
if (index === signersdata.length - 1) { if (index === signersdata.length - 1) {
setUniqueId(updateSigner[updateSigner.length - 1]?.Id || ""); setUniqueId(updateSigner[updateSigner.length - 1]?.Id || "");
setIsSelectId(index - 1 || 0); setIsSelectId(index - 1 || 0);
setBlockColor(color[index - 1 || 0]);
} else { } else {
setUniqueId(updateSigner[index]?.Id || ""); setUniqueId(updateSigner[index]?.Id || "");
setIsSelectId(index); setIsSelectId(index);
setBlockColor(color[index]);
} }
setSignerPos(updatePlaceholderUser); setSignerPos(updatePlaceholderUser);
@@ -1190,6 +1230,15 @@ const TemplatePlaceholder = () => {
closeWithMask={false} 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 */} {/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage <RenderAllPdfPage
signPdfUrl={pdfDetails[0].URL} signPdfUrl={pdfDetails[0].URL}
@@ -1215,7 +1264,7 @@ const TemplatePlaceholder = () => {
handleClose={() => setIsSendAlert(false)} handleClose={() => setIsSendAlert(false)}
> >
<div style={{ height: "100%", padding: 20 }}> <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> </div>
</ModalUi> </ModalUi>
<ModalUi <ModalUi
@@ -1316,6 +1365,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe}
/> />
<DropdownWidgetOption <DropdownWidgetOption
type="checkbox" type="checkbox"
@@ -1326,6 +1376,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe}
/> />
<DropdownWidgetOption <DropdownWidgetOption
type="dropdown" type="dropdown"
@@ -1336,6 +1387,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe}
/> />
<PlaceholderCopy <PlaceholderCopy
isPageCopy={isPageCopy} isPageCopy={isPageCopy}
@@ -1443,6 +1495,10 @@ const TemplatePlaceholder = () => {
title={"Roles"} title={"Roles"}
initial={true} initial={true}
isTemplateFlow={true} isTemplateFlow={true}
sendInOrder={pdfDetails[0].SendinOrder}
setSignersData={setSignersData}
blockColor={blockColor}
setBlockColor={setBlockColor}
/> />
</div> </div>
) : ( ) : (
@@ -1453,6 +1509,7 @@ const TemplatePlaceholder = () => {
className="autoSignScroll" className="autoSignScroll"
> >
<SignerListPlace <SignerListPlace
isMailSend={isMailSend}
signerPos={signerPos} signerPos={signerPos}
signersdata={signersdata} signersdata={signersdata}
isSelectListId={isSelectListId} isSelectListId={isSelectListId}
@@ -1466,10 +1523,14 @@ const TemplatePlaceholder = () => {
handleRoleChange={handleRoleChange} handleRoleChange={handleRoleChange}
handleOnBlur={handleOnBlur} handleOnBlur={handleOnBlur}
title={"Roles"} title={"Roles"}
sendInOrder={pdfDetails[0]?.SendinOrder}
setSignersData={setSignersData}
blockColor={blockColor}
setBlockColor={setBlockColor}
/> />
<div data-tut="reactourSecond"> <div data-tut="reactourSecond">
<WidgetComponent <WidgetComponent
pdfUrl={isMailSend} isMailSend={isMailSend}
dragSignature={dragSignature} dragSignature={dragSignature}
signRef={signRef} signRef={signRef}
handleDivClick={handleDivClick} handleDivClick={handleDivClick}
@@ -1508,6 +1569,7 @@ const TemplatePlaceholder = () => {
isAddUser={isAddUser} isAddUser={isAddUser}
uniqueId={uniqueId} uniqueId={uniqueId}
closePopup={closePopup} closePopup={closePopup}
signersData={signersdata}
/> />
</div> </div>
<ModalUi <ModalUi
@@ -1526,6 +1588,7 @@ const TemplatePlaceholder = () => {
isOpen={isNameModal} isOpen={isNameModal}
handleClose={handleNameModal} handleClose={handleNameModal}
handleData={handleWidgetdefaultdata} handleData={handleWidgetdefaultdata}
isSubscribe={isSubscribe}
/> />
</div> </div>
); );
+99 -12
View File
@@ -6,10 +6,16 @@ import dp from "../assets/images/dp.png";
import Title from "../components/Title"; import Title from "../components/Title";
import sanitizeFileName from "../primitives/sanitizeFileName"; import sanitizeFileName from "../primitives/sanitizeFileName";
import axios from "axios"; import axios from "axios";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribed } from "../constant/Utils";
import Upgrade from "../primitives/Upgrade";
function UserProfile() { function UserProfile() {
const navigate = useNavigate(); const navigate = useNavigate();
let UserProfile = JSON.parse(localStorage.getItem("UserInformation")); let UserProfile = JSON.parse(localStorage.getItem("UserInformation"));
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl")); const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId")); const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [editmode, setEditMode] = useState(false); const [editmode, setEditMode] = useState(false);
@@ -19,15 +25,30 @@ function UserProfile() {
const [isLoader, setIsLoader] = useState(false); const [isLoader, setIsLoader] = useState(false);
const [percentage, setpercentage] = useState(0); const [percentage, setpercentage] = useState(0);
const [isDisableDocId, setIsDisableDocId] = useState(false); 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(() => { useEffect(() => {
getUserDetail();
}, []);
const getUserDetail = async () => {
const extClass = localStorage.getItem("Extand_Class"); const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass); const jsonSender = JSON.parse(extClass);
const HeaderDocId = jsonSender[0]?.HeaderDocId; const HeaderDocId = jsonSender[0]?.HeaderDocId;
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
if (HeaderDocId) { if (HeaderDocId) {
setIsDisableDocId(HeaderDocId); setIsDisableDocId(HeaderDocId);
} }
}, []); };
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
setIsLoader(true); setIsLoader(true);
@@ -52,6 +73,7 @@ function UserProfile() {
SetPhone(res.phone); SetPhone(res.phone);
setImage(res.ProfilePic); setImage(res.ProfilePic);
localStorage.setItem("username", res.name); localStorage.setItem("username", res.name);
localStorage.setItem("profileImg", res.ProfilePic);
await updateExtUser({ Name: res.name, Phone: res.phone }); await updateExtUser({ Name: res.name, Phone: res.phone });
alert("Profile updated successfully."); alert("Profile updated successfully.");
setEditMode(false); setEditMode(false);
@@ -79,7 +101,9 @@ function UserProfile() {
const body = { const body = {
Phone: obj.Phone, Phone: obj.Phone,
Name: obj.Name, Name: obj.Name,
HeaderDocId: isDisableDocId HeaderDocId: isDisableDocId,
JobTitle: jobTitle,
Company: company
}; };
await axios.put( await axios.put(
parseBaseUrl + "classes/" + extClass + "/" + ExtUserId, parseBaseUrl + "classes/" + extClass + "/" + ExtUserId,
@@ -133,9 +157,9 @@ function UserProfile() {
// console.log("File URL:", response.url()); // console.log("File URL:", response.url());
if (response.url()) { if (response.url()) {
setImage(response.url()); setImage(response.url());
localStorage.setItem("profileImg", response.url());
setpercentage(0); setpercentage(0);
SaveFileSize(size, response.url()); const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url(); return response.url();
} }
} catch (error) { } catch (error) {
@@ -153,7 +177,6 @@ function UserProfile() {
const handleDisableDocId = () => { const handleDisableDocId = () => {
setIsDisableDocId((prevChecked) => !prevChecked); setIsDisableDocId((prevChecked) => !prevChecked);
}; };
return ( return (
<React.Fragment> <React.Fragment>
<Title title={"Profile"} /> <Title title={"Profile"} />
@@ -250,7 +273,41 @@ function UserProfile() {
<span className="font-semibold">Email:</span>{" "} <span className="font-semibold">Email:</span>{" "}
<span>{UserProfile && UserProfile.email}</span> <span>{UserProfile && UserProfile.email}</span>
</li> </li>
<li className="flex justify-between items-center border-y-[1px] border-gray-300 py-2 break-all"> <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 className="font-semibold">Is Email verified:</span>{" "}
<span> <span>
{UserProfile && UserProfile.emailVerified {UserProfile && UserProfile.emailVerified
@@ -258,10 +315,33 @@ function UserProfile() {
: "Not verified"} : "Not verified"}
</span> </span>
</li> </li>
<li className="flex justify-between items-center border-y-[1px] border-gray-300 py-2 break-all"> <li className="border-y-[1px] border-gray-300 break-all">
<span className="font-semibold">Disable DocumentId :</span>{" "} <div className="flex justify-between items-center py-2">
<span> <span
<label className="relative inline-flex items-center cursor-pointer mb-0"> className={
isSubscribe || !isEnableSubscription
? "font-semibold"
: "font-semibold text-gray-300"
}
>
Disable DocumentId :{" "}
<Tooltip
url={
"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"
}
isSubscribe={isSubscribe}
/>{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>{" "}
<label
className={
isSubscribe || !isEnableSubscription
? `${
editmode ? "cursor-pointer" : ""
} relative inline-flex items-center mb-0`
: "relative inline-flex items-center mb-0 pointer-events-none opacity-50"
}
>
<input <input
disabled={editmode ? false : true} disabled={editmode ? false : true}
checked={isDisableDocId} checked={isDisableDocId}
@@ -270,9 +350,16 @@ function UserProfile() {
value="" value=""
className="sr-only peer" className="sr-only peer"
/> />
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div> <div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-1 peer-focus:ring-black rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-black peer-checked:bg-blue-600"></div>
</label> </label>
</span> </div>
{!isEnableSubscription && (
<PremiumAlertHeader
message={
"Disable documentId is free in beta, this feature will incur a fee later."
}
/>
)}
</li> </li>
</ul> </ul>
<div className="flex justify-center pt-2 pb-3 md:pt-3 md:pb-4"> <div className="flex justify-center pt-2 pb-3 md:pt-3 md:pb-4">
+116 -52
View File
@@ -1,13 +1,17 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Title from "../components/Title"; import Title from "../components/Title";
import axios from "axios"; import axios from "axios";
import { useNavigate } from "react-router-dom";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import { rejectBtn, submitBtn } from "../constant/const"; import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { openInNewTab } from "../constant/Utils"; import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import Parse from "parse"; import Parse from "parse";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
function Webhook() { function Webhook() {
const navigation = useNavigate();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl")); const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId")); const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [webhook, setWebhook] = useState(); const [webhook, setWebhook] = useState();
@@ -15,6 +19,8 @@ function Webhook() {
const [isGenerate, setIsGenerate] = useState(false); const [isGenerate, setIsGenerate] = useState(false);
const [isErr, setIsErr] = useState(false); const [isErr, setIsErr] = useState(false);
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [error, setError] = useState("");
useEffect(() => { useEffect(() => {
fetchWebhook(); fetchWebhook();
@@ -23,6 +29,10 @@ function Webhook() {
const fetchWebhook = async () => { const fetchWebhook = async () => {
const email = Parse.User.current().getEmail(); const email = Parse.User.current().getEmail();
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
const params = { email: email }; const params = { email: email };
try { try {
const extRes = await Parse.Cloud.run("getUserDetails", params); const extRes = await Parse.Cloud.run("getUserDetails", params);
@@ -38,41 +48,48 @@ function Webhook() {
}; };
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
setIsLoader(true); if (webhook && webhook.startsWith("https://")) {
setIsModal(false); setIsLoader(true);
try { setIsModal(false);
const params = { url: webhook }; try {
const url = parseBaseUrl + "functions/savewebhook"; const params = { url: webhook };
const headers = { const url = parseBaseUrl + "functions/savewebhook";
"Content-Type": "application/json", const headers = {
"X-Parse-Application-Id": parseAppId, "Content-Type": "application/json",
sessiontoken: localStorage.getItem("accesstoken") "X-Parse-Application-Id": parseAppId,
}; sessiontoken: localStorage.getItem("accesstoken")
await axios.post(url, params, { headers: headers }).then((res) => { };
if (res.data && res.data.result && res.data.result.Webhook) { await axios.post(url, params, { headers: headers }).then((res) => {
setWebhook(res.data.result.Webhook); if (res.data && res.data.result && res.data.result.Webhook) {
setIsGenerate(true); setWebhook(res.data.result.Webhook);
setTimeout(() => { setIsGenerate(true);
setIsGenerate(false); setTimeout(() => {
}, 1500); setIsGenerate(false);
setIsLoader(false); }, 1500);
} else { setIsLoader(false);
console.error("Error while generating webhook"); } else {
setIsLoader(false); console.error("Error while generating webhook");
setIsErr(true); setIsLoader(false);
setTimeout(() => { setIsErr(true);
setIsErr(false); setTimeout(() => {
}, 1500); setIsErr(false);
} }, 1500);
}); }
} catch (error) { });
setIsLoader(false); } catch (error) {
setIsErr(true); setIsLoader(false);
setTimeout(() => { setIsErr(true);
setIsErr(false); setTimeout(() => {
}, 1500); setIsErr(false);
}, 1500);
console.log("err", error); console.log("err", error);
}
} else {
setError("The webhook url should always start with https://");
setTimeout(() => {
setError("");
}, 1500);
} }
}; };
@@ -82,6 +99,7 @@ function Webhook() {
<Title title={"Webhook"} /> <Title title={"Webhook"} />
{isGenerate && <Alert type="success">Webhook added successfully!</Alert>} {isGenerate && <Alert type="success">Webhook added successfully!</Alert>}
{isErr && <Alert type="danger">Something went wrong!</Alert>} {isErr && <Alert type="danger">Something went wrong!</Alert>}
{isLoader ? ( {isLoader ? (
<div <div
style={{ style={{
@@ -101,25 +119,41 @@ function Webhook() {
</div> </div>
) : ( ) : (
<div className="bg-white flex flex-col justify-center shadow rounded"> <div className="bg-white flex flex-col justify-center shadow rounded">
<h1 className="ml-4 mt-3 mb-2 font-semibold">Webhook</h1> {!isEnableSubscription && <PremiumAlertHeader />}
<ul className="w-full flex flex-col p-2 text-sm"> <h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
OpenSign Webhook{" "}
<Tooltip
url={"https://docs.opensignlabs.com/docs/API-docs/get-webhook"}
isSubscribe={true}
/>
</h1>
<ul
className={
isSubscribe || !isEnableSubscription
? "w-full flex flex-col p-2 text-sm "
: "w-full flex flex-col p-2 text-sm opacity-20 pointer-events-none select-none"
}
>
<li <li
className={`flex justify-between items-center border-y-[1px] border-gray-300 break-all py-2`} className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
> >
<span className="w-[40%]">Webhook:</span>{" "} <div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
<span id="token" className="w-[60%] md:text-end cursor-pointer"> <span className="">Webhook:</span>{" "}
{webhook && webhook} <span id="token" className=" md:text-end cursor-pointer">
</span> {webhook ? webhook : "_____"}
</span>
</div>
<button
type="button"
onClick={handleModal}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
>
{webhook ? "Update Webhook" : "Add Webhook"}
</button>
</li> </li>
</ul> </ul>
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
<button <div className="flex items-center justify-center ">
type="button"
onClick={handleModal}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
>
{webhook ? "Update Webhook" : "Add Webhook"}
</button>
<button <button
type="button" type="button"
onClick={() => onClick={() =>
@@ -127,17 +161,47 @@ function Webhook() {
"https://docs.opensignlabs.com/docs/API-docs/save-update-webhook" "https://docs.opensignlabs.com/docs/API-docs/save-update-webhook"
) )
} }
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none" className="rounded hover:bg-[#15b4e9] border-[1px] my-2 border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
> >
View Docs View Docs
</button> </button>
</div> </div>
{!isSubscribe && isEnableSubscription && (
<>
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
Upgrade to PRO Plan
</h1>
<ul className={"w-full flex flex-col p-2 text-sm "}>
<li
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
>
<div className="w-[70%] flex-col md:flex-row flex items-center gap-3 ">
<span className="">$29.99/month:</span>{" "}
<span id="token" className=" md:text-end cursor-pointer">
First 100 documents included, then just $0.15 per
document.
</span>
</div>
{!isSubscribe && isEnableSubscription && (
<button
type="button"
onClick={() => navigation("/subscription")}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
>
Upgrade Now
</button>
)}
</li>
</ul>
</>
)}
<ModalUi <ModalUi
isOpen={isModal} isOpen={isModal}
title={"Regenerate Token"} title={"Regenerate Token"}
handleClose={handleModal} handleClose={handleModal}
> >
{error && <Alert type="danger">{error}</Alert>}
<div className="m-[20px]"> <div className="m-[20px]">
<div className="text-lg font-normal text-black"> <div className="text-lg font-normal text-black">
<label className="text-sm ml-2">Webhook</label> <label className="text-sm ml-2">Webhook</label>
+2 -2
View File
@@ -54,11 +54,11 @@ const AddUser = (props) => {
contactQuery.set("Email", email); contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest"); contactQuery.set("UserRole", "contracts_Guest");
if (localStorage.getItem("TenetId")) { if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", { contactQuery.set("TenantId", {
__type: "Pointer", __type: "Pointer",
className: "partners_Tenant", className: "partners_Tenant",
objectId: localStorage.getItem("TenetId") objectId: localStorage.getItem("TenantId")
}); });
} }
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
import React, { Suspense } from "react";
const LazyPage = ({ Page }) => {
return (
<Suspense
fallback={
<div
style={{
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
>
<div
style={{
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37"
></div>
</div>
}
>
<Page />
</Suspense>
);
};
export default LazyPage;
@@ -7,12 +7,16 @@ const LinkUserModal = (props) => {
return ( return (
<ModalUi <ModalUi
title={"Add/Choose Signer"} title={"Add/Choose Signer"}
isOpen={props.isAddUser[props.uniqueId]} isOpen={
props?.isAddSigner ||
(props?.isAddUser && props?.isAddUser[props?.uniqueId])
}
handleClose={props.closePopup} handleClose={props.closePopup}
> >
<SelectSigners <SelectSigners
details={props.handleAddUser} details={props.handleAddUser}
closePopup={props.closePopup} closePopup={props.closePopup}
signersData={props?.signersData}
/> />
<div <div
style={{ style={{
+12 -2
View File
@@ -1,4 +1,6 @@
import React from "react"; import React from "react";
import { isEnableSubscription } from "../constant/const";
import PremiumAlertHeader from "./PremiumAlertHeader";
const ModalUi = ({ const ModalUi = ({
children, children,
@@ -8,12 +10,13 @@ const ModalUi = ({
handleClose, handleClose,
showHeader = true, showHeader = true,
showClose = true, showClose = true,
styleClass styleClass,
showHeaderMessage
}) => { }) => {
return ( return (
<> <>
{isOpen && ( {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 <div
className={ className={
styleClass styleClass
@@ -37,6 +40,13 @@ const ModalUi = ({
)} )}
</div> </div>
)} )}
{!isEnableSubscription && showHeaderMessage && (
<PremiumAlertHeader
message={
"Cutomize Email is free in beta, this feature will incur a fee later."
}
/>
)}
<div>{children}</div> <div>{children}</div>
</div> </div>
</div> </div>
@@ -8,19 +8,11 @@ function CustomModal({
bodyMssg, bodyMssg,
footerMessage, footerMessage,
declineDoc, declineDoc,
setIsDecline, setIsDecline
containerWH
}) { }) {
const isMobile = window.innerWidth < 767;
return ( return (
show && ( show && (
<div <div className="bg-black bg-opacity-[75%] absolute z-[999] flex flex-col items-center justify-center w-full h-full rounded">
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-white rounded outline-none md:w-[40%] w-[80%]"> <div className="bg-white rounded outline-none md:w-[40%] w-[80%]">
<div className="bg-[#de4337] text-white p-[10px] rounded-t"> <div className="bg-[#de4337] text-white p-[10px] rounded-t">
{headMsg && headMsg} {headMsg && headMsg}
@@ -0,0 +1,11 @@
import React from "react";
const PremiumAlertHeader = ({ message }) => (
<marquee className="bg-yellow-300 text-black px-2">
{message
? message
: "Currently free in Beta, this feature will incur a fee later."}
</marquee>
);
export default PremiumAlertHeader;
+48 -14
View File
@@ -1,18 +1,52 @@
import React from "react"; import React from "react";
// import * as HoverCard from "@radix-ui/react-hover-card";
// import "../styles/opensigndrive.css";
import { Tooltip as ReactTooltip } from "react-tooltip"; import { Tooltip as ReactTooltip } from "react-tooltip";
const Tooltip = ({ children, message }) => ( import { openInNewTab } from "../constant/Utils";
<> import { isEnableSubscription } from "../constant/const";
<a data-tooltip-id="my-tooltip" data-tooltip-content={message}> const Tooltip = ({ id, message, url, iconColor, isSubscribe }) =>
{children ? ( url ? (
children <button
) : ( onClick={() => openInNewTab(url)}
<i className="fa-solid fa-question border-[1.5px] px-1.5 py-0.5 rounded-full border-[#33bbff] text-xs text-[#33bbff]"></i> className={
)} isSubscribe || !isEnableSubscription
</a> ? "text-center"
<ReactTooltip id="my-tooltip" className="max-w-[200px]" /> : "text-center opacity-20 pointer-events-none"
</> }
); >
<sup>
<i
className="fa-solid fa-question rounded-full"
style={{
borderColor: iconColor ? iconColor : "#33bbff",
color: iconColor ? iconColor : "#33bbff",
fontSize: 13,
borderWidth: 1.5,
padding: "1.5px 4px"
}}
></i>
</sup>
</button>
) : (
<>
<a
data-tooltip-id={id ? id : "my-tooltip"}
data-tooltip-content={message}
className="z-50"
>
<sup>
<i
className="fa-solid fa-question rounded-full"
style={{
borderColor: iconColor ? iconColor : "#33bbff",
color: iconColor ? iconColor : "#33bbff",
fontSize: 13,
borderWidth: 1.5,
padding: "1.5px 4px"
}}
></i>
</sup>
</a>
<ReactTooltip id={id ? id : "my-tooltip"} className="max-w-[200px]" />
</>
);
export default Tooltip; export default Tooltip;
+27
View File
@@ -0,0 +1,27 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { openInNewTab } from "../constant/Utils";
function Upgrade({ message, newWindow }) {
const navigation = useNavigate();
return (
<sup>
<span
onClick={() => {
if (newWindow) {
const url = window.location.origin + "/subscription";
openInNewTab(url);
} else {
navigation("/subscription");
}
}}
className="text-blue-800 text-sm cursor-pointer underline"
>
{message ? message : "Upgrade now"}
</span>
</sup>
);
}
export default Upgrade;
+10 -1
View File
@@ -9,9 +9,15 @@ const Validate = () => {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
const userDetails = JSON.parse(
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
)
);
// Use the session token to validate the user // Use the session token to validate the user
const userQuery = new Parse.Query(Parse.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") sessionToken: localStorage.getItem("accesstoken")
}); });
if (user) { if (user) {
@@ -24,7 +30,10 @@ const Validate = () => {
setIsUserValid(false); setIsUserValid(false);
} }
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const handleLoginBtn = () => { const handleLoginBtn = () => {
try { try {
Parse.User.logOut(); Parse.User.logOut();
-200
View File
@@ -1,200 +0,0 @@
import Parse from "parse";
import axios from "axios";
import { appInfo } from "../../constant/appinfo";
//For fetching application Information
export const fetchAppInfo = () => async (dispatch) => {
localStorage.setItem("_appName", appInfo.appname);
localStorage.setItem("_app_objectId", appInfo.objectId);
localStorage.setItem("baseUrl", `${appInfo.baseUrl}/`);
localStorage.setItem("parseAppId", appInfo.appId);
localStorage.setItem("appLogo", appInfo.applogo);
localStorage.setItem("appVersion", appInfo.version);
localStorage.removeItem("userSettings");
localStorage.setItem("userSettings", JSON.stringify(appInfo.settings));
localStorage.setItem("appTitle", appInfo.appTitle);
localStorage.setItem("fev_Icon", appInfo.fev_Icon);
// console.log("response ", response);
dispatch({ type: "FATCH_APPINFO", payload: appInfo });
};
//for simple login
export const login = (username, password) => async (dispatch) => {
let res = {};
let baseUrl = localStorage.getItem("baseUrl");
let parseAppId = localStorage.getItem("parseAppId");
try {
await Parse.User.logIn(username, password).then(
async function (res1) {
var resultjson = res1.toJSON();
res = res1.toJSON();
localStorage.setItem("userEmail", username);
localStorage.setItem("username", resultjson.name);
localStorage.setItem("accesstoken", resultjson.sessionToken);
localStorage.setItem("scriptId", true);
if (resultjson.ProfilePic) {
localStorage.setItem("profileImg", resultjson.ProfilePic);
} else {
localStorage.setItem("profileImg", "");
}
let url = `${baseUrl}functions/UserGroups`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessionToken: resultjson.sessionToken
};
let body = {
appname: localStorage.getItem("domain")
};
let userGroup = "",
userGroup1 = "";
await axios
.post(url, JSON.stringify(body), { headers: headers })
.then(async (response) => {
userGroup = response.data.result[0];
userGroup1 = response.data.result[1];
localStorage.setItem("_userGroup", userGroup);
});
let appSetings = JSON.parse(localStorage.getItem("userSettings"));
let defaultmenuid = "",
PageLanding = "",
pageType = "";
appSetings.forEach(async (element) => {
if (`${localStorage.getItem("_appName")}_appeditor` === userGroup) {
if (element.role === userGroup1) {
defaultmenuid = element.menuId;
PageLanding = element.pageId;
pageType = element.pageType;
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("pageType", pageType);
let _role = userGroup1.replace(
`${localStorage.getItem("_appName")}_`,
""
);
if (userGroup1) {
localStorage.setItem("_user_role", _role);
} else {
localStorage.setItem("_user_role", _role);
}
}
} else if (element.role === userGroup) {
defaultmenuid = element.menuId;
PageLanding = element.pageId;
pageType = element.pageType;
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("pageType", pageType);
localStorage.setItem("extended_class", element.extended_class);
let _role = userGroup.replace(
`${localStorage.getItem("domain")}_`,
""
);
if (userGroup) {
localStorage.setItem("_user_role", _role);
} else {
localStorage.setItem("_user_role", _role);
}
try {
const currentUser = Parse.User.current();
await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
}).then(
(results) => {
let userinfo = results.toJSON();
if (userinfo.TenantId) {
localStorage.setItem("TenetId", userinfo.TenantId.objectId);
}
// console.log("tour found", results);
},
(error) => {
console.error("Error while fetching tour", error);
}
);
} catch (error) {
console.log("err ", error);
}
}
});
dispatch({ type: "APP_LOGIN", payload: res });
if (pageType !== "") {
window.location = `/${pageType}/${PageLanding}`;
} else {
alert("You dont have access to this application.");
localStorage.setItem("accesstoken", null);
}
},
function () {
alert("Invalid Login");
localStorage.setItem("accesstoken", null);
}
);
} catch (err) {
console.log(err);
alert("You dont have access to this application.");
localStorage.setItem("accesstoken", null);
}
};
//for reset password
export const forgetPassword = (username) => async () => {
try {
await Parse.User.requestPasswordReset(username).then(
async function (res1) {
// var resultjson = res1;
// console.log("post data", resultjson.length);
if (res1.data === undefined) {
alert("Reset password link has been sent to your email id ");
}
},
function () {
// alert("Password Reset Done")
}
);
} catch (err) {
console.log(err);
}
};
// Role field wizard
export const fetchRoleEnum = (name) => async (dispatch) => {
let response = [];
let baseUrl = localStorage.getItem("baseUrl");
let parseAppId = localStorage.getItem("parseAppId");
try {
let url = `${baseUrl}roles?where={"name":{"$regex":"${name}","$ne":"${localStorage.getItem(
"domain"
)}_appeditor"}}`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
};
await axios.get(url, { headers: headers }).then((res) => {
let temp = [];
res.data.results.forEach((x) => {
temp.push(x["name"]);
});
response = temp;
});
} catch (e) {
console.error("Problem", e);
}
dispatch({ type: "FETCH_ROLE", payload: response });
};
export const showTenantName = (name) => async (dispatch) => {
dispatch({ type: "SHOW_TENANT", payload: name || null });
};
export const save_tourSteps = (steps) => async (dispatch) => {
dispatch({ type: "SAVE_TOURSTEPS", payload: steps });
};
export const remove_tourSteps = () => async (dispatch) => {
dispatch({ type: "REMOVE_TOURSTEPS", payload: [] });
};
+13 -8
View File
@@ -1,9 +1,14 @@
const ShowTenant = (state = "", action) => { import { createSlice } from "@reduxjs/toolkit";
switch (action.type) {
case "SHOW_TENANT": const showTenantSlice = createSlice({
name: "showTenant",
initialState: "",
reducers: {
showTenant: (state, action) => {
return action.payload; return action.payload;
default: },
return state; },
} });
};
export default ShowTenant; export const { showTenant } = showTenantSlice.actions;
export default showTenantSlice.reducer;
@@ -1,11 +1,17 @@
const tourStepReducer = (state = "", action) => { import { createSlice } from "@reduxjs/toolkit";
switch (action.type) {
case "SAVE_TOURSTEPS": const tourStepSlice = createSlice({
name: "tourStep",
initialState: [],
reducers: {
saveTourSteps: (state, action) => {
return action.payload; return action.payload;
case "REMOVE_TOURSTEPS": },
removeTourSteps: () => {
return []; return [];
default: }
return state;
} }
}; });
export default tourStepReducer;
export const { saveTourSteps, removeTourSteps } = tourStepSlice.actions;
export default tourStepSlice.reducer;
+2 -4
View File
@@ -1,12 +1,10 @@
import { combineReducers } from "redux"; import { combineReducers } from "redux";
import infoReducer from "./infoReducer"; import infoReducer from "./infoReducer";
import loginReducer from "./loginReducer";
import ShowTenant from "./ShowTenant"; import ShowTenant from "./ShowTenant";
import TourStepsReducer from "./TourStepsReducer"; import TourStepsReducer from "./TourStepsReducer";
export default combineReducers({ export default combineReducers({
appInfo: infoReducer, appInfo: infoReducer,
login: loginReducer, TourSteps: TourStepsReducer,
ShowTenant, ShowTenant
TourSteps: TourStepsReducer
}); });
@@ -1,9 +1,24 @@
const infoReducer = (state = {}, action) => { import { createSlice } from "@reduxjs/toolkit";
switch (action.type) { import { appInfo } from "../../constant/appinfo";
case "FATCH_APPINFO":
return action.payload; const infoSlice = createSlice({
default: name: "info",
return state; initialState: {},
reducers: {
fetchAppInfo: () => {
localStorage.setItem("_appName", appInfo.appname);
localStorage.setItem("_app_objectId", appInfo.objectId);
localStorage.setItem("baseUrl", `${appInfo.baseUrl}/`);
localStorage.setItem("parseAppId", appInfo.appId);
localStorage.setItem("appLogo", appInfo.applogo);
localStorage.setItem("appVersion", appInfo.version);
localStorage.removeItem("userSettings");
localStorage.setItem("userSettings", JSON.stringify(appInfo.settings));
localStorage.setItem("appTitle", appInfo.appTitle);
localStorage.setItem("fev_Icon", appInfo.fev_Icon);
return appInfo;
}
} }
}; });
export default infoReducer; export const { fetchAppInfo } = infoSlice.actions;
export default infoSlice.reducer;
@@ -1,9 +0,0 @@
const loginReducer = (state = {}, action) => {
switch (action.type) {
case "APP_LOGIN":
return action.payload;
default:
return state;
}
};
export default loginReducer;
+15 -4
View File
@@ -1,5 +1,16 @@
import { createStore, applyMiddleware } from "redux"; // import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk"; // import thunk from "redux-thunk";
import reducers from "./reducers"; // import reducers from "./reducers";
export const store = createStore(reducers, applyMiddleware(thunk)); // export const store = createStore(reducers, applyMiddleware(thunk));
import { configureStore } from "@reduxjs/toolkit";
import infoReducer from "./reducers/infoReducer";
import ShowTenant from "./reducers/ShowTenant";
import TourStepsReducer from "./reducers/TourStepsReducer";
export const store = configureStore({
reducer: {
appInfo: infoReducer,
TourSteps: TourStepsReducer,
ShowTenant
}
});
+58
View File
@@ -102,3 +102,61 @@
background-position: right 1rem top 50%; background-position: right 1rem top 50%;
} }
} }
.warning {
position: absolute;
padding: 5px;
border: 1px solid black;
background: white;
width: 250px;
border-radius: 5px;
animation: inAnimation 1s ease-in-out;
z-index: 1;
}
.defaultvalueWarning {
position: absolute;
left: 20px;
}
.defaultvalueWarning::before {
content: "";
width: 0px;
height: 0px;
border-style: outset;
border-width: 0px 10px 10px 10px;
border-color: transparent transparent black transparent;
display: inline-block;
position: absolute;
top: -29%;
left: 0;
right: 75%;
margin: 0 auto;
}
@keyframes inAnimation {
0% {
opacity: 0;
visibility: hidden;
}
25% {
opacity: 1;
visibility: visible;
}
50% {
opacity: 1;
visibility: visible;
}
75% {
opacity: 1;
visibility: visible;
}
100% {
opacity: 1;
visibility: visible;
}
}
-21
View File
@@ -18,27 +18,6 @@
z-index: 1; z-index: 1;
} }
.nameWarning {
position: absolute;
bottom: -70px;
left: 10px;
}
.nameWarning::before {
content: "";
width: 0px;
height: 0px;
border-style: outset;
border-width: 0px 10px 10px 10px;
border-color: transparent transparent black transparent;
display: inline-block;
position: absolute;
top: -29%;
left: 0;
right: 75%;
margin: 0 auto;
}
.signWarning { .signWarning {
bottom: 70px; bottom: 70px;
left: 50px; left: 50px;
+14 -60
View File
@@ -15,15 +15,8 @@
border-width: 0.2px; border-width: 0.2px;
} }
.dropdownContainer { .dragCursor {
display: flex; cursor: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAASElEQVR4nGNgwAMkJSUbpKSkOvCpIaT5PxSTbogUQjMYMwxeIIXmVFIxA8UGDDyQGg0DnIDi6JKUlCxHMqCeZAOghjSAMD5FAKfeaURdUFxCAAAAAElFTkSuQmCC"), auto;
flex-direction: column;
overflow-y: scroll;
max-height: 250px;
}
.dropdownContainer::-webkit-scrollbar {
display: none;
} }
.react-datepicker__input-container { .react-datepicker__input-container {
@@ -402,14 +395,6 @@
margin-left: 10px; 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 { .defaultBtn {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.18);
padding: 3px 26px; padding: 3px 26px;
@@ -427,42 +412,6 @@
outline: none; 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 { .signerList {
overflow-y: scroll; overflow-y: scroll;
@@ -504,6 +453,14 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.disabled {
opacity: 0.5;
/* Example: reduce opacity to visually indicate disabled state */
pointer-events: none;
/* Prevents mouse clicks and other events */
/* background-color: #888; */
}
.useEmail { .useEmail {
font-size: 10px; font-size: 10px;
color: #424242; color: #424242;
@@ -560,13 +517,6 @@
position: "relative"; position: "relative";
} }
.signatureHeader {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 5px;
}
.signerComponent { .signerComponent {
/* box-shadow: rgba(17, 12, 46, 0.15) 0px 48px 100px 0px; */ /* box-shadow: rgba(17, 12, 46, 0.15) 0px 48px 100px 0px; */
@@ -854,6 +804,7 @@ option {
z-index: 999; z-index: 999;
border-radius: 2px; border-radius: 2px;
resize: none; resize: none;
overflow: hidden;
} }
.labelTextArea:focus { .labelTextArea:focus {
@@ -1017,6 +968,9 @@ option {
} }
} }
.scroll-hide::-webkit-scrollbar {
display: none;
}
@media screen and (max-width: 350px) and (min-width: 311px) { @media screen and (max-width: 350px) and (min-width: 311px) {
.signatureCanvas { .signatureCanvas {
width: 280px; width: 280px;
+205
View File
@@ -1,3 +1,7 @@
import axios from 'axios';
const appId = process.env.APP_ID;
const serverUrl = process.env.SERVER_URL;
export function customAPIurl() { export function customAPIurl() {
const url = new URL(process.env.SERVER_URL); const url = new URL(process.env.SERVER_URL);
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin; return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
@@ -38,3 +42,204 @@ export function replaceMailVaribles(subject, body, variables) {
}; };
return result; return result;
} }
export const saveFileUsage = async (size, imageUrl, userId) => {
//checking server url and save file's size
try {
const tenantQuery = new Parse.Query('partners_Tenant');
tenantQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
const tenant = await tenantQuery.first();
if (tenant) {
const tenantPtr = {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: tenant.id,
};
const _tenantPtr = JSON.stringify(tenantPtr);
try {
const res = await axios.get(
`${serverUrl}/classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
{
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
},
}
);
const response = res.data.results;
let data;
// console.log("response", response);
if (response && response.length > 0) {
data = {
usedStorage: response[0].usedStorage ? response[0].usedStorage + size : size,
};
await axios.put(
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
data,
{
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
},
}
);
} else {
data = { usedStorage: size, PartnersTenant: tenantPtr };
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
},
});
}
} catch (err) {
console.log('err in save usage', err);
}
saveDataFile(size, imageUrl, tenantPtr);
}
} catch (err) {
console.log('err in fetch tenant Id', err);
}
};
//function for save fileUrl and file size in particular client db class partners_DataFiles
const saveDataFile = async (size, imageUrl, tenantPtr) => {
const data = {
FileUrl: imageUrl,
FileSize: size,
TenantPtr: tenantPtr,
};
// console.log("data save",file, data)
try {
await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
},
});
} catch (err) {
console.log('error in save usage ', err);
}
};
export const updateMailCount = async extUserId => {
// Update count in contracts_Users class
const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', extUserId);
try {
const contractUser = await query.first({ useMasterKey: true });
if (contractUser) {
contractUser.increment('EmailCount', 1);
await contractUser.save(null, { useMasterKey: true });
} else {
// Create new entry if not found
const ContractsUsers = Parse.Object.extend('contracts_users');
const newContractUser = new ContractsUsers();
newContractUser.set('EmailCount', 1);
await newContractUser.save(null, { useMasterKey: true });
}
} catch (error) {
console.log('Error updating EmailCount in contracts_users: ' + error.message);
}
};
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 //--npm modules
import express from 'express'; import express from 'express';
import cors from 'cors'; import cors from 'cors';
export const app = express();
import dotenv from 'dotenv'; 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(); dotenv.config();
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true })); app.use(express.urlencoded({ limit: '50mb', extended: true }));
import uploadFile from './uploadFile.js';
app.post('/file_upload', uploadFile); 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 });
}
}
@@ -284,6 +284,7 @@ export default async function createDocumentWithTemplate(request, response) {
subject: subject, subject: subject,
from: sender, from: sender,
html: html, html: html,
extUserId: template.ExtUserPtr.objectId,
}; };
sendMail = await axios.post(url, params, { headers: headers }); sendMail = await axios.post(url, params, { headers: headers });
} catch (error) { } catch (error) {
@@ -1,5 +1,12 @@
import axios from 'axios'; import axios from 'axios';
import { color, customAPIurl, replaceMailVaribles } from '../../../../Utils.js'; import {
color,
customAPIurl,
replaceMailVaribles,
saveFileUsage,
formatWidgetOptions,
sanitizeFileName,
} from '../../../../Utils.js';
// `sendDoctoWebhook` is used to send res data of document on webhook // `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) { async function sendDoctoWebhook(doc, WebhookUrl, userId) {
@@ -8,6 +15,7 @@ async function sendDoctoWebhook(doc, WebhookUrl, userId) {
event: 'created', event: 'created',
...doc, ...doc,
}; };
await axios await axios
.post(WebhookUrl, params, { .post(WebhookUrl, params, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -65,6 +73,7 @@ export default async function createDocumentwithCoordinate(request, response) {
if (!reqToken) { if (!reqToken) {
return response.status(400).json({ error: 'Please Provide API Token' }); return response.status(400).json({ error: 'Please Provide API Token' });
} }
try { try {
const tokenQuery = new Parse.Query('appToken'); const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', reqToken); tokenQuery.equalTo('token', reqToken);
@@ -81,17 +90,21 @@ export default async function createDocumentwithCoordinate(request, response) {
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let fileUrl; let fileUrl;
if (request.files?.[0]) { if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, { const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'), base64: base64,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const file = new Parse.File(`${name}.pdf`, { const filename = sanitizeFileName(`${name}.pdf`);
base64: base64File, const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
});
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users'); const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr); contractsUser.equalTo('UserId', userPtr);
@@ -170,17 +183,17 @@ export default async function createDocumentwithCoordinate(request, response) {
for (const widget of signer.widgets) { for (const widget of signer.widgets) {
const pageNumber = widget.page; const pageNumber = widget.page;
const options = formatWidgetOptions(widget.type, widget.options);
const page = placeHolder.find(page => page.pageNumber === pageNumber); const page = placeHolder.find(page => page.pageNumber === pageNumber);
const signOpt = { name: 'signature', status: 'required' };
const widgetData = { const widgetData = {
isStamp: widget.type === 'stamp', isStamp: widget.type === 'stamp' || widget.type === 'image',
key: randomId(), key: randomId(),
isDrag: false, isDrag: false,
scale: 1, scale: 1,
isMobile: false, isMobile: false,
zIndex: 1, zIndex: 1,
type: widget.type, type: widget.type === 'textbox' ? 'text input' : widget.type,
options: widget.type === 'signature' ? signOpt : widget.options, options: options,
Width: widget.w, Width: widget.w,
Height: widget.h, Height: widget.h,
xPosition: widget.x, xPosition: widget.x,
@@ -242,7 +255,6 @@ export default async function createDocumentwithCoordinate(request, response) {
year: 'numeric', year: 'numeric',
}); });
let sender = parseExtUser.Email; let sender = parseExtUser.Email;
let sendMail;
const serverUrl = process.env.SERVER_URL; const serverUrl = process.env.SERVER_URL;
const newServer = serverUrl.replaceAll('/', '%2F'); const newServer = serverUrl.replaceAll('/', '%2F');
const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`; const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`;
@@ -330,9 +342,10 @@ export default async function createDocumentwithCoordinate(request, response) {
subject: subject, subject: subject,
from: sender, from: sender,
html: html, html: html,
extUserId: extUser.id,
}; };
sendMail = await axios.post(url, params, { headers: headers }); await axios.post(url, params, { headers: headers });
} catch (error) { } catch (error) {
console.log('error', error); console.log('error', error);
} }
@@ -1,6 +1,4 @@
import { customAPIurl } from '../../../../Utils.js'; import { saveFileUsage } from '../../../../Utils.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function createTemplate(request, response) { export default async function createTemplate(request, response) {
const name = request.body?.title; const name = request.body?.title;
const note = request.body?.note; const note = request.body?.note;
@@ -36,12 +34,17 @@ export default async function createTemplate(request, response) {
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, { const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'), base64: base64,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users'); const contractsUser = new Parse.Query('contracts_Users');
@@ -1,5 +1,11 @@
import axios from 'axios'; import axios from 'axios';
import { color, customAPIurl } from '../../../../Utils.js'; import {
color,
customAPIurl,
saveFileUsage,
formatWidgetOptions,
sanitizeFileName,
} from '../../../../Utils.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000); const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function createTemplatewithCoordinate(request, response) { export default async function createTemplatewithCoordinate(request, response) {
@@ -34,17 +40,21 @@ export default async function createTemplatewithCoordinate(request, response) {
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let fileUrl; let fileUrl;
if (request.files?.[0]) { if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, { const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'), base64: base64,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const file = new Parse.File(`${name}.pdf`, { const filename = sanitizeFileName(`${name}.pdf`);
base64: base64File, const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
});
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users'); const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr); contractsUser.equalTo('UserId', userPtr);
@@ -129,16 +139,16 @@ export default async function createTemplatewithCoordinate(request, response) {
for (const widget of signer.widgets) { for (const widget of signer.widgets) {
const pageNumber = widget.page; const pageNumber = widget.page;
const page = placeHolder.find(page => page.pageNumber === pageNumber); const page = placeHolder.find(page => page.pageNumber === pageNumber);
const signOpt = { name: 'signature', status: 'required' }; const options = formatWidgetOptions(widget.type, widget.options);
const widgetData = { const widgetData = {
isStamp: widget.type === 'stamp', isStamp: widget.type === 'stamp' || widget.type === 'image',
key: randomId(), key: randomId(),
isDrag: false, isDrag: false,
scale: 1, scale: 1,
isMobile: false, isMobile: false,
zIndex: 1, zIndex: 1,
type: widget.type, type: widget.type === 'textbox' ? 'text input' : widget.type,
options: widget.type === 'signature' ? signOpt : widget.options, options: options,
Width: widget.w, Width: widget.w,
Height: widget.h, Height: widget.h,
xPosition: widget.x, xPosition: widget.x,
@@ -36,17 +36,22 @@ export default async function draftDocument(request, response) {
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let fileUrl; let fileUrl;
if (request.files?.[0]) { if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, { const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'), base64: base64,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const file = new Parse.File(`${name}.pdf`, { const file = new Parse.File(`${name}.pdf`, {
base64: base64File, base64: base64File,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users'); const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr); contractsUser.equalTo('UserId', userPtr);
+26 -4
View File
@@ -1,5 +1,5 @@
import PDF from './parsefunction/pdf/PDF.min.js'; import PDF from './parsefunction/pdf/PDF.min.js';
import sendmail from './parsefunction/sendMail.js'; import sendmailv3 from './parsefunction/sendMailv3.js';
import GoogleSign from './parsefunction/GoogleSign.js'; import GoogleSign from './parsefunction/GoogleSign.js';
import ZohoDetails from './parsefunction/ZohoDetails.js'; import ZohoDetails from './parsefunction/ZohoDetails.js';
import usersignup from './parsefunction/usersignup.js'; import usersignup from './parsefunction/usersignup.js';
@@ -10,7 +10,6 @@ import DocumentAftersave from './parsefunction/DocumentAftersave.js';
import ContactbookAftersave from './parsefunction/ContactBookAftersave.js'; import ContactbookAftersave from './parsefunction/ContactBookAftersave.js';
import ContractUsersAftersave from './parsefunction/ContractUsersAftersave.js'; import ContractUsersAftersave from './parsefunction/ContractUsersAftersave.js';
import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js'; import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js';
import SendMailv1 from './parsefunction/SendMailv1.js';
import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js'; import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js';
import getUserId from './parsefunction/getUserId.js'; import getUserId from './parsefunction/getUserId.js';
import getUserDetails from './parsefunction/getUserDetails.js'; import getUserDetails from './parsefunction/getUserDetails.js';
@@ -23,17 +22,28 @@ import TemplateAfterSave from './parsefunction/TemplateAfterSave.js';
import GetTemplate from './parsefunction/GetTemplate.js'; import GetTemplate from './parsefunction/GetTemplate.js';
import savewebhook from './parsefunction/saveWebhook.js'; import savewebhook from './parsefunction/saveWebhook.js';
import callWebhook from './parsefunction/callWebhook.js'; 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('AddUserToRole', addUserToGroups);
Parse.Cloud.define('UserGroups', getUserGroups); Parse.Cloud.define('UserGroups', getUserGroups);
Parse.Cloud.define('signPdf', PDF); Parse.Cloud.define('signPdf', PDF);
Parse.Cloud.define('sendmailv3', sendmail); Parse.Cloud.define('sendmailv3', sendmailv3);
Parse.Cloud.define('googlesign', GoogleSign); Parse.Cloud.define('googlesign', GoogleSign);
Parse.Cloud.define('zohodetails', ZohoDetails); Parse.Cloud.define('zohodetails', ZohoDetails);
Parse.Cloud.define('usersignup', usersignup); Parse.Cloud.define('usersignup', usersignup);
Parse.Cloud.define('facebooksign', FacebookSign); Parse.Cloud.define('facebooksign', FacebookSign);
Parse.Cloud.define('SendOTPMailV1', sendMailOTPv1); Parse.Cloud.define('SendOTPMailV1', sendMailOTPv1);
Parse.Cloud.define('sendmail', SendMailv1);
Parse.Cloud.define('AuthLoginAsMail', AuthLoginAsMail); Parse.Cloud.define('AuthLoginAsMail', AuthLoginAsMail);
Parse.Cloud.define('getUserId', getUserId); Parse.Cloud.define('getUserId', getUserId);
Parse.Cloud.define('getUserDetails', getUserDetails); Parse.Cloud.define('getUserDetails', getUserDetails);
@@ -45,7 +55,19 @@ Parse.Cloud.define('getapitoken', getapitoken);
Parse.Cloud.define('getTemplate', GetTemplate); Parse.Cloud.define('getTemplate', GetTemplate);
Parse.Cloud.define('savewebhook', savewebhook); Parse.Cloud.define('savewebhook', savewebhook);
Parse.Cloud.define('callwebhook', callWebhook); 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_Document', DocumentAftersave);
Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave); Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave);
Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave); Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave);
Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave); 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;
@@ -0,0 +1,33 @@
async function DocumentBeforesave(request) {
try {
// below code is used to update document when user sent document or self signed
const document = request.object;
const oldDocument = request.original;
// Check if SignedUrl field has been added (transition from undefined to defined)
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);
try {
const contractUser = await query.first({ useMasterKey: true });
if (contractUser) {
contractUser.increment('DocumentCount', 1);
await contractUser.save(null, { useMasterKey: true });
} else {
// Create new entry if not found
const ContractsUsers = Parse.Object.extend('contracts_users');
const newContractUser = new ContractsUsers();
newContractUser.set('DocumentCount', 1);
await newContractUser.save(null, { useMasterKey: true });
}
} catch (error) {
console.log('Error updating document count in contracts_users: ' + error.message);
}
}
} catch (err) {
console.log('err in document beforesave', err.message);
}
}
export default DocumentBeforesave;
@@ -1,47 +1,65 @@
import axios from 'axios' import { updateMailCount } from '../../Utils.js';
async function getDocument(docId) {
try {
const query = new Parse.Query('contracts_Document');
query.equalTo('objectId', docId);
query.include('ExtUserPtr');
query.include('CreatedBy');
query.include('Signers');
query.include('AuditTrail.UserPtr');
query.include('Placeholders');
query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true });
const _res = res.toJSON();
return _res?.ExtUserPtr?.objectId;
} catch (err) {
console.log('err ', err);
}
}
async function sendMailOTPv1(request) { async function sendMailOTPv1(request) {
try { try {
//--for elearning app side //--for elearning app side
let code = Math.floor(1000 + Math.random() * 9000); let code = Math.floor(1000 + Math.random() * 9000);
let email = request.params.email; let email = request.params.email;
var TenantId = request.params.TenantId ? request.params.TenantId : undefined; var TenantId = request.params.TenantId ? request.params.TenantId : undefined;
// console.log("In tempSendOTPv2");
// console.log(JSON.stringify(request));
if (email) { if (email) {
axios({ const recipient = request.params.email;
method: 'POST', const mailsender = process.env.SMTP_ENABLE
url: process.env.SERVER_URL + '/functions/sendmail', ? process.env.SMTP_USER_EMAIL
headers: { : process.env.MAILGUN_SENDER;
'Content-Type': 'application/json', try {
'X-Parse-Application-Id': process.env.APP_ID, await Parse.Cloud.sendEmail({
}, from: 'Opensign™' + ' <' + mailsender + '>',
params: { recipient: recipient,
otp: code, subject: 'Your OpenSign™ OTP',
email: email, text: 'This email is a test.',
TenantId: TenantId, html:
}, "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for OpenSign™ verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>" +
}).then( code +
function (httpResponse) {}, '</p></div> </div> </div></body></html>',
function (httpResponse) { });
console.error('sms Request failed with response code ' + httpResponse.status); console.log('OTP sent');
return Promise.reject('sms Request failed with response code ' + httpResponse.status); if (request.params?.docId) {
const extUserId = await getDocument(request.params?.docId);
if (extUserId) {
updateMailCount(extUserId);
}
} }
); } catch (err) {
console.log('error in send OTP mail', err);
}
const tempOtp = new Parse.Query('defaultdata_Otp'); const tempOtp = new Parse.Query('defaultdata_Otp');
tempOtp.equalTo('Email', email); tempOtp.equalTo('Email', email);
const resultOTP = await tempOtp.first({ useMasterKey: true }); const resultOTP = await tempOtp.first({ useMasterKey: true });
console.log('resultOTP', resultOTP); // console.log('resultOTP', resultOTP);
if (resultOTP !== undefined) { if (resultOTP !== undefined) {
const updateOtpQuery = new Parse.Query('defaultdata_Otp'); const updateOtpQuery = new Parse.Query('defaultdata_Otp');
const updateOtp = await updateOtpQuery.get(resultOTP.id, { const updateOtp = await updateOtpQuery.get(resultOTP.id, {
useMasterKey: true, useMasterKey: true,
}); });
updateOtp.set('OTP', code); updateOtp.set('OTP', code);
const updateRes = updateOtp.save(null, { useMasterKey: true }); updateOtp.save(null, { useMasterKey: true });
// console.log("update otp Res in tempSendOtp ", updateRes); // console.log("update otp Res in tempSendOtp ", updateRes);
} else { } else {
const otpClass = Parse.Object.extend('defaultdata_Otp'); const otpClass = Parse.Object.extend('defaultdata_Otp');
@@ -49,17 +67,17 @@ async function sendMailOTPv1(request) {
newOtpQuery.set('OTP', code); newOtpQuery.set('OTP', code);
newOtpQuery.set('Email', email); newOtpQuery.set('Email', email);
newOtpQuery.set('TenantId', TenantId); newOtpQuery.set('TenantId', TenantId);
const newRes = await newOtpQuery.save(null, { useMasterKey: true }); await newOtpQuery.save(null, { useMasterKey: true });
// console.log("new otp Res in tempSendOtp ", newRes); // console.log("new otp Res in tempSendOtp ", newRes);
} }
return 'Otp send'; return 'Otp send';
} else { } else {
return Promise.reject('Please Enter valid email'); return 'Please Enter valid email';
} }
} catch (err) { } catch (err) {
console.log('err in sendMailOTPv1'); console.log('err in sendMailOTPv1');
console.log(err); console.log(err);
return Promise.reject(err); return err;
} }
} }
export default sendMailOTPv1; export default sendMailOTPv1;
@@ -1,27 +0,0 @@
async function SendMailv1(request) {
console.log('in SendMailv1');
try {
const recipient = request.params.email;
const otp = request.params.otp;
const res = await Parse.Cloud.sendEmail({
from: 'Test user' + ' <' + process.env.SMTP_ENABLE ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER + '>',
recipient: recipient,
subject: 'Your OpenSign™ OTP',
text: 'This email is a test.',
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for OpenSign™ verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>" +
otp +
'</p></div> </div> </div></body></html>',
// "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body style='text-align: center;'><div style='display:flex;flex-direction:column;justify-content:center;align-item:center;margin:40px'> <p style='font-weight: bolder; font-size: large;'>Hello,</p> <span>Your OTP for LegaDaft verification . </span> <p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px'>76984</p><span>Thank You!</span> </div> </body></html>"
});
console.log('Res');
console.log(res);
return otp;
} catch (err) {
console.log('err in SendMailv1');
console.log(err);
return err;
}
}
export default SendMailv1;

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