Compare commits

..
158 Commits
Author SHA1 Message Date
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
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
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
prafull-opensignlabsandGitHub e643d96797 Merge pull request #506 from OpenSignLabs/api_signembed
fix: handle uncaught error in createdocument and createmplate API
2024-03-19 13:24:05 +05:30
prafull-opensignlabs 1ff4160ca4 fix: handle uncaught error in createdocument and createmplate API 2024-03-19 13:23:10 +05:30
prafull-opensignlabsandGitHub 62f66bf524 Merge pull request #505 from OpenSignLabs/api_signembed
fix: widgets are not embedding in pdf using API
2024-03-19 13:12:46 +05:30
prafull-opensignlabs de3bc37fa5 fix: widgets are not embeding in pdf using API 2024-03-19 13:11:23 +05:30
AmolandGitHub ea660384c3 Merge pull request #504 from OpenSignLabs/prafull-opensignlabs-patch-4
chore: Update mongo url in local env
2024-03-19 12:01:08 +05:30
prafull-opensignlabsandGitHub 210cb5e754 chore: Update mongo url in local env 2024-03-19 12:00:28 +05:30
AmolandGitHub ed45bfef84 Merge pull request #503 from OpenSignLabs/prafull-opensignlabs-patch-1 2024-03-19 10:59:23 +05:30
prafull-opensignlabsandGitHub a8e77e93f7 chore: change serverURL 2024-03-19 10:58:32 +05:30
prafull-opensignlabsandGitHub 969071bb28 Merge pull request #501 from OpenSignLabs/placeholder_sign
fix: show warning on placeholder_sign page for document already send to signers for signature
2024-03-19 10:29:33 +05:30
RaktimaNXG 76491a77f1 fix: add comment 2024-03-19 10:27:41 +05:30
AmolandGitHub 9a51eed2bc Merge pull request #502 from OpenSignLabs/fix_maxupload 2024-03-19 10:09:48 +05:30
prafull-opensignlabs a675a3c8fd refactor: change console dir to console log 2024-03-18 19:49:13 +05:30
prafull-opensignlabs fea30930eb fix: handle uncaught err of max upload pdf in form 2024-03-18 19:47:56 +05:30
RaktimaNXG 7373b086e4 fix: change warning messages 2024-03-18 19:00:02 +05:30
RaktimaNXG 2fc1eda761 fix: remove console 2024-03-18 18:22:14 +05:30
RaktimaNXG 2c100ad399 fix: show warning on placeholder_sign page for document already send to signers for signature 2024-03-18 17:40:27 +05:30
prafull-opensignlabsandGitHub 1cefe01aee Merge pull request #499 from OpenSignLabs/fix_mobile_issue
fix:  width issue of text widget & default data for widgets information pop-ups in mobile display.
2024-03-18 17:09:17 +05:30
RaktimaNXG f1cf395761 fix: textInput widget popup select arrow key design 2024-03-18 17:05:15 +05:30
RaktimaNXG 4af3d8f78b fix: dragging issue of text widget in mobile view 2024-03-18 16:21:14 +05:30
AmolandGitHub cf21ac7405 Merge pull request #500 from OpenSignLabs/fix_Smtp 2024-03-18 15:16:27 +05:30
prafull-opensignlabs 5f11e92c0c fix: handle uncaught error of SMTP 2024-03-18 15:14:23 +05:30
RaktimaNXG c3ffda4dd2 fix: resolving width inconsistencies with of text widgets & default data for widgets information pop-ups in mobile display. 2024-03-18 12:45:08 +05:30
AmolandGitHub c929b6b7a9 Update Dockerhubfile 2024-03-18 00:19:06 +05:30
AmolandGitHub eb2abd3e6d Merge pull request #493 from OpenSignLabs/widget_modal 2024-03-15 19:33:53 +05:30
prafull-opensignlabs 1c4c2e87b4 refactor: change design of widgets in mobile 2024-03-15 19:30:33 +05:30
prafull-opensignlabs 4fda5b4531 refactor: stop showing initial popup when wdigets drag 2024-03-15 19:04:02 +05:30
AmolandGitHub df61e143af Merge pull request #492 from OpenSignLabs/update_menu 2024-03-15 14:27:06 +05:30
prafull-opensignlabs 3a55c35445 feat: add create document btn in dashboard, update menu & change Root to Opensign™ drive in folder input 2024-03-15 14:25:50 +05:30
prafull-opensignlabsandGitHub 08476ba3d6 Merge pull request #490 from OpenSignLabs/feat_label
fix: date is not changable in signyourself flow of mobile
2024-03-14 21:40:40 +05:30
prafull-opensignlabs db9ae71d67 fix: date is not changable in signyourself flow of mobile 2024-03-14 21:39:22 +05:30
prafull-opensignlabsandGitHub 39806e3ad6 Merge pull request #489 from OpenSignLabs/feat_label
fix: checkbox not embeding in pdf
2024-03-14 21:25:18 +05:30
prafull-opensignlabs 7882c61b81 fix: checkbox not embeding in pdf 2024-03-14 21:22:48 +05:30
AndrewandGitHub b8f886902f Merge pull request #488 from OpenSignLabs/feat_label 2024-03-14 20:38:38 +05:30
prafull-opensignlabs 17660dae21 fix: border of radio button & checkbox is not proper 2024-03-14 20:12:41 +05:30
prafull-opensignlabs 7584667643 fix: when click on label in radio or checkbox widget it's border not visible 2024-03-14 18:35:40 +05:30
AndrewandGitHub b47d1cff07 Merge pull request #487 from OpenSignLabs/feat_label 2024-03-14 17:00:22 +05:30
prafull-opensignlabs b99d8e6d6e feat: add show/hide label in checkbox and radio widget 2024-03-14 16:50:46 +05:30
prafull-opensignlabsandGitHub 8b15bceac8 Merge pull request #472 from OpenSignLabs/signature_setting
fix: already placed signature widget setting icon issue with old data
2024-03-14 10:04:24 +05:30
RaktimaNXG 86b0e90948 fix: date z-index and default data of email,name,company name, job title widgets in guest login flow 2024-03-13 20:25:18 +05:30
prafull-opensignlabsandGitHub 3bc57a999c Merge pull request #486 from OpenSignLabs/fix_formreset
fix: reset button not working in forms
2024-03-13 20:18:33 +05:30
prafull-opensignlabs 27730c89e8 fix: signers not get reset in request signature form on reset button 2024-03-13 20:17:22 +05:30
prafull-opensignlabs 82eeffa6e4 fix: reset button not working in forms 2024-03-13 18:36:24 +05:30
RaktimaNXG 53147caebc fix: checkbox checked issue in signyour-self flow 2024-03-13 17:47:31 +05:30
RaktimaNXG 6004522d5c feat: change radio widgets name radio to radio button 2024-03-13 16:36:03 +05:30
AndrewandGitHub 266c2463c9 Merge pull request #485 from OpenSignLabs/update_tour 2024-03-13 15:19:55 +05:30
prafull-opensignlabs 1b215f53e7 feat: add help for select folder input 2024-03-13 15:17:04 +05:30
RaktimaNXG 73b2ba6abd fix: default date issue and remove commented code, unnecessary using useEffect 2024-03-13 14:30:25 +05:30
RaktimaNXG c04fd80a38 feat: change label widget name to text and text widget name to text input 2024-03-13 11:40:17 +05:30
prafull-opensignlabs 61b123cb61 refator: update dashboard tour messages 2024-03-13 10:31:49 +05:30
RaktimaNXG 08e805569f fix: disable date change in placeholder and template flow 2024-03-13 09:56:59 +05:30
RaktimaNXG 375aa0b7e7 fix: invalid date issue for indian format 2024-03-12 20:02:11 +05:30
RaktimaNXG b94e0fa052 refactor: change order of minimum & maximum input in checkbox setting 2024-03-12 16:50:50 +05:30
prafull-opensignlabsandGitHub b6752549ed Merge pull request #484 from OpenSignLabs/fix_e_sign
fix: E-sign is not available in completed document
2024-03-11 17:42:40 +05:30
prafull-opensignlabs 51505af6d8 fix: E-sign is not available in completed document 2024-03-11 17:35:20 +05:30
RaktimaNXG 0e5cffa1fa fix: widgets issue after placing on multiple pages 2024-03-11 16:59:10 +05:30
RaktimaNXG 885b5287b4 fix: condition of render setting icon for widgets 2024-03-08 18:39:02 +05:30
RaktimaNXG 5348c6620a fix: signature widget setting icon for placed with old data 2024-03-08 17:51:00 +05:30
100 changed files with 9702 additions and 4063 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.
MASTER_KEY=XnAadwKxxByMr
# Mongodb URI to connect to
MONGODB_URI=mongodb://host.docker.internal: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.
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)
+1 -1
View File
@@ -1,6 +1,6 @@
build:
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
run:
+11 -11
View File
@@ -42,24 +42,24 @@ Please star ⭐ the repo to support us! 😀
### 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
- **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.
- **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.
- **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.
- **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 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", "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 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. 
- **"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.
- **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.
- **OpenSign™ Drive:** It is a centralised secure vault for your signed documents that makes storing, signing, organizing, sharing & achieving your docs a breeze.
- **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 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.
- **API Support(coming soon):** OpenSign™ API allows seamless integration into existing systems and software. APIs will soon be available as a cloud hosted solution.
- **Integrations:** Seamless integrations with various Cloud storage systems, CRMs & enterprise platforms is coming soon.
- **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 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/86db91b7-6c2f-4885-a33c-58f4fd35ec89 height='200'>
@@ -76,7 +76,7 @@ Please refer to the [Installation Guide](INSTALLATION.md) for detailed instructi
### 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
# Run the application
ENTRYPOINT npm run start-dev
ENTRYPOINT npm run start
+3
View File
@@ -14,6 +14,9 @@ RUN npm install
COPY apps/OpenSign/ .
COPY apps/OpenSign/.husky .
# build
RUN npm run build
# Make port 3000 available to the world outside this container
EXPOSE 3000
+646 -710
View File
File diff suppressed because it is too large Load Diff
+18 -20
View File
@@ -3,44 +3,42 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.1",
"@radix-ui/themes": "^2.0.3",
"@react-pdf/renderer": "^3.3.8",
"@react-pdf/renderer": "^3.4.0",
"@reduxjs/toolkit": "^2.2.1",
"axios": "^1.6.0",
"daisyui": "^3.9.2",
"axios": "^1.6.8",
"file-saver": "^2.0.5",
"html-react-parser": "^4.2.2",
"jwt-decode": "^4.0.0",
"moment": "^2.29.4",
"moment": "^2.30.1",
"parse": "^4.3.1",
"pdf-lib": "^1.17.1",
"print-js": "^1.6.0",
"radix-ui": "^1.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.1",
"react-datepicker": "^6.2.0",
"react-bootstrap": "^2.10.2",
"react-datepicker": "^6.4.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^8.0.3",
"react-dnd-touch-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-drag-listview": "^2.0.0",
"react-draggable": "^4.4.6",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-konva": "^18.2.10",
"react-modal": "^3.16.1",
"react-pdf": "^7.7.1",
"react-redux": "^8.1.3",
"react-redux": "^9.1.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.8.0",
"react-signature-canvas": "^1.0.6",
"react-tooltip": "^5.26.3",
"reactour": "^1.19.2",
"redux": "^4.2.1",
"redux-thunk": "^2.4.2",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"regex-parser": "^2.3.0",
"serve": "^14.2.1",
"styled-components": "^4.4.1",
@@ -48,7 +46,7 @@
},
"scripts": {
"start": "serve -s build",
"start-dev" :"react-scripts start",
"start-dev": "react-scripts start",
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
"build": "npm run version && react-scripts build",
@@ -82,16 +80,16 @@
}
},
"devDependencies": {
"@babel/runtime-corejs2": "^7.23.9",
"@babel/runtime-corejs2": "^7.24.1",
"autoprefixer": "^10.4.18",
"commitizen": "^4.3.0",
"eslint": "^8.51.0",
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react": "^7.34.1",
"lint-staged": "^15.2.2",
"postcss": "^8.4.35",
"prettier": "^2.8.0",
"pretty-quick": "^3.1.3",
"tailwindcss": "^3.3.3"
"postcss": "^8.4.37",
"prettier": "^2.8.8",
"pretty-quick": "^3.3.1",
"tailwindcss": "^3.4.1"
}
}
+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 { pdfjs } from "react-pdf";
import Login from "./pages/Login";
@@ -9,12 +9,13 @@ import HomeLayout from "./layout/HomeLayout";
import PageNotFound from "./pages/PageNotFound";
import ValidateRoute from "./primitives/ValidateRoute";
import Validate from "./primitives/Validate";
import ManageSign from "./pages/Managesign";
import TemplatePlaceholder from "./pages/TemplatePlaceholder";
import SignYourSelf from "./pages/SignyourselfPdf";
import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage";
import { isEnableSubscription } from "./constant/const";
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
@@ -23,9 +24,10 @@ const Subscriptions = lazy(() => import("./pages/PlanSubscriptions"));
const ChangePassword = lazy(() => import("./pages/ChangePassword"));
const UserProfile = lazy(() => import("./pages/UserProfile"));
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 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`;
const Loader = () => {
@@ -78,83 +80,105 @@ function App() {
<Loader />
) : (
<BrowserRouter>
<Suspense fallback={<Loader />}>
<Routes>
<Route element={<ValidateRoute />}>
<Route exact path="/" element={<Login />} />
<Route exact path="/signup" element={<Signup />} />
</Route>
<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>
<Routes>
<Route element={<ValidateRoute />}>
<Route exact path="/" element={<Login />} />
<Route path="/signup" element={<LazyPage Page={Signup} />} />
</Route>
<Route element={<Validate />}>
<Route
path="/loadmf/signmicroapp/login/:id/:userMail/:contactBookId/:serverUrl"
element={<GuestLogin />}
path="/load/template/:templateId"
element={<TemplatePlaceholder />}
/>
<Route
path="/login/:id/:userMail/:contactBookId/:serverUrl"
element={<GuestLogin />}
exact
path="/load/placeholdersign/:docId"
element={<PlaceHolderSign />}
/>
<Route path="/debugpdf" element={<DebugPdf />} />
<Route path="/forgetpassword" element={<ForgetPassword />} />
{process.env.REACT_APP_ENABLE_SUBSCRIPTION && (
<>
<Route exact path="/pgsignup" element={<Pgsignup />} />
<Route path="/subscription" element={<Subscriptions />} />
</>
)}
<Route element={<HomeLayout />}>
<Route path="/changepassword" element={<ChangePassword />} />
<Route path="/form/:id" element={<Form />} />
<Route path="/report/:id" element={<Report />} />
<Route path="/dashboard/:id" element={<Dashboard />} />
<Route path="/profile" element={<UserProfile />} />
<Route path="/generatetoken" element={<GenerateToken />} />
<Route path="/webhook" element={<Webhook />} />
<Route path="/managesign" element={<ManageSign />} />
<Route path="/opensigndrive" element={<Opensigndrive />} />
<Route
exact
path="/load/recipientSignPdf/:docId/:contactBookId"
element={<PdfRequestFiles />}
/>
</Route>
<Route
path="/loadmf/signmicroapp/login/:id/:userMail/:contactBookId/:serverUrl"
element={<LazyPage Page={GuestLogin} />}
/>
<Route
path="/login/:id/:userMail/:contactBookId/:serverUrl"
element={<LazyPage Page={GuestLogin} />}
/>
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
<Route
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
{isEnableSubscription && (
<>
<Route
path="/template/:templateId"
element={<TemplatePlaceholder />}
path="/pgsignup"
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
path="/placeHolderSign/:docId"
element={<PlaceHolderSign />}
path="/subscription"
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
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>
</Suspense>
</>
)}
<Route element={<HomeLayout />}>
<Route
path="/changepassword"
element={<LazyPage Page={ChangePassword} />}
/>
<Route path="/form/:id" element={<Form />} />
<Route path="/report/:id" element={<Report />} />
<Route path="/dashboard/:id" element={<Dashboard />} />
<Route
path="/profile"
element={<LazyPage Page={UserProfile} />}
/>
<Route
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>
)}
</div>
+2 -2
View File
@@ -56,11 +56,11 @@ const AddSigner = (props) => {
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest");
if (localStorage.getItem("TenetId")) {
if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", {
__type: "Pointer",
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("UserRole", "contracts_User");
if (localStorage.getItem("TenetId")) {
if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenetId")
objectId: localStorage.getItem("TenantId")
});
}
+28 -6
View File
@@ -4,20 +4,32 @@ import FullScreenButton from "./FullScreenButton";
import { useNavigate } from "react-router-dom";
import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize";
import { openInNewTab } from "../constant/Utils";
import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const";
const Header = ({ showSidebar }) => {
const navigation = useNavigate();
const navigate = useNavigate();
const { width } = useWindowSize();
let applogo = localStorage.getItem("appLogo") || "";
let username = localStorage.getItem("username");
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true);
const toggleDropdown = () => {
setIsOpen(!isOpen);
};
useEffect(() => {
checkSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function checkSubscription() {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
}
const closeDropdown = () => {
setIsOpen(false);
Parse.User.logOut();
@@ -43,7 +55,7 @@ const Header = ({ showSidebar }) => {
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
navigation("/");
navigate("/");
};
//handle to close profile drop down menu onclick screen
@@ -83,6 +95,16 @@ const Header = ({ showSidebar }) => {
id="profile-menu"
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>
)}
<div>
<FullScreenButton />
</div>
@@ -123,7 +145,7 @@ const Header = ({ showSidebar }) => {
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
onClick={() => {
setIsOpen(false);
navigation("/profile");
navigate("/profile");
}}
>
<i className="fa-regular fa-user"></i> Profile
@@ -132,7 +154,7 @@ const Header = ({ showSidebar }) => {
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
onClick={() => {
setIsOpen(false);
navigation("/changepassword");
navigate("/changepassword");
}}
>
<i className="fa-solid fa-lock"></i> Change Password
@@ -0,0 +1,47 @@
import React from "react";
import "../../styles/loader.css";
import { useNavigate } from "react-router-dom";
import { openInNewTab } from "../../constant/Utils";
const DashboardButton = (props) => {
const navigate = useNavigate();
function openReport() {
if (props.Data && props.Data.Redirect_type) {
const Redirect_type = props.Data.Redirect_type;
const id = props.Data.Redirect_id;
if (Redirect_type === "Form") {
navigate(`/form/${id}`);
} else if (Redirect_type === "Report") {
navigate(`/report/${id}`);
} else if (Redirect_type === "Url") {
openInNewTab(id);
}
}
}
return (
<div
onClick={() => openReport()}
className={`${
props.Data && props.Data.Redirect_type
? "cursor-pointer"
: "cursor-default"
} w-full px-3 py-2 flex text-white rounded-md shadow overflow-hidden`}
>
<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">
<i
className={`${
props.Icon ? props.Icon : "fa fa-solid fa-info"
} text-[25px] lg:text-[30px]`}
></i>
</span>
<div className="">
<div className="text-base lg:text-lg text-black"> {props.Label}</div>
</div>
</div>
</div>
);
};
export default DashboardButton;
@@ -4,6 +4,7 @@ import Parse from "parse";
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
import "../../styles/loader.css";
import { useNavigate } from "react-router-dom";
import Tooltip from "../../primitives/Tooltip";
const DashboardCard = (props) => {
const navigate = useNavigate();
@@ -335,7 +336,7 @@ const DashboardCard = (props) => {
props.Data && props.Data.Redirect_type
? "cursor-pointer"
: "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">
<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 className="text-xs absolute top-2 right-1">
<Tooltip id={props.Label} iconColor={"white"} message={props?.Data?.tourMessage} />
</div>
</div>
);
};
@@ -1,14 +1,53 @@
import React, { Suspense, lazy } from "react";
const DashboardButton = lazy(() => import("./DashboardButton"));
const DashboardCard = lazy(() => import("./DashboardCard"));
const DashboardReport = lazy(() => import("./DashboardReport"));
const buttonList = [
{
label: "Sign yourself",
redirectId: "sHAnZphf69",
redirectType: "Form",
icon: "fas fa-pen-nib"
},
{
label: "Request signature",
redirectId: "8mZzFxbG1z",
redirectType: "Form",
icon: "fa-solid fa-paper-plane"
}
];
const GetDashboard = (props) => {
const Button = ({ label, redirectId, redirectType, icon }) => (
<div className={"bg-white rounded-md shadow w-full"}>
<Suspense
fallback={
<div style={{ height: "300px" }}>
<div
style={{
marginLeft: "45%",
marginTop: "150px",
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37"
></div>
</div>
}
>
<DashboardButton
Icon={icon}
Label={label}
Data={{ Redirect_type: redirectType, Redirect_id: redirectId }}
/>
</Suspense>
</div>
);
const renderSwitchWithTour = (col) => {
switch (col.widget.type) {
case "Card":
return (
<div
className={"bg-[#2ed8b6] rounded-md shadow mb-4 md:mb-0"}
className={"bg-[#2ed8b6] rounded-md shadow mb-3 md:mb-0"}
data-tut={col.widget.data.tourSection}
style={{ background: col.widget.bgColor }}
>
@@ -42,7 +81,7 @@ const GetDashboard = (props) => {
return (
<div data-tut={col.widget.data.tourSection}>
<Suspense fallback={<div>please wait</div>}>
<div className="mb-4 md:mb-0">
<div className="mb-3 md:mb-0">
<DashboardReport Record={col.widget} />
</div>
</Suspense>
@@ -58,7 +97,7 @@ const GetDashboard = (props) => {
case "Card":
return (
<div
className={"bg-[#2ed8b6] rounded-md shadow mb-4 md:mb-0"}
className={"bg-[#2ed8b6] rounded-md shadow mb-3 md:mb-0"}
style={{ background: col.widget.bgColor }}
>
<Suspense fallback={<div>please wait</div>}>
@@ -76,7 +115,7 @@ const GetDashboard = (props) => {
case "report": {
return (
<Suspense fallback={<div>please wait</div>}>
<div className="mb-4 md:mb-0">
<div className="mb-3 md:mb-0">
<DashboardReport Record={col.widget} />
</div>
</Suspense>
@@ -88,6 +127,22 @@ const GetDashboard = (props) => {
};
return (
<div>
<div className="mb-3">
<div
data-tut={"tourbutton"}
className="flex flex-col md:flex-row gap-6 md:gap-8"
>
{buttonList.map((btn) => (
<Button
key={btn.label}
label={btn.label}
redirectType={btn.redirectType}
redirectId={btn.redirectId}
icon={btn.icon}
/>
))}
</div>
</div>
{props.dashboard.map((val, key) => (
<div key={"a" + key} className="row">
{val.columns.map((col, i) =>
@@ -8,19 +8,11 @@ import { saveAs } from "file-saver";
import { useNavigate } from "react-router-dom";
import Table from "react-bootstrap/Table";
import * as HoverCard from "@radix-ui/react-hover-card";
import SelectFolder from "../shared/fields/SelectFolder"; //check this one
import ModalUi from "../../primitives/ModalUi";
import { themeColor } from "../../constant/const";
import FolderModal from "../shared/fields/FolderModal";
function DriveBody({
pdfData,
setFolderName,
setDocId,
setIsLoading,
setPdfData,
isList,
setIsAlert
}) {
function DriveBody(props) {
const [rename, setRename] = useState("");
const [renameValue, setRenameValue] = useState("");
const inputRef = useRef(null);
@@ -50,28 +42,25 @@ function DriveBody({
name: data.Name,
objectId: data.objectId
};
setFolderName((prev) => [...prev, folderData]);
const loadObj = {
props.setFolderName((prev) => [...prev, folderData]);
props.setIsLoading({
isLoad: true,
message: "This might take some time"
};
setIsLoading(loadObj);
setDocId(data.objectId);
});
props.setDocId(data.objectId);
props.setPdfData([]);
props.setSkip(0);
};
//function for change doc name and update doc name in _document class
const handledRenameDoc = async (data) => {
setRename("");
const trimmedValue = renameValue.trim();
if (trimmedValue.length > 0) {
const updateName = {
Name: renameValue
};
const docId = data.objectId;
const docData = pdfData;
const docData = props.pdfData;
const updatedData = docData.map((item) => {
if (item.objectId === docId) {
// If the item's ID matches the target ID, update the name
@@ -80,8 +69,7 @@ function DriveBody({
// If the item's ID doesn't match, keep it unchanged
return item;
});
setPdfData(updatedData);
props.setPdfData(updatedData);
await axios
.put(
`${localStorage.getItem("baseUrl")}classes/${localStorage.getItem(
@@ -102,7 +90,7 @@ function DriveBody({
})
.catch((err) => {
console.log("Err ", err);
setIsAlert({
props.setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
@@ -199,13 +187,13 @@ function DriveBody({
.then((result) => {
const res = result.data;
if (res) {
const updatedData = pdfData.filter((x) => x.objectId !== docId);
setPdfData(updatedData);
const updatedData = props.pdfData.filter((x) => x.objectId !== docId);
props.setPdfData(updatedData);
}
})
.catch((err) => {
console.log("Err ", err);
setIsAlert({
props.setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
@@ -226,8 +214,8 @@ function DriveBody({
? true
: false
: selecFolderId
? false
: true;
? false
: true;
if (!checkExist) {
if (moveFolderId) {
updateData = {
@@ -262,10 +250,10 @@ function DriveBody({
// console.log("Listdata ", Listdata);
const res = Listdata.data;
if (res) {
const updatedData = pdfData.filter(
const updatedData = props.pdfData.filter(
(x) => x.objectId !== updateDocId
);
setPdfData(updatedData);
props.setPdfData(updatedData);
}
})
.catch((err) => {
@@ -292,14 +280,13 @@ function DriveBody({
//component to handle type of document and render according to type
const handleFolderData = (data, ind, listType) => {
let createddate, status, isDecline, signerExist, isComplete, isPlaceholder;
let createddate, status, isDecline, signerExist, isComplete;
if (data.Type !== "Folder") {
const expireDate = data.ExpiryDate && data.ExpiryDate.iso;
const createdDate = data.createdAt && data.createdAt;
createddate = new Date(createdDate).toLocaleDateString();
isComplete = data.IsCompleted && data.IsCompleted ? true : false;
isDecline = data.IsDeclined && data.IsDeclined;
isPlaceholder = data.Placeholders && data.Placeholders;
signerExist = data.Signers && data.Signers;
const signedUrl = data.SignedUrl;
@@ -314,18 +301,7 @@ function DriveBody({
status = "Completed";
} else if (isDecline) {
status = "Declined";
} else if (!signerExist || signerExist?.length === 0) {
status = "Draft";
} else if (
signerExist?.length > 0 &&
(!isPlaceholder || isPlaceholder?.length === 0)
) {
status = "Draft";
} else if (
signerExist?.length > 0 &&
isPlaceholder?.length > 0 &&
!signedUrl
) {
} else if (!signedUrl) {
status = "Draft";
} else if (isExpire) {
status = "Expired";
@@ -584,7 +560,7 @@ function DriveBody({
//component to handle type of document and render according to type
return (
<>
{isList ? (
{props.isList ? (
<div className="container" style={{ overflowX: "auto" }}>
<Table striped bordered hover>
<thead>
@@ -597,7 +573,7 @@ function DriveBody({
</tr>
</thead>
<tbody>
{pdfData.map((data, ind) => {
{props.pdfData.map((data, ind) => {
return (
<React.Fragment key={ind}>
{handleFolderData(data, ind, "table")}
@@ -609,7 +585,7 @@ function DriveBody({
</div>
) : (
<div className="pdfContainer">
{pdfData.map((data, ind) => {
{props.pdfData.map((data, ind) => {
return (
<div className="box" key={ind}>
{handleFolderData(data, ind, "list")}
@@ -618,13 +594,14 @@ function DriveBody({
})}
</div>
)}
{isOpenMoveModal && (
<SelectFolder
<FolderModal
onSuccess={handleMoveFolder}
isOpenModal={isOpenMoveModal}
folderCls={"contracts_Document"}
setIsOpenMoveModal={setIsOpenMoveModal}
setPdfData={setPdfData}
setPdfData={props.setPdfData}
/>
)}
<ModalUi
@@ -30,7 +30,7 @@ const AddRoleModal = (props) => {
margin: "10px 0 10px 5px"
}}
>
e.g: Hr, Director, Manager, New joinee, Accountant, etc...
e.g: Customer, Hr, Director, Manager, Student, etc...
</p>
<div>
<div
@@ -1,6 +1,9 @@
import React, { useEffect, useState } from "react";
import { themeColor } from "../../constant/const";
import { isEnableSubscription, themeColor } from "../../constant/const";
import ModalUi from "../../primitives/ModalUi";
import { radioButtonWidget } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
function DropdownWidgetOption(props) {
const [dropdownOptionList, setDropdownOptionList] = useState([
"option-1",
@@ -10,6 +13,7 @@ function DropdownWidgetOption(props) {
const [maxCount, setMaxCount] = useState(0);
const [dropdownName, setDropdownName] = useState(props.type);
const [isReadOnly, setIsReadOnly] = useState(false);
const [isHideLabel, setIsHideLabel] = useState(false);
const [status, setStatus] = useState("required");
const [defaultValue, setDefaultValue] = useState("");
const statusArr = ["required", "optional"];
@@ -19,6 +23,7 @@ function DropdownWidgetOption(props) {
setDropdownOptionList(["option-1", "option-2"]);
setDropdownName(props.type);
setIsReadOnly(false);
setIsHideLabel(false);
setMinCount(0);
setMaxCount(0);
setDefaultCheckbox([]);
@@ -39,6 +44,7 @@ function DropdownWidgetOption(props) {
props.currWidgetsDetails?.options?.validation?.maxRequiredCount
);
setIsReadOnly(props.currWidgetsDetails?.options?.isReadOnly);
setIsHideLabel(props.currWidgetsDetails?.options?.isHideLabel);
setStatus(props.currWidgetsDetails?.options?.status || "required");
setDefaultValue(props.currWidgetsDetails?.options?.defaultValue || "");
setDefaultCheckbox(props.currWidgetsDetails?.options?.defaultValue || []);
@@ -106,13 +112,15 @@ function DropdownWidgetOption(props) {
null,
null,
status,
defaultData
defaultData,
isHideLabel
);
// props.setShowDropdown(false);
setDropdownOptionList(["option-1", "option-2"]);
setDropdownName(props.type);
// props.setCurrWidgetsDetails({});
setIsReadOnly(false);
setIsHideLabel(false);
setMinCount(0);
setMaxCount(0);
setDefaultCheckbox([]);
@@ -138,35 +146,15 @@ function DropdownWidgetOption(props) {
};
return (
//props.showDropdown
<ModalUi
// styleClass={"dropdownModal"}
isOpen={props.showDropdown}
title={props.title}
closeOff={true}
>
<div style={{ height: "100%", padding: 20 }}>
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
<div className="h-full p-[15px]">
<form
onSubmit={(e) => {
e.preventDefault();
handleSaveOption();
}}
>
<div className="dropdownContainer">
{["checkbox", "radio"].includes(props.type) &&
!props.isSignYourself && (
<div>
<input
type="checkbox"
checked={isReadOnly}
onChange={(e) => {
setIsReadOnly(e.target.checked);
}}
/>
<label style={{ marginLeft: "10px" }}>Is read only</label>
</div>
)}
<div>
<label style={{ fontSize: "13px", fontWeight: "600" }}>
Name<span style={{ color: "red", fontSize: 13 }}> *</span>
</label>
@@ -178,36 +166,6 @@ function DropdownWidgetOption(props) {
className="drodown-input"
/>
{props.type === "checkbox" && !props.isSignYourself && (
<>
<label style={{ fontSize: "13px", fontWeight: "600" }}>
Minimun check
</label>
<input
required
defaultValue={0}
value={minCount}
onChange={(e) => {
const count = handleSetMinMax(e);
setMinCount(count);
}}
className="drodown-input"
/>
<label style={{ fontSize: "13px", fontWeight: "600" }}>
Maximum check
</label>
<input
required
defaultValue={0}
value={maxCount}
onChange={(e) => {
const count = handleSetMinMax(e);
setMaxCount(count);
}}
className="drodown-input"
/>
</>
)}
<label
style={{ fontSize: "13px", fontWeight: "600", marginTop: "5px" }}
>
@@ -274,7 +232,6 @@ function DropdownWidgetOption(props) {
></i>
</div>
))}
<i
onClick={handleAddInput}
style={{
@@ -285,8 +242,61 @@ function DropdownWidgetOption(props) {
}}
className="fa-solid fa-square-plus"
></i>
<div>
{props.type === "checkbox" && !props.isSignYourself && (
<>
<label
style={{
fontSize: "13px",
fontWeight: "600",
color: !props.isSubscribe && "gray"
}}
>
Minimun check
</label>
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
<input
required
defaultValue={0}
value={minCount}
onChange={(e) => {
const count = handleSetMinMax(e);
setMinCount(count);
}}
className={
props.isSubscribe || !isEnableSubscription
? "drodown-input"
: "disabled 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>
{["dropdown", "radio"].includes(props.type) && (
{["dropdown", radioButtonWidget].includes(props.type) && (
<>
<label
style={{
@@ -321,7 +331,7 @@ function DropdownWidgetOption(props) {
</select>
</>
)}
{props.type !== "checkbox" && props.type !== "radio" && (
{props.type !== "checkbox" && props.type !== radioButtonWidget && (
<>
<div
style={{
@@ -358,19 +368,58 @@ function DropdownWidgetOption(props) {
</div>
</>
)}
</div>
{["checkbox", radioButtonWidget].includes(props.type) && (
<div className="flex flex-row gap-5 mt-2 items-center text-center">
{!props.isSignYourself && (
<div>
<input
id="isreadonly"
type="checkbox"
checked={isReadOnly}
onChange={(e) => {
setIsReadOnly(e.target.checked);
}}
/>
<label className="ml-1" htmlFor="isreadonly">
Is read only
</label>
</div>
)}
<div>
<input
id="ishidelabel"
type="checkbox"
checked={isHideLabel}
onChange={(e) => {
setIsHideLabel(e.target.checked);
}}
/>
<label className="ml-1" htmlFor="ishidelabel">
Hide labels
</label>
</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
style={{
height: "1px",
backgroundColor: "#9f9f9f",
width: "100%",
marginTop: "15px",
marginBottom: "15px"
}}
className={`${
props.type === "checkbox" && !props.isSignYourself
? "mb-[15px]"
: "my-[15px]"
} w-full h-[1px] bg-[#9f9f9f]`}
></div>
<button
// onClick={() => handleSaveOption()}
disabled={dropdownOptionList.length === 0 && true}
style={{
background: themeColor,
@@ -15,7 +15,8 @@ function EmailComponent({
setSuccessEmail,
pdfName,
sender,
setIsAlert
setIsAlert,
extUserId
}) {
const [emailList, setEmailList] = useState([]);
const [emailValue, setEmailValue] = useState();
@@ -38,6 +39,7 @@ function EmailComponent({
const openSignUrl = "https://www.opensignlabs.com/contact-us";
const themeBGcolor = themeColor;
let params = {
extUserId: extUserId,
pdfName: pdfName,
url: pdfUrl,
recipient: emailList[i],
+290 -192
View File
@@ -2,17 +2,123 @@ import React, { useState, useEffect } from "react";
import BorderResize from "./BorderResize";
import PlaceholderBorder from "./PlaceholderBorder";
import { Rnd } from "react-rnd";
import { defaultWidthHeight, isMobile } from "../../constant/Utils";
import {
defaultWidthHeight,
handleCopyNextToWidget,
isMobile,
onChangeInput,
radioButtonWidget,
textInputWidget,
textWidget
} from "../../constant/Utils";
import PlaceholderType from "./PlaceholderType";
import moment from "moment";
import "../../styles/opensigndrive.css";
const selectFormat = (data) => {
switch (data) {
case "L":
return "MM/dd/yyyy";
case "DD-MM-YYYY":
return "dd-MM-yyyy";
case "DD/MM/YYYY":
return "dd/MM/yyyy";
case "LL":
return "MMMM dd, yyyy";
case "DD MMM, YYYY":
return "dd MMM, yyyy";
case "YYYY-MM-DD":
return "yyyy-MM-dd";
case "MM-DD-YYYY":
return "MM-dd-yyyy";
case "MM.DD.YYYY":
return "MM.dd.yyyy";
case "MMM DD, YYYY":
return "MMM dd, yyyy";
case "DD MMMM, YYYY":
return "dd MMMM, yyyy";
default:
return "MM/dd/yyyy";
}
};
const changeDateToMomentFormat = (format) => {
switch (format) {
case "MM/dd/yyyy":
return "L";
case "dd-MM-yyyy":
return "DD-MM-YYYY";
case "dd/MM/yyyy":
return "DD/MM/YYYY";
case "MMMM dd, yyyy":
return "LL";
case "dd MMM, yyyy":
return "DD MMM, YYYY";
case "yyyy-MM-dd":
return "YYYY-MM-DD";
case "MM-dd-yyyy":
return "MM-DD-YYYY";
case "MM.dd.yyyy":
return "MM.DD.YYYY";
case "MMM dd, yyyy":
return "MMM DD, YYYY";
case "dd MMMM, yyyy":
return "DD MMMM, YYYY";
default:
return "L";
}
};
//function to get default date
const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
let date;
if (format && format === "dd-MM-yyyy") {
const newdate = selectedDate
? selectedDate
: moment(new Date()).format(changeDateToMomentFormat(format));
const [day, month, year] = newdate.split("-");
date = new Date(`${year}-${month}-${day}`);
} else {
date = new Date(selectedDate);
}
const value = date;
return value;
};
//function to get default format
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
function Placeholder(props) {
const [isDraggingEnabled, setDraggingEnabled] = useState(true);
const [isShowDateFormat, setIsShowDateFormat] = useState(false);
const [selectDate, setSelectDate] = useState();
const [selectDate, setSelectDate] = useState({
date:
props.pos.type === "date"
? moment(
getDefaultdate(
props?.pos?.options?.response,
props.pos?.options?.validation?.format
).getTime()
).format(
changeDateToMomentFormat(props.pos?.options?.validation?.format)
)
: "",
format:
props.pos.type === "date"
? getDefaultFormat(props.pos?.options?.validation?.format)
: ""
});
const [dateFormat, setDateFormat] = useState([]);
const [saveDateFormat, setSaveDateFormat] = useState("");
const [startDate, setStartDate] = useState(
props.pos.type === "date" &&
getDefaultdate(
props?.pos?.options?.response,
props.pos?.options?.validation?.format
)
);
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
width: null,
height: null
});
const dateFormatArr = [
"L",
"DD-MM-YYYY",
@@ -25,70 +131,41 @@ function Placeholder(props) {
"DD MMMM, YYYY"
];
const selectFormat = (data) => {
switch (data) {
case "L":
return "MM/dd/yyyy";
case "DD-MM-YYYY":
return "dd-MM-yyyy";
case "DD/MM/YYYY":
return "dd/MM/yyyy";
case "LL":
return "MMMM dd, yyyy";
case "DD MMM, YYYY":
return "dd MMM, YYYY";
case "YYYY-MM-DD":
return "YYYY-MM-dd";
case "MM-DD-YYYY":
return "MM-dd-YYYY";
case "MM.DD.YYYY":
return "MM.dd.YYYY";
case "MMM DD, YYYY":
return "MMM dd, YYYY";
case "DD MMMM, YYYY":
return "dd MMMM, YYYY";
default:
return "dd/MM/yyyy";
}
};
useEffect(() => {
const updateWidth = () => {
const rndElement = document.getElementById(props.pos.key);
if (rndElement) {
const { width, height } = rndElement.getBoundingClientRect();
setGetCheckboxRenderWidth({ width: width, height: height });
}
};
// Delay to ensure rendering is complete
const timer = setTimeout(updateWidth, 0);
return () => clearTimeout(timer);
}, [props.pos]);
useEffect(() => {
//set default current date and default format MM/dd/yyyy
if (props.isSignYourself) {
const date = new Date();
const milliseconds = date.getTime();
const newDate = moment(milliseconds).format("MM/DD/YYYY");
const dateObj = {
date: newDate,
format: "MM/dd/YYYY"
};
setSelectDate(dateObj);
} else {
const defaultRes = props?.pos?.options?.response;
const defaultFormat = props.pos?.options?.validation?.format;
const updateDate = defaultRes
? new Date(props?.pos?.options?.response)
: new Date();
const dateFormat = defaultFormat ? defaultFormat : "MM/DD/YYYY";
const milliseconds = updateDate.getTime();
const newDate = moment(milliseconds).format(dateFormat);
const dateObj = {
date: newDate,
format: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/YYYY"
};
setSelectDate(dateObj);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const closeMenuOnOutsideClick = (e) => {
if (!isDraggingEnabled && !e.target.closest("#changeIsDragging")) {
setDraggingEnabled(true);
}
};
//function for add selected date and format in selectFormat
document.addEventListener("click", closeMenuOnOutsideClick);
return () => {
// Cleanup the event listener when the component unmounts
document.removeEventListener("click", closeMenuOnOutsideClick);
};
}, [isDraggingEnabled]);
//function change format array list with selected date and format
const changeDateFormat = () => {
const updateDate = [];
dateFormatArr.map((data) => {
let date;
if (selectDate.format === "dd-MM-yyyy") {
if (selectDate && selectDate.format === "dd-MM-yyyy") {
const [day, month, year] = selectDate.date.split("-");
date = new Date(`${year}-${month}-${day}`);
} else {
@@ -104,13 +181,15 @@ function Placeholder(props) {
});
setDateFormat(updateDate);
};
useEffect(() => {
if (props.isPlaceholder || props.isSignYourself) {
selectDate && changeDateFormat();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectDate]);
//handle to close drop down menu onclick screen
//it detect outside click of date dropdown menu
useEffect(() => {
const closeMenuOnOutsideClick = (e) => {
if (isShowDateFormat && !e.target.closest("#menu-container")) {
@@ -124,20 +203,21 @@ function Placeholder(props) {
};
}, [isShowDateFormat]);
//onclick placeholder function to open signature pad
const handlePlaceholderClick = () => {
//`handleWidgetIdandPopup` is used to set current widget id and open relative popup
const handleWidgetIdandPopup = () => {
if (props.setSelectWidgetId) {
props.setSelectWidgetId(props.pos.key);
}
const widgetTypeExist = [
"text",
textInputWidget,
"checkbox",
"name",
"company",
"job title",
"date",
"email"
"email",
textWidget
].includes(props.pos.type);
if (widgetTypeExist) {
@@ -167,7 +247,7 @@ function Placeholder(props) {
} else if (
props.isPlaceholder &&
!props.isDragging &&
props.pos.type !== "label"
props.pos.type !== textWidget
) {
if (props.pos.key === props.selectWidgetId) {
props.handleLinkUser(props.data.Id);
@@ -194,9 +274,20 @@ function Placeholder(props) {
}
}
};
const handleWidgetsOnclick = () => {
if (props.pos.type === "radio") {
const handleOnClickPlaceholder = () => {
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
if (!props.isNeedSign) {
props.setWidgetType(props.pos.type);
}
if (props.isNeedSign && props.data?.signerObjId === props.signerObjId) {
handleWidgetIdandPopup();
} else if (props.isPlaceholder || props.isSignYourself) {
handleWidgetIdandPopup();
}
};
//`handleOnClickSettingIcon` is used set current widget details and open setting of it
const handleOnClickSettingIcon = () => {
if (props.pos.type === radioButtonWidget) {
props.setIsRadio(true);
} else if (props.pos.type === "dropdown") {
props?.setShowDropdown(true);
@@ -206,13 +297,78 @@ function Placeholder(props) {
props?.handleNameModal(true);
}
if (props.isPlaceholder) {
if (props.isPlaceholder && props.type !== textWidget) {
props.setUniqueId(props.data.Id);
}
props.setSignKey(props.pos.key);
props.setWidgetType(props.pos.type);
props.setCurrWidgetsDetails(props.pos);
};
//function to set required state value onclick on widget's copy icon
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) {
props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id);
} else if (props.data && props.pos.type === textWidget) {
props.setTempSignerId(props.uniqueId);
props.setSignerObjId(props?.data?.signerObjId);
props.setUniqueId(props?.data?.Id);
}
//checking widget's type and open widget copy modal for required widgets
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
const handleSaveDate = (data, isDateChange) => {
let updateDate = data.date;
//check if date change by user
if (isDateChange) {
//`changeDateToMomentFormat` is used to convert date as per required to moment package
updateDate = moment(data.date).format(
changeDateToMomentFormat(data.format)
);
}
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
//`getDefaultdate` is used to convert update date in new Date() format
const date = moment(
getDefaultdate(updateDate, data?.format).getTime()
).format(changeDateToMomentFormat(data?.format));
//`onChangeInput` is used to save data related to date in a placeholder field
onChangeInput(
date,
props.pos.key,
props.xyPostion,
props.index,
props.setXyPostion,
props.data && props.data.Id,
false,
data?.format
);
setSelectDate({ date: date, format: data?.format });
};
const PlaceholderIcon = () => {
return (
props.isShowBorder && (
@@ -223,48 +379,42 @@ function Placeholder(props) {
<i
onClick={(e) => {
e.stopPropagation();
handleWidgetsOnclick();
handleOnClickSettingIcon();
}}
onTouchEnd={(e) => {
e.stopPropagation();
handleWidgetsOnclick();
handleOnClickSettingIcon();
}}
className="fa-solid fa-gear settingIcon"
style={{
color: "#188ae2",
right: "9px",
top: "-28px"
right: "29px",
top: "-19px"
}}
></i>
) : (
props.pos.type !== "date" &&
props.pos.type !== "label" &&
props.pos.type !== "signature" &&
!props.isSignYourself && (
((!props?.pos?.type && props.pos.isStamp) ||
(props?.pos?.type &&
!["date", textWidget, "signature"].includes(
props.pos.type
) &&
!props.isSignYourself)) && (
<i
onClick={(e) => {
e.stopPropagation();
handleWidgetsOnclick();
handleOnClickSettingIcon();
}}
onTouchEnd={(e) => {
e.stopPropagation();
handleWidgetsOnclick();
handleOnClickSettingIcon();
}}
className="fa-solid fa-gear settingIcon"
style={{
color: "#188ae2",
right: ["checkbox", "radio"].includes(props.pos.type)
? "24px"
: "47px",
top: ["checkbox", "radio"].includes(props.pos.type)
? "-28px"
: "-19px"
}}
style={{ color: "#188ae2", right: "47px", top: "-19px" }}
></i>
)
)}
{props.pos.type !== "label" && !props.isSignYourself && (
{props.pos.type !== textWidget && !props.isSignYourself && (
<i
data-tut="reactourLinkUser"
className="fa-regular fa-user signUserIcon"
@@ -278,19 +428,7 @@ function Placeholder(props) {
props.handleLinkUser(props.data.Id);
props.setUniqueId(props.data.Id);
}}
style={{
color: "#188ae2",
right:
props.pos.type === "checkbox" ||
props.pos.type === "radio"
? "8px"
: "32px",
top:
props.pos.type === "checkbox" ||
props.pos.type === "radio"
? "-28px"
: "-18px"
}}
style={{ color: "#188ae2", right: "32px", top: "-18px" }}
></i>
)}
</>
@@ -347,10 +485,12 @@ function Placeholder(props) {
e.stopPropagation();
setIsShowDateFormat(!isShowDateFormat);
setSelectDate(data);
handleSaveDate(data);
}}
onClick={() => {
setIsShowDateFormat(!isShowDateFormat);
setSelectDate(data);
handleSaveDate(data);
}}
className="dropdown-item itemColor"
style={{ fontSize: "12px" }}
@@ -364,35 +504,9 @@ function Placeholder(props) {
)}
<i
className="fa-regular fa-copy signCopy"
onClick={(e) => {
if (props.data) {
props.setSignerObjId(props.data.signerObjId);
props.setUniqueId(props.data.Id);
}
e.stopPropagation();
props.setIsPageCopy(true);
props.setSignKey(props.pos.key);
}}
onTouchEnd={(e) => {
if (props.data) {
props.setSignerObjId(props.data.signerObjId);
props.setUniqueId(props.data.Id);
}
e.stopPropagation();
props.setIsPageCopy(true);
props.setSignKey(props.pos.key);
}}
style={{
color: "#188ae2",
right:
props.pos.type === "checkbox" || props.pos.type === "radio"
? "-9px"
: "12px",
top:
props.pos.type === "checkbox" || props.pos.type === "radio"
? "-28px"
: "-18px"
}}
onClick={(e) => handleCopyPlaceholder(e)}
onTouchEnd={(e) => handleCopyPlaceholder(e)}
style={{ color: "#188ae2", right: "12px", top: "-18px" }}
></i>
<i
className="fa-regular fa-circle-xmark signCloseBtn"
@@ -415,17 +529,7 @@ function Placeholder(props) {
props.setIsStamp(false);
}
}}
style={{
color: "#188ae2",
right:
props.pos.type === "checkbox" || props.pos.type === "radio"
? "-27px"
: "-8px",
top:
props.pos.type === "checkbox" || props.pos.type === "radio"
? "-28px"
: "-18px"
}}
style={{ color: "#188ae2", right: "-8px", top: "-18px" }}
></i>
</>
)
@@ -434,11 +538,11 @@ function Placeholder(props) {
return (
<Rnd
id={props.pos.key}
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
//ref={nodeRef}
key={props.pos.key}
lockAspectRatio={
props.pos.type !== "label" &&
props.pos.type !== textWidget &&
(props.pos.Width
? props.pos.Width / props.pos.Height
: defaultWidthHeight(props.pos.type).width /
@@ -454,10 +558,10 @@ function Placeholder(props) {
props.data && props.isNeedSign
? props.data?.signerObjId === props.signerObjId &&
props.pos.type !== "checkbox" &&
props.pos.type !== "radio"
props.pos.type !== radioButtonWidget
? true
: false
: props.pos.type !== "radio" &&
: props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId &&
true,
@@ -467,8 +571,8 @@ function Placeholder(props) {
bounds="parent"
className="signYourselfBlock"
style={{
border: props.pos.type !== "checkbox" && "1px solid #007bff",
borderRadius: props.pos.type !== "checkbox" && "2px",
border: "1px solid #007bff",
borderRadius: "2px",
textAlign:
props.pos.type !== "name" &&
props.pos.type !== "company" &&
@@ -482,21 +586,27 @@ function Placeholder(props) {
: "all-scroll",
zIndex:
props.pos.type === "date"
? "99"
? props.pos.key === props.selectWidgetId
? 99 + 1
: 99
: props?.pos?.zIndex
? props.pos.zIndex
: "5",
background: props.data
? props.data.blockColor
: props.pos.type !== "checkbox" && "rgb(203 233 237)"
background: props.data ? props.data.blockColor : "rgb(203 233 237)"
}}
onDrag={() => {
setDraggingEnabled(true);
props.handleTabDrag && props.handleTabDrag(props.pos.key);
}}
size={{
width: props.posWidth(props.pos, props.isSignYourself),
height: props.posHeight(props.pos, props.isSignYourself)
width:
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget || props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself)
}}
onResizeStart={() => {
setDraggingEnabled(true);
@@ -508,7 +618,7 @@ function Placeholder(props) {
disableDragging={
props.isNeedSign
? true
: props.isPlaceholder && props.pos.type !== "date"
: props.isPlaceholder && ![textWidget].includes(props.pos.type)
? false
: !isDraggingEnabled
}
@@ -532,41 +642,23 @@ function Placeholder(props) {
false
);
}}
onClick={() => {
!props.isNeedSign && props.setWidgetType(props.pos.type);
props.isNeedSign && props.data?.signerObjId === props.signerObjId
? handlePlaceholderClick()
: props.isPlaceholder
? handlePlaceholderClick()
: props.isSignYourself && handlePlaceholderClick();
}}
onClick={() => handleOnClickPlaceholder()}
>
{props.isShowBorder &&
props.pos.type !== "radio" &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId ? (
<BorderResize
right={
props.pos.type === "checkbox" || props.pos.type === "radio"
? -21
: -12
}
top={
props.pos.type === "checkbox" || props.pos.type === "radio"
? -21
: -11
}
/>
<BorderResize right={-12} top={-11} />
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
props.data?.signerObjId === props.signerObjId &&
props.pos.type !== "radio" &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" ? (
<BorderResize />
) : (
<></>
)
) : (
props.pos.type !== "radio" &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId && <BorderResize />
)}
@@ -576,6 +668,7 @@ function Placeholder(props) {
setDraggingEnabled={setDraggingEnabled}
pos={props.pos}
isPlaceholder={props.isPlaceholder}
getCheckboxRenderWidth={getCheckboxRenderWidth}
/>
)}
{isMobile ? (
@@ -583,18 +676,21 @@ function Placeholder(props) {
style={{
left: props.xPos(props.pos, props.isSignYourself),
top: props.yPos(props.pos, props.isSignYourself),
width: props.posWidth(props.pos, props.isSignYourself),
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
// "auto", //props.posWidth(props.pos, props.isSignYourself),
// height: props.posHeight(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself),
zIndex: "10"
}}
onTouchEnd={() => {
!props.isNeedSign && props.setWidgetType(props.pos.type);
props.isNeedSign && props.data?.signerObjId === props.signerObjId
? handlePlaceholderClick()
: props.isPlaceholder
? handlePlaceholderClick()
: props.isSignYourself && handlePlaceholderClick();
}}
onTouchEnd={() => handleOnClickPlaceholder()}
>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
@@ -615,9 +711,10 @@ function Placeholder(props) {
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setSaveDateFormat={setSaveDateFormat}
saveDateFormat={saveDateFormat}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
/>
</div>
) : (
@@ -640,9 +737,10 @@ function Placeholder(props) {
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setSaveDateFormat={setSaveDateFormat}
saveDateFormat={saveDateFormat}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
/>
</>
)}
@@ -2,30 +2,46 @@ import React from "react";
import { themeColor } from "../../constant/const";
import {
defaultWidthHeight,
resizeBorderExtraWidth
isMobile,
radioButtonWidget,
resizeBorderExtraWidth,
textWidget
} from "../../constant/Utils";
function PlaceholderBorder(props) {
const getResizeBorderExtraWidth =
props.pos.type === "checkbox" || props.pos.type === "radio"
? 38
: resizeBorderExtraWidth();
const getResizeBorderExtraWidth = resizeBorderExtraWidth();
const defaultWidth = defaultWidthHeight(props.pos.type).width;
const defaultHeight = defaultWidthHeight(props.pos.type).height;
const handleMinWidth = () => {
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
} else {
return props.pos.Width
? props.pos.Width + getResizeBorderExtraWidth
: defaultWidth + getResizeBorderExtraWidth;
}
};
const handleMinHeight = () => {
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
} else {
return props.pos.Height
? props.pos.Height + getResizeBorderExtraWidth
: defaultHeight + getResizeBorderExtraWidth;
}
};
return (
<div
onMouseEnter={props?.setDraggingEnabled(true)}
onMouseEnter={() => !isMobile && props?.setDraggingEnabled(true)}
onTouchEnd={() =>
props.pos.type === textWidget && props?.setDraggingEnabled(true)
}
className="borderResize"
style={{
borderColor: themeColor,
borderStyle: "dashed",
width: props.pos.Width
? props.pos.Width + getResizeBorderExtraWidth
: defaultWidth + getResizeBorderExtraWidth,
height: props.pos.Height
? props.pos.Height + getResizeBorderExtraWidth
: defaultHeight + getResizeBorderExtraWidth,
minWidth: handleMinWidth(),
minHeight: handleMinHeight(),
borderWidth: "0.2px",
overflow: "hidden"
}}
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import { themeColor } from "../../constant/const";
import ModalUi from "../../primitives/ModalUi";
import { randomId } from "../../constant/Utils";
import { randomId, textWidget } from "../../constant/Utils";
function PlaceholderCopy(props) {
const copyType = ["All pages", "All pages but last", "All pages but first"];
@@ -180,7 +180,13 @@ function PlaceholderCopy(props) {
copyPlaceholder("first");
}
};
const handleUniqueId = () => {
if (props.widgetType === textWidget) {
props.setUniqueId(props?.tempSignerId);
props.setTempSignerId("");
}
props.setIsPageCopy(false);
};
return (
<ModalUi
isOpen={props.isPageCopy}
@@ -220,7 +226,7 @@ function PlaceholderCopy(props) {
<button
onClick={() => {
handleApplyCopy();
props.setIsPageCopy(false);
handleUniqueId();
}}
style={{ background: themeColor }}
type="button"
@@ -232,7 +238,9 @@ function PlaceholderCopy(props) {
<button
type="button"
className="finishBtn cancelBtn"
onClick={() => props.setIsPageCopy(false)}
onClick={() => {
handleUniqueId();
}}
>
Cancel
</button>
@@ -1,5 +1,15 @@
import React, { useEffect, useState, forwardRef, useRef } from "react";
import { getMonth, getYear, onChangeInput, range } from "../../constant/Utils";
import {
getMonth,
getYear,
isMobile,
onChangeHeightOfTextArea,
onChangeInput,
radioButtonWidget,
range,
textInputWidget,
textWidget
} from "../../constant/Utils";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import "../../styles/signature.css";
@@ -7,10 +17,7 @@ import RegexParser from "regex-parser";
function PlaceholderType(props) {
const type = props?.pos?.type;
const [selectOption, setSelectOption] = useState(
props.pos?.options?.defaultValue ? props.pos?.options?.defaultValue : ""
);
const [startDate, setStartDate] = useState(new Date());
const [selectOption, setSelectOption] = useState("");
const [validatePlaceholder, setValidatePlaceholder] = useState("");
const inputRef = useRef(null);
const [textValue, setTextValue] = useState();
@@ -80,7 +87,7 @@ function PlaceholderType(props) {
case "number":
setValidatePlaceholder("12345");
break;
case "text":
case textInputWidget:
setValidatePlaceholder("enter text");
break;
default:
@@ -89,10 +96,7 @@ function PlaceholderType(props) {
}
useEffect(() => {
if (props.isNeedSign && type === "date") {
const updateDate = new Date();
setStartDate(updateDate);
} else if (type && type === "checkbox" && props.isNeedSign) {
if (type && type === "checkbox" && props.isNeedSign) {
const isDefaultValue = props.pos.options?.defaultValue;
if (isDefaultValue) {
setSelectedCheckbox(isDefaultValue);
@@ -103,7 +107,18 @@ function PlaceholderType(props) {
checkRegularExpress(props.pos?.options?.validation?.type);
}
setTextValue(
props.pos?.options?.defaultValue ? props.pos?.options?.defaultValue : ""
props.pos?.options?.response
? props.pos?.options?.response
: props.pos?.options?.defaultValue
? props.pos?.options?.defaultValue
: ""
);
setSelectOption(
props.pos?.options?.response
? props.pos?.options?.response
: props.pos?.options?.defaultValue
? props.pos?.options?.defaultValue
: ""
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -120,54 +135,8 @@ function PlaceholderType(props) {
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos?.options?.defaultValue]);
}, [props.pos]);
useEffect(() => {
if (type && type === "date") {
if (props.selectDate) {
let updateDate;
if (props.selectDate.format === "dd-MM-yyyy") {
const [day, month, year] = props.saveDateFormat.split("-");
updateDate = new Date(`${year}-${month}-${day}`);
} else {
if (props?.saveDateFormat) {
updateDate = new Date(props.saveDateFormat);
}
}
// const updateDate = new Date(props.saveDateFormat);
setStartDate(updateDate);
const dateObj = {
date: props.saveDateFormat,
format: props.selectDate
? props.selectDate?.format
: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/YYYY"
};
props.setSelectDate(dateObj);
onChangeInput(
props.saveDateFormat,
props.pos.key,
props.xyPostion,
props.index,
props.setXyPostion,
props.data && props.data.Id,
false,
props.selectDate?.format
? props.selectDate.format
: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/YYYY"
);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.saveDateFormat]);
const dateValue = (value) => {
props.setSaveDateFormat(value);
return <span>{value}</span>;
};
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div
className="inputPlaceholder"
@@ -175,7 +144,7 @@ function PlaceholderType(props) {
onClick={onClick}
ref={ref}
>
{dateValue(value)}
{value}
<i className="fa-regular fa-calendar" style={{ marginLeft: "5px" }}></i>
</div>
));
@@ -307,12 +276,38 @@ function PlaceholderType(props) {
isRadio
);
};
//function to set onchange date
const handleOnDateChange = (date) => {
props.setStartDate(date);
const isDateChange = true;
const dateObj = {
date: date,
format: props.selectDate.format
};
props.handleSaveDate(dateObj, isDateChange);
};
//handle height on enter press in text area
const handleEnterPress = (e) => {
const height = 15;
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) {
case "signature":
return props.pos.SignUrl ? (
<img
alt="signimg"
draggable="false"
src={props.pos.SignUrl}
style={{
width: "99%",
@@ -334,6 +329,7 @@ function PlaceholderType(props) {
return props.pos.SignUrl ? (
<img
alt="signimg"
draggable="false"
src={props.pos.SignUrl}
style={{
width: "99%",
@@ -356,51 +352,55 @@ function PlaceholderType(props) {
<div style={{ zIndex: props.isSignYourself && "99" }}>
{props.pos.options?.values?.map((data, ind) => {
return (
<input
key={ind}
style={{
width: props.pos.Width,
display: "flex",
justifyContent: "center",
marginBottom: "6px",
marginTop: "5px"
}}
onBlur={handleInputBlur}
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
type="checkbox"
checked={selectCheckbox(ind)}
onChange={(e) => {
if (e.target.checked) {
if (!props.isPlaceholder) {
const maxRequired =
props.pos.options?.validation?.maxRequiredCount;
const maxCountInt = maxRequired && parseInt(maxRequired);
<div key={ind} className="flex items-center text-center gap-0.5">
<input
style={{
// width: props.pos.Width,
display: "flex",
justifyContent: "center",
marginTop: ind === 0 ? 0 : "5px"
}}
onBlur={handleInputBlur}
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
type="checkbox"
checked={selectCheckbox(ind)}
onChange={(e) => {
if (e.target.checked) {
if (!props.isPlaceholder) {
const maxRequired =
props.pos.options?.validation?.maxRequiredCount;
const maxCountInt =
maxRequired && parseInt(maxRequired);
if (maxCountInt > 0) {
if (
selectedCheckbox &&
selectedCheckbox?.length <= maxCountInt - 1
) {
if (maxCountInt > 0) {
if (
selectedCheckbox &&
selectedCheckbox?.length <= maxCountInt - 1
) {
handleCheckboxValue(e.target.checked, ind);
}
} else {
handleCheckboxValue(e.target.checked, ind);
}
} else {
handleCheckboxValue(e.target.checked, ind);
}
} else {
handleCheckboxValue(e.target.checked, ind);
}
} else {
handleCheckboxValue(e.target.checked, ind);
}
}}
/>
}}
/>
{!props.pos.options?.isHideLabel && (
<label className="text-xs mb-0 text-center">{data}</label>
)}
</div>
);
})}
</div>
);
case "text":
case textInputWidget:
return props.isSignYourself ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<input
@@ -462,7 +462,7 @@ function PlaceholderType(props) {
>
{/* Default/Title option */}
<option value="" disabled hidden>
{props.pos.options.name}
{props?.pos?.options?.name}
</option>
{props.pos?.options?.values.map((data, ind) => {
@@ -485,6 +485,7 @@ function PlaceholderType(props) {
return props.pos.SignUrl ? (
<img
alt="signimg"
draggable="false"
src={props.pos.SignUrl}
style={{
width: "99%",
@@ -639,21 +640,21 @@ function PlaceholderType(props) {
</div>
)}
disabled={
props.isNeedSign && props.data?.signerObjId !== props.signerObjId
props.isPlaceholder ||
(props.isNeedSign &&
props.data?.signerObjId !== props.signerObjId)
}
onBlur={handleInputBlur}
closeOnScroll={true}
className="inputPlaceholder"
style={{ outlineColor: "#007bff" }}
selected={
startDate
? startDate
props?.startDate
? props?.startDate
: props.pos.options?.response &&
new Date(props.pos.options.response)
}
onChange={(date) => {
setStartDate(date);
}}
onChange={(date) => handleOnDateChange(date)}
popperPlacement="top-end"
customInput={<ExampleCustomInput />}
dateFormat={
@@ -661,7 +662,7 @@ function PlaceholderType(props) {
? props.selectDate?.format
: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/YYYY"
: "MM/dd/yyyy"
}
/>
</div>
@@ -670,6 +671,7 @@ function PlaceholderType(props) {
return props.pos.SignUrl ? (
<img
alt="signimg"
draggable="false"
src={props.pos.SignUrl}
style={{
width: "99%",
@@ -721,43 +723,50 @@ function PlaceholderType(props) {
<span>{type}</span>
</div>
);
case "radio":
case radioButtonWidget:
return (
<div>
{props.pos.options?.values.map((data, ind) => {
return (
<input
key={ind}
style={{
width: props.pos.Width,
display: "flex",
justifyContent: "center",
marginBottom: "6px",
marginTop: "5px"
}}
type="radio"
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
checked={handleRadioCheck(data)}
onChange={(e) => {
if (!props.isPlaceholder) {
handleCheckRadio(e.target.checked, data);
<div key={ind} className="flex items-center text-center gap-0.5">
<input
style={{
width: props.pos.Width,
display: "flex",
justifyContent: "center",
marginTop: ind === 0 ? 0 : "5px"
}}
type="radio"
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
}}
/>
checked={handleRadioCheck(data)}
onChange={(e) => {
if (!props.isPlaceholder) {
handleCheckRadio(e.target.checked, data);
}
}}
/>
{!props.pos.options?.isHideLabel && (
<label className="text-xs mb-0">{data}</label>
)}
</div>
);
})}
</div>
);
case "label":
case textWidget:
return (
<textarea
placeholder="Enter label"
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
onBlur={handleInputBlur}
onChange={(e) => {
setTextValue(e.target.value);
onChangeInput(
e.target.value,
props.pos.key,
@@ -768,7 +777,11 @@ function PlaceholderType(props) {
false
);
}}
className="labelTextArea"
className={
isMobile
? "labelTextArea labelWidthMobile"
: "labelTextArea labelWidthDesktop"
}
style={{ whiteSpace: "pre-wrap" }}
cols="50"
/>
@@ -778,6 +791,7 @@ function PlaceholderType(props) {
<div style={{ pointerEvents: "none" }}>
<img
alt="signimg"
draggable="false"
src={props.pos.SignUrl}
style={{
width: "99%",
+132 -48
View File
@@ -1,36 +1,15 @@
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 color = [
"#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 [animationParent] = useAutoAnimate();
const [isHover, setIsHover] = useState();
const [isEdit, setIsEdit] = useState(false);
//function for onhover signer name change background color
@@ -63,27 +42,100 @@ const RecipientList = (props) => {
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 (
<>
{props.signersdata.length > 0 &&
props.signersdata.map((obj, ind) => {
return (
<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"
onMouseEnter={() => setIsHover(ind)}
onMouseLeave={() => setIsHover(null)}
key={ind}
className={
props.sendInOrder
? props.isMailSend
? "disabled"
: "dragCursor"
: props.isMailSend && "disabled"
}
style={
isHover === ind || props.isSelectListId === ind
? onHoverStyle(ind, obj.blockColor)
(!isMobile && isHover === ind) || props.isSelectListId === ind
? onHoverStyle(ind, obj?.blockColor)
: nonHoverStyle(ind)
}
onClick={() => {
onClick={(e) => {
e.preventDefault();
props.setSignerObjId(obj?.objectId || "");
props.setIsSelectId(ind);
props.setContractName(obj?.className || "");
props.setUniqueId(obj.Id);
props.setRoleName(obj.Role);
props.setBlockColor(obj?.blockColor);
if (props.handleModal) {
props.handleModal();
}
@@ -98,10 +150,9 @@ const RecipientList = (props) => {
}}
>
<div
className="signerStyle"
style={{
background: obj.blockColor
? darkenColor(obj.blockColor, 0.4)
background: obj?.blockColor
? darkenColor(obj?.blockColor, 0.4)
: nameColor[ind % nameColor.length],
width: 30,
height: 30,
@@ -113,6 +164,7 @@ const RecipientList = (props) => {
}}
>
<span
className={props.sendInOrder && "dragCursor"}
style={{
fontSize: "12px",
textAlign: "center",
@@ -140,13 +192,12 @@ const RecipientList = (props) => {
}}
>
{obj.Name ? (
<span className="userName" style={{ cursor: "default" }}>
{obj.Name}
</span>
<span className={"userName"}>{obj.Name}</span>
) : (
<>
<span
className="userName"
style={{ cursor: "pointer" }}
onClick={() => {
setIsEdit({ [obj.Id]: true });
props.setRoleName(obj.Role);
@@ -157,7 +208,8 @@ const RecipientList = (props) => {
ref={inputRef}
style={{
backgroundColor: "transparent",
width: "inherit"
width: "inherit",
padding: "3px"
}}
value={obj.Role}
onChange={(e) => props.handleRoleChange(e, obj.Id)}
@@ -173,25 +225,57 @@ const RecipientList = (props) => {
}}
/>
) : (
obj.Role
<span style={{ padding: "3px" }}>{obj.Role}</span>
)}
</span>
</>
)}
{obj.Name && (
<span className="useEmail" style={{ cursor: "default" }}>
{obj.Role}
</span>
)}
{obj.Name && <span className={"useEmail"}>{obj.Role}</span>}
</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 && (
<div
onClick={(e) => {
e.stopPropagation();
props.handleDeleteUser(obj.Id);
}}
style={{ cursor: "pointer" }}
style={{ cursor: "pointer", marginLeft: "5px" }}
>
<i className="fa-regular fa-trash-can"></i>
</div>
+10 -1
View File
@@ -56,7 +56,9 @@ function RenderPdf({
selectWidgetId,
unSignedWidgetId,
setIsCheckbox,
handleNameModal
handleNameModal,
setTempSignerId,
uniqueId
}) {
const isMobile = window.innerWidth < 767;
const newWidth = containerWH.width;
@@ -268,6 +270,9 @@ function RenderPdf({
setIsInitial={setIsInitial}
setValidateAlert={setValidateAlert}
unSignedWidgetId={unSignedWidgetId}
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
setCurrWidgetsDetails={setCurrWidgetsDetails}
/>
</React.Fragment>
)
@@ -393,6 +398,8 @@ function RenderPdf({
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
handleNameModal={handleNameModal}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
/>
</React.Fragment>
);
@@ -574,6 +581,8 @@ function RenderPdf({
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
handleNameModal={handleNameModal}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
/>
</React.Fragment>
);
+102 -60
View File
@@ -20,7 +20,9 @@ function SignPad({
isInitial,
setIsInitial,
setIsStamp,
widgetType
widgetType,
currWidgetsDetails,
setCurrWidgetsDetails
}) {
const [penColor, setPenColor] = useState("blue");
const allColor = ["blue", "red", "black"];
@@ -31,6 +33,7 @@ function SignPad({
const [signValue, setSignValue] = useState("");
const [textWidth, setTextWidth] = useState(null);
const [textHeight, setTextHeight] = useState(null);
const [signatureType, setSignatureType] = useState("draw");
const fontOptions = [
{ value: "Fasthand" },
{ value: "Dancing Script" },
@@ -48,19 +51,11 @@ function SignPad({
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
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
const handleClear = () => {
setCurrWidgetsDetails({});
if (isTab === "draw") {
if (canvasRef.current) {
canvasRef.current.clear();
@@ -72,11 +67,10 @@ function SignPad({
} else if (isTab === "uploadImage") {
setImage("");
}
setIsInitial(false);
// setIsInitial(false);
};
//function for set signature url
const handleSignatureChange = () => {
// canvasRef.current.backgroundColor = 'rgb(165, 26, 26)'
setSignature(canvasRef.current.toDataURL());
setIsSignImg(canvasRef.current.toDataURL());
};
@@ -101,28 +95,28 @@ function SignPad({
<button
onClick={() => {
setCurrWidgetsDetails({});
if (!image) {
if (isTab === "mysignature") {
setIsSignImg("");
if (isInitial) {
onSaveSign("initials");
onSaveSign(signatureType, "initials");
} else {
onSaveSign("default");
onSaveSign(null, "default");
}
} else {
if (isTab === "type") {
setIsSignImg("");
onSaveSign(false, textWidth, textHeight);
onSaveSign(null, false, textWidth, textHeight);
} else {
setIsSignImg("");
onSaveSign();
onSaveSign(signatureType);
}
}
setPenColor("blue");
} else {
setIsSignImg("");
onSaveImage();
onSaveImage(signatureType);
}
setIsSignPad(false);
setIsInitial(false);
@@ -152,7 +146,35 @@ function SignPad({
</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(() => {
const loadFont = async () => {
try {
@@ -186,7 +208,7 @@ function SignPad({
}, [isTab]);
//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
const textContent = text;
const fontfamily = fontStyle
@@ -199,6 +221,7 @@ function SignPad({
const span = document.createElement("span");
span.textContent = textContent;
span.style.font = `20px ${fontfamily}`; // here put your text size and font family
span.style.color = color ? color : penColor;
span.style.display = "hidden";
document.body.appendChild(span); // Replace 'container' with the ID of the container element
@@ -216,13 +239,9 @@ function SignPad({
canvasElement.width = width * 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
ctx.font = font;
ctx.fillStyle = "black"; // Set the text color
ctx.fillStyle = color ? color : penColor; // Set the text color
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.scale(pixelRatio, pixelRatio);
@@ -235,6 +254,45 @@ function SignPad({
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 (
<div>
{isSignPad && (
@@ -303,6 +361,7 @@ function SignPad({
setIsDefaultSign(false);
setIsImageSelect(true);
setIsTab("uploadImage");
setSignatureType("");
}}
style={{
color:
@@ -327,6 +386,7 @@ function SignPad({
setIsDefaultSign(false);
setIsImageSelect(false);
setIsTab("type");
setSignatureType("");
setImage();
}}
style={{
@@ -354,6 +414,7 @@ function SignPad({
setIsDefaultSign(true);
setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
setImage();
}}
style={{
@@ -384,6 +445,7 @@ function SignPad({
setIsDefaultSign(true);
setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
setImage();
}}
style={{
@@ -423,6 +485,7 @@ function SignPad({
setIsDefaultSign(false);
setImage();
setIsTab("draw");
setSignatureType("draw");
setSignValue("");
setIsStamp(false);
}}
@@ -457,6 +520,7 @@ function SignPad({
background: "rgb(255, 255, 255)",
objectFit: "contain"
}}
draggable="false"
src={isInitial ? myInitial : defaultSign}
/>
</div>
@@ -517,6 +581,7 @@ function SignPad({
alt="print img"
ref={imageRef}
src={image.src}
draggable="false"
style={{
objectFit: "contain",
height: "100%",
@@ -547,7 +612,7 @@ function SignPad({
<input
maxLength={isInitial ? 3 : 30}
style={{ fontFamily: fontSelect }}
style={{ fontFamily: fontSelect, color: penColor }}
type="text"
className="signatureInput"
placeholder="Your signature"
@@ -578,7 +643,8 @@ function SignPad({
<div
style={{
padding: "5px 10px 5px 10px",
fontSize: "20px"
fontSize: "20px",
color: penColor
}}
>
{signValue ? signValue : "Your signature"}
@@ -588,7 +654,15 @@ function SignPad({
})}
</div>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
marginTop: "10px"
}}
>
<PenColorComponent convertToImg={convertToImg} />
<SaveBtn />
</div>
</div>
@@ -622,39 +696,7 @@ function SignPad({
marginTop: "10px"
}}
>
<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={() => {
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>
<PenColorComponent />
<SaveBtn />
</div>
</>
@@ -1,6 +1,7 @@
import React from "react";
import { themeColor } from "../../constant/const";
import RecipientList from "./RecipientList";
import { Tooltip } from "react-tooltip";
function SignerListPlace(props) {
return (
@@ -13,6 +14,58 @@ function SignerListPlace(props) {
>
<span className="signedStyle">
{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>
</div>
<div className="signerList">
@@ -23,6 +76,10 @@ function SignerListPlace(props) {
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.handleAddSigner()}
style={{
opacity: props.isMailSend && "0.5",
pointerEvents: props.isMailSend && "none"
}}
>
<i className="fa-solid fa-plus"></i>
<span style={{ marginLeft: 2 }}>Add role</span>
@@ -3,7 +3,12 @@ import ModalUi from "../../primitives/ModalUi";
import RecipientList from "./RecipientList";
import { useDrag } from "react-dnd";
import WidgetList from "./WidgetList";
import { widgets } from "../../constant/Utils";
import {
radioButtonWidget,
textInputWidget,
textWidget,
widgets
} from "../../constant/Utils";
import { themeColor } from "../../constant/const";
function WidgetComponent({
dragSignature,
@@ -29,8 +34,11 @@ function WidgetComponent({
handleRoleChange,
handleOnBlur,
title,
isTemplateFlow
setSignersData,
sendInOrder,
isTemplateFlow,
setBlockColor,
blockColor
}) {
const [isSignersModal, setIsSignersModal] = useState(false);
@@ -58,16 +66,15 @@ function WidgetComponent({
isDragCheck: !!monitor.isDragging()
})
});
const [, text] = useDrag({
const [, textInput] = useDrag({
type: "BOX",
item: {
type: "BOX",
id: 7,
text: "text"
text: textInputWidget
},
collect: (monitor) => ({
isDragText: !!monitor.isDragging()
isDragTextInput: !!monitor.isDragging()
})
});
const [, initials] = useDrag({
@@ -153,28 +160,28 @@ function WidgetComponent({
isDragEmail: !!monitor.isDragging()
})
});
const [, radio] = useDrag({
const [, radioButton] = useDrag({
type: "BOX",
item: {
type: "BOX",
id: 15,
text: "radio"
text: radioButtonWidget
},
collect: (monitor) => ({
isDragRadio: !!monitor.isDragging()
isDragRadiotton: !!monitor.isDragging()
})
});
const [, label] = useDrag({
const [, text] = useDrag({
type: "BOX",
item: {
type: "BOX",
id: 16,
text: "label"
text: textWidget
},
collect: (monitor) => ({
isDragLabel: !!monitor.isDragging()
isDragText: !!monitor.isDragging()
})
});
const isMobile = window.innerWidth < 767;
@@ -204,17 +211,17 @@ function WidgetComponent({
dragSignature,
dragStamp,
initials,
label,
name,
jobTitle,
company,
date,
text,
textInput,
checkbox,
dropdown,
radio,
radioButton,
image,
date,
name,
email,
company,
jobTitle
email
];
const getWidgetArray = widgets;
const newUpdateSigner = getWidgetArray.map((obj, ind) => {
@@ -227,13 +234,15 @@ function WidgetComponent({
const filterWidgets = widget.filter(
(data) =>
data.type !== "dropdown" && data.type !== "radio" && data.type !== "label"
data.type !== "dropdown" &&
data.type !== radioButtonWidget &&
data.type !== textInputWidget
);
const labelWidget = widget.filter((data) => data.type !== "label");
const textWidgetData = widget.filter((data) => data.type !== textWidget);
const updateWidgets = isSignYourself
? filterWidgets
: isTemplateFlow
? labelWidget
? textWidgetData
: widget;
return (
@@ -250,19 +259,17 @@ function WidgetComponent({
<div
data-tut={dataTut}
style={{
background: isSelectListId
? color[isSelectListId % color.length]
: color[0],
background: blockColor
? blockColor
: isSelectListId
? color[isSelectListId % color.length]
: color[0],
padding: "10px 20px",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
onClick={() => {
// if (signersdata?.length) {
handleModal();
// }
}}
onClick={() => handleModal()}
>
<span
style={{
@@ -340,7 +347,7 @@ function WidgetComponent({
display: "flex",
overflowX: "scroll",
whiteSpace: "nowrap",
padding: "10px"
padding: "10px 5px 10px 1px"
}}
>
<WidgetList
@@ -357,7 +364,12 @@ function WidgetComponent({
</div>
)
) : (
<div data-tut={dataTut} className="signerComponent">
<div
data-tut={dataTut}
className={
isMailSend ? "disabled signerComponent " : "signerComponent"
}
>
<div
style={{
background: themeColor,
@@ -384,20 +396,25 @@ function WidgetComponent({
handleClose={handleModal}
>
{signersdata.length > 0 ? (
<RecipientList
signerPos={signerPos}
signersdata={signersdata}
isSelectListId={isSelectListId}
setSignerObjId={setSignerObjId}
setIsSelectId={setIsSelectId}
setContractName={setContractName}
setUniqueId={setUniqueId}
setRoleName={setRoleName}
handleDeleteUser={handleDeleteUser}
handleRoleChange={handleRoleChange}
handleOnBlur={handleOnBlur}
handleModal={handleModal}
/>
<div style={{ maxHeight: "600px", overflow: "auto" }}>
<RecipientList
signerPos={signerPos}
signersdata={signersdata}
isSelectListId={isSelectListId}
setSignerObjId={setSignerObjId}
setIsSelectId={setIsSelectId}
setContractName={setContractName}
setUniqueId={setUniqueId}
setRoleName={setRoleName}
handleDeleteUser={handleDeleteUser}
handleRoleChange={handleRoleChange}
handleOnBlur={handleOnBlur}
handleModal={handleModal}
sendInOrder={sendInOrder}
setSignersData={setSignersData}
setBlockColor={setBlockColor}
/>
</div>
) : (
<div
style={{
@@ -4,7 +4,7 @@ import { getWidgetType } from "../../constant/Utils";
function WidgetList(props) {
return props.updateWidgets.map((item, ind) => {
return (
<div key={ind} style={{ marginBottom: "10px" }}>
<div key={ind} style={{ marginBottom: "5px" }}>
<div
className="widgets"
onClick={() => {
@@ -2,6 +2,10 @@ import React, { useEffect, useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import "../../styles/AddUser.css";
import RegexParser from "regex-parser";
import { textInputWidget } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
const WidgetNameModal = (props) => {
const [formdata, setFormdata] = useState({
@@ -13,7 +17,7 @@ const WidgetNameModal = (props) => {
});
const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"];
const inputOpt = ["email", "number"];
const inputOpt = ["text", "email", "number"];
useEffect(() => {
if (props.defaultdata) {
@@ -97,7 +101,24 @@ const WidgetNameModal = (props) => {
handleClose={props.handleClose && props.handleClose}
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">
<label htmlFor="name" style={{ fontSize: 13 }}>
Name
@@ -111,14 +132,24 @@ const WidgetNameModal = (props) => {
required
/>
</div>
{(props.defaultdata?.type === "text" ||
props.widgetName === "text") && (
{(props.defaultdata?.type === textInputWidget ||
props.widgetName === textInputWidget) && (
<>
<div className="form-section">
<label htmlFor="textvalidate" style={{ fontSize: 13 }}>
<label
htmlFor="textvalidate"
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{ fontSize: 13 }}
>
Validation
</label>
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
<div
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{
display: "flex",
flexDirection: "row",
@@ -128,18 +159,7 @@ const WidgetNameModal = (props) => {
>
<div style={{ width: "100%", position: "relative" }}>
<input
style={{
padding: "0.5rem 0.75rem",
width: "92%",
borderWidth: "1px",
borderColor: "#d1d5db",
borderTopLeftRadius: "0.375rem",
borderBottomLeftRadius: "0.375rem",
outline: "none",
fontSize: "0.75rem",
position: "relative",
zIndex: 2
}}
className="relative z-20 w-[87%] md:w-[92%] p-2.5 border-[1px] border-[#d1d5db] rounded-l-md outline-none text-xs"
name="textvalidate"
placeholder="Enter custom expression"
value={formdata.textvalidate}
@@ -147,8 +167,7 @@ const WidgetNameModal = (props) => {
// onBlur={() => handleBlurRegex()}
/>
<select
style={{ position: "absolute", left: 0, zIndex: 1 }}
className="addUserInput"
className="validationlist addUserInput"
name="textvalidate"
value={formdata.textvalidate}
onChange={(e) => handleChange(e)}
@@ -172,6 +191,7 @@ const WidgetNameModal = (props) => {
</div>
</div>
</div>
<div className="form-section">
<label htmlFor="name" style={{ fontSize: 13 }}>
Default value
@@ -181,25 +201,25 @@ const WidgetNameModal = (props) => {
name="defaultValue"
value={formdata.defaultValue}
onChange={(e) => handledefaultChange(e)}
onBlur={() =>
isValid === false &&
setFormdata({ ...formdata, defaultValue: "" })
}
autoComplete="off"
onBlur={() => {
if (isValid === false) {
setFormdata({ ...formdata, defaultValue: "" });
setIsValid(true);
}
}}
/>
{isValid === false ? (
<p style={{ color: "Red", fontSize: 8 }}>
invalid default value
</p>
) : (
<p
style={{
color: "transparent",
fontSize: 10,
margin: "3px 8px"
}}
{isValid === false && (
<div
className="warning defaultvalueWarning"
style={{ fontSize: 12 }}
>
.
</p>
<i
className="fas fa-exclamation-circle"
style={{ color: "#fab005", fontSize: 15 }}
></i>{" "}
invalid default value
</div>
)}
</div>
</>
@@ -243,8 +263,8 @@ const WidgetNameModal = (props) => {
})}
</div>
</div>
{(props.defaultdata?.type === "text" ||
props?.widgetName === "text") && (
{(props.defaultdata?.type === textInputWidget ||
props?.widgetName === textInputWidget) && (
<div className="form-section">
<label htmlFor="hint" style={{ fontSize: 13 }}>
Hint
@@ -11,6 +11,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
const [name, setName] = useState("");
const [folderList, setFolderList] = useState([]);
const [isAlert, setIsAlert] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [selectedParent, setSelectedParent] = useState();
const [alert, setAlert] = useState({ type: "info", message: "" });
useEffect(() => {
@@ -42,6 +43,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
};
const handleCreateFolder = async (event) => {
event.preventDefault();
handleLoader(true);
if (name) {
const currentUser = Parse.User.current();
const exsitQuery = new Parse.Query(folderCls);
@@ -52,7 +54,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
}
const templExist = await exsitQuery.first();
if (templExist) {
setAlert({ type: "dange", message: "Folder already exist!" });
setAlert({ type: "danger", message: "Folder already exist!" });
setIsAlert(true);
setTimeout(() => {
setIsAlert(false);
@@ -74,6 +76,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
template.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
const res = await template.save();
if (res) {
handleLoader(false);
setAlert({
type: "success",
message: "Folder created successfully!"
@@ -88,6 +91,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
}
}
} else {
handleLoader(false);
setAlert({ type: "info", message: "Please fill folder name" });
setIsAlert(true);
setTimeout(() => {
@@ -98,10 +102,21 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
const handleOptions = (e) => {
setSelectedParent(e.target.value);
};
const handleLoader = (status) => {
setIsLoader(status);
};
return (
<div>
{isAlert && <Alert type={alert.type}>{alert.message}</Alert>}
<div id="createFolder">
<div id="createFolder" className="relative">
{isLoader && (
<div className="absolute h-full w-full flex justify-center items-center ">
<div
style={{ fontSize: "45px", color: "#3dd3e0" }}
className="loader-37"
></div>
</div>
)}
<h1 className="text-base font-semibold">Create Folder</h1>
<div className="text-xs mt-2">
<label className="block">
@@ -133,6 +148,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => {
<div>
<button
onClick={handleCreateFolder}
disabled={isLoader}
className="flex items-center rounded p-2 bg-[#32a3ac] text-white mt-3"
>
<i className="fa-solid fa-plus mr-1"></i>
@@ -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;
@@ -2,8 +2,9 @@ import React, { useEffect, useState } from "react";
import Parse from "parse";
import CreateFolder from "./CreateFolder";
import ModalUi from "../../../primitives/ModalUi";
import Tooltip from "../../../primitives/Tooltip";
const SelectFolder = ({ required, onSuccess, folderCls }) => {
const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
const [isOpen, SetIsOpen] = useState(false);
const [clickFolder, setClickFolder] = useState("");
const [selectFolder, setSelectedFolder] = useState({});
@@ -22,6 +23,21 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
}
// eslint-disable-next-line
}, [isOpen]);
// below useEffect is used to reset folder selection if user pass isReset = true from parent component
useEffect(() => {
if (isReset == true) {
handleReset();
}
}, [isReset]);
const handleReset = () => {
setFolderPath({});
setSelectedFolder("");
setClickFolder("");
setIsAdd(false);
setFolderList([]);
setTabList([]);
};
// `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 {
@@ -47,6 +63,8 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
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 });
@@ -73,8 +91,9 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
}
};
// `handleSubmit` is used to pass folderPtr to parent component
const handleSubmit = () => {
let url = "Root";
let url = "OpenSign™ Drive";
tabList.forEach((t) => {
url = url + " / " + t.Name;
});
@@ -85,6 +104,8 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
}
SetIsOpen(false);
};
// `handleCancel` is used to clear list of folder, close popup and folderUrl
const handleCancel = () => {
SetIsOpen(false);
setClickFolder({});
@@ -92,6 +113,7 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
setTabList([]);
};
// `handleCancel` is call when user click on folder name from path/tab in popup
const removeTabListItem = async (e, i) => {
e.preventDefault();
// setEditable(false);
@@ -121,9 +143,11 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
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 = () => {
setFolderList([]);
if (clickFolder && clickFolder.ObjectId) {
@@ -145,7 +169,7 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
{required && <span className="text-red-500 text-[13px]">*</span>}
</label>
</div>
<div className="rounded px-[20px] py-[20px] bg-white border border-gray-200 shadow flex max-w-sm gap-8 items-center">
<div className="relative rounded px-[20px] py-[20px] bg-white border border-gray-200 shadow flex max-w-sm gap-8 items-center">
<div>
<i
className="far fa-folder-open text-[40px] text-[#33bbff]"
@@ -156,7 +180,9 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
<div className="font-semibold ">
<div className="flex items-center gap-2">
<p>
{selectFolder && selectFolder.Name ? selectFolder.Name : "Root"}
{selectFolder && selectFolder.Name
? selectFolder.Name
: "OpenSign™ Drive"}
</p>
<div className="text-black text-sm" onClick={() => SetIsOpen(true)}>
<i
@@ -170,20 +196,27 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
{selectFolder && selectFolder.Name ? `(${folderPath})` : ""}
</p>
</div>
<div className="absolute top-2 right-1 cursor-pointer">
<Tooltip
message={
"If you do not select a folder, your signed document will be saved in the Main OpenSign drive folder."
}
/>
</div>
</div>
<ModalUi
title={"Select Folder"}
isOpen={isOpen}
handleClose={handleCancel}
>
<div className="w-full min-w-[300px] md:min-w-[500px] px-3">
<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="Root"
title="OpenSign™ Drive"
onClick={(e) => removeTabListItem(e)}
>
Root /{" "}
OpenSign Drive /{" "}
</span>
{tabList &&
tabList.map((tab, i) => (
@@ -201,23 +234,25 @@ const SelectFolder = ({ required, onSuccess, folderCls }) => {
<hr />
</div>
<div className="mt-2 mb-3">
{!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 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>
))}
))}
</div>
{isAdd && (
<CreateFolder
parentFolderId={clickFolder && clickFolder.ObjectId}
@@ -3,6 +3,7 @@ import Select from "react-select";
import AddSigner from "../../AddSigner";
import Modal from "react-modal";
import Parse from "parse";
import Tooltip from "../../../primitives/Tooltip";
function arrayMove(array, from, to) {
array = array.slice();
array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
@@ -19,7 +20,7 @@ const SignersInput = (props) => {
Modal.setAppElement("body");
const [state, setState] = useState(undefined);
// const [editFormData, setEditFormData] = useState([]);
const [selected, setSelected] = React.useState([]);
const [selected, setSelected] = useState([]);
const [isModal, setIsModel] = useState(false);
const onChange = (selectedOptions) => setSelected(selectedOptions);
const [modalIsOpen, setModalIsOpen] = useState(false);
@@ -63,6 +64,11 @@ const SignersInput = (props) => {
useEffect(() => {
GetSelectListData();
}, []);
useEffect(() => {
if (props.isReset && props.isReset === true) {
setSelected([]);
}
}, [props.isReset]);
useEffect(() => {
if (selected && selected.length) {
@@ -99,9 +105,15 @@ const SignersInput = (props) => {
return (
<div className="text-xs mt-2 ">
<label className="block">
<label className="block relative">
Signers
{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>
<div style={{ display: "flex", gap: 5 }}>
<div style={{ flexWrap: "wrap", width: "100%" }}>
+1 -1
View File
@@ -10,7 +10,7 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
? `/${item.pageType}/${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}
tabIndex={isOpen ? 0 : -1}
role="menuitem"
@@ -13,7 +13,7 @@ const Submenu = ({ item, closeSidebar }) => {
<li role="none">
<button
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-haspopup="true"
aria-controls={`submenu-${title}`}
@@ -39,7 +39,7 @@ const Submenu = ({ item, closeSidebar }) => {
? `/${childItem.pageType}/${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}
role="menuitem"
tabIndex={submenuOpen ? 0 : -1}
+334 -79
View File
@@ -3,12 +3,68 @@ import moment from "moment";
import { themeColor } from "./const";
import React from "react";
import { rgb } from "pdf-lib";
import Parse from "parse";
export const isMobile = window.innerWidth < 767;
export const textInputWidget = "text input";
export const textWidget = "text";
export const radioButtonWidget = "radio button";
export const openInNewTab = (url) => {
window.open(url, "_blank", "noopener,noreferrer");
};
//function to get subcripition details from Extand user class
export async function checkIsSubscribed() {
const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass);
const user = await Parse.Cloud.run("getUserDetails", {
email: jsonSender[0].Email
});
const freeplan = user?.get("Plan") && user?.get("Plan")?.plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
return false;
} else if (billingDate) {
if (billingDate > new Date()) {
return true;
} else {
return false;
}
} else {
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) => {
return new Promise((resolve) => {
const reader = new FileReader();
@@ -19,10 +75,12 @@ export const toDataUrl = (file) => {
});
};
//function for getting document details for getDrive
export const getDrive = async (documentId) => {
//function for getting document details for getDrive cloud function
export const getDrive = async (documentId, skip = 0, limit = 100) => {
const data = {
docId: documentId && documentId
docId: documentId && documentId,
limit: limit,
skip: skip
};
const driveDeatils = await axios
.post(`${localStorage.getItem("baseUrl")}functions/getDrive`, data, {
@@ -162,12 +220,32 @@ export const widgets = [
iconSize: "15px"
},
{
type: "label",
type: "name",
icon: "fa-solid fa-user",
iconSize: "21px"
},
{
type: "job title",
icon: "fa-solid fa-address-card",
iconSize: "17px"
},
{
type: "company",
icon: "fa-solid fa-building",
iconSize: "25px"
},
{
type: "date",
icon: "fa-solid fa-calendar-days",
iconSize: "20px"
},
{
type: textWidget,
icon: "fa-solid fa-text-width",
iconSize: "20px"
},
{
type: "text",
type: textInputWidget,
icon: "fa-solid fa-font",
iconSize: "21px"
},
@@ -182,7 +260,7 @@ export const widgets = [
iconSize: "19px"
},
{
type: "radio",
type: radioButtonWidget,
icon: "fa-regular fa-circle-dot",
iconSize: "20px"
},
@@ -191,40 +269,19 @@ export const widgets = [
icon: "fa-solid fa-image",
iconSize: "20px"
},
{
type: "date",
icon: "fa-solid fa-calendar-days",
iconSize: "20px"
},
{
type: "name",
icon: "fa-solid fa-user",
iconSize: "21px"
},
{
type: "email",
icon: "fa-solid fa-envelope",
iconSize: "20px"
},
{
type: "company",
icon: "fa-solid fa-building",
iconSize: "25px"
},
{
type: "job title",
icon: "fa-solid fa-address-card",
iconSize: "17px"
}
];
const getDate = () => {
export const getDate = () => {
const date = new Date();
const milliseconds = date.getTime();
const newDate = moment(milliseconds).format("MM/DD/YYYY");
return newDate;
};
export const addWidgetOptions = (type) => {
const defaultOpt = {
name: "",
@@ -236,8 +293,11 @@ export const addWidgetOptions = (type) => {
case "stamp":
return defaultOpt;
case "checkbox":
return defaultOpt;
case "text":
return {
...defaultOpt,
options: { isReadOnly: false, isHideLabel: false }
};
case textInputWidget:
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
case "initials":
return defaultOpt;
@@ -248,16 +308,25 @@ export const addWidgetOptions = (type) => {
case "job title":
return { ...defaultOpt, validation: { type: "text", pattern: "" } };
case "date":
return { ...defaultOpt, response: getDate() };
return {
...defaultOpt,
response: getDate(),
validation: { format: "MM/dd/yyyy", type: "date-format" }
};
case "image":
return defaultOpt;
case "email":
return { ...defaultOpt, validation: { type: "email", pattern: "" } };
case "dropdown":
return defaultOpt;
case "radio":
return { ...defaultOpt, values: [] };
case "label":
case radioButtonWidget:
return {
...defaultOpt,
values: [],
isReadOnly: false,
isHideLabel: false
};
case textWidget:
return defaultOpt;
default:
return {};
@@ -274,26 +343,14 @@ export const getWidgetType = (item, marginLeft) => {
marginLeft: marginLeft && `${marginLeft}px`
}}
>
<div
style={{
display: "flex",
alignItems: "center",
marginLeft: "5px"
}}
>
<i
className="fa-sharp fa-solid fa-grip-vertical"
style={{ color: "#908d8d", fontSize: "13px" }}
></i>
<span
style={{
fontWeight: "400",
fontSize: "15px",
// padding: "3px 20px 0px 20px",
color: "black",
marginLeft: "5px"
}}
>
<div className="flex items-center mr-1">
{!isMobile && (
<i
className="fa-sharp fa-solid fa-grip-vertical"
style={{ color: "#908d8d", fontSize: "13px", marginLeft: 4 }}
></i>
)}
<span className=" font-[400] text-[15px] text-black ml-[5px]">
{item.type}
</span>
</div>
@@ -323,7 +380,7 @@ export const defaultWidthHeight = (type) => {
return { width: 150, height: 60 };
case "checkbox":
return { width: 15, height: 15 };
case "text":
case textInputWidget:
return { width: 150, height: 25 };
case "dropdown":
return { width: 120, height: 22 };
@@ -341,9 +398,9 @@ export const defaultWidthHeight = (type) => {
return { width: 70, height: 70 };
case "email":
return { width: 150, height: 20 };
case "radio":
case radioButtonWidget:
return { width: 15, height: 30 };
case "label":
case textWidget:
return { width: 150, height: 17 };
default:
return { width: 150, height: 60 };
@@ -692,13 +749,27 @@ export const onChangeInput = (
const updatePosition = getXYdata.map((positionData) => {
if (positionData.key === signKey) {
return {
...positionData,
options: {
...positionData.options,
response: value
}
};
if (dateFormat) {
return {
...positionData,
options: {
...positionData.options,
response: value,
validation: {
type: "date-format",
format: dateFormat // This indicates the required date format explicitly.
}
}
};
} else {
return {
...positionData,
options: {
...positionData.options,
response: value
}
};
}
}
return positionData;
});
@@ -713,6 +784,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) => {
function widgetDataValue(type) {
switch (type) {
@@ -812,6 +959,7 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
//function for save button to save signature or image url
export function onSaveSign(
type,
xyPostion,
index,
signKey,
@@ -852,6 +1000,7 @@ export function onSaveSign(
Width: posWidth,
Height: posHeight,
SignUrl: signatureImg,
signatureType: type && type,
options: {
...position.options,
response: signatureImg
@@ -970,7 +1119,6 @@ export const multiSignEmbed = async (
} else {
updateItem = item.pos;
}
const newWidth = containerWH.width;
const scale = isMobile ? pdfOriginalWidth / newWidth : 1;
const pageNo = item.pageNumber;
@@ -1060,7 +1208,7 @@ export const multiSignEmbed = async (
? labelDefaultHeight
: scaleHeight;
const widgetHeight =
position.type === "radio"
position.type === radioButtonWidget
? 10
: position.type === "checkbox"
? 10
@@ -1125,7 +1273,7 @@ export const multiSignEmbed = async (
}
};
const widgetTypeExist = [
"text",
textInputWidget,
"name",
"company",
"job title",
@@ -1133,13 +1281,14 @@ export const multiSignEmbed = async (
"email"
].includes(position.type);
if (position.type === "checkbox") {
let addYPosition, isCheck;
let addYPosition = 0,
isCheck;
if (position?.options?.values.length > 0) {
position?.options?.values.forEach((item, ind) => {
const checkboxRandomId = "checkbox" + randomId();
let yPosition;
const height = 10;
const height = 13;
if (
position?.options?.response &&
position?.options?.response?.length > 0
@@ -1157,9 +1306,17 @@ export const multiSignEmbed = async (
yPosition = yPos(position, ind);
addYPosition = height + 8;
}
if (!position?.options?.isHideLabel) {
// below line of code is used to embed label with radio button in pdf
page.drawText(item, {
x: xPos(position) + 17,
y: yPosition + 2,
size: height
});
}
checkbox.addToPage(page, {
x: xPos(position),
y: yPosition,
y: yPosition - 3,
width: height,
height: height
});
@@ -1171,7 +1328,7 @@ export const multiSignEmbed = async (
checkbox.enableReadOnly();
});
}
} else if (position.type === "label") {
} else if (position.type === textWidget) {
const font = await pdfDoc.embedFont("Helvetica");
const fontSize = 12;
let textContent;
@@ -1180,19 +1337,21 @@ export const multiSignEmbed = async (
} else if (position?.options?.defaultValue) {
textContent = position?.options?.defaultValue;
}
const fixedWidth = scaleWidth; // Set your fixed width
const isNewOnEnterLineExist = textContent.includes("\n");
// Function to break text into lines based on the fixed width
const breakTextIntoLines = (textContent, width) => {
const NewbreakTextIntoLines = (textContent, width) => {
const lines = [];
let currentLine = "";
for (const word of textContent.split(" ")) {
//get text line width
const lineWidth = font.widthOfTextAtSize(
`${currentLine} ${word}`,
fontSize
);
//check text content line width is less or equal to container width
if (lineWidth <= width) {
currentLine += ` ${word}`;
} else {
@@ -1203,7 +1362,33 @@ export const multiSignEmbed = async (
lines.push(currentLine.trim());
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
const labelDefaultHeight = defaultWidthHeight(position.type).height;
@@ -1251,10 +1436,11 @@ export const multiSignEmbed = async (
x: xPos(position),
y: yPos(position),
width: scaleWidth,
height: scaleHeight
height: scaleHeight,
borderWidth: 0
});
dropdown.enableReadOnly();
} else if (position.type === "radio") {
} else if (position.type === radioButtonWidget) {
const radioRandomId = "radio" + randomId();
const radioGroup = form.createRadioGroup(radioRandomId);
let addYPosition = 18;
@@ -1268,6 +1454,14 @@ export const multiSignEmbed = async (
yPosition = yPos(position);
}
if (!position?.options?.isHideLabel) {
// below line of code is used to embed label with radio button in pdf
page.drawText(data, {
x: xPos(position) + 15,
y: yPosition + 2,
size: 11
});
}
radioGroup.addOptionToPage(data, page, {
x: xPos(position),
y: yPosition,
@@ -1295,9 +1489,7 @@ export const multiSignEmbed = async (
}
});
}
const pdfBytes = await pdfDoc.saveAsBase64({ useObjectStreams: false });
return pdfBytes;
};
@@ -1537,3 +1729,66 @@ export const getYear = (date) => {
const newYear = new Date(date).getFullYear();
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);
}
};
+1
View File
@@ -9,3 +9,4 @@ export const modalSubmitBtnColor = "#17a2b8";
export const modalCancelBtnColor = "white";
export const themeColor = "#47a3ad";
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 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
const tenantPtr = {
__type: "Pointer",
className: "partners_Tenant",
objectId: tenantId
};
const _tenantPtr = JSON.stringify(tenantPtr);
try {
const response = await axios
.get(`${serverUrl}classes/partners_TenantCredits`, {
const res = await axios.get(
`${serverUrl}classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
})
.then((result) => {
const res = result.data;
// console.log("res", res);
return res.results;
})
.catch((err) => {
console.log("axois err ", err);
});
}
);
const response = res.data.results;
let data;
// console.log("response", response);
if (response && response.length > 0) {
@@ -30,69 +29,49 @@ export const SaveFileSize = async (size, imageUrl) => {
? 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": 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, {
await axios.put(
`${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`,
data,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
})
.then(() => {
// const res = result.data;
// console.log("res", res);
})
.catch((err) => {
console.log("axois err ", err);
});
}
);
} else {
data = { usedStorage: size, PartnersTenant: tenantPtr };
await axios.post(`${serverUrl}classes/partners_TenantCredits`, data, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
});
}
} catch (e) {
console.log("org app error", e);
} catch (err) {
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
const saveDataFile = async (size, imageUrl) => {
const saveDataFile = async (size, imageUrl, tenantPtr) => {
const data = {
FileUrl: imageUrl,
FileSize: size
FileSize: size,
TenantPtr: tenantPtr
};
// console.log("data save",file, data)
await axios
.post(`${serverUrl}classes/partners_DataFiles`, data, {
try {
await axios.post(`${serverUrl}classes/partners_DataFiles`, data, {
headers: {
"Content-Type": "application/json",
"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);
}
};
+19 -8
View File
@@ -18,7 +18,8 @@ export default function reportJson(id) {
btnIcon: "fa fa-plus",
redirectUrl: "draftDocument"
}
]
],
helpMsg: "These are documents you have started but have not finalized for sending."
};
// Need your sign report
case "4Hhwbp482K":
@@ -33,7 +34,9 @@ export default function reportJson(id) {
btnIcon: "fa fa-eye",
redirectUrl: "pdfRequestFiles"
}
]
],
helpMsg:
"This is a list of documents that are waiting for your signature"
};
// In progess report
case "1MwEuxLEkF":
@@ -48,7 +51,9 @@ export default function reportJson(id) {
btnIcon: "fa fa-eye",
redirectUrl: "pdfRequestFiles"
}
]
],
helpMsg:
"This is a list of documents you've sent to other parties for signature."
};
// completed documents report
case "kQUoW4hUXz":
@@ -63,7 +68,8 @@ export default function reportJson(id) {
btnIcon: "fa fa-eye",
redirectUrl: "draftDocument"
}
]
],
helpMsg: "This is a list of documents that have been signed by all parties."
};
// declined documents report
case "UPr2Fm5WY3":
@@ -78,7 +84,8 @@ export default function reportJson(id) {
btnIcon: "fa fa-eye",
redirectUrl: "draftDocument"
}
]
],
helpMsg: "This is a list of documents that have been declined by one of the signers."
};
// Expired Documents report
case "zNqBHXHsYH":
@@ -93,7 +100,8 @@ export default function reportJson(id) {
btnIcon: "fa fa-eye",
redirectUrl: "draftDocument"
}
]
],
helpMsg: "This is a list of documents that have reached their expiration date."
};
// Recently sent for signatures report show on dashboard
case "d9k3UfYHBc":
@@ -153,7 +161,8 @@ export default function reportJson(id) {
btnIcon: "fa-solid fa-trash"
}
],
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
case "6TeaPr321t":
@@ -175,7 +184,9 @@ export default function reportJson(id) {
btnIcon: "fa fa-plus",
redirectUrl: "template"
}
]
],
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:
return null;
+69 -64
View File
@@ -1,65 +1,70 @@
[
{
"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"
]
}
]
{
"planName": "OPENSIGN™ FREE",
"currency": "",
"monthlyPrice": "Free",
"yearlyPrice": "Free",
"subtitle": "Free Unlimited E-signatures, Forever.",
"btnText": "Subscribe",
"url": "",
"target": "_blank",
"benefits": [
"Unlimited envelopes",
"Sign documents yourself",
"Request signatures from others",
"14 field types",
"Automatic e-signatures",
"Completion certificates",
"Send in order",
"Document templates",
"Import from Dropbox",
"Contact book",
"Document expiry support",
"Decline document support",
"Email notifications",
"Recipient authentication using OTP",
"And much more"
]
},
{
"planName": "OPENSIGN™ PROFESSIONAL",
"currency": "$",
"monthlyPrice": "29.99",
"yearlyPrice": "329.99",
"subtitle": "Exclusive Access to advanced features.",
"btnText": "Subscribe",
"url": "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d1a09dfa8085585cdd4ec4d7f32137f3/professional-monthly",
"yearlyUrl": "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly",
"target": "_self",
"benefits": [
"Everything in OpenSign™ professional",
"Field validations",
"Regular expression validations",
"Organize docs in OpenSign™ Drive",
"Webhooks",
"Zapier integration",
"API Access",
"100 API signatures included",
"DocumentId removal from signed docs",
"Custom email templates(coming soon)"
]
},
{
"planName": "OPENSIGN™ ENTERPRISE",
"currency": "",
"monthlyPrice": "Request Price",
"yearlyPrice": "Request Price",
"subtitle": "Scalable Features with priority support.",
"btnText": "Contact us",
"url": "https://www.opensignlabs.com/contact-us",
"target": "_blank",
"benefits": [
"All features",
"Custom domain",
"Custom branding",
"Uptime SLA",
"SSO support",
"Priority support"
]
}
]
+48 -12
View File
@@ -9,6 +9,7 @@ import { useSelector } from "react-redux";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import { useNavigate, useLocation, Outlet } from "react-router-dom";
import { isEnableSubscription } from "../constant/const";
const HomeLayout = () => {
const navigate = useNavigate();
@@ -18,6 +19,12 @@ const HomeLayout = () => {
const arr = useSelector((state) => state.TourSteps);
const [isUserValid, setIsUserValid] = 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([]);
useEffect(() => {
(async () => {
try {
@@ -27,8 +34,7 @@ const HomeLayout = () => {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
setIsUserValid(true);
setIsLoader(false);
checkIsSubscribed();
} else {
setIsUserValid(false);
}
@@ -37,14 +43,36 @@ const HomeLayout = () => {
setIsUserValid(false);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// reactour state
const [isCloseBtn, setIsCloseBtn] = useState(true);
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
const [tourConfigs, setTourConfigs] = useState([]);
async function checkIsSubscribed() {
const currentUser = Parse.User.current();
const user = await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
});
if (isEnableSubscription) {
const freeplan = user?.get("Plan") && user?.get("Plan").plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
setIsUserValid(true);
setIsLoader(false);
} else if (billingDate) {
if (billingDate > new Date()) {
setIsUserValid(true);
setIsLoader(false);
} else {
navigate(`/subscription`);
}
} else {
navigate(`/subscription`);
}
} else {
setIsUserValid(true);
setIsLoader(false);
}
}
const showSidebar = () => {
setIsOpen((value) => !value);
};
@@ -94,10 +122,16 @@ const HomeLayout = () => {
position: "top"
// style: { backgroundColor: "#abd4d2" },
},
{
selector: '[data-tut="tourbutton"]',
content: `To upload documents for self-signing or to request others signatures, simply select the respective buttons.`,
position: "top"
// style: { backgroundColor: "#abd4d2" },
},
...resArr,
{
selector: '[data-tut="reactourLast"]',
content: `You are good to go`,
content: `You are ready to start using OpenSign! If you need support feel free to contact us.`,
position: "top"
// style: { backgroundColor: "#abd4d2" },
}
@@ -192,7 +226,7 @@ const HomeLayout = () => {
return (
<div>
<div className="sticky top-0 z-50">
<Header showSidebar={showSidebar} />
{!isLoader && <Header showSidebar={showSidebar} />}
</div>
{isUserValid ? (
<>
@@ -217,8 +251,10 @@ const HomeLayout = () => {
<>
<div className="flex md:flex-row flex-col z-50">
<Sidebar isOpen={isOpen} closeSidebar={closeSidebar} />
<div className="relative h-screen flex flex-col justify-between w-full overflow-y-auto">
<div
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="z-30">
<Footer />
+6 -7
View File
@@ -4,10 +4,11 @@ import "../styles/loader.css";
import GetDashboard from "../components/dashboard/GetDashboard";
import { useNavigate, useParams } from "react-router-dom";
import Title from "../components/Title";
import { save_tourSteps } from "../redux/actions";
import { connect } from "react-redux";
const Dashboard = (props) => {
import { useDispatch } from "react-redux";
import { saveTourSteps } from "../redux/reducers/TourStepsReducer";
const Dashboard = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const { id } = useParams();
const [dashboard, setdashboard] = useState([]);
const [classnameArray, setclassnameArray] = useState([]);
@@ -88,7 +89,7 @@ const Dashboard = (props) => {
// style: { backgroundColor: "#abd4d2" },
};
});
props.save_tourSteps(arr);
dispatch(saveTourSteps(arr));
// console.log("arr ", arr);
setclassnameArray(classArray);
if (localStorage.getItem("DashboardDefaultFilter")) {
@@ -133,6 +134,4 @@ const Dashboard = (props) => {
);
};
export default connect(null, {
save_tourSteps
})(Dashboard);
export default Dashboard;
+14 -20
View File
@@ -1,12 +1,15 @@
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { fetchAppInfo, forgetPassword } from "../redux/actions";
import Title from "../components/Title";
import { NavLink } from "react-router-dom";
import login_img from "../assets/images/login_img.svg";
import Parse from "parse";
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({
email: "",
password: "",
@@ -27,14 +30,11 @@ function ForgotPassword(props) {
const handleSubmit = async (event) => {
event.preventDefault();
localStorage.setItem("appLogo", props.appInfo.applogo);
localStorage.setItem("appName", props.appInfo.appname);
localStorage.setItem("defaultmenuid", props.appInfo.defaultmenuid);
localStorage.setItem("PageLanding", props.appInfo.LandingPageId);
localStorage.setItem(
"userSettings",
JSON.stringify(props.appInfo.settings)
);
localStorage.setItem("appLogo", appInfo.applogo);
localStorage.setItem("appName", appInfo.appname);
// localStorage.setItem("defaultmenuid", props.appInfo.defaultmenuid);
// localStorage.setItem("PageLanding", props.appInfo.LandingPageId);
localStorage.setItem("userSettings", JSON.stringify(appInfo.settings));
if (state.email) {
const username = state.email;
try {
@@ -50,14 +50,14 @@ function ForgotPassword(props) {
};
useEffect(() => {
props.fetchAppInfo();
dispatch(fetchAppInfo());
resize();
window.addEventListener("resize", resize);
return () => window.removeEventListener("resize", resize);
// eslint-disable-next-line
}, []);
let image = props.appInfo.applogo;
let image = appInfo.applogo;
return (
<div className="bg-white">
<Title title="Forgot password page" />
@@ -129,10 +129,4 @@ function ForgotPassword(props) {
);
}
const mapStateToProps = (state) => {
return { appInfo: state.appInfo, forgotPassword: state.forgotPassword };
};
export default connect(mapStateToProps, { fetchAppInfo, forgetPassword })(
ForgotPassword
);
export default ForgotPassword;
+50 -19
View File
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { formJson } from "../json/FormJson";
import AddUser from "../components/AddUser";
@@ -10,7 +10,9 @@ import SelectFolder from "../components/shared/fields/SelectFolder";
import SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
import { SaveFileSize } from "../constant/saveFileSize";
// `Form` render all type of Form on this basis of their provided in path
function Form() {
const { id } = useParams();
@@ -27,42 +29,47 @@ function Form() {
}
const Forms = (props) => {
const maxFileSize = 20;
const navigate = useNavigate();
const [signers, setSigners] = useState([]);
const [folder, setFolder] = useState({ ObjectId: "", Name: "" });
const [formData, setFormData] = useState({
Name: "",
Description: "",
Note: "Please review and sign this document",
Note: "",
TimeToCompleteDays: 15,
SendinOrder: "false"
});
const [fileupload, setFileUpload] = useState([]);
const [fileload, setfileload] = useState(false);
const [percentage, setpercentage] = useState(0);
const [isReset, setIsReset] = useState(false);
const [isAlert, setIsAlert] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [isErr, setIsErr] = useState("");
const handleStrInput = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
useEffect(() => {
handleReset();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.title]);
const handleFileInput = (e) => {
setpercentage(0);
try {
let files = e.target.files;
if (typeof files[0] !== "undefined") {
const mb = Math.round(files[0].bytes / Math.pow(1024, 2));
if (mb > 10) {
const mb = Math.round(files[0].size / Math.pow(1024, 2));
if (mb > maxFileSize) {
alert(
`The selected file size is too large. Please select a file less than ${Math.round(
10
)} MB`
`The selected file size is too large. Please select a file less than ${maxFileSize} MB`
);
setFileUpload([]);
e.target.value = "";
return;
} else {
handleFileUpload(files[0]);
}
handleFileUpload(files[0]);
} else {
alert("Please select file.");
return false;
@@ -76,6 +83,7 @@ const Forms = (props) => {
const handleFileUpload = async (file) => {
setfileload(true);
const fileName = file.name;
const size = file.size;
const name = sanitizeFileName(fileName);
const pdfFile = file;
const parseFile = new Parse.File(name, pdfFile);
@@ -95,6 +103,8 @@ const Forms = (props) => {
setFileUpload(response.url());
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
} catch (error) {
@@ -107,13 +117,14 @@ const Forms = (props) => {
const dropboxSuccess = async (files) => {
setfileload(true);
const file = files[0];
const size = file.bytes;
const url = file.link;
const mb = Math.round(file.bytes / Math.pow(1024, 2));
if (mb > 10) {
if (mb > maxFileSize) {
setTimeout(() => {
alert(
`The selected file size is too large. Please select a file less than 10 MB`
`The selected file size is too large. Please select a file less than ${maxFileSize} MB`
);
}, 500);
return;
@@ -135,6 +146,8 @@ const Forms = (props) => {
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
} catch (error) {
@@ -160,7 +173,9 @@ const Forms = (props) => {
"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("URL", fileupload);
@@ -222,15 +237,22 @@ const Forms = (props) => {
};
const handleReset = () => {
setIsReset(true);
setSigners([]);
setFolder({ ObjectId: "", Name: "" });
setFormData({
Name: "",
Description: "",
Note: ""
Note:
props.title === "Sign Yourself"
? "Note to myself"
: "Please review and sign this document",
TimeToCompleteDays: 15,
SendinOrder: "true"
});
setFileUpload([]);
setpercentage(0);
setTimeout(() => setIsReset(false), 50);
};
return (
<div className="shadow-md rounded my-2 p-3 bg-[#ffffff] md:border-[1px] md:border-gray-600/50">
@@ -315,7 +337,10 @@ const Forms = (props) => {
</div>
<div className="text-xs mt-2">
<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>
<input
name="Name"
@@ -334,7 +359,9 @@ const Forms = (props) => {
onChange={(e) => handleStrInput(e)}
/>
</div>
{props.signers && <SignersInput onChange={handleSigners} required />}
{props.signers && (
<SignersInput onChange={handleSigners} isReset={isReset} required />
)}
<div className="text-xs mt-2">
<label className="block">
Note<span className="text-red-500 text-[13px]">*</span>
@@ -347,7 +374,11 @@ const Forms = (props) => {
required
/>
</div>
<SelectFolder onSuccess={handleFolder} folderCls={props.Cls} />
<SelectFolder
onSuccess={handleFolder}
folderCls={props.Cls}
isReset={isReset}
/>
{props.title === "Request Signatures" && (
<div className="text-xs mt-2">
@@ -399,10 +430,10 @@ const Forms = (props) => {
type="submit"
disabled={isSubmit}
>
Submit
Next
</button>
<div
className="bg-[#188ae2] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 text-center ml-[2px] focus:outline-none"
className="cursor-pointer bg-[#188ae2] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 text-center ml-[2px] focus:outline-none"
onClick={() => handleReset()}
>
Reset
+80 -21
View File
@@ -1,12 +1,16 @@
import React, { useEffect, useState } from "react";
import Title from "../components/Title";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi";
import { rejectBtn, submitBtn } from "../constant/const";
import { openInNewTab } from "../constant/Utils";
import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
function GenerateToken() {
const navigation = useNavigate();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [apiToken, SetApiToken] = useState("");
@@ -15,6 +19,7 @@ function GenerateToken() {
const [isGenerate, setIsGenerate] = useState(false);
const [isErr, setIsErr] = useState(false);
const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => {
fetchToken();
@@ -23,6 +28,11 @@ function GenerateToken() {
const fetchToken = async () => {
try {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
const url = parseBaseUrl + "functions/getapitoken";
const headers = {
"Content-Type": "application/json",
@@ -33,6 +43,7 @@ function GenerateToken() {
if (res) {
SetApiToken(res.data.result.result);
}
setIsLoader(false);
} catch (err) {
SetApiToken();
@@ -88,6 +99,7 @@ function GenerateToken() {
}, 1500); // Reset copied state after 1.5 seconds
};
const handleModal = () => setIsModal(!isModal);
return (
<React.Fragment>
<Title title={"API Token"} />
@@ -115,29 +127,47 @@ function GenerateToken() {
</div>
) : (
<div className="bg-white flex flex-col justify-center shadow rounded">
<h1 className="ml-4 mt-3 mb-2 font-semibold">API Token</h1>
<ul className="w-full flex flex-col p-2 text-sm">
{!isEnableSubscription && <PremiumAlertHeader />}
<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
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>{" "}
<span
id="token"
className="w-[60%] md:text-end cursor-pointer"
onClick={() => copytoclipboard(apiToken)}
<div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
<span className="">Api Token:</span>{" "}
<span
id="token"
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}
</span>
{apiToken ? "Regenerate Token" : "Generate Token"}
</button>
</li>
</ul>
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
<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 ? "Regenerate Token" : "Generate Token"}
</button>
<div className="flex items-center justify-center ">
<button
type="button"
onClick={() =>
@@ -145,12 +175,41 @@ function GenerateToken() {
"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
</button>
</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
isOpen={isModal}
title={"Regenerate Token"}
+11 -1
View File
@@ -4,6 +4,7 @@ import "../styles/loginPage.css";
import loader from "../assets/images/loader2.gif";
import axios from "axios";
import { themeColor } from "../constant/const";
import { contractUsers } from "../constant/Utils";
function GuestLogin() {
const { id, userMail, contactBookId, serverUrl } = useParams();
@@ -58,7 +59,8 @@ function GuestLogin() {
"X-Parse-Application-Id": parseId
};
let body = {
email: email.toString()
email: email.toString(),
docId: id
};
let Otp = await axios.post(url, body, { headers: headers });
@@ -103,11 +105,19 @@ function GuestLogin() {
} else {
let _user = user.data.result;
const parseId = localStorage.getItem("parseAppId");
const contractUserDetails = await contractUsers(_user.email);
localStorage.setItem("UserInformation", JSON.stringify(_user));
localStorage.setItem(
`Parse/${parseId}/currentUser`,
JSON.stringify(_user)
);
if (contractUserDetails) {
localStorage.setItem(
"Extand_Class",
JSON.stringify(contractUserDetails)
);
}
localStorage.setItem("username", _user.name);
localStorage.setItem("accesstoken", _user.sessionToken);
//save isGuestSigner true in local to handle login flow header in mobile view
+235 -136
View File
@@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import "../styles/loader.css";
import { connect } from "react-redux";
import { fetchAppInfo, showTenantName } from "../redux/actions";
import { useDispatch } from "react-redux";
import axios from "axios";
import Title from "../components/Title";
import GoogleSignInBtn from "../components/LoginGoogle";
@@ -11,12 +10,19 @@ import { NavLink, useNavigate, useLocation } from "react-router-dom";
import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize";
import ModalUi from "../primitives/ModalUi";
import { modalCancelBtnColor, modalSubmitBtnColor } from "../constant/const";
import {
isEnableSubscription,
modalCancelBtnColor,
modalSubmitBtnColor
} from "../constant/const";
import Alert from "../primitives/Alert";
function Login(props) {
import { appInfo } from "../constant/appinfo";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
function Login() {
const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
const { width } = useWindowSize();
const [state, setState] = useState({
email: "",
@@ -37,14 +43,14 @@ function Login(props) {
Destination: ""
});
const [isModal, setIsModal] = useState(false);
const image = props?.appInfo?.applogo || undefined;
const image = appInfo?.applogo || undefined;
useEffect(() => {
if (localStorage.getItem("accesstoken")) {
setState({ ...state, loading: true });
GetLoginData();
}
props.fetchAppInfo();
dispatch(fetchAppInfo());
// eslint-disable-next-line
}, []);
const handleChange = (event) => {
@@ -61,8 +67,8 @@ function Login(props) {
setState({ ...state, loading: true });
let baseUrl = localStorage.getItem("baseUrl");
let parseAppId = localStorage.getItem("parseAppId");
localStorage.setItem("appLogo", props.appInfo.applogo);
localStorage.setItem("appName", props.appInfo.appname);
localStorage.setItem("appLogo", appInfo.applogo);
localStorage.setItem("appName", appInfo.appname);
// Pass the username and password to logIn function
await Parse.User.logIn(email, password)
.then(async (user) => {
@@ -81,8 +87,8 @@ function Login(props) {
// Check extended class user role and tenentId
try {
let userRoles = [];
if (props.appInfo.settings) {
let userSettings = props.appInfo.settings;
if (appInfo.settings) {
let userSettings = appInfo.settings;
//Get Current user roles
let url = `${baseUrl}functions/UserGroups`;
@@ -172,8 +178,10 @@ function Login(props) {
}
});
if (tenentInfo.length) {
props.showTenantName(
tenentInfo[0].tenentName || ""
dispatch(
showTenant(
tenentInfo[0].tenentName || ""
)
);
localStorage.setItem(
"TenantName",
@@ -205,15 +213,17 @@ function Login(props) {
x.TenantId.TenantName || ""
};
localStorage.setItem(
"TenetId",
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
}
});
if (tenentInfo.length) {
props.showTenantName(
tenentInfo[0].tenentName || ""
dispatch(
showTenant(
tenentInfo[0].tenentName || ""
)
);
localStorage.setItem(
"TenantName",
@@ -233,10 +243,7 @@ function Login(props) {
element.pageType
);
setState({ ...state, loading: false });
if (
process.env
.REACT_APP_ENABLE_SUBSCRIPTION
) {
if (isEnableSubscription) {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
@@ -247,10 +254,15 @@ function Login(props) {
"userDetails",
JSON.stringify(LocalUserDetails)
);
const freeplan =
results[0].get("Plan") &&
results[0].get("Plan").plan_code;
const billingDate =
results[0].get("Next_billing_date") &&
results[0].get("Next_billing_date");
if (billingDate) {
if (freeplan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (billingDate > new Date()) {
localStorage.removeItem(
"userDetails"
@@ -286,9 +298,7 @@ function Login(props) {
element.pageType
);
setState({ ...state, loading: false });
if (
process.env.REACT_APP_ENABLE_SUBSCRIPTION
) {
if (isEnableSubscription) {
const LocalUserDetails = {
name: _user.name,
email: email,
@@ -385,6 +395,18 @@ function Login(props) {
const setThirdpartyLoader = (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 baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
@@ -394,6 +416,11 @@ function Login(props) {
"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(() => {
window.localStorage.setItem("accesstoken", sessionToken);
});
@@ -412,8 +439,8 @@ function Login(props) {
// Check extended class user role and tenentId
try {
let userRoles = [];
if (props.appInfo.settings) {
let userSettings = props.appInfo.settings;
if (appInfo.settings) {
let userSettings = appInfo.settings;
//Get Current user roles
let url = `${baseUrl}functions/UserGroups`;
@@ -503,8 +530,8 @@ function Login(props) {
}
});
if (tenentInfo.length) {
props.showTenantName(
tenentInfo[0].tenentName || ""
dispatch(
showTenant(tenentInfo[0].tenentName || "")
);
localStorage.setItem(
"TenantName",
@@ -536,15 +563,15 @@ function Login(props) {
tenentName: x.TenantId.TenantName || ""
};
localStorage.setItem(
"TenetId",
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
}
});
if (tenentInfo.length) {
props.showTenantName(
tenentInfo[0].tenentName || ""
dispatch(
showTenant(tenentInfo[0].tenentName || "")
);
localStorage.setItem(
"TenantName",
@@ -565,18 +592,28 @@ function Login(props) {
);
setThirdpartyLoader(false);
setState({ ...state, loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
if (isEnableSubscription) {
if (billingDate) {
if (billingDate > new Date()) {
localStorage.removeItem("userDetails");
navigate(redirectUrl);
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
}
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
} else {
navigate(`/subscription`, { replace: true });
}
} else {
navigate(redirectUrl);
@@ -591,17 +628,29 @@ function Login(props) {
localStorage.setItem("pageType", element.pageType);
setState({ ...state, loading: false });
setThirdpartyLoader(false);
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
if (isEnableSubscription) {
if (billingDate) {
if (billingDate > new Date()) {
localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
navigate(`/subscription`, { replace: true });
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
}
} else {
navigate(`/subscription`, { replace: true });
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
}
} else {
navigate(redirectUrl);
@@ -730,94 +779,153 @@ function Login(props) {
} else {
_currentRole = userRoles[0];
}
let SettingsUser = JSON.parse(userSettings);
SettingsUser.forEach(async (element) => {
if (element.role === _currentRole) {
let _role = _currentRole.replace(
`${localStorage.getItem("_appName")}_`,
""
);
localStorage.setItem("_user_role", _role);
// Get TenentID from Extendend Class
localStorage.setItem(
"extended_class",
element.extended_class
);
const currentUser = Parse.User.current();
await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
}).then(
(result) => {
let tenentInfo = [];
const results = [result];
if (results) {
let extendedInfo_stringify = JSON.stringify(results);
let extendedInfo = JSON.parse(extendedInfo_stringify);
if (extendedInfo.length > 1) {
extendedInfo.forEach((x) => {
if (x.TenantId) {
let obj = {
tenentId: x.TenantId.objectId,
tenentName: x.TenantId.TenantName || ""
};
tenentInfo.push(obj);
}
});
localStorage.setItem("showpopup", true);
localStorage.setItem("PageLanding", element.pageId);
localStorage.setItem("defaultmenuid", element.menuId);
localStorage.setItem("pageType", element.pageType);
navigate("/");
} else {
extendedInfo.forEach((x) => {
if (x.TenantId) {
let obj = {
tenentId: x.TenantId.objectId,
tenentName: x.TenantId.TenantName || ""
if (_currentRole && _currentRole !== "contracts_Guest") {
let SettingsUser = JSON.parse(userSettings);
SettingsUser.forEach(async (item) => {
if (item.role === _currentRole) {
let _role = _currentRole.replace(`${appInfo.appname}_`, "");
localStorage.setItem("_user_role", _role);
// Get TenentID from Extendend Class
localStorage.setItem("extended_class", item.extended_class);
const currentUser = Parse.User.current();
const userSettings = appInfo.settings;
const setting = userSettings.find(
(x) => x.role === _currentRole
);
const redirectUrl =
location?.state?.from ||
`/${setting.pageType}/${setting.pageId}`;
await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
}).then(
(result) => {
let tenentInfo = [];
const results = [result];
if (results) {
let extendedInfo_stringify = JSON.stringify(results);
let extendedInfo = JSON.parse(extendedInfo_stringify);
if (extendedInfo.length > 1) {
extendedInfo.forEach((x) => {
if (x.TenantId) {
let obj = {
tenentId: x.TenantId.objectId,
tenentName: x.TenantId.TenantName || ""
};
tenentInfo.push(obj);
}
});
localStorage.setItem("showpopup", true);
localStorage.setItem("PageLanding", item.pageId);
localStorage.setItem("defaultmenuid", item.menuId);
localStorage.setItem("pageType", item.pageType);
navigate("/");
} else {
extendedInfo.forEach((x) => {
if (x.TenantId) {
let obj = {
tenentId: x.TenantId.objectId,
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(
"TenetId",
x.TenantId.objectId
"userDetails",
JSON.stringify(LocalUserDetails)
);
tenentInfo.push(obj);
const billingDate =
results[0].get("Next_billing_date") &&
results[0].get("Next_billing_date");
const freeplan =
results[0]?.get("Plan") &&
results[0]?.get("Plan").plan_code;
if (freeplan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (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);
localStorage.setItem("defaultmenuid", element.menuId);
localStorage.setItem("pageType", element.pageType);
navigate(`/${element.pageType}/${element.pageId}`);
}
} else {
localStorage.setItem("PageLanding", setting.pageId);
localStorage.setItem("defaultmenuid", setting.menuId);
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);
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 () {
},
(error) => {
setState({
...state,
loading: false,
alertType: "danger",
alertMsg: ""
alertMsg: "You don`t have access to this application!"
});
}, 2000);
localStorage.setItem("accesstoken", null);
console.error("Error while fetching Follow", error);
}
);
}
});
setTimeout(function () {
setState({
...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 {
console.log("User Role Not Found.");
}
@@ -943,7 +1051,7 @@ function Login(props) {
</div>
</div>
)}
{props.isloginVisible && props.isloginVisible ? (
{appInfo && appInfo.appId ? (
<>
<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 ">
@@ -1043,7 +1151,11 @@ function Login(props) {
</button>
<NavLink
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" } : {}}
>
Create Account
@@ -1051,7 +1163,7 @@ function Login(props) {
</div>
</form>
<br />
{(props.appInfo.fbAppId || props.appInfo.googleClietId) && (
{(appInfo.fbAppId || appInfo.googleClietId) && (
<div
style={{
display: "flex",
@@ -1082,9 +1194,9 @@ function Login(props) {
justifyContent: "center"
}}
>
{/* {props.appInfo.fbAppId && props.appInfo.fbAppId !== "" ? (
{/* {appInfo.fbAppId && appInfo.fbAppId !== "" ? (
<LoginFacebook
FBCred={props.appInfo.fbAppId}
FBCred={appInfo.fbAppId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
@@ -1100,10 +1212,9 @@ function Login(props) {
justifyContent: "center"
}}
>
{props.appInfo.googleClietId &&
props.appInfo.googleClietId !== "" ? (
{appInfo.googleClietId && appInfo.googleClietId !== "" ? (
<GoogleSignInBtn
GoogleCred={props.appInfo.googleClietId}
GoogleCred={appInfo.googleClietId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
@@ -1213,16 +1324,4 @@ function Login(props) {
</div>
);
}
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);
export default Login;
+23 -86
View File
@@ -5,6 +5,7 @@ import "../styles/signature.css";
import { toDataUrl } from "../constant/Utils";
import Parse from "parse";
import { appInfo } from "../constant/appinfo";
import { SaveFileSize } from "../constant/saveFileSize";
const ManageSign = () => {
const appName = appInfo.appname;
const [penColor, setPenColor] = useState("blue");
@@ -13,7 +14,6 @@ const ManageSign = () => {
const [signName, setSignName] = useState("");
const [signature, setSignature] = useState("");
const [warning, setWarning] = useState(false);
const [nameWarning, setNameWarning] = useState(false);
const [isvalue, setIsValue] = useState(false);
const allColor = ["blue", "red", "black"];
const [isLoader, setIsLoader] = useState(true);
@@ -105,9 +105,7 @@ const ManageSign = () => {
e.preventDefault();
const isUrl = image.includes("https");
if (!signName) {
setNameWarning(true);
} else if (!isvalue) {
if (!isvalue) {
setWarning(true);
setTimeout(() => setWarning(false), 1000);
} else {
@@ -161,7 +159,11 @@ const ManageSign = () => {
try {
const parseFile = new Parse.File(file.name, file);
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) {
console.log("sign upload err", err);
setIsLoader(false);
@@ -222,26 +224,13 @@ const ManageSign = () => {
setIsValue(true);
};
return (
<div
style={{
backgroundColor: "white",
maxHeight: "500px",
overflow: "auto"
}}
>
<div className="relative h-full bg-white flex shadow rounded overflow-auto">
{isLoader && (
<div className="absolute bg-black bg-opacity-75 inset-0 z-50 flex justify-center items-center">
<div style={{ height: "300px" }}>
<div
style={{
marginLeft: "45%",
marginTop: "150px",
fontSize: "45px",
color: "#3dd3e0"
}}
className="loader-37"
></div>
</div>
<div className="absolute bg-black bg-opacity-75 z-50 w-full h-full flex justify-center items-center">
<div
style={{ color: "#3dd3e0" }}
className="loader-37 text-[45px]"
></div>
</div>
)}
{isSuccess && (
@@ -269,61 +258,9 @@ const ManageSign = () => {
paddingBottom: 8
}}
>
Manage Signature
My Signature
</div>
<div style={{ display: "flex", flexDirection: "column" }}>
<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 className="signBlock mt-4">
<div>
<div style={{ position: "relative" }}>
<div
@@ -429,10 +366,10 @@ const ManageSign = () => {
key === 0 && penColor === "blue"
? "2px solid blue"
: key === 1 && penColor === "red"
? "2px solid red"
: key === 2 && penColor === "black"
? "2px solid black"
: "2px solid white"
? "2px solid red"
: key === 2 && penColor === "black"
? "2px solid black"
: "2px solid white"
}}
onClick={() => {
if (key === 0) {
@@ -537,10 +474,10 @@ const ManageSign = () => {
key === 0 && initialPen === "blue"
? "2px solid blue"
: key === 1 && initialPen === "red"
? "2px solid red"
: key === 2 && initialPen === "black"
? "2px solid black"
: "2px solid white"
? "2px solid red"
: key === 2 && initialPen === "black"
? "2px solid black"
: "2px solid white"
}}
onClick={() => {
if (key === 0) {
+176 -193
View File
@@ -1,20 +1,41 @@
import React, { useEffect, useState, useRef } from "react";
import "../styles/opensigndrive.css";
import loader from "../assets/images/loader2.gif";
import DriveBody from "../components/opensigndrive/DriveBody";
import { themeColor, iconColor } from "../constant/const";
import { getDrive } from "../constant/Utils";
import { useNavigate } from "react-router-dom";
import Title from "../components/Title";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
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() {
const navigate = useNavigate();
const scrollRef = useRef(null);
const [isList, setIsList] = useState(false);
const [selectedSort, setSelectedSort] = useState("Date");
const [sortingOrder, setSortingOrder] = useState("Decending");
const [sortingOrder, setSortingOrder] = useState("Descending");
const [pdfData, setPdfData] = useState([]);
const [isFolder, setIsFolder] = useState(false);
const [newFolderName, setNewFolderName] = useState();
@@ -30,6 +51,17 @@ function Opensigndrive() {
const [folderName, setFolderName] = useState([]);
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
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 orderName = {
Ascending: "Ascending",
Descending: "Descending",
Name: "Name",
Date: "Date"
};
const currentUser =
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
@@ -40,103 +72,83 @@ function Opensigndrive() {
const jsonCurrentUser = JSON.parse(currentUser);
useEffect(() => {
if (docId) {
getPdfFolderDocumentList();
} else {
getPdfDocumentList();
}
getPdfDocumentList();
// eslint-disable-next-line
}, [docId]);
//function for get all pdf document list
const getPdfDocumentList = async () => {
const load = {
isLoad: true,
message: "This might take some time"
};
let driveDetails;
setIsLoading(load);
const getPdfDocumentList = async (disbaleLoading) => {
setLoading(true);
if (!disbaleLoading) {
setIsLoading({ isLoad: true, message: "This might take some time" });
}
try {
driveDetails = await getDrive();
if (driveDetails) {
if (driveDetails.length > 0) {
setPdfData(driveDetails);
sortApps(null, null, driveDetails);
}
const data = [
{
name: "OpenSign™ Drive",
objectId: ""
}
];
setFolderName(data);
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
} else if (driveDetails === "Error: Something went wrong!") {
const loadObj = {
isLoad: false
};
const driveDetails = await getDrive(docId, skip, limit);
if (driveDetails && driveDetails === "Error: Something went wrong!") {
setHandleError("Error: Something went wrong!");
setIsLoading(loadObj);
} else if (driveDetails && driveDetails.length > 0) {
setSkip((prevSkip) => prevSkip + limit);
sortingData(null, null, driveDetails, true);
}
if (!docId) {
setFolderName([{ name: "OpenSign™ Drive", objectId: "" }]);
}
} catch (e) {
setIsAlert({
isShow: true,
alertMessage: "something went wrong"
});
}
};
//function for get parent folder document list
const getPdfFolderDocumentList = async () => {
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!"
} finally {
setLoading(false);
setIsLoading({
isLoad: false
});
}
};
//function for get all pdf document list
const getParentFolder = async () => {
setIsFolder(true);
//function to fetch drive details list on scroll bottom
const handleScroll = () => {
//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
const handleRoute = (index) => {
setPdfData([]);
setSkip(0);
const updateFolderName = folderName.filter((x, i) => {
if (i <= index) {
return x;
}
});
setFolderName(updateFolderName);
const getLastId = updateFolderName[updateFolderName.length - 1];
setDocId(getLastId.objectId);
@@ -207,34 +219,38 @@ function Opensigndrive() {
}
};
const sortingApp = (appInfo, type, order) => {
if (type === "Name") {
if (order === "Accending") {
//function to use sorting document list according to type and order
const sortedBy = (appInfo, type, order) => {
if (type === orderName.Name) {
if (order === orderName.Ascending) {
return appInfo.sort((a, b) => (a.Name > b.Name ? 1 : -1));
} else if (order === "Decending") {
} else if (order === orderName.Descending) {
return appInfo.sort((a, b) => (a.Name > b.Name ? -1 : 1));
}
} else if (type === "Date") {
if (order === "Accending") {
} else if (type === orderName.Date) {
if (order === orderName.Ascending) {
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));
}
}
};
const sortApps = (type, order, driveDetails) => {
const selectedSortType = type ? type : selectedSort ? selectedSort : "Date";
const sortOrder = order ? order : sortingOrder ? sortingOrder : "Decending";
//function to use get sorting type, order and document list to sort
const sortingData = (type, order, driveDetails, isInitial) => {
const selectedSortType = type ? type : selectedSort;
const sortOrder = order ? order : sortingOrder;
let sortingData = driveDetails;
if (selectedSortType === "Name") {
sortingApp(sortingData, "Name", sortOrder);
} else if (selectedSortType === "Date") {
sortingApp(sortingData, "Date", sortOrder);
//check isInitial true it means sort previous 20 and get on scrolling 20 = 40 document list
const allPdfData = isInitial ? [...pdfData, ...driveDetails] : driveDetails;
//call sortedBy function according to selected Type and order
if (selectedSortType === orderName.Name) {
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
@@ -301,6 +317,7 @@ function Opensigndrive() {
// Cleanup the event listener when the component unmounts
document.removeEventListener("click", closeMenuOnOutsideClick);
};
// eslint-disable-next-line
}, [isShowSort, isNewFol]);
const handleFolderTab = (folderData) => {
@@ -542,7 +559,7 @@ function Opensigndrive() {
>
<span
className="dropdown-item itemColor"
onClick={() => getParentFolder()}
onClick={() => setIsFolder(true)}
>
<i
style={{ marginRight: "5px" }}
@@ -606,95 +623,56 @@ function Opensigndrive() {
aria-labelledby="dropdownMenuButton"
aria-expanded={isShowSort ? "true" : "false"}
>
<span
onClick={() => {
setSelectedSort("Name");
sortApps("Name", null, pdfData);
}}
className="dropdown-item itemColor"
>
{selectedSort !== "Name" ? (
<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>
)}
Name
</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>
{sortingValue.map((value, ind) => {
return (
<span
key={ind}
onClick={() => {
setSelectedSort(value);
sortingData(value, null, pdfData);
}}
className="dropdown-item itemColor"
style={{
paddingLeft: selectedSort !== value && "33px"
}}
>
{selectedSort === value && (
<i
className="fa fa-check"
aria-hidden="true"
style={{ marginRight: "5px" }}
></i>
)}
{value}
</span>
);
})}
<hr className="hrStyle" />
<span
onClick={() => {
setSortingOrder("Accending");
sortApps(null, "Accending", pdfData);
}}
className="dropdown-item itemColor"
>
{sortingOrder !== "Accending" ? (
<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>
)}
Accending
</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>
{sortOrder.map((order, ind) => {
return (
<span
key={ind}
onClick={() => {
setSortingOrder(order);
sortingData(null, order, pdfData);
}}
className="dropdown-item itemColor"
style={{
paddingLeft: sortingOrder !== order && "33px"
}}
>
{sortingOrder === order && (
<i
className="fa fa-check"
aria-hidden="true"
style={{ marginRight: "5px" }}
></i>
)}
{order}
</span>
);
})}
</div>
</div>
@@ -734,18 +712,23 @@ function Opensigndrive() {
<span style={{ fontWeight: "bold" }}>No Data Found!</span>
</div>
) : (
<DriveBody
pdfData={pdfData}
setFolderName={setFolderName}
setIsLoading={setIsLoading}
setDocId={setDocId}
getPdfFolderDocumentList={getPdfFolderDocumentList}
getPdfDocumentList={getPdfDocumentList}
isDocId={docId}
setPdfData={setPdfData}
isList={isList}
setIsAlert={setIsAlert}
/>
<React.Suspense fallback={<Loader />}>
<DriveBody
pdfData={pdfData}
setFolderName={setFolderName}
setIsLoading={setIsLoading}
setDocId={setDocId}
getPdfDocumentList={getPdfDocumentList}
isDocId={docId}
setPdfData={setPdfData}
isList={isList}
setIsAlert={setIsAlert}
setSkip={setSkip}
/>
{loading && (
<div style={{ textAlign: "center" }}>Loading...</div>
)}
</React.Suspense>
)}
</>
)}
File diff suppressed because it is too large Load Diff
+15 -24
View File
@@ -2,14 +2,17 @@ import React, { useState, useEffect } from "react";
import "../styles/pgsignup.css";
import Parse from "parse";
import axios from "axios";
import { fetchAppInfo, showTenantName } from "../redux/actions/index";
import { connect } from "react-redux";
import { useDispatch } from "react-redux";
import Title from "../components/Title";
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 location = useLocation();
const dispatch = useDispatch();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [formData, setFormData] = useState({
@@ -24,8 +27,7 @@ const PgSignUp = (props) => {
// below useEffect is used to fetch App data and save to redux state
useEffect(() => {
// Parse.User.logOut()
props.fetchAppInfo();
dispatch(fetchAppInfo());
handleSignedUpUser();
// eslint-disable-next-line
}, []);
@@ -243,8 +245,8 @@ const PgSignUp = (props) => {
// Check extended class user role and tenentId
try {
let userRoles = [];
if (props.appInfo.settings) {
let userSettings = props.appInfo.settings;
if (appInfo.settings) {
let userSettings = appInfo.settings;
//Get Current user roles
let url = `${baseUrl}functions/UserGroups`;
@@ -319,8 +321,8 @@ const PgSignUp = (props) => {
}
});
if (tenentInfo.length) {
props.showTenantName(
tenentInfo[0].tenentName || ""
dispatch(
showTenant(tenentInfo[0].tenentName || "")
);
localStorage.setItem(
"TenantName",
@@ -351,15 +353,15 @@ const PgSignUp = (props) => {
tenentName: x.TenantId.TenantName || ""
};
localStorage.setItem(
"TenetId",
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
}
});
if (tenentInfo.length) {
props.showTenantName(
tenentInfo[0].tenentName || ""
dispatch(
showTenant(tenentInfo[0].tenentName || "")
);
localStorage.setItem(
"TenantName",
@@ -537,15 +539,4 @@ const PgSignUp = (props) => {
);
};
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
})(PgSignUp);
export default PgSignUp;
+163 -59
View File
@@ -3,7 +3,7 @@ import axios from "axios";
import Parse from "parse";
import "../styles/signature.css";
import { PDFDocument } from "pdf-lib";
import { themeColor } from "../constant/const";
import { isEnableSubscription, themeColor } from "../constant/const";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd";
@@ -24,7 +24,11 @@ import {
randomId,
defaultWidthHeight,
multiSignEmbed,
addWidgetOptions
addWidgetOptions,
textInputWidget,
textWidget,
radioButtonWidget,
color
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import { useNavigate } from "react-router-dom";
@@ -35,6 +39,7 @@ import TourContentWithBtn from "../primitives/TourContentWithBtn";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import WidgetNameModal from "../components/pdf/WidgetNameModal";
import { SaveFileSize } from "../constant/saveFileSize";
function PlaceHolderSign() {
const navigate = useNavigate();
@@ -76,6 +81,8 @@ function PlaceHolderSign() {
const [isResize, setIsResize] = useState(false);
const [zIndex, setZIndex] = useState(1);
const [signKey, setSignKey] = useState();
const [tempSignerId, setTempSignerId] = useState("");
const [blockColor, setBlockColor] = useState("");
const [pdfLoadFail, setPdfLoadFail] = useState({
status: false,
type: "load"
@@ -91,28 +98,19 @@ function PlaceHolderSign() {
const [widgetType, setWidgetType] = useState("");
const [isUiLoading, setIsUiLoading] = useState(false);
const [isRadio, setIsRadio] = useState(false);
const [currWidgetsDetails, setCurrWidgetsDetails] = useState([]);
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
const [selectWidgetId, setSelectWidgetId] = useState("");
const [isCheckbox, setIsCheckbox] = useState(false);
const [isNameModal, setIsNameModal] = useState(false);
const [widgetName, setWidgetName] = useState(false);
const [mailStatus, setMailStatus] = useState("");
const [isCurrUser, setIsCurrUser] = useState(false);
const color = [
"#93a3db",
"#e6c3db",
"#c0e3bc",
"#bce3db",
"#b8ccdb",
"#ceb8db",
"#ffccff",
"#99ffcc",
"#cc99ff",
"#ffcc99",
"#66ccff",
"#ffffcc"
];
const [isSubscribe, setIsSubscribe] = useState(false);
const [isAlreadyPlace, setIsAlreadyPlace] = useState({
status: false,
message: ""
});
const [extUserId, setExtUserId] = useState("");
const isMobile = window.innerWidth < 767;
const [, drop] = useDrop({
accept: "BOX",
@@ -184,20 +182,78 @@ function PlaceHolderSign() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current]);
async function checkIsSubscribed(email) {
const user = await Parse.Cloud.run("getUserDetails", {
email: email
});
const freeplan = user?.get("Plan") && user?.get("Plan").plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
return true;
} else if (billingDate) {
if (billingDate > new Date()) {
setIsSubscribe(true);
return true;
} else {
navigate(`/subscription`);
}
} else {
navigate(`/subscription`);
}
}
//function for get document details
const getDocumentDetails = async () => {
//getting document details
const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) {
// const alreadyPlaceholder =
// documentData[0].Placeholders && documentData[0].Placeholders;
// if (alreadyPlaceholder && alreadyPlaceholder.length > 0) {
// setIsAlreadyPlace(true);
// }
// setSignersData(documentData[0]);
// setIsSelectId(0);
// setSignerObjId(documentData[0].Signers[0].objectId);
// setContractName(documentData[0].Signers[0].className);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
if (isEnableSubscription) {
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
}
const alreadyPlaceholder = documentData[0]?.SignedUrl;
// Check if document is sent for signing
if (alreadyPlaceholder) {
// Check if the document is completed
const isCompleted =
documentData[0].IsCompleted && documentData[0]?.IsCompleted;
// Get the expiration date of the document
const expireDate = documentData[0].ExpiryDate.iso;
// Check if the document has been declined
const declined =
documentData[0].IsDeclined && documentData[0]?.IsDeclined;
// Get the expiration update date in milliseconds
const expireUpdateDate = new Date(expireDate).getTime();
// Get the current date in milliseconds
const currDate = new Date().getTime();
if (isCompleted) {
// If document is completed
setIsAlreadyPlace({
status: true,
message: "This document has been signed by all Signers."
});
} else if (declined) {
// If document has been declined
setIsAlreadyPlace({
status: true,
message:
"This document has been declined by one or more recipient(s)."
});
} else if (currDate > expireUpdateDate) {
// If document has expired
setIsAlreadyPlace({
status: true,
message: "This Document is no longer available."
});
} else {
// If document is dispatched for signing
setIsAlreadyPlace({
status: true,
message: "The document has already been dispatched for signing."
});
}
}
setPdfDetails(documentData);
if (documentData[0].Signers && documentData[0].Signers.length > 0) {
@@ -234,14 +290,17 @@ function PlaceHolderSign() {
});
setSignersData(updatedSigners);
setUniqueId(updatedSigners[0].Id);
setBlockColor(updatedSigners[0].blockColor);
} else {
const updatedSigners = documentData[0].Signers.map((x, index) => ({
...x,
Id: randomId(),
Role: "User " + (index + 1)
Role: "User " + (index + 1),
blockColor: color[index % color.length]
}));
setSignersData(updatedSigners);
setUniqueId(updatedSigners[0].Id);
setBlockColor(updatedSigners[0].blockColor);
}
} else {
setRoleName("User 1");
@@ -261,6 +320,7 @@ function PlaceHolderSign() {
setSignersData(updatedSigners);
setIsSelectId(0);
setUniqueId(updatedSigners[0].Id);
setBlockColor(updatedSigners[0].blockColor);
}
}
} else if (
@@ -313,9 +373,6 @@ function PlaceHolderSign() {
//function for setting position after drop signature button over pdf
const addPositionOfSignature = (item, monitor) => {
if (item && item.text) {
setWidgetName(item.text);
}
getSignerPos(item, monitor);
};
@@ -332,7 +389,6 @@ function PlaceHolderSign() {
let dropData = [];
let placeHolder;
const dragTypeValue = item?.text ? item.text : monitor.type;
if (item === "onclick") {
const dropObj = {
//onclick put placeholder center on pdf
@@ -380,18 +436,16 @@ function PlaceHolderSign() {
pos: dropData
};
}
setSelectWidgetId(key);
if (signer) {
let filterSignerPos;
if (dragTypeValue === "label") {
if (dragTypeValue === textWidget) {
filterSignerPos = signerPos.filter((data) => data.Role === "prefill");
} else {
filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
}
const { blockColor, Role } = signer;
//adding placholder in existing signer pos array (placaholder)
if (filterSignerPos.length > 0) {
const getPlaceHolder = filterSignerPos[0].placeHolder;
@@ -412,7 +466,7 @@ function PlaceHolderSign() {
};
updatePlace.push(xyPos);
let updatesignerPos;
if (dragTypeValue === "label") {
if (dragTypeValue === textWidget) {
updatesignerPos = signerPos.map((x) =>
x.Role === "prefill" ? { ...x, placeHolder: updatePlace } : x
);
@@ -425,7 +479,7 @@ function PlaceHolderSign() {
setSignerPos(updatesignerPos);
} else {
let updatesignerPos;
if (dragTypeValue === "label") {
if (dragTypeValue === textWidget) {
updatesignerPos = signerPos.map((x) =>
x.Role === "prefill"
? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
@@ -442,9 +496,8 @@ function PlaceHolderSign() {
}
} else {
//adding new placeholder for selected signer in pos array (placeholder)
// const signerData = signerPos;
let placeHolderPos;
if (dragTypeValue === "label") {
if (dragTypeValue === textWidget) {
placeHolderPos = {
signerPtr: {},
signerObjId: "",
@@ -484,14 +537,13 @@ function PlaceHolderSign() {
setShowDropdown(true);
} else if (dragTypeValue === "checkbox") {
setIsCheckbox(true);
} else if (dragTypeValue === "radio") {
} else if (dragTypeValue === radioButtonWidget) {
setIsRadio(true);
} else if (dragTypeValue !== "label" && dragTypeValue !== "signature") {
setIsNameModal(true);
}
setWidgetType(dragTypeValue);
setSignKey(key);
setCurrWidgetsDetails({});
setWidgetName(dragTypeValue);
}
}
};
@@ -710,6 +762,9 @@ function PlaceHolderSign() {
// Save the Parse File if needed
const pdfData = await pdfFile.save();
const pdfUrl = pdfData.url();
const tenantId = localStorage.getItem("TenantId");
const buffer = atob(pdfBytes);
SaveFileSize(buffer.length, pdfUrl, tenantId);
return pdfUrl;
} catch (e) {
console.log("error", e);
@@ -736,7 +791,7 @@ function PlaceHolderSign() {
if (getPrefill && isLabel) {
const alert = {
mssg: "label",
mssg: textWidget,
alert: true
};
setIsSendAlert(alert);
@@ -803,6 +858,7 @@ function PlaceHolderSign() {
: "";
const themeBGcolor = themeColor;
let params = {
extUserId: extUserId,
recipient: signerMail[i].Email,
subject: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign ${pdfDetails?.[0].Name}`,
from: sender,
@@ -1000,7 +1056,7 @@ function PlaceHolderSign() {
selector: '[data-tut="reactourSecond"]',
content: () => (
<TourContentWithBtn
message={`Drag the signature or stamp 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}
/>
),
@@ -1051,7 +1107,8 @@ function PlaceHolderSign() {
addOption,
deleteOption,
status,
defaultValue
defaultValue,
isHideLabel
) => {
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
if (filterSignerPos.length > 0) {
@@ -1067,7 +1124,7 @@ function PlaceHolderSign() {
const getPosData = getXYdata;
const addSignPos = getPosData.map((position) => {
if (position.key === signKey) {
if (widgetType === "radio") {
if (widgetType === radioButtonWidget) {
if (addOption) {
return {
...position,
@@ -1089,7 +1146,8 @@ function PlaceHolderSign() {
...position.options,
name: dropdownName,
values: dropdownOptions,
isReadOnly: isReadOnly,
isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false,
defaultValue: defaultValue
}
};
@@ -1121,7 +1179,8 @@ function PlaceHolderSign() {
maxRequiredCount: maxCount
},
defaultValue: defaultValue,
isReadOnly: isReadOnly
isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false
}
};
}
@@ -1186,7 +1245,7 @@ function PlaceHolderSign() {
const getPosData = getXYdata;
const addSignPos = getPosData.map((position) => {
if (position.key === signKey) {
if (position.type === "text") {
if (position.type === textInputWidget) {
return {
...position,
options: {
@@ -1335,7 +1394,6 @@ function PlaceHolderSign() {
const closePopup = () => {
setIsAddUser({});
};
return (
<>
<Title title={state?.title ? state.title : "New Document"} />
@@ -1406,13 +1464,13 @@ function PlaceHolderSign() {
<ModalUi
headerColor={
isSendAlert.mssg === "sure" || isSendAlert.mssg === "label"
isSendAlert.mssg === "sure" || isSendAlert.mssg === textWidget
? "#dc3545"
: isSendAlert.mssg === "confirm" && themeColor
}
isOpen={isSendAlert.alert}
title={
isSendAlert.mssg === "sure" || isSendAlert.mssg === "label"
isSendAlert.mssg === "sure" || isSendAlert.mssg === textWidget
? "Fields required"
: isSendAlert.mssg === "confirm" && "Send Mail"
}
@@ -1421,8 +1479,8 @@ function PlaceHolderSign() {
<div style={{ height: "100%", padding: 20 }}>
{isSendAlert.mssg === "sure" ? (
<p>Please add field for all recipients.</p>
) : isSendAlert.mssg === "label" ? (
<p>Please confirm that you have filled label widget.</p>
) : isSendAlert.mssg === textWidget ? (
<p>Please confirm that you have filled the text field.</p>
) : (
isSendAlert.mssg === "confirm" && (
<p>
@@ -1553,11 +1611,14 @@ function PlaceHolderSign() {
allPages={allPages}
pageNumber={pageNumber}
signKey={signKey}
// signerObjId={signerObjId}
Id={uniqueId}
widgetType={widgetType}
setUniqueId={setUniqueId}
tempSignerId={tempSignerId}
setTempSignerId={setTempSignerId}
/>
<DropdownWidgetOption
type="radio"
type={radioButtonWidget}
title="Radio group"
showDropdown={isRadio}
setShowDropdown={setIsRadio}
@@ -1565,6 +1626,7 @@ function PlaceHolderSign() {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<DropdownWidgetOption
type="checkbox"
@@ -1575,6 +1637,7 @@ function PlaceHolderSign() {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<DropdownWidgetOption
type="dropdown"
@@ -1585,6 +1648,7 @@ function PlaceHolderSign() {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
{/* pdf header which contain funish back button */}
@@ -1639,6 +1703,8 @@ function PlaceHolderSign() {
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
handleNameModal={setIsNameModal}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
/>
)}
</div>
@@ -1675,13 +1741,19 @@ function PlaceHolderSign() {
setUniqueId={setUniqueId}
setRoleName={setRoleName}
initial={true}
sendInOrder={pdfDetails[0].SendinOrder}
setSignersData={setSignersData}
blockColor={blockColor}
setBlockColor={setBlockColor}
/>
</div>
) : (
<div>
<div className="signerComponent">
<div className="signerComponent" aria-disabled>
<div
style={{ maxHeight: window.innerHeight - 70 + "px" }}
style={{
maxHeight: window.innerHeight - 70 + "px"
}}
className="autoSignScroll"
>
<SignerListPlace
@@ -1693,11 +1765,16 @@ function PlaceHolderSign() {
setContractName={setContractName}
setUniqueId={setUniqueId}
setRoleName={setRoleName}
sendInOrder={pdfDetails[0].SendinOrder}
setSignersData={setSignersData}
blockColor={blockColor}
setBlockColor={setBlockColor}
isMailSend={isMailSend}
// handleAddSigner={handleAddSigner}
/>
<div data-tut="reactourSecond">
<WidgetComponent
pdfUrl={isMailSend}
isMailSend={isMailSend}
dragSignature={dragSignature}
signRef={signRef}
handleDivClick={handleDivClick}
@@ -1748,7 +1825,33 @@ function PlaceHolderSign() {
</button>
</div>
</ModalUi>
<ModalUi
headerColor={"#dc3545"}
isOpen={isAlreadyPlace.status}
title={"Document Alert"}
showClose={false}
>
<div style={{ height: "100%", padding: 20 }}>
<p>{isAlreadyPlace.message}</p>
<div
style={{
height: "1px",
backgroundColor: "#9f9f9f",
width: "100%",
marginTop: "15px",
marginBottom: "15px"
}}
></div>
<button
onClick={() => handleRecipientSign()}
type="button"
className="finishBtn cancelBtn"
>
View
</button>
</div>
</ModalUi>
<LinkUserModal
handleAddUser={handleAddUser}
isAddUser={isAddUser}
@@ -1761,6 +1864,7 @@ function PlaceHolderSign() {
isOpen={isNameModal}
handleClose={handleNameModal}
handleData={handleWidgetdefaultdata}
isSubscribe={isSubscribe}
/>
</div>
</>
+121 -36
View File
@@ -1,9 +1,9 @@
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router";
import { NavLink } from "react-router-dom";
import { NavLink, useNavigate } from "react-router-dom";
import checkmark from "../assets/images/checkmark.png";
import plansArr from "../json/plansArr.json";
import Title from "../components/Title";
import Parse from "parse";
const listItemStyle = {
paddingLeft: "20px", // Add padding to create space for the image
backgroundImage: `url(${checkmark})`, // Set your image as the list style image
@@ -35,15 +35,35 @@ const PlanSubscriptions = () => {
company +
phone;
useEffect(() => {
if (localStorage.getItem("accesstoken")) {
setIsLoader(false);
setYearlyVisible(false);
} else {
navigate("/", { replace: true });
}
// if (localStorage.getItem("accesstoken")) {
setIsLoader(false);
// } else {
// navigate("/", { replace: true });
// }
// 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 (
<>
<Title title={"Subscriptions"} />
@@ -69,23 +89,40 @@ const PlanSubscriptions = () => {
style={{
backgroundColor: "white",
overflowY: "auto",
maxHeight: "100%",
"--theme-color": "#7952b3",
"--plan-width": 30
maxHeight: "100%"
}}
>
<div
id="monthlyPlans"
className={`${yearlyVisible ? "none" : "block my-2"}`}
>
<div className="flex justify-center w-full my-2">
<ul className=" flex flex-col md:flex-row h-full bg-white justify-center border-collapse border-[1px] border-gray-300">
<div id="monthlyPlans" className="block my-2">
<div className="flex flex-col justify-center items-center w-full">
<div className="mb-6 mt-2 flex flex-row border-[1px] p-2 border-gray-300 rounded text-sm">
<span
onClick={() => setYearlyVisible(false)}
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) => (
<li
className="flex flex-col md:my-0 text-center border-[1px] border-gray-300 w-[260px]"
className="flex flex-col md:my-0 text-center border-collapse border-[1px] border-gray-300 w-[260px]"
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 min-h-[320px]">
<h3 className="text-[#002862] uppercase">
{item.planName}
</h3>
@@ -99,17 +136,23 @@ const PlanSubscriptions = () => {
<div className="">
<span className="text-3xl">
{item.currency && <small>{item.currency}</small>}
{item.price}
{yearlyVisible
? item?.yearlyPrice
: item.monthlyPrice}
</span>
<p className="font-semibold pt-2 text-sm">
{yearlyVisible ? "Billed Yearly" : "Billed Monthly"}
</p>
<div
className={`${
item.subtitle.length <= 32
? "w-[150px] text-center"
? "w-[150px] h-[40px] text-center"
: ""
} text-sm text-center my-2`}
>
<p
<div
style={{
textAlign: "center",
backgroundColor: item.subtitlecolor
? item.subtitlecolor
: "white"
@@ -124,23 +167,33 @@ const PlanSubscriptions = () => {
) : (
<span>{item.subtitle}</span>
)}
</p>
</div>
</div>
</div>
<NavLink
to={
item.btnText === "Subscribe"
? item.url + details
: item.url
}
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:no-underline hover:text-white"
target={item.target}
>
{item.btnText}
</NavLink>
{item.url ? (
<NavLink
to={
item.btnText === "Subscribe"
? yearlyVisible
? item.yearlyUrl + details
: item.url + details
: item.url
}
className="bg-[#002862] w-full mt-1 text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
target={item.target}
>
{item.btnText}
</NavLink>
) : (
<button
className="bg-[#002862] w-full mt-1 text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
onClick={() => handleFreePlan()}
>
{item.btnText}
</button>
)}
</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">
{item.benefits.map((subitem, index) => (
<li style={listItemStyle} key={index} className="m-1">
@@ -154,6 +207,38 @@ const PlanSubscriptions = () => {
))}
</ul>
</div>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
className="text-sm"
>
<hr
className={"border-[1px] border-gray-300 w-[20%]"}
style={{ color: "grey" }}
/>
<span style={{ color: "grey" }} className="px-2 ">
or
</span>
<hr
className={"border-[1px] border-gray-300 w-[20%]"}
style={{ color: "grey" }}
/>
</div>
<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>
)}
+3
View File
@@ -12,6 +12,7 @@ const Report = () => {
const [List, setList] = useState([]);
const [isLoader, setIsLoader] = useState(true);
const [reportName, setReportName] = useState("");
const [reporthelp, setReportHelp] = useState("");
const [actions, setActions] = useState([]);
const [heading, setHeading] = useState([]);
const [isNextRecord, setIsNextRecord] = useState(false);
@@ -53,6 +54,7 @@ const Report = () => {
setHeading(json.heading);
setReportName(json.reportName);
setForm(json.form);
setReportHelp(json?.helpMsg)
const currentUser = Parse.User.current().id;
const headers = {
@@ -156,6 +158,7 @@ const Report = () => {
isMoreDocs={isMoreDocs}
docPerPage={docPerPage}
form={form}
report_help={reporthelp}
/>
) : (
<PageNotFound prefix={"Report"} />
+60 -36
View File
@@ -1,17 +1,21 @@
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import Parse from "parse";
import axios from "axios";
import Title from "../components/Title";
import { fetchAppInfo, showTenantName } from "../redux/actions";
import { useNavigate, NavLink } from "react-router-dom";
import { useNavigate, NavLink, useLocation } from "react-router-dom";
import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize";
import Alert from "../primitives/Alert";
const Signup = (props) => {
import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import { isEnableSubscription } from "../constant/const";
const Signup = () => {
const { width } = useWindowSize();
const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [password, setPassword] = useState("");
@@ -29,7 +33,7 @@ const Signup = (props) => {
const [specialCharValid, setSpecialCharValid] = useState(false);
const togglePasswordVisibility = () => setShowPassword(!showPassword);
const image = props.appInfo.applogo;
const image = appInfo.applogo;
const clearStorage = async () => {
if (Parse.User.current()) {
@@ -65,6 +69,19 @@ const Signup = (props) => {
localStorage.setItem("baseUrl", baseUrl);
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) => {
event.preventDefault();
if (lengthValid && caseDigitValid && specialCharValid) {
@@ -90,7 +107,7 @@ const Signup = (props) => {
res
.then(async (r) => {
if (r) {
let roleData = props.appInfo.settings;
let roleData = appInfo.settings;
if (roleData && roleData.length > 0) {
const params = {
userDetails: {
@@ -99,7 +116,7 @@ const Signup = (props) => {
name: name,
email: email,
phone: phone,
role: props.appInfo.defaultRole
role: appInfo.defaultRole
}
};
try {
@@ -108,7 +125,13 @@ const Signup = (props) => {
params
);
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) {
alert(err.message);
@@ -153,7 +176,7 @@ const Signup = (props) => {
}
};
const handleNavigation = async (sessionToken) => {
const handleNavigation = async (sessionToken, isFreeplan = false) => {
const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
const res = await axios.get(baseUrl + "users/me", {
@@ -181,8 +204,8 @@ const Signup = (props) => {
// Check extended class user role and tenentId
try {
let userRoles = [];
if (props.appInfo.settings) {
let userSettings = props.appInfo.settings;
if (appInfo.settings) {
let userSettings = appInfo.settings;
//Get Current user roles
let url = `${baseUrl}functions/UserGroups`;
@@ -256,8 +279,8 @@ const Signup = (props) => {
}
});
if (tenentInfo.length) {
props.showTenantName(
tenentInfo[0].tenentName || ""
dispatch(
showTenant(tenentInfo[0].tenentName || "")
);
localStorage.setItem(
"TenantName",
@@ -293,15 +316,15 @@ const Signup = (props) => {
tenentName: x.TenantId.TenantName || ""
};
localStorage.setItem(
"TenetId",
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
}
});
if (tenentInfo.length) {
props.showTenantName(
tenentInfo[0].tenentName || ""
dispatch(
showTenant(tenentInfo[0].tenentName || "")
);
localStorage.setItem(
"TenantName",
@@ -321,8 +344,14 @@ const Signup = (props) => {
element.pageType
);
setState({ loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
navigate(`/subscription`, { replace: true });
if (isEnableSubscription) {
if (isFreeplan) {
navigate(
`/${element.pageType}/${element.pageId}`
);
} else {
navigate(`/subscription`, { replace: true });
}
} else {
alert("Registered user successfully");
navigate(
@@ -339,8 +368,14 @@ const Signup = (props) => {
);
localStorage.setItem("pageType", element.pageType);
setState({ loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
navigate(`/subscription`, { replace: true });
if (isEnableSubscription) {
if (isFreeplan) {
navigate(
`/${element.pageType}/${element.pageId}`
);
} else {
navigate(`/subscription`, { replace: true });
}
} else {
navigate(
`/${element.pageType}/${element.pageId}`
@@ -433,7 +468,7 @@ const Signup = (props) => {
}
};
useEffect(() => {
props.fetchAppInfo();
dispatch(fetchAppInfo());
// eslint-disable-next-line
}, []);
@@ -479,7 +514,7 @@ const Signup = (props) => {
<Title title={"Signup page"} />
{props.isloginVisible && props.isloginVisible ? (
{appInfo && appInfo.applogo ? (
<div>
<div className="md:m-10 lg:m-16 md:p-4 lg:p-10 p-5 bg-[#ffffff] md:border-[1px] md:border-gray-400 ">
<div className="w-[250px] h-[66px] inline-block">
@@ -622,7 +657,7 @@ const Signup = (props) => {
</button>
<NavLink
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" } : {}}
>
Login
@@ -657,15 +692,4 @@ const Signup = (props) => {
);
};
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
})(Signup);
export default Signup;
+37 -29
View File
@@ -3,7 +3,6 @@ import { PDFDocument } from "pdf-lib";
import "../styles/signature.css";
import { themeColor } from "../constant/const";
import axios from "axios";
import moment from "moment";
import Loader from "../primitives/LoaderWithMsg";
import loader from "../assets/images/loader2.gif";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
@@ -25,7 +24,9 @@ import {
onSaveSign,
contractUsers,
contactBook,
randomId
randomId,
getDate,
textWidget
} from "../constant/Utils";
import { useParams } from "react-router-dom";
import Tour from "reactour";
@@ -89,7 +90,7 @@ function SignYourSelf() {
const [showAlreadySignDoc, setShowAlreadySignDoc] = useState({
status: false
});
const [currWidgetsDetails, setCurrWidgetsDetails] = useState([]);
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
const [isCheckbox, setIsCheckbox] = useState(false);
const [widgetType, setWidgetType] = useState("");
const [pdfLoadFail, setPdfLoadFail] = useState({
@@ -98,6 +99,7 @@ function SignYourSelf() {
});
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isDontShow, setIsDontShow] = useState(false);
const [extUserId, setExtUserId] = useState("");
const divRef = useRef(null);
const nodeRef = useRef(null);
const [, drop] = useDrop({
@@ -179,14 +181,14 @@ function SignYourSelf() {
//function for get document details for perticular signer with signer'object id
const getDocumentDetails = async (showComplete) => {
let isCompleted;
//getting document details
const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) {
setPdfDetails(documentData);
const isCompleted =
documentData[0].IsCompleted && documentData[0].IsCompleted;
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
isCompleted = documentData[0].IsCompleted && documentData[0].IsCompleted;
if (isCompleted) {
const docStatus = {
isCompleted: isCompleted
@@ -262,8 +264,7 @@ function SignYourSelf() {
setSignerUserId(contractUsersRes[0].objectId);
const tourstatuss =
contractUsersRes[0].TourStatus && contractUsersRes[0].TourStatus;
if (tourstatuss && tourstatuss.length > 0) {
if (tourstatuss && tourstatuss.length > 0 && !isCompleted) {
setTourStatus(tourstatuss);
const checkTourRecipients = tourstatuss.filter(
(data) => data.signyourself
@@ -271,6 +272,8 @@ function SignYourSelf() {
if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].signyourself);
}
} else {
setCheckTourStatus(true);
}
const loadObj = {
isLoad: false
@@ -290,7 +293,8 @@ function SignYourSelf() {
const tourstatuss =
contractContactBook[0].TourStatus &&
contractContactBook[0].TourStatus;
if (tourstatuss && tourstatuss.length > 0) {
if (tourstatuss && tourstatuss.length > 0 && !isCompleted) {
setTourStatus(tourstatuss);
const checkTourRecipients = tourstatuss.filter(
(data) => data.signyourself
@@ -298,6 +302,8 @@ function SignYourSelf() {
if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].signyourself);
}
} else {
setCheckTourStatus(true);
}
} else {
setHandleError("No Data Found!");
@@ -308,13 +314,6 @@ function SignYourSelf() {
setIsLoading(loadObj);
}
};
const getDate = () => {
const date = new Date();
const milliseconds = date.getTime();
const newDate = moment(milliseconds).format("MM/DD/YYYY");
return newDate;
};
const getWidgetValue = (type) => {
switch (type) {
case "name":
@@ -333,6 +332,7 @@ function SignYourSelf() {
return "";
}
};
const addWidgetOptions = (type) => {
switch (type) {
case "signature":
@@ -347,8 +347,7 @@ function SignYourSelf() {
return {
name: "checkbox"
};
case "text":
case textWidget:
return {
name: "text"
};
@@ -386,7 +385,8 @@ function SignYourSelf() {
case "date":
return {
name: "date",
defaultValue: getDate()
response: getDate(),
validation: { format: "MM/dd/yyyy", type: "date-format" }
};
case "image":
return {
@@ -434,13 +434,13 @@ function SignYourSelf() {
Width: widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth
: dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).width
: "",
? defaultWidthHeight(dragTypeValue).width
: "",
Height: widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getHeight
: dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).height
: "",
? defaultWidthHeight(dragTypeValue).height
: "",
options: addWidgetOptions(dragTypeValue)
};
@@ -532,7 +532,6 @@ function SignYourSelf() {
checkSigned = requiredWidgets[i]?.options?.response;
if (!checkSigned) {
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
let checkDefaultSigned = requiredWidgets[i]?.options?.defaultValue;
if (!checkSignUrl) {
if (!checkDefaultSigned) {
@@ -591,12 +590,12 @@ function SignYourSelf() {
flag,
containerWH
);
// console.log('pdf',pdfBytes)
// console.log("pdf", pdfBytes);
//function for call to embed signature in pdf and get digital signature pdf
await signPdfFun(pdfBytes, documentId);
}
}
// console.log("signyourself", xyPostion);
//function for get digital signature
const signPdfFun = async (base64Url, documentId) => {
let singleSign = {
@@ -710,7 +709,7 @@ function SignYourSelf() {
};
//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 signatureImg = isDefaultSign
? isDefaultSign === "initials"
@@ -733,6 +732,7 @@ function SignYourSelf() {
}
}
const getUpdatePosition = onSaveSign(
type,
xyPostion,
index,
signKey,
@@ -769,6 +769,7 @@ function SignYourSelf() {
//function for delete signature block
const handleDeleteSign = (key) => {
setCurrWidgetsDetails({});
const updateResizeData = [];
let filterData = xyPostion[index].pos.filter((data) => data.key !== key);
@@ -875,7 +876,10 @@ function SignYourSelf() {
maxCount,
isReadOnly,
addOption,
deleteOption
deleteOption,
status,
defaultValue,
isHideLabel
) => {
const getPageNumer = xyPostion.filter(
(data) => data.pageNumber === pageNumber
@@ -907,7 +911,8 @@ function SignYourSelf() {
...position.options,
name: dropdownName,
values: dropdownOptions,
isReadOnly: isReadOnly
isReadOnly: isReadOnly,
isHideLabel: isHideLabel || false
}
};
}
@@ -1103,6 +1108,8 @@ function SignYourSelf() {
setIsInitial={setIsInitial}
setIsStamp={setIsStamp}
widgetType={widgetType}
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
/>
{/*render email component to send email after finish signature on document */}
<EmailComponent
@@ -1114,6 +1121,7 @@ function SignYourSelf() {
setSuccessEmail={setSuccessEmail}
sender={jsonSender}
setIsAlert={setIsAlert}
extUserId={extUserId}
/>
{/* pdf header which contain funish back button */}
<Header
+62 -15
View File
@@ -3,7 +3,7 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { useParams, useNavigate } from "react-router-dom";
import axios from "axios";
import "../styles/signature.css";
import { themeColor } from "../constant/const";
import { isEnableSubscription, themeColor } from "../constant/const";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd";
@@ -21,7 +21,9 @@ import {
addZIndex,
createDocument,
defaultWidthHeight,
addWidgetOptions
addWidgetOptions,
textInputWidget,
radioButtonWidget
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css";
@@ -33,6 +35,7 @@ import AddRoleModal from "../components/pdf/AddRoleModal";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import Parse from "parse";
const TemplatePlaceholder = () => {
const navigate = useNavigate();
const { templateId } = useParams();
@@ -50,6 +53,7 @@ const TemplatePlaceholder = () => {
const [isSelectListId, setIsSelectId] = useState();
const [isSendAlert, setIsSendAlert] = useState(false);
const [isCreateDocModal, setIsCreateDocModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isLoading, setIsLoading] = useState({
isLoad: true,
message: "This might take some time"
@@ -75,6 +79,7 @@ const TemplatePlaceholder = () => {
const [showDropdown, setShowDropdown] = useState(false);
const [widgetType, setWidgetType] = useState("");
const [isRadio, setIsRadio] = useState(false);
const [blockColor, setBlockColor] = useState("");
const [selectWidgetId, setSelectWidgetId] = useState("");
const [isNameModal, setIsNameModal] = useState(false);
const [pdfLoadFail, setPdfLoadFail] = useState({
@@ -165,7 +170,26 @@ const TemplatePlaceholder = () => {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current]);
async function checkIsSubscribed(email) {
const user = await Parse.Cloud.run("getUserDetails", {
email: email
});
const freeplan = user?.get("Plan") && user?.get("Plan").plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
return true;
} else if (billingDate) {
if (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
const fetchTemplate = async () => {
try {
@@ -187,6 +211,9 @@ const TemplatePlaceholder = () => {
: [];
if (documentData && documentData.length > 0) {
if (isEnableSubscription) {
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
}
setPdfDetails(documentData);
setIsSigners(true);
if (documentData[0].Signers && documentData[0].Signers.length > 0) {
@@ -221,6 +248,7 @@ const TemplatePlaceholder = () => {
});
setSignersData(updatedSigners);
setUniqueId(updatedSigners[0].Id);
setBlockColor(updatedSigners[0].blockColor);
} else {
const updatedSigners = documentData[0].Signers.map((x, index) => ({
...x,
@@ -229,6 +257,7 @@ const TemplatePlaceholder = () => {
}));
setSignersData(updatedSigners);
setUniqueId(updatedSigners[0].Id);
setBlockColor(updatedSigners[0].blockColor);
}
} else {
setRoleName("User 1");
@@ -247,6 +276,7 @@ const TemplatePlaceholder = () => {
setUniqueId(updatedSigners[0].Id);
setSignersData(updatedSigners);
setIsSelectId(0);
setBlockColor(updatedSigners[0].blockColor);
}
}
} else if (
@@ -442,10 +472,8 @@ const TemplatePlaceholder = () => {
setShowDropdown(true);
} else if (dragTypeValue === "checkbox") {
setIsCheckbox(true);
} else if (dragTypeValue === "radio") {
} else if (dragTypeValue === radioButtonWidget) {
setIsRadio(true);
} else if (dragTypeValue !== "label" && dragTypeValue !== "signature") {
setIsNameModal(true);
}
setCurrWidgetsDetails({});
setWidgetType(dragTypeValue);
@@ -854,6 +882,7 @@ const TemplatePlaceholder = () => {
setIsModalRole(false);
setRoleName("");
setUniqueId(Id);
setBlockColor(color[index]);
setIsSelectId(index);
setIsMailSend(false);
};
@@ -870,9 +899,11 @@ const TemplatePlaceholder = () => {
if (index === signersdata.length - 1) {
setUniqueId(updateSigner[updateSigner.length - 1]?.Id || "");
setIsSelectId(index - 1 || 0);
setBlockColor(color[index - 1 || 0]);
} else {
setUniqueId(updateSigner[index]?.Id || "");
setIsSelectId(index);
setBlockColor(color[index]);
}
setSignerPos(updatePlaceholderUser);
@@ -970,7 +1001,8 @@ const TemplatePlaceholder = () => {
addOption,
deleteOption,
status,
defaultValue
defaultValue,
isHideLabel
) => {
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
if (filterSignerPos.length > 0) {
@@ -986,11 +1018,10 @@ const TemplatePlaceholder = () => {
const getPosData = getXYdata;
const addSignPos = getPosData.map((position) => {
if (position.key === signKey) {
if (widgetType === "radio") {
if (widgetType === radioButtonWidget) {
if (addOption) {
return {
...position,
Height: position.Height
? position.Height + 15
: defaultWidthHeight(widgetType).height + 15
@@ -1010,7 +1041,9 @@ const TemplatePlaceholder = () => {
name: dropdownName,
values: dropdownOptions,
status: status,
defaultValue: defaultValue
defaultValue: defaultValue,
isReadOnly: isReadOnly || false,
isHideLabel: isHideLabel || false
}
};
}
@@ -1040,8 +1073,9 @@ const TemplatePlaceholder = () => {
minRequiredCount: minCount,
maxRequiredCount: maxCount
},
isReadOnly: isReadOnly,
defaultValue: defaultValue
isReadOnly: isReadOnly || false,
defaultValue: defaultValue,
isHideLabel: isHideLabel || false
}
};
}
@@ -1106,7 +1140,7 @@ const TemplatePlaceholder = () => {
const getPosData = getXYdata;
const addSignPos = getPosData.map((position) => {
if (position.key === signKey) {
if (position.type === "text") {
if (position.type === textInputWidget) {
return {
...position,
options: {
@@ -1305,7 +1339,7 @@ const TemplatePlaceholder = () => {
</div>
</ModalUi>
<DropdownWidgetOption
type="radio"
type={radioButtonWidget}
title="Radio group"
showDropdown={isRadio}
setShowDropdown={setIsRadio}
@@ -1313,6 +1347,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<DropdownWidgetOption
type="checkbox"
@@ -1323,6 +1358,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<DropdownWidgetOption
type="dropdown"
@@ -1333,6 +1369,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<PlaceholderCopy
isPageCopy={isPageCopy}
@@ -1440,6 +1477,10 @@ const TemplatePlaceholder = () => {
title={"Roles"}
initial={true}
isTemplateFlow={true}
sendInOrder={pdfDetails[0].SendinOrder}
setSignersData={setSignersData}
blockColor={blockColor}
setBlockColor={setBlockColor}
/>
</div>
) : (
@@ -1450,6 +1491,7 @@ const TemplatePlaceholder = () => {
className="autoSignScroll"
>
<SignerListPlace
isMailSend={isMailSend}
signerPos={signerPos}
signersdata={signersdata}
isSelectListId={isSelectListId}
@@ -1463,10 +1505,14 @@ const TemplatePlaceholder = () => {
handleRoleChange={handleRoleChange}
handleOnBlur={handleOnBlur}
title={"Roles"}
sendInOrder={pdfDetails[0]?.SendinOrder}
setSignersData={setSignersData}
blockColor={blockColor}
setBlockColor={setBlockColor}
/>
<div data-tut="reactourSecond">
<WidgetComponent
pdfUrl={isMailSend}
isMailSend={isMailSend}
dragSignature={dragSignature}
signRef={signRef}
handleDivClick={handleDivClick}
@@ -1523,6 +1569,7 @@ const TemplatePlaceholder = () => {
isOpen={isNameModal}
handleClose={handleNameModal}
handleData={handleWidgetdefaultdata}
isSubscribe={isSubscribe}
/>
</div>
);
+54 -10
View File
@@ -6,6 +6,11 @@ import dp from "../assets/images/dp.png";
import Title from "../components/Title";
import sanitizeFileName from "../primitives/sanitizeFileName";
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() {
const navigate = useNavigate();
@@ -19,15 +24,24 @@ function UserProfile() {
const [isLoader, setIsLoader] = useState(false);
const [percentage, setpercentage] = useState(0);
const [isDisableDocId, setIsDisableDocId] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => {
getUserDetail();
}, []);
const getUserDetail = async () => {
const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass);
const HeaderDocId = jsonSender[0]?.HeaderDocId;
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
if (HeaderDocId) {
setIsDisableDocId(HeaderDocId);
}
}, []);
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoader(true);
@@ -135,7 +149,8 @@ function UserProfile() {
setImage(response.url());
localStorage.setItem("profileImg", response.url());
setpercentage(0);
SaveFileSize(size, response.url());
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
} catch (error) {
@@ -153,7 +168,6 @@ function UserProfile() {
const handleDisableDocId = () => {
setIsDisableDocId((prevChecked) => !prevChecked);
};
return (
<React.Fragment>
<Title title={"Profile"} />
@@ -250,7 +264,7 @@ function UserProfile() {
<span className="font-semibold">Email:</span>{" "}
<span>{UserProfile && UserProfile.email}</span>
</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 py-2 break-all">
<span className="font-semibold">Is Email verified:</span>{" "}
<span>
{UserProfile && UserProfile.emailVerified
@@ -258,10 +272,33 @@ function UserProfile() {
: "Not verified"}
</span>
</li>
<li className="flex justify-between items-center border-y-[1px] border-gray-300 py-2 break-all">
<span className="font-semibold">Disable DocumentId :</span>{" "}
<span>
<label className="relative inline-flex items-center cursor-pointer mb-0">
<li className="border-y-[1px] border-gray-300 break-all">
<div className="flex justify-between items-center py-2">
<span
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
disabled={editmode ? false : true}
checked={isDisableDocId}
@@ -270,9 +307,16 @@ function UserProfile() {
value=""
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>
</span>
</div>
{!isEnableSubscription && (
<PremiumAlertHeader
message={
"Disable documentId is free in beta, this feature will incur a fee later."
}
/>
)}
</li>
</ul>
<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 Title from "../components/Title";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi";
import { rejectBtn, submitBtn } from "../constant/const";
import { openInNewTab } from "../constant/Utils";
import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import Parse from "parse";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
function Webhook() {
const navigation = useNavigate();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [webhook, setWebhook] = useState();
@@ -15,6 +19,8 @@ function Webhook() {
const [isGenerate, setIsGenerate] = useState(false);
const [isErr, setIsErr] = useState(false);
const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
fetchWebhook();
@@ -23,6 +29,10 @@ function Webhook() {
const fetchWebhook = async () => {
const email = Parse.User.current().getEmail();
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
const params = { email: email };
try {
const extRes = await Parse.Cloud.run("getUserDetails", params);
@@ -38,41 +48,48 @@ function Webhook() {
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoader(true);
setIsModal(false);
try {
const params = { url: webhook };
const url = parseBaseUrl + "functions/savewebhook";
const headers = {
"Content-Type": "application/json",
"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) {
setWebhook(res.data.result.Webhook);
setIsGenerate(true);
setTimeout(() => {
setIsGenerate(false);
}, 1500);
setIsLoader(false);
} else {
console.error("Error while generating webhook");
setIsLoader(false);
setIsErr(true);
setTimeout(() => {
setIsErr(false);
}, 1500);
}
});
} catch (error) {
setIsLoader(false);
setIsErr(true);
setTimeout(() => {
setIsErr(false);
}, 1500);
if (webhook && webhook.startsWith("https://")) {
setIsLoader(true);
setIsModal(false);
try {
const params = { url: webhook };
const url = parseBaseUrl + "functions/savewebhook";
const headers = {
"Content-Type": "application/json",
"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) {
setWebhook(res.data.result.Webhook);
setIsGenerate(true);
setTimeout(() => {
setIsGenerate(false);
}, 1500);
setIsLoader(false);
} else {
console.error("Error while generating webhook");
setIsLoader(false);
setIsErr(true);
setTimeout(() => {
setIsErr(false);
}, 1500);
}
});
} catch (error) {
setIsLoader(false);
setIsErr(true);
setTimeout(() => {
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"} />
{isGenerate && <Alert type="success">Webhook added successfully!</Alert>}
{isErr && <Alert type="danger">Something went wrong!</Alert>}
{isLoader ? (
<div
style={{
@@ -101,25 +119,41 @@ function Webhook() {
</div>
) : (
<div className="bg-white flex flex-col justify-center shadow rounded">
<h1 className="ml-4 mt-3 mb-2 font-semibold">Webhook</h1>
<ul className="w-full flex flex-col p-2 text-sm">
{!isEnableSubscription && <PremiumAlertHeader />}
<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
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>{" "}
<span id="token" className="w-[60%] md:text-end cursor-pointer">
{webhook && webhook}
</span>
<div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
<span className="">Webhook:</span>{" "}
<span id="token" className=" md:text-end cursor-pointer">
{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>
</ul>
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
<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>
<div className="flex items-center justify-center ">
<button
type="button"
onClick={() =>
@@ -127,17 +161,47 @@ function 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
</button>
</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
isOpen={isModal}
title={"Regenerate Token"}
handleClose={handleModal}
>
{error && <Alert type="danger">{error}</Alert>}
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
<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("UserRole", "contracts_Guest");
if (localStorage.getItem("TenetId")) {
if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenetId")
objectId: localStorage.getItem("TenantId")
});
}
@@ -6,6 +6,7 @@ import ModalUi from "./ModalUi";
import AddSigner from "../components/AddSigner";
import { modalSubmitBtnColor, modalCancelBtnColor } from "../constant/const";
import Alert from "./Alert";
import Tooltip from "./Tooltip";
const ReportTable = ({
ReportName,
List,
@@ -15,7 +16,8 @@ const ReportTable = ({
setIsNextRecord,
isMoreDocs,
docPerPage,
form
form,
report_help
}) => {
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(1);
@@ -255,7 +257,14 @@ const ReportTable = ({
</Alert>
)}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<div className="font-light">{ReportName}</div>
<div className="font-light">
{ReportName}{" "}
{report_help && (
<span className="text-xs md:text-[13px] font-normal">
<Tooltip message={report_help} />
</span>
)}
</div>
{ReportName === "Templates" && (
<i
onClick={() => navigate("/form/template")}
+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;
@@ -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;
+52
View File
@@ -0,0 +1,52 @@
import React from "react";
import { Tooltip as ReactTooltip } from "react-tooltip";
import { openInNewTab } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const";
const Tooltip = ({ id, message, url, iconColor, isSubscribe }) =>
url ? (
<button
onClick={() => openInNewTab(url)}
className={
isSubscribe || !isEnableSubscription
? "text-center"
: "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;
+19
View File
@@ -0,0 +1,19 @@
import React from "react";
import { useNavigate } from "react-router-dom";
function Upgrade() {
const navigation = useNavigate();
return (
<sup>
<span
onClick={() => navigation("/subscription")}
className="text-blue-800 text-sm cursor-pointer hover:underline"
>
Upgrade now
</span>
</sup>
);
}
export default Upgrade;
+2
View File
@@ -24,7 +24,9 @@ const Validate = () => {
setIsUserValid(false);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleLoginBtn = () => {
try {
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) => {
switch (action.type) {
case "SHOW_TENANT":
import { createSlice } from "@reduxjs/toolkit";
const showTenantSlice = createSlice({
name: "showTenant",
initialState: "",
reducers: {
showTenant: (state, action) => {
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) => {
switch (action.type) {
case "SAVE_TOURSTEPS":
import { createSlice } from "@reduxjs/toolkit";
const tourStepSlice = createSlice({
name: "tourStep",
initialState: [],
reducers: {
saveTourSteps: (state, action) => {
return action.payload;
case "REMOVE_TOURSTEPS":
},
removeTourSteps: () => {
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 infoReducer from "./infoReducer";
import loginReducer from "./loginReducer";
import ShowTenant from "./ShowTenant";
import TourStepsReducer from "./TourStepsReducer";
export default combineReducers({
appInfo: infoReducer,
login: loginReducer,
ShowTenant,
TourSteps: TourStepsReducer
TourSteps: TourStepsReducer,
ShowTenant
});
@@ -1,9 +1,24 @@
const infoReducer = (state = {}, action) => {
switch (action.type) {
case "FATCH_APPINFO":
return action.payload;
default:
return state;
import { createSlice } from "@reduxjs/toolkit";
import { appInfo } from "../../constant/appinfo";
const infoSlice = createSlice({
name: "info",
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 thunk from "redux-thunk";
import reducers from "./reducers";
// import { createStore, applyMiddleware } from "redux";
// import thunk from "redux-thunk";
// 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
}
});
+85
View File
@@ -60,6 +60,7 @@
color: #ffffff;
outline: none;
}
/* For classes: bg-[#188ae2] text-sm text-white px-4 py-2 rounded ml-2 shadow focus:outline-none */
.resetbutton {
background-color: #188ae2;
@@ -75,3 +76,87 @@
outline: none;
margin-left: 8px;
}
.validationlist {
position: absolute;
left: 0;
z-index: 1;
appearance: none;
-moz-appearance: "none";
-webkit-appearance: "none";
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAnUlEQVR4nO2QMQrCQBBFX51NbWMp2uox7Gw8h22uYEguIZNKyB0lMIElTNzdGBvdB8PC8Gces5DJ/Cxn4Aa4QM5pbsgncQF6rQYoZ3IFcPey11jByRsaqzYuctqfZo8xki3QBURuRtDpfBQ74GEsaYHN5It8wYFEhgExlj2NniwRhC7qP70gRSRrCN6JZE2BJZJvCEb2QKVvJvMvvAD2WzaK/35kGAAAAABJRU5ErkJggg==");
background-repeat: no-repeat;
background-position: right 0.7rem top 50%;
background-size: 1rem auto;
}
@media (max-width: 375px) {
.validationlist {
background-position: right 0.5rem top 50%;
}
}
@media (min-width:375px) and (max-width: 767px) {
.validationlist {
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;
}
.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 {
bottom: 70px;
left: 50px;
+24 -15
View File
@@ -15,15 +15,8 @@
border-width: 0.2px;
}
.dropdownContainer {
display: flex;
flex-direction: column;
overflow-y: scroll;
max-height: 250px;
}
.dropdownContainer::-webkit-scrollbar {
display: none;
.dragCursor {
cursor: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAASElEQVR4nGNgwAMkJSUbpKSkOvCpIaT5PxSTbogUQjMYMwxeIIXmVFIxA8UGDDyQGg0DnIDi6JKUlCxHMqCeZAOghjSAMD5FAKfeaURdUFxCAAAAAElFTkSuQmCC"), auto;
}
.react-datepicker__input-container {
@@ -163,7 +156,6 @@
.signatureBtn {
border: 1.5px solid #47a3ad;
border-radius: 3px;
display: flex;
padding: 0px;
@@ -504,7 +496,11 @@
overflow: hidden !important;
text-overflow: ellipsis;
}
.disabled {
opacity: 0.5; /* Example: reduce opacity to visually indicate disabled state */
pointer-events: none; /* Prevents mouse clicks and other events */
/* background-color: #888; */
}
.useEmail {
font-size: 10px;
color: #424242;
@@ -838,10 +834,18 @@ option {
justify-content: flex-start;
}
.labelTextArea {
font-size: 12px;
.labelWidthDesktop {
min-width: 100%;
min-height: 100%;
}
.labelWidthMobile {
width: 100%;
height: 100%;
}
.labelTextArea {
font-size: 12px;
border: 1px solid #007bff;
outline: none;
z-index: 999;
@@ -945,7 +949,7 @@ option {
to prevent sudden quick movement (as the
navigation bar gets a new position at the top of the
page (position:fixed and top:0) */
.stickyHead + .content {
.stickyHead+.content {
padding-top: 60px;
}
@@ -1174,6 +1178,7 @@ option {
font-size: 12px;
margin-left: 5px;
}
.defaultOptions:focus {
outline: none;
}
@@ -1193,6 +1198,10 @@ option {
overflow-y: auto;
}
.signatureBtn {
width: max-content;
}
.dropdownModal {
position: fixed;
top: 50%;
@@ -1222,4 +1231,4 @@ option {
min-width: 78%;
overflow-y: auto;
}
}
}
+112
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() {
const url = new URL(process.env.SERVER_URL);
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
@@ -38,3 +42,111 @@ export function replaceMailVaribles(subject, body, variables) {
};
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);
}
};
@@ -284,6 +284,7 @@ export default async function createDocumentWithTemplate(request, response) {
subject: subject,
from: sender,
html: html,
extUserId: template.ExtUserPtr.objectId,
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
@@ -1,5 +1,5 @@
import axios from 'axios';
import { color, customAPIurl, replaceMailVaribles } from '../../../../Utils.js';
import { color, customAPIurl, replaceMailVaribles, saveFileUsage } from '../../../../Utils.js';
// `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) {
@@ -8,6 +8,7 @@ async function sendDoctoWebhook(doc, WebhookUrl, userId) {
event: 'created',
...doc,
};
await axios
.post(WebhookUrl, params, {
headers: { 'Content-Type': 'application/json' },
@@ -65,6 +66,7 @@ export default async function createDocumentwithCoordinate(request, response) {
if (!reqToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
}
try {
const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', reqToken);
@@ -81,17 +83,22 @@ export default async function createDocumentwithCoordinate(request, response) {
if (signers && signers.length > 0) {
let fileUrl;
if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'),
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const file = new Parse.File(`${name}.pdf`, {
base64: base64File,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
@@ -165,29 +172,26 @@ export default async function createDocumentwithCoordinate(request, response) {
'Signers',
contact?.map(x => x.contactPtr)
);
let updatePlaceholders = contact.map((signer, index) => {
let updatePlaceholders = contact.map(signer => {
const placeHolder = [];
for (const widget of signer.widgets) {
const pageNumber = widget.page;
const page = placeHolder.find(page => page.pageNumber === pageNumber);
const signOpt = { name: 'signature', status: 'required' };
const widgetData = {
xPosition: widget.x,
yPosition: widget.y,
isStamp: widget.type === 'stamp',
key: randomId(),
isDrag: false,
firstXPos: widget.x,
firstYPos: widget.y,
yBottom: 0,
scale: 1,
isMobile: false,
zIndex: 1,
type: widget.type,
widgetValue: '',
options: widget.type === 'signature' ? signOpt : widget.options,
Width: widget.w,
Height: widget.h,
xPosition: widget.x,
yPosition: widget.y,
};
if (page) {
@@ -245,7 +249,6 @@ export default async function createDocumentwithCoordinate(request, response) {
year: 'numeric',
});
let sender = parseExtUser.Email;
let sendMail;
const serverUrl = process.env.SERVER_URL;
const newServer = serverUrl.replaceAll('/', '%2F');
const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`;
@@ -333,9 +336,10 @@ export default async function createDocumentwithCoordinate(request, response) {
subject: subject,
from: sender,
html: html,
extUserId: extUser.id,
};
sendMail = await axios.post(url, params, { headers: headers });
await axios.post(url, params, { headers: headers });
} catch (error) {
console.log('error', error);
}
@@ -1,6 +1,4 @@
import { customAPIurl } from '../../../../Utils.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000);
import { saveFileUsage } from '../../../../Utils.js';
export default async function createTemplate(request, response) {
const name = request.body?.title;
const note = request.body?.note;
@@ -36,12 +34,17 @@ export default async function createTemplate(request, response) {
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'),
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
@@ -123,7 +126,7 @@ export default async function createTemplate(request, response) {
}
return response.json({
objectId: res.id,
url: baseUrl.origin + '/load/signmicroapp/template/' + res.id,
url: baseUrl.origin + '/template/' + res.id,
});
} else {
return response.status(405).json({ error: 'Invalid API Token!' });
@@ -1,5 +1,5 @@
import axios from 'axios';
import { color, customAPIurl } from '../../../../Utils.js';
import { color, customAPIurl, saveFileUsage } from '../../../../Utils.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function createTemplatewithCoordinate(request, response) {
@@ -34,17 +34,22 @@ export default async function createTemplatewithCoordinate(request, response) {
if (signers && signers.length > 0) {
let fileUrl;
if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'),
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const file = new Parse.File(`${name}.pdf`, {
base64: base64File,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
@@ -129,23 +134,20 @@ export default async function createTemplatewithCoordinate(request, response) {
for (const widget of signer.widgets) {
const pageNumber = widget.page;
const page = placeHolder.find(page => page.pageNumber === pageNumber);
const signOpt = { name: 'signature', status: 'required' };
const widgetData = {
xPosition: widget.x,
yPosition: widget.y,
isStamp: widget.type === 'stamp',
key: randomId(),
isDrag: false,
firstXPos: widget.x,
firstYPos: widget.y,
yBottom: 0,
scale: 1,
isMobile: false,
zIndex: 1,
type: widget.type,
widgetValue: '',
options: widget.type === 'signature' ? signOpt : widget.options,
Width: widget.w,
Height: widget.h,
xPosition: widget.x,
yPosition: widget.y,
};
if (page) {
@@ -14,6 +14,7 @@ export default async function draftDocument(request, response) {
const SendinOrder = request.body.sendInOrder || false;
// console.log('fileData ', fileData);
const protocol = customAPIurl();
const baseUrl = new URL(process.env.SERVER_URL);
try {
const reqToken = request.headers['x-api-token'];
@@ -35,17 +36,22 @@ export default async function draftDocument(request, response) {
if (signers && signers.length > 0) {
let fileUrl;
if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'),
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const file = new Parse.File(`${name}.pdf`, {
base64: base64File,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
@@ -135,7 +141,7 @@ export default async function draftDocument(request, response) {
}
return response.json({
objectId: res.id,
url: protocol + '/load/signmicroapp/placeholdersign/' + res.id,
url: `${baseUrl.origin}/placeholdersign/${res.id}`,
});
} else {
if (request.posthog) {
+8 -4
View File
@@ -1,5 +1,5 @@
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 ZohoDetails from './parsefunction/ZohoDetails.js';
import usersignup from './parsefunction/usersignup.js';
@@ -10,7 +10,6 @@ import DocumentAftersave from './parsefunction/DocumentAftersave.js';
import ContactbookAftersave from './parsefunction/ContactBookAftersave.js';
import ContractUsersAftersave from './parsefunction/ContractUsersAftersave.js';
import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js';
import SendMailv1 from './parsefunction/SendMailv1.js';
import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js';
import getUserId from './parsefunction/getUserId.js';
import getUserDetails from './parsefunction/getUserDetails.js';
@@ -23,17 +22,19 @@ import TemplateAfterSave from './parsefunction/TemplateAfterSave.js';
import GetTemplate from './parsefunction/GetTemplate.js';
import savewebhook from './parsefunction/saveWebhook.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';
Parse.Cloud.define('AddUserToRole', addUserToGroups);
Parse.Cloud.define('UserGroups', getUserGroups);
Parse.Cloud.define('signPdf', PDF);
Parse.Cloud.define('sendmailv3', sendmail);
Parse.Cloud.define('sendmailv3', sendmailv3);
Parse.Cloud.define('googlesign', GoogleSign);
Parse.Cloud.define('zohodetails', ZohoDetails);
Parse.Cloud.define('usersignup', usersignup);
Parse.Cloud.define('facebooksign', FacebookSign);
Parse.Cloud.define('SendOTPMailV1', sendMailOTPv1);
Parse.Cloud.define('sendmail', SendMailv1);
Parse.Cloud.define('AuthLoginAsMail', AuthLoginAsMail);
Parse.Cloud.define('getUserId', getUserId);
Parse.Cloud.define('getUserDetails', getUserDetails);
@@ -45,7 +46,10 @@ Parse.Cloud.define('getapitoken', getapitoken);
Parse.Cloud.define('getTemplate', GetTemplate);
Parse.Cloud.define('savewebhook', savewebhook);
Parse.Cloud.define('callwebhook', callWebhook);
Parse.Cloud.define('freesubscription', SubscribeFree);
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave);
Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave);
Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave);
Parse.Cloud.beforeSave('contracts_Document', DocumentBeforesave);
Parse.Cloud.beforeSave('contracts_Template', TemplateBeforeSave);
@@ -0,0 +1,35 @@
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')) {
const 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) {
try {
//--for elearning app side
let code = Math.floor(1000 + Math.random() * 9000);
let email = request.params.email;
var TenantId = request.params.TenantId ? request.params.TenantId : undefined;
// console.log("In tempSendOTPv2");
// console.log(JSON.stringify(request));
if (email) {
axios({
method: 'POST',
url: process.env.SERVER_URL + '/functions/sendmail',
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': process.env.APP_ID,
},
params: {
otp: code,
email: email,
TenantId: TenantId,
},
}).then(
function (httpResponse) {},
function (httpResponse) {
console.error('sms Request failed with response code ' + httpResponse.status);
return Promise.reject('sms Request failed with response code ' + httpResponse.status);
const recipient = request.params.email;
const mailsender = process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER;
try {
await Parse.Cloud.sendEmail({
from: 'Opensign™' + ' <' + mailsender + '>',
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'>" +
code +
'</p></div> </div> </div></body></html>',
});
console.log('OTP sent');
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');
tempOtp.equalTo('Email', email);
const resultOTP = await tempOtp.first({ useMasterKey: true });
console.log('resultOTP', resultOTP);
// console.log('resultOTP', resultOTP);
if (resultOTP !== undefined) {
const updateOtpQuery = new Parse.Query('defaultdata_Otp');
const updateOtp = await updateOtpQuery.get(resultOTP.id, {
useMasterKey: true,
});
updateOtp.set('OTP', code);
const updateRes = updateOtp.save(null, { useMasterKey: true });
updateOtp.save(null, { useMasterKey: true });
// console.log("update otp Res in tempSendOtp ", updateRes);
} else {
const otpClass = Parse.Object.extend('defaultdata_Otp');
@@ -49,17 +67,17 @@ async function sendMailOTPv1(request) {
newOtpQuery.set('OTP', code);
newOtpQuery.set('Email', email);
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);
}
return 'Otp send';
} else {
return Promise.reject('Please Enter valid email');
return 'Please Enter valid email';
}
} catch (err) {
console.log('err in sendMailOTPv1');
console.log(err);
return Promise.reject(err);
return err;
}
}
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;
@@ -0,0 +1,43 @@
export default async function SubscribeFree(request) {
const userId = request.params.userId;
const userPtr = { __type: 'Pointer', className: '_User', objectId: userId };
try {
const extQuery = new Parse.Query('contracts_Users');
extQuery.equalTo('UserId', userPtr);
const extUser = await extQuery.first({ useMasterKey: true });
if (extUser) {
if (extUser?.get('Plan')?.plan_code === 'freeplan') {
return { status: 'success', result: 'already subscribed!' };
} else if (extUser?.get('Next_billing_date') < new Date()) {
try {
const extUpdate = new Parse.Object('contracts_Users');
extUpdate.id = extUser.id;
extUpdate.set('Plan', { plan_code: 'freeplan' });
await extUpdate.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
}
} else if (extUser?.get('Next_billing_date') > new Date()) {
return { status: 'success', result: 'already subscribed!' };
} else {
try {
const extUpdate = new Parse.Object('contracts_Users');
extUpdate.id = extUser.id;
extUpdate.set('Plan', { plan_code: 'freeplan' });
await extUpdate.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
}
}
} else {
return { status: 'error', result: 'User not found!' };
}
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
}
}
@@ -0,0 +1,31 @@
async function TemplateBeforeSave(request) {
try {
if (!request.original) {
// below code is used to update template when user sent template or self signed
const template = request.object;
// Update count in contracts_Users class
const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', template.get('ExtUserPtr').id);
try {
const contractUser = await query.first({ useMasterKey: true });
if (contractUser) {
contractUser.increment('TemplateCount', 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('TemplateCount', 1);
await newContractUser.save(null, { useMasterKey: true });
}
} catch (error) {
console.log('Error updating template count in contracts_users: ' + error.message);
}
}
} catch (err) {
console.log('err in template beforesave', err.message);
}
}
export default TemplateBeforeSave;
@@ -2,7 +2,8 @@ import axios from 'axios';
export default async function getDrive(request) {
const serverUrl = process.env.SERVER_URL;
const appId = process.env.APP_ID;
const limit = request.params.limit;
const skip = request.params.skip;
const classUrl = serverUrl + '/classes/contracts_Document';
const docId = request.params.docId;
try {
@@ -16,9 +17,9 @@ export default async function getDrive(request) {
if (userId) {
let url;
if (docId) {
url = `${classUrl}?where={"Folder":{"__type":"Pointer","className":"contracts_Document","objectId":"${docId}"},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"${userId}"},"IsArchive":{"$ne":true}}&include=ExtUserPtr,Signers,Folder`;
url = `${classUrl}?where={"Folder":{"__type":"Pointer","className":"contracts_Document","objectId":"${docId}"},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"${userId}"},"IsArchive":{"$ne":true}}&include=ExtUserPtr,Signers,Folder&skip=${skip}&limit=${limit}`;
} else {
url = `${classUrl}?where={"Folder":{"$exists":false},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"${userId}"},"IsArchive":{"$ne":true}}&include=ExtUserPtr,Signers`;
url = `${classUrl}?where={"Folder":{"$exists":false},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"${userId}"},"IsArchive":{"$ne":true}}&include=ExtUserPtr,Signers&skip=${skip}&limit=${limit}`;
}
try {
const res = await axios.get(url, {
+107 -123
View File
@@ -1,7 +1,9 @@
import SignPDF from './SignPDF.min.cjs';
import fs from 'node:fs';
import axios from 'axios';
import { plainAddPlaceholder } from 'node-signpdf/dist/helpers/index.js';
import { pdflibAddPlaceholder } from './customSignPdf/pdflibplaceholder.min.js';
import { PDFDocument } from 'pdf-lib';
import { saveFileUsage } from '../../../Utils.js';
const serverUrl = process.env.SERVER_URL,
APPID = process.env.APP_ID,
masterKEY = process.env.MASTER_KEY;
@@ -15,20 +17,20 @@ async function uploadFile(e, a) {
console.log('Err ', e), fs.unlinkSync(a);
}
}
async function updateDoc(t, s, r, i, n, o) {
async function updateDoc(t, s, r, o, i, n) {
try {
var d = {
UserPtr: { __type: 'Pointer', className: o, objectId: r },
UserPtr: { __type: 'Pointer', className: n, objectId: r },
SignedUrl: s,
Activity: 'Signed',
ipAddress: i,
ipAddress: o,
};
let e;
var l = (e = n.AuditTrail && 0 < n.AuditTrail.length ? [...n.AuditTrail, d] : [d]).filter(
var l = (e = i.AuditTrail && 0 < i.AuditTrail.length ? [...i.AuditTrail, d] : [d]).filter(
e => 'Signed' === e.Activity
);
let a = !1;
!((n.Signers && 0 < n.Signers.length && l.length !== n.Signers.length) || !(a = !0));
!((i.Signers && 0 < i.Signers.length && l.length !== i.Signers.length) || !(a = !0));
var c = { SignedUrl: s, AuditTrail: e, IsCompleted: a };
await axios.put(serverUrl + '/classes/contracts_Document/' + t, c, {
headers: {
@@ -42,39 +44,16 @@ async function updateDoc(t, s, r, i, n, o) {
return console.log('update doc err ', e), 'err';
}
}
async function sendMail(e) {
var a = e.url,
t = e.sender,
s = e.pdfName,
a = {
url: a,
from: 'OpenSign™',
recipient: e.receiver,
subject: 'You have signed the doc - ' + s,
pdfName: s,
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=https://qikinnovation.ams3.digitaloceanspaces.com/logo.png height='50' style='padding:20px'/> </div><div style='padding:2px;font-family:system-ui; background-color: #47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document Copy</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document " +
s +
' Standard is attached to this email. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' +
t.Mail +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>',
};
await axios.post(serverUrl + '/functions/sendmailv3', a, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
}
async function sendCompletedMail(e) {
var a = e.url,
t = e.sender,
s = e.pdfName,
a = {
r = e.receiver,
e = {
extUserId: e.extUserId,
url: a,
from: 'OpenSign™',
recipient: e.receiver,
recipient: r,
subject: `Document ${s} has been signed by all parties`,
pdfName: s,
html:
@@ -84,7 +63,7 @@ async function sendCompletedMail(e) {
t.Mail +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>',
};
await axios.post(serverUrl + '/functions/sendmailv3', a, {
await axios.post(serverUrl + '/functions/sendmailv3', e, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
@@ -150,12 +129,11 @@ async function sendDoctoWebhook(t, e, a, s) {
}
}));
}
async function PDF(i, n) {
async function PDF(i) {
try {
i.params.sign;
var e = i.params.docId,
a = i.params.userId,
o = await axios.get(
n = await axios.get(
serverUrl + '/classes/contracts_Document/' + e + '?include=ExtUserPtr,Signers',
{
headers: {
@@ -165,133 +143,139 @@ async function PDF(i, n) {
},
}
),
t = await axios.get(serverUrl + '/users/me', {
d = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken,
},
});
if (!t.data || !t.data.objectId) return { status: 'error', message: 'this user not allowed!' };
if (!d.data || !d.data.objectId) return { status: 'error', message: 'This user not allowed!' };
{
var d,
var t,
s,
l,
c,
p = JSON.stringify({ objectId: a });
let s, r;
r = a
? (d = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + p, {
c = JSON.stringify({ objectId: a });
let r, o;
o = a
? (t = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + c, {
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken,
},
})).data && 0 < d.data.results.length
? ((s = d), 'contracts_Contactbook')
: ((s = await axios.get(serverUrl + '/classes/contracts_Users?where=' + p, {
})).data && 0 < t.data.results.length
? ((r = t), 'contracts_Contactbook')
: ((r = await axios.get(serverUrl + '/classes/contracts_Users?where=' + c, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
})),
'contracts_Users')
: ((l = JSON.stringify({
UserId: { __type: 'Pointer', className: '_User', objectId: t.data.objectId },
: ((s = JSON.stringify({
UserId: { __type: 'Pointer', className: '_User', objectId: d.data.objectId },
})),
(c = await axios.get(serverUrl + '/classes/contracts_Users?where=' + l, {
(l = await axios.get(serverUrl + '/classes/contracts_Users?where=' + s, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
})).data && 0 < c.data.results.length
? ((s = c), 'contracts_Users')
: ((s = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + l, {
})).data && 0 < l.data.results.length
? ((r = l), 'contracts_Users')
: ((r = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + s, {
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken,
},
})),
'contracts_Contactbook'));
var m = s.data.results[0].Name,
g = s.data.results[0].Email;
var p = r.data.results[0].Name,
m = r.data.results[0].Email;
if (!i.params.pdfFile) return { status: 'error', message: 'pdf file not present!' };
{
let e = Buffer.from(i.params.pdfFile, 'base64');
var h = process.env.PFX_BASE64,
u = Buffer.from(h, 'base64'),
f = {
UserPtr: { __type: 'Pointer', className: r, objectId: s.data.results[0].objectId },
var g = process.env.PFX_BASE64,
u = Buffer.from(g, 'base64'),
h = {
UserPtr: { __type: 'Pointer', className: o, objectId: r.data.results[0].objectId },
SignedUrl: '',
Activity: 'Signed',
ipAddress: i.headers['x-real-ip'],
};
let a;
var y = (a =
o.data.AuditTrail && 0 < o.data.AuditTrail.length
? [...o.data.AuditTrail, f]
: [f]).filter(e => 'Signed' === e.Activity);
var f = (a =
n.data.AuditTrail && 0 < n.data.AuditTrail.length
? [...n.data.AuditTrail, h]
: [h]).filter(e => 'Signed' === e.Activity);
let t = !1;
!(
(o.data.Signers && 0 < o.data.Signers.length && y.length !== o.data.Signers.length) ||
(n.data.Signers && 0 < n.data.Signers.length && f.length !== n.data.Signers.length) ||
!(t = !0)
);
var v,
P,
b = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
x = './exports/' + b,
U =
(t
? ((v = o.data.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
(e =
v && 0 < v.length
? plainAddPlaceholder({
pdfBuffer: e,
reason: 'Digitally signed by OpenSign for ' + v?.join(', '),
location: 'location',
signatureLength: 1e4,
})
: plainAddPlaceholder({
pdfBuffer: e,
reason: 'Digitally signed by OpenSign for ' + m + ' <' + g + '>',
location: 'location',
signatureLength: 1e4,
})),
(P = await new SignPDF(e, u).signPDF()),
fs.writeFileSync(x, P))
: fs.writeFileSync(x, e),
await uploadFile(b, x));
if (U && U.imageUrl) {
const n = await updateDoc(
i.params.docId,
U.imageUrl,
s.data.results[0].objectId,
i.headers['x-real-ip'],
o.data,
r
);
var I,
w = {
url: U.imageUrl,
sender: { Mail: o.data.ExtUserPtr.Email, Name: o.data.ExtUserPtr.Name },
pdfName: o.data.Name,
receiver: g,
};
var P,
y,
v,
U,
b,
I,
S = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
x = './exports/' + S;
let s = e.length;
s = (
t
? ((P = n.data.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
(e =
P && 0 < P.length
? ((y = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: y,
reason: 'Digitally signed by OpenSign for ' + P?.join(', '),
location: 'location',
signatureLength: 15e3,
}),
(v = await y.save()),
Buffer.from(v))
: ((U = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: U,
reason: 'Digitally signed by OpenSign for ' + p + ' <' + m + '>',
location: 'location',
signatureLength: 15e3,
}),
(b = await U.save()),
Buffer.from(b))),
(I = await new SignPDF(e, u).signPDF()),
fs.writeFileSync(x, I),
I)
: (fs.writeFileSync(x, e), e)
).length;
var A,
w,
D = await uploadFile(S, x);
if (D && D.imageUrl)
return (
o.data.IsSendMail && !1 === o.data.IsSendMail
? console.log("don't send mail")
: sendMail(w),
sendDoctoWebhook(o, U.imageUrl, 'signed', s?.data.results?.[0]),
n &&
n.isCompleted &&
((I = {
url: U.imageUrl,
sender: { Mail: o.data.ExtUserPtr.Email, Name: 'OpenSign™' },
pdfName: o.data.Name,
receiver: o.data.ExtUserPtr.Email,
(A = await updateDoc(
i.params.docId,
D.imageUrl,
r.data.results[0].objectId,
i.headers['x-real-ip'],
n.data,
o
)),
sendDoctoWebhook(n, D.imageUrl, 'signed', r?.data.results?.[0]),
saveFileUsage(s, D.imageUrl, d.data.objectId),
A &&
A.isCompleted &&
((w = {
url: D.imageUrl,
sender: { Mail: n.data.ExtUserPtr.Email, Name: 'OpenSign™' },
pdfName: n.data.Name,
receiver: n.data.ExtUserPtr.Email,
extUserId: n.data.ExtUserPtr.objectId,
}),
o.data.IsSendMail && !1 === o.data.IsSendMail
n.data.IsSendMail && !1 === n.data.IsSendMail
? console.log("don't send mail")
: sendCompletedMail(I),
sendDoctoWebhook(o, U.imageUrl, 'completed')),
: sendCompletedMail(w),
sendDoctoWebhook(n, D.imageUrl, 'completed')),
fs.unlinkSync(x),
console.log('New Signed PDF created called: ' + x),
'success' === n.message
? { status: 'success', data: U.imageUrl }
'success' === A.message
? { status: 'success', data: D.imageUrl }
: { status: 'error', message: 'please provide required parameters!' }
);
}
}
}
} catch (e) {
@@ -1 +1,20 @@
const ERROR_TYPE_UNKNOWN=1,ERROR_TYPE_INPUT=2,ERROR_TYPE_PARSE=3,ERROR_VERIFY_SIGNATURE=4;class SignPdfError extends Error{constructor(R,E=ERROR_TYPE_UNKNOWN){super(R),this.type=E}}SignPdfError.TYPE_UNKNOWN=ERROR_TYPE_UNKNOWN,SignPdfError.TYPE_INPUT=ERROR_TYPE_INPUT,SignPdfError.TYPE_PARSE=ERROR_TYPE_PARSE,SignPdfError.VERIFY_SIGNATURE=ERROR_VERIFY_SIGNATURE;export default SignPdfError;export{ERROR_TYPE_UNKNOWN,ERROR_TYPE_INPUT,ERROR_TYPE_PARSE,ERROR_VERIFY_SIGNATURE};
const ERROR_TYPE_UNKNOWN = 1,
ERROR_TYPE_INPUT = 2,
ERROR_TYPE_PARSE = 3,
ERROR_VERIFY_SIGNATURE = 4;
class SignPdfError extends Error {
constructor(R, E = ERROR_TYPE_UNKNOWN) {
super(R), (this.type = E);
}
}
(SignPdfError.TYPE_UNKNOWN = ERROR_TYPE_UNKNOWN),
(SignPdfError.TYPE_INPUT = ERROR_TYPE_INPUT),
(SignPdfError.TYPE_PARSE = ERROR_TYPE_PARSE),
(SignPdfError.VERIFY_SIGNATURE = ERROR_VERIFY_SIGNATURE);
export {
ERROR_TYPE_UNKNOWN,
ERROR_TYPE_INPUT,
ERROR_TYPE_PARSE,
ERROR_VERIFY_SIGNATURE,
SignPdfError,
};
@@ -0,0 +1,87 @@
import {
DEFAULT_BYTE_RANGE_PLACEHOLDER,
DEFAULT_SIGNATURE_LENGTH,
SUBFILTER_ADOBE_PKCS7_DETACHED,
} from './const.min.js';
import { SignPdfError } from './SignPdfError.min.js';
import {
PDFArray,
PDFDict,
PDFHexString,
PDFName,
PDFNumber,
PDFInvalidObject,
PDFString,
} from 'pdf-lib';
const pdflibAddPlaceholder = ({
pdfDoc: e = void 0,
pdfPage: o = void 0,
reason: t,
contactInfo: r = 'emailfromp1289@gmail.com',
name: i = 'Name from p12',
location: n,
signingTime: a = void 0,
signatureLength: P = DEFAULT_SIGNATURE_LENGTH,
byteRangePlaceholder: F = DEFAULT_BYTE_RANGE_PLACEHOLDER,
subFilter: D = SUBFILTER_ADOBE_PKCS7_DETACHED,
widgetRect: f = [0, 0, 0, 0],
appName: g = void 0,
}) => {
if (void 0 === e && void 0 === o)
throw new SignPdfError('PDFDoc or PDFPage must be set.', SignPdfError.TYPE_INPUT);
var e = e ?? o.doc,
o = o ?? e.getPages()[0],
m = PDFArray.withContext(e.context),
F =
(m.push(PDFNumber.of(0)),
m.push(PDFName.of(F)),
m.push(PDFName.of(F)),
m.push(PDFName.of(F)),
PDFHexString.of(String.fromCharCode(0).repeat(P))),
P = g ? { App: { Name: g } } : {},
g = e.context.obj({
Type: 'Sig',
Filter: 'Adobe.PPKLite',
SubFilter: D,
ByteRange: m,
Contents: F,
Reason: PDFString.of(t),
M: PDFString.fromDate(a ?? new Date()),
ContactInfo: PDFString.of(r),
Name: PDFString.of(i),
Location: PDFString.of(n),
Prop_Build: { Filter: { Name: 'Adobe.PPKLite' }, ...P },
}),
D = new Uint8Array(g.sizeInBytes()),
m = (g.copyBytesInto(D, 0), PDFInvalidObject.of(D)),
F = e.context.register(m);
const s = PDFArray.withContext(e.context);
f.forEach(e => s.push(PDFNumber.of(e)));
(t = e.context.formXObject([], { BBox: f, Resources: {} })),
(a = e.context.obj({
Type: 'Annot',
Subtype: 'Widget',
FT: 'Sig',
Rect: s,
V: F,
T: PDFString.of('Signature1'),
F: 4,
P: o.ref,
AP: { N: e.context.register(t) },
})),
(r = e.context.register(a));
let c = o.node.lookupMaybe(PDFName.of('Annots'), PDFArray),
d =
((c = void 0 === c ? e.context.obj([]) : c).push(r),
o.node.set(PDFName.of('Annots'), c),
e.catalog.lookupMaybe(PDFName.of('AcroForm'), PDFDict));
void 0 === d &&
((d = e.context.obj({ Fields: [] })),
(i = e.context.register(d)),
e.catalog.set(PDFName.of('AcroForm'), i));
let N;
N = d.has(PDFName.of('SigFlags')) ? d.get(PDFName.of('SigFlags')) : PDFNumber.of(0);
n = PDFNumber.of(3 | N.asNumber());
d.set(PDFName.of('SigFlags'), n), d.get(PDFName.of('Fields')).push(r);
};
export { pdflibAddPlaceholder };
@@ -11,11 +11,7 @@ export default function reportJson(id, userId) {
IsCompleted: { $ne: true },
IsDeclined: { $ne: true },
IsArchive: { $ne: true },
$or: [
{ Signers: { $eq: [] } },
{ Signers: null },
{ Signers: { $exists: true }, Placeholders: null },
],
SignedUrl: { $exists: false },
CreatedBy: {
__type: 'Pointer',
className: '_User',
@@ -33,6 +29,7 @@ export default function reportJson(id, userId) {
'Signers.Phone',
],
};
// Need your sign report
case '4Hhwbp482K':
return {
@@ -273,11 +270,7 @@ export default function reportJson(id, userId) {
IsCompleted: { $ne: true },
IsDeclined: { $ne: true },
IsArchive: { $ne: true },
$or: [
{ Signers: { $eq: [] } },
{ Signers: null },
{ Signers: { $exists: true }, Placeholders: null },
],
SignedUrl: { $exists: false },
CreatedBy: {
__type: 'Pointer',
className: '_User',
@@ -3,8 +3,9 @@ import https from 'https';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import { createTransport } from 'nodemailer';
import { updateMailCount } from '../../Utils.js';
async function sendmail(req) {
async function sendmailv3(req) {
try {
let transporterSMTP;
let mailgunClient;
@@ -77,6 +78,9 @@ async function sendmail(req) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return {
status: 'success',
};
@@ -85,6 +89,9 @@ async function sendmail(req) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res);
if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return {
status: 'success',
};
@@ -109,6 +116,9 @@ async function sendmail(req) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return {
status: 'success',
};
@@ -117,6 +127,9 @@ async function sendmail(req) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res);
if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return {
status: 'success',
};
@@ -124,11 +137,11 @@ async function sendmail(req) {
}
}
} catch (err) {
console.log('err ', err);
console.log('err in sendmailv3', err);
if (err) {
return { status: 'error' };
}
}
}
export default sendmail;
export default sendmailv3;
@@ -63,13 +63,12 @@ export default async function (request) {
sessionToken: user.sessionToken, //localStorage.getItem("accesstoken"),
};
let body = {
appName: extClass, //props.appInfo.appname,
roleName: userDetails.role, //props.appInfo.defaultRole,
appName: extClass,
roleName: userDetails.role,
userId: user.id,
};
let role = await axios.post(roleurl, body, { headers: headers });
// console.log("role ", role);
// props.appInfo.defaultRole
const partnerCls = Parse.Object.extend("partners_Tenant");
const partnerQuery = new partnerCls();
partnerQuery.set("UserId", {
@@ -111,7 +110,7 @@ export default async function (request) {
className: "_User",
objectId: user.id,
});
newObj.set("UserRole", userDetails.role); // props.appInfo.defaultRole
newObj.set("UserRole", userDetails.role);
newObj.set("Email", userDetails.email);
newObj.set("Name", userDetails.name);
newObj.set("Phone", userDetails.phone);
@@ -0,0 +1,219 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
// TODO: set className here
const className = 'w_dashboard';
const dashboard = new Parse.Query(className);
const updatedashboard = await dashboard.get('35KBoSgoAK');
updatedashboard.set('rows', [
{
columns: [
{
colxs: '12',
colmd: '6',
collg: '6',
widget: {
type: 'Card',
icon: 'fas fa-signature',
bgColor: '#f0058e',
label: 'Need your Signature',
description: null,
data: {
id: 'e04ee9dc-932d-6b80-0202-703fd154eb74',
queryType: '',
class: 'contracts_Document',
query:
'where={"$and":[{"AuditTrail.UserPtr":{"$ne":{"__type":"Pointer","className": "contracts_Users","objectId":"#objectId#"}}},{"AuditTrail.Activity":{"$ne":"Signed"}}],"Signers":{"$in":[{"__type":"Pointer","className":"contracts_Users","objectId":"#objectId#"}]},"IsDeclined":{"$ne":true},"IsCompleted":{"$ne":true},"Type":{"$ne":"Folder"},"Placeholders":{"$ne":null},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&count=1',
key: 'count',
Redirect_type: 'Report',
Redirect_id: '4Hhwbp482K',
tourSection: 'tourcard1',
tourMessage:
'Clicking on this card will take you to the list of documents awaiting your review.',
},
},
},
{
colxs: '12',
colmd: '6',
collg: '6',
widget: {
type: 'Card',
icon: 'fas fa-sign-out-alt',
bgColor: '#g0058e',
label: 'Out for signatures',
description: null,
data: {
id: 'c1935cc0-28d4-3d73-b72a-8c1e3d18a17f',
queryType: '',
class: 'contracts_Document',
query:
'where={"Type":{"#*ne":"Folder"},"Signers":{"#*exists":true,"#*ne":[]},"Placeholders":{"#*ne":null},"IsCompleted":{"#*ne":true},"IsDeclined":{"#*ne":true},"IsArchive":{"#*ne":true},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"#UserId.objectId#"},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&count=1',
key: 'count',
Redirect_type: 'Report',
Redirect_id: '1MwEuxLEkF',
tourSection: 'tourcard2',
tourMessage:
'Clicking on this card will take you to a list of documents awaiting signature.',
},
},
},
{
colxs: '12',
colmd: '6',
collg: '6',
widget: {
type: 'report',
reportId: '5Go51Q7T8r',
data: {
tourSection: 'tourreport1',
tourMessage: 'This is a list of documents that are waiting for your signature.',
},
},
},
{
colxs: '12',
colmd: '6',
collg: '6',
widget: {
type: 'report',
reportId: 'd9k3UfYHBc',
data: {
tourSection: 'tourreport2',
tourMessage:
"This is a list of documents you've sent to other parties for signature.",
},
},
},
{
colxs: '12',
colmd: '12',
collg: '12',
widget: {
type: 'report',
reportId: 'kC5mfynCi4',
data: {
tourSection: 'tourreport3',
tourMessage:
'This are documents you have started but have not finalized for sending.',
},
},
},
],
},
]);
return updatedashboard.save();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
const className = 'w_dashboard';
const dashboard = new Parse.Query(className);
const revertdashboard = await dashboard.get('35KBoSgoAK');
revertdashboard.set('rows', [
{
columns: [
{
colxs: '12',
colmd: '6',
collg: '6',
widget: {
type: 'Card',
icon: 'fas fa-signature',
bgColor: '#f0058e',
label: 'Need your Signature',
description: null,
data: {
id: 'e04ee9dc-932d-6b80-0202-703fd154eb74',
queryType: '',
class: 'contracts_Document',
query:
'where={"$and":[{"AuditTrail.UserPtr":{"$ne":{"__type":"Pointer","className": "contracts_Users","objectId":"#objectId#"}}},{"AuditTrail.Activity":{"$ne":"Signed"}}],"Signers":{"$in":[{"__type":"Pointer","className":"contracts_Users","objectId":"#objectId#"}]},"IsDeclined":{"$ne":true},"IsCompleted":{"$ne":true},"Type":{"$ne":"Folder"},"Placeholders":{"$ne":null},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&count=1',
key: 'count',
Redirect_type: 'Report',
Redirect_id: '4Hhwbp482K',
tourSection: 'tourcard1',
tourMessage:
'This card shows the aggregate count of documents that are awaiting your signature. Clicking on this card will seamlessly take you to the detailed list of such documents for your review and action.',
},
},
},
{
colxs: '12',
colmd: '6',
collg: '6',
widget: {
type: 'Card',
icon: 'fas fa-sign-out-alt',
bgColor: '#g0058e',
label: 'Out for signatures',
description: null,
data: {
id: 'c1935cc0-28d4-3d73-b72a-8c1e3d18a17f',
queryType: '',
class: 'contracts_Document',
query:
'where={"Type":{"#*ne":"Folder"},"Signers":{"#*exists":true,"#*ne":[]},"Placeholders":{"#*ne":null},"IsCompleted":{"#*ne":true},"IsDeclined":{"#*ne":true},"IsArchive":{"#*ne":true},"CreatedBy":{"__type":"Pointer","className":"_User","objectId":"#UserId.objectId#"},"ExpiryDate":{"#*gt":{"__type":"#Date#","iso":"#today#"}}}&count=1',
key: 'count',
Redirect_type: 'Report',
Redirect_id: '1MwEuxLEkF',
tourSection: 'tourcard2',
tourMessage:
"This card indicates the total number of documents you've sent out for signatures. A single click on this card will navigate you to a comprehensive list of these documents, allowing you to monitor their status.",
},
},
},
{
colxs: '12',
colmd: '6',
collg: '6',
widget: {
type: 'report',
reportId: '5Go51Q7T8r',
data: {
tourSection: 'tourreport1',
tourMessage:
'Here lies a curated list of documents that are pending your signature. These are requests from other users that require your immediate attention for completion.',
},
},
},
{
colxs: '12',
colmd: '6',
collg: '6',
widget: {
type: 'report',
reportId: 'd9k3UfYHBc',
data: {
tourSection: 'tourreport2',
tourMessage:
"This area displays all the documents you've dispatched for signatures from other parties. It serves as a real-time tracker for documents pending external approval.",
},
},
},
{
colxs: '12',
colmd: '12',
collg: '12',
widget: {
type: 'report',
reportId: 'kC5mfynCi4',
data: {
tourSection: 'tourreport3',
tourMessage:
"In this section, you'll find your draft documents awaiting completion. These are the documents you've initiated but haven't yet finalized for sending.",
},
},
},
],
},
]);
return revertdashboard.save();
};
@@ -0,0 +1,690 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
const className = 'w_menu';
const userMenu = new Parse.Query(className);
const updateUserMenu = await userMenu.get('H9vRfEYKhT');
updateUserMenu.set('menuItems', [
{
icon: 'fas fa-tachometer-alt',
title: 'Dashboard',
target: '',
pageType: 'dashboard',
description: '',
objectId: '35KBoSgoAK',
},
{
icon: 'fas fa-pen-nib',
title: 'Sign yourself',
target: '_self',
pageType: 'form',
description: '',
objectId: 'sHAnZphf69',
},
{
icon: 'fa-solid fa-paper-plane',
title: 'Request signatures',
target: '_self',
pageType: 'form',
description: '',
objectId: '8mZzFxbG1z',
},
{
icon: 'far fa-newspaper',
title: 'Templates',
target: '_self',
pageType: null,
description: null,
objectId: null,
children: [
{
icon: 'fas fa-file-signature',
title: 'Create template',
target: '_self',
pageType: 'form',
description: '',
objectId: 'template',
},
{
icon: 'fa-solid fa-file-contract',
title: 'Manage templates',
target: '_self',
pageType: 'report',
description: '',
objectId: '6TeaPr321t',
},
],
},
{
icon: 'fas fa-folder',
title: 'OpenSign™ Drive',
target: '_self',
pageType: '',
description: '',
objectId: 'opensigndrive',
},
{
icon: 'fas fa-address-card',
title: 'Reports',
target: '_self',
pageType: null,
description: '',
objectId: null,
children: [
{
icon: 'fas fa-signature',
title: 'Need your sign',
target: '_self',
pageType: 'report',
description: '',
objectId: '4Hhwbp482K',
},
{
icon: 'fas fa-tasks',
title: 'In Progress',
target: '_self',
pageType: 'report',
description: '',
objectId: '1MwEuxLEkF',
},
{
icon: 'fas fa-check-circle',
title: 'Completed',
target: '_self',
pageType: 'report',
description: '',
objectId: 'kQUoW4hUXz',
},
{
icon: 'fas fa-edit',
title: 'Drafts',
target: '_self',
pageType: 'report',
description: '',
objectId: 'ByHuevtCFY',
},
{
icon: 'fas fa-times-circle',
title: 'Declined',
target: '_self',
pageType: 'report',
description: '',
objectId: 'UPr2Fm5WY3',
},
{
icon: 'fas fa-hourglass-end',
title: 'Expired',
target: '_self',
pageType: 'report',
description: '',
objectId: 'zNqBHXHsYH',
},
{
icon: 'fa-solid fa-address-book',
title: 'Contactbook',
target: '_self',
pageType: 'report',
description: '',
objectId: '5KhaPr482K',
},
],
},
{
icon: 'fas fa-cog',
title: 'Settings',
target: '_self',
pageType: null,
description: '',
objectId: null,
children: [
{
icon: 'fas fa-pen-fancy',
title: 'My Signature',
target: '_self',
pageType: '',
description: '',
objectId: 'managesign',
},
{
icon: 'fa-solid fa-key',
title: 'API Token',
target: '_self',
pageType: 'generatetoken',
description: '',
objectId: '',
},
{
icon: 'fa-solid fa-globe',
title: 'Webhook',
target: '_self',
pageType: 'webhook',
description: '',
objectId: '',
},
],
},
]);
const AdminMenu = new Parse.Query(className);
const updateAdminMenu = await AdminMenu.get('VPh91h0ZHk');
updateAdminMenu.set('menuItems', [
{
icon: 'fas fa-tachometer-alt',
title: 'Dashboard',
target: '',
pageType: 'dashboard',
description: '',
objectId: '35KBoSgoAK',
},
{
icon: 'fas fa-pen-nib',
title: 'Sign yourself',
target: '_self',
pageType: 'form',
description: '',
objectId: 'sHAnZphf69',
},
{
icon: 'fa-solid fa-paper-plane',
title: 'Request signatures',
target: '_self',
pageType: 'form',
description: '',
objectId: '8mZzFxbG1z',
},
{
icon: 'far fa-newspaper',
title: 'Templates',
target: '_self',
pageType: null,
description: null,
objectId: null,
children: [
{
icon: 'fas fa-file-signature',
title: 'Create template',
target: '_self',
pageType: 'form',
description: '',
objectId: 'template',
},
{
icon: 'fa-solid fa-file-contract',
title: 'Manage templates',
target: '_self',
pageType: 'report',
description: '',
objectId: '6TeaPr321t',
},
],
},
{
icon: 'fas fa-folder',
title: 'OpenSign™ Drive',
target: '_self',
pageType: '',
description: '',
objectId: 'opensigndrive',
},
{
icon: 'fas fa-address-card',
title: 'Reports',
target: '_self',
pageType: null,
description: '',
objectId: null,
children: [
{
icon: 'fas fa-signature',
title: 'Need your sign',
target: '_self',
pageType: 'report',
description: '',
objectId: '4Hhwbp482K',
},
{
icon: 'fas fa-tasks',
title: 'In Progress',
target: '_self',
pageType: 'report',
description: '',
objectId: '1MwEuxLEkF',
},
{
icon: 'fas fa-check-circle',
title: 'Completed',
target: '_self',
pageType: 'report',
description: '',
objectId: 'kQUoW4hUXz',
},
{
icon: 'fas fa-edit',
title: 'Drafts',
target: '_self',
pageType: 'report',
description: '',
objectId: 'ByHuevtCFY',
},
{
icon: 'fas fa-times-circle',
title: 'Declined',
target: '_self',
pageType: 'report',
description: '',
objectId: 'UPr2Fm5WY3',
},
{
icon: 'fas fa-hourglass-end',
title: 'Expired',
target: '_self',
pageType: 'report',
description: '',
objectId: 'zNqBHXHsYH',
},
{
icon: 'fa-solid fa-address-book',
title: 'Contactbook',
target: '_self',
pageType: 'report',
description: '',
objectId: '5KhaPr482K',
},
],
},
{
icon: 'fas fa-cog',
title: 'Settings',
target: '_self',
pageType: null,
description: '',
objectId: null,
children: [
{
icon: 'fas fa-pen-fancy',
title: 'My Signature',
target: '_self',
pageType: '',
description: '',
objectId: 'managesign',
},
{
icon: 'far fa-user',
title: 'Add User',
target: '_self',
pageType: 'form',
description: '',
objectId: 'lM0xRnM3iE',
},
{
icon: 'fa-solid fa-key',
title: 'API Token',
target: '_self',
pageType: 'generatetoken',
description: '',
objectId: '',
},
{
icon: 'fa-solid fa-globe',
title: 'Webhook',
target: '_self',
pageType: 'webhook',
description: '',
objectId: '',
},
],
},
]);
// TODO: Set the schema here
// Example:
// schema.addString('name').addNumber('cash');
const batch = [updateUserMenu, updateAdminMenu];
return Parse.Object.saveAll(batch, { useMasterKey: true });
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
// TODO: set className here
const className = 'w_menu';
const userMenu = new Parse.Query(className);
const revertUserMenu = await userMenu.get('H9vRfEYKhT');
revertUserMenu.set('menuItems', [
{
icon: 'fas fa-tachometer-alt',
title: 'Dashboard',
target: '',
pageType: 'dashboard',
description: '',
objectId: '35KBoSgoAK',
},
{
icon: 'far fa-newspaper',
title: 'New Document',
target: '_self',
pageType: null,
description: null,
objectId: null,
children: [
{
icon: 'fas fa-pen-nib',
title: 'Sign yourself',
target: '_self',
pageType: 'form',
description: '',
objectId: 'sHAnZphf69',
},
{
icon: 'fa-solid fa-paper-plane',
title: 'Request signatures',
target: '_self',
pageType: 'form',
description: '',
objectId: '8mZzFxbG1z',
},
{
icon: 'fas fa-file-signature',
title: 'New template',
target: '_self',
pageType: 'form',
description: '',
objectId: 'template',
},
],
},
{
icon: 'fa-solid fa-file-contract',
title: 'Templates',
target: '_self',
pageType: 'report',
description: '',
objectId: '6TeaPr321t',
},
{
icon: 'fas fa-folder',
title: 'OpenSign™ Drive',
target: '_self',
pageType: '',
description: '',
objectId: 'opensigndrive',
},
{
icon: 'fas fa-address-card',
title: 'Reports',
target: '_self',
pageType: null,
description: '',
objectId: null,
children: [
{
icon: 'fas fa-signature',
title: 'Need your sign',
target: '_self',
pageType: 'report',
description: '',
objectId: '4Hhwbp482K',
},
{
icon: 'fas fa-tasks',
title: 'In Progress',
target: '_self',
pageType: 'report',
description: '',
objectId: '1MwEuxLEkF',
},
{
icon: 'fas fa-check-circle',
title: 'Completed',
target: '_self',
pageType: 'report',
description: '',
objectId: 'kQUoW4hUXz',
},
{
icon: 'fas fa-edit',
title: 'Drafts',
target: '_self',
pageType: 'report',
description: '',
objectId: 'ByHuevtCFY',
},
{
icon: 'fas fa-times-circle',
title: 'Declined',
target: '_self',
pageType: 'report',
description: '',
objectId: 'UPr2Fm5WY3',
},
{
icon: 'fas fa-hourglass-end',
title: 'Expired',
target: '_self',
pageType: 'report',
description: '',
objectId: 'zNqBHXHsYH',
},
{
icon: 'fa-solid fa-address-book',
title: 'Contactbook',
target: '_self',
pageType: 'report',
description: '',
objectId: '5KhaPr482K',
},
],
},
{
icon: 'fas fa-cog',
title: 'Settings',
target: '_self',
pageType: null,
description: '',
objectId: null,
children: [
{
icon: 'fas fa-pen-fancy',
title: 'My Signature',
target: '_self',
pageType: '',
description: '',
objectId: 'managesign',
},
{
icon: 'fa-solid fa-key',
title: 'API Token',
target: '_self',
pageType: 'generatetoken',
description: '',
objectId: '',
},
{
icon: 'fa-solid fa-globe',
title: 'Webhook',
target: '_self',
pageType: 'webhook',
description: '',
objectId: '',
},
],
},
]);
const adminMenu = new Parse.Query(className);
const revertAdminMenu = await adminMenu.get('VPh91h0ZHk');
revertAdminMenu.set('menuItems', [
{
icon: 'fas fa-tachometer-alt',
title: 'Dashboard',
target: '',
pageType: 'dashboard',
description: '',
objectId: '35KBoSgoAK',
},
{
icon: 'far fa-newspaper',
title: 'New Document',
target: '_self',
pageType: null,
description: null,
objectId: null,
children: [
{
icon: 'fas fa-pen-nib',
title: 'Sign yourself',
target: '_self',
pageType: 'form',
description: '',
objectId: 'sHAnZphf69',
},
{
icon: 'fa-solid fa-paper-plane',
title: 'Request signatures',
target: '_self',
pageType: 'form',
description: '',
objectId: '8mZzFxbG1z',
},
{
icon: 'fas fa-file-signature',
title: 'New template',
target: '_self',
pageType: 'form',
description: '',
objectId: 'template',
},
],
},
{
icon: 'fa-solid fa-file-contract',
title: 'Templates',
target: '_self',
pageType: 'report',
description: '',
objectId: '6TeaPr321t',
},
{
icon: 'fas fa-folder',
title: 'OpenSign™ Drive',
target: '_self',
pageType: '',
description: '',
objectId: 'opensigndrive',
},
{
icon: 'fas fa-address-card',
title: 'Reports',
target: '_self',
pageType: null,
description: '',
objectId: null,
children: [
{
icon: 'fas fa-signature',
title: 'Need your sign',
target: '_self',
pageType: 'report',
description: '',
objectId: '4Hhwbp482K',
},
{
icon: 'fas fa-tasks',
title: 'In Progress',
target: '_self',
pageType: 'report',
description: '',
objectId: '1MwEuxLEkF',
},
{
icon: 'fas fa-check-circle',
title: 'Completed',
target: '_self',
pageType: 'report',
description: '',
objectId: 'kQUoW4hUXz',
},
{
icon: 'fas fa-edit',
title: 'Drafts',
target: '_self',
pageType: 'report',
description: '',
objectId: 'ByHuevtCFY',
},
{
icon: 'fas fa-times-circle',
title: 'Declined',
target: '_self',
pageType: 'report',
description: '',
objectId: 'UPr2Fm5WY3',
},
{
icon: 'fas fa-hourglass-end',
title: 'Expired',
target: '_self',
pageType: 'report',
description: '',
objectId: 'zNqBHXHsYH',
},
{
icon: 'fa-solid fa-address-book',
title: 'Contactbook',
target: '_self',
pageType: 'report',
description: '',
objectId: '5KhaPr482K',
},
],
},
{
icon: 'fas fa-cog',
title: 'Settings',
target: '_self',
pageType: null,
description: '',
objectId: null,
children: [
{
icon: 'fas fa-pen-fancy',
title: 'My Signature',
target: '_self',
pageType: '',
description: '',
objectId: 'managesign',
},
{
icon: 'far fa-user',
title: 'Add User',
target: '_self',
pageType: 'form',
description: '',
objectId: 'lM0xRnM3iE',
},
{
icon: 'fa-solid fa-key',
title: 'API Token',
target: '_self',
pageType: 'generatetoken',
description: '',
objectId: '',
},
{
icon: 'fa-solid fa-globe',
title: 'Webhook',
target: '_self',
pageType: 'webhook',
description: '',
objectId: '',
},
],
},
]);
const batch = [revertUserMenu, revertAdminMenu];
return Parse.Object.saveAll(batch, { useMasterKey: true });
};
+46 -24
View File
@@ -54,26 +54,41 @@ if (process.env.USE_LOCAL !== 'TRUE') {
let transporterMail;
let mailgunClient;
let mailgunDomain;
let isMailAdapter = false;
if (process.env.SMTP_ENABLE) {
transporterMail = createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 465,
secure: process.env.SMTP_SECURE || true,
auth: {
user: process.env.SMTP_USER_EMAIL,
pass: process.env.SMTP_PASS,
},
});
try {
transporterMail = createTransport({
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 465,
secure: process.env.SMTP_SECURE || true,
auth: {
user: process.env.SMTP_USER_EMAIL,
pass: process.env.SMTP_PASS,
},
});
await transporterMail.verify();
isMailAdapter = true;
} catch (err) {
isMailAdapter = false;
console.log('Please provide valid SMTP credentials');
}
} else if (process.env.MAILGUN_API_KEY) {
const mailgun = new Mailgun(formData);
mailgunClient = mailgun.client({
username: 'api',
key: process.env.MAILGUN_API_KEY,
});
mailgunDomain = process.env.MAILGUN_DOMAIN;
try {
const mailgun = new Mailgun(formData);
mailgunClient = mailgun.client({
username: 'api',
key: process.env.MAILGUN_API_KEY,
});
mailgunDomain = process.env.MAILGUN_DOMAIN;
isMailAdapter = true;
} catch (error) {
isMailAdapter = false;
console.log('Please provide valid Mailgun credentials');
}
}
const mailsender = process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER;
export const config = {
databaseURI:
process.env.DATABASE_URI || process.env.MONGODB_URI || 'mongodb://localhost:27017/dev',
@@ -82,25 +97,24 @@ export const config = {
},
appId: process.env.APP_ID || 'myAppId',
maxLimit: 500,
maxUploadSize: '30mb',
masterKey: process.env.MASTER_KEY, //Add your master key here. Keep it secret!
masterKeyIps: ['0.0.0.0/0', '::/0'], // '::1'
serverURL: process.env.SERVER_URL || 'http://localhost:8080/app', // Don't forget to change to https if needed
verifyUserEmails: process.env.SMTP_ENABLE || process.env.MAILGUN_API_KEY ? true : false,
serverURL: 'http://localhost:8080/app', // Don't forget to change to https if needed
verifyUserEmails: isMailAdapter === true ? true : false,
publicServerURL: process.env.SERVER_URL || 'http://localhost:8080/app',
// Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'Open Sign',
appName: 'Opensign',
allowClientClassCreation: false,
allowExpiredAuthDataToken: false,
encodeParseObjectInCloudFunction: true,
...(process.env.SMTP_ENABLE || process.env.MAILGUN_API_KEY
...(isMailAdapter === true
? {
emailAdapter: {
module: 'parse-server-api-mail-adapter',
options: {
// The email address from which emails are sent.
sender: process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER,
sender: 'Opensign™' + ' <' + mailsender + '>',
// The email templates.
templates: {
// The template used by Parse Server to send an email for password
@@ -133,6 +147,14 @@ export const config = {
google: {
enabled: true,
},
ldap: {
enabled: true,
url: 'ldap://ldap.forumsys.com:389',
suffix: 'dc=example,dc=com',
// dn: 'ou=mathematicians, dc=example, dc=com',
groupCn: 'mathematicians',
groupFilter: '(&(uniqueMember=uid=,dc=example,dc=com)(objectClass=groupOfUniqueNames))',
},
},
};
// Client-keys like the javascript key or the .NET key are not necessary with parse-server
+3312 -585
View File
File diff suppressed because it is too large Load Diff
+20 -23
View File
@@ -18,46 +18,43 @@
"watch": "nodemon index.js"
},
"dependencies": {
"aws-sdk": "^2.1569.0",
"axios": "1.6.7",
"aws-sdk": "^2.1581.0",
"axios": "^1.6.8",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.0.3",
"express": "4.18.2",
"dotenv": "^16.4.5",
"express": "^4.18.3",
"express-sse": "^0.5.3",
"form-data": "^4.0.0",
"generate-api-key": "^1.0.2",
"jsonschema": "^1.4.1",
"mailgun.js": "^9.3.0",
"mongoose": "^8.2.0",
"mailgun.js": "^10.2.1",
"mongoose": "^8.2.2",
"multer": "^1.4.5-lts.1",
"multer-s3": "^2.10.0",
"multer-s3": "^3.0.1",
"node-forge": "^1.3.1",
"node-signpdf": "^1.5.1",
"nodemailer": "^6.9.11",
"parse": "4.3.1",
"nodemailer": "^6.9.12",
"parse": "^5.0.0",
"parse-dbtool": "^1.2.0",
"parse-server": "6.5.0",
"parse-server": "^6.5.5",
"parse-server-api-mail-adapter": "^3.1.1",
"parse-server-fs-adapter": "1.0.1",
"parse-server-fs-adapter": "^1.0.1",
"parse-server-s3-adapter": "^1.2.0",
"pdf-lib": "^1.16.0",
"pdfkit": "^0.13.0",
"posthog-node": "^3.6.2",
"razorpay": "^2.8.6",
"request": "^2.88.2"
"pdf-lib": "^1.17.1",
"pdfkit": "^0.14.0",
"posthog-node": "^3.6.3"
},
"type": "module",
"devDependencies": {
"@babel/eslint-parser": "7.21.3",
"eslint": "8.38.0",
"jasmine": "4.6.0",
"mongodb-runner": "5.5.3",
"@babel/eslint-parser": "^7.23.10",
"eslint": "^8.57.0",
"jasmine": "^5.1.0",
"mongodb-runner": "^5.5.4",
"nodemon": "^3.1.0",
"nyc": "15.1.0",
"prettier": "2.8.7"
"prettier": "^3.2.5"
},
"engines": {
"node": ">=12.22.10 <19"
"node": ">=12.22.10 <21"
}
}