diff --git a/apps/OpenSign/src/components/AppendFormInForm.js b/apps/OpenSign/src/components/AppendFormInForm.js index 22017e423..f5c1d51c2 100644 --- a/apps/OpenSign/src/components/AppendFormInForm.js +++ b/apps/OpenSign/src/components/AppendFormInForm.js @@ -46,6 +46,7 @@ const AppendFormInForm = (props) => { // Define a function to handle form submission const handleSubmit = async (e) => { e.preventDefault(); + e.stopPropagation(); setIsLoader(true); Parse.serverURL = parseBaseUrl; Parse.initialize(parseAppId); diff --git a/apps/OpenSign/src/components/TreeWidget.js b/apps/OpenSign/src/components/TreeWidget.js index ecbce7af4..569112d71 100644 --- a/apps/OpenSign/src/components/TreeWidget.js +++ b/apps/OpenSign/src/components/TreeWidget.js @@ -5,7 +5,7 @@ import Parse from "parse"; import axios from "axios"; import "../styles/spinner.css"; import TreeFormComponent from "./TreeFormComponent"; -import TreeEditForm from "./TreeEditForm"; +// import TreeEditForm from "./TreeEditForm"; import "../styles/modal.css"; import Modal from "react-modal"; @@ -22,7 +22,7 @@ const TreeWidget = (props) => { const [schemaState, setSchemaState] = useState({}); const [TabURL, setTabURL] = useState(""); const [editable, setEditable] = useState(false); - const [editId, setEditId] = useState(""); + // const [editId, setEditId] = useState(""); const [defaultState, setDefaultState] = useState(false); const [isShowModal, setIsShowModal] = useState(false); const selectFolderHandle = async () => { diff --git a/apps/OpenSign/src/components/fields/CreateFolder.js b/apps/OpenSign/src/components/fields/CreateFolder.js new file mode 100644 index 000000000..9b6f4beec --- /dev/null +++ b/apps/OpenSign/src/components/fields/CreateFolder.js @@ -0,0 +1,148 @@ +import React, { useEffect, useState } from "react"; +import Parse from "parse"; +import { templateCls } from "../../constant/const"; +import Alert from "../../primitives/Alert"; + +const CreateFolder = ({ parentFolderId, onSuccess, folderCls }) => { + const folderPtr = { + __type: "Pointer", + className: folderCls, + objectId: parentFolderId + }; + const [name, setName] = useState(""); + const [folderList, setFolderList] = useState([]); + const [isAlert, setIsAlert] = useState(false); + const [selectedParent, setSelectedParent] = useState(); + const [alert, setAlert] = useState({ type: "info", message: "" }); + useEffect(() => { + fetchFolder(); + // eslint-disable-next-line + }, []); + + const fetchFolder = async () => { + try { + const fetchFolder = new Parse.Query(folderCls); + if (parentFolderId) { + fetchFolder.equalTo("Folder", folderPtr); + fetchFolder.equalTo("Type", "Folder"); + } else { + fetchFolder.doesNotExist("Folder"); + fetchFolder.equalTo("Type", "Folder"); + } + + const res = await fetchFolder.find(); + if (res) { + const result = JSON.parse(JSON.stringify(res)); + if (result) { + setFolderList(result); + } + } + } catch (error) { + console.log("Err ", error); + } + }; + const handleCreateFolder = async (event) => { + event.preventDefault(); + if (name) { + const currentUser = Parse.User.current(); + const exsitQuery = new Parse.Query(templateCls); + exsitQuery.equalTo("Name", name); + exsitQuery.equalTo("Type", "Folder"); + if (parentFolderId) { + exsitQuery.equalTo("Folder", folderPtr); + } + const templExist = await exsitQuery.first(); + if (templExist) { + setAlert({ type: "dange", message: "Folder already exist!" }); + setIsAlert(true); + setTimeout(() => { + setIsAlert(false); + }, 1000); + } else { + const template = new Parse.Object(templateCls); + template.set("Name", name); + template.set("Type", "Folder"); + + if (selectedParent) { + template.set("Folder", { + __type: "Pointer", + className: folderCls, + objectId: selectedParent + }); + } else if (parentFolderId) { + template.set("Folder", folderPtr); + } + template.set("CreatedBy", Parse.User.createWithoutData(currentUser.id)); + const res = await template.save(); + if (res) { + if (onSuccess) { + setAlert({ + type: "success", + message: "Folder created successfully!" + }); + setIsAlert(true); + setTimeout(() => { + setIsAlert(false); + }, 1000); + onSuccess(res); + } + } + } + } else { + setAlert({ type: "info", message: "Please fill folder name" }); + setIsAlert(true); + setTimeout(() => { + setIsAlert(false); + }, 1000); + } + }; + const handleOptions = (e) => { + setSelectedParent(e.target.value); + }; + return ( +
+ {isAlert && {alert.message}} +
+

Create Folder

+
+ + setName(e.target.value)} + required + /> +
+
+ + +
+
+ +
+
+
+ ); +}; + +export default CreateFolder; diff --git a/apps/OpenSign/src/components/fields/FileUpload.js b/apps/OpenSign/src/components/fields/FileUpload.js index 09a11e8d2..66eca92b0 100644 --- a/apps/OpenSign/src/components/fields/FileUpload.js +++ b/apps/OpenSign/src/components/fields/FileUpload.js @@ -271,10 +271,12 @@ const FileUpload = (props) => { - + {process.env.DROPBOX_APP_KEY && ( + + )} ) : (
@@ -294,10 +296,12 @@ const FileUpload = (props) => { accept="application/pdf,application/vnd.ms-excel" onChange={onChange} /> - + {process.env.DROPBOX_APP_KEY && ( + + )}
)} diff --git a/apps/OpenSign/src/components/fields/SelectFolder.js b/apps/OpenSign/src/components/fields/SelectFolder.js new file mode 100644 index 000000000..434bbed7d --- /dev/null +++ b/apps/OpenSign/src/components/fields/SelectFolder.js @@ -0,0 +1,271 @@ +import React, { useEffect, useState } from "react"; +import Parse from "parse"; +import CreateFolder from "./CreateFolder"; +import { templateCls } from "../../constant/const"; + +const SelectFolder = ({ required, onSuccess }) => { + const [isOpen, SetIsOpen] = useState(false); + const [clickFolder, setClickFolder] = useState(""); + const [selectFolder, setSelectedFolder] = useState({}); + const [folderList, setFolderList] = useState([]); + const [tabList, setTabList] = useState([]); + const [isLoader, setIsLoader] = useState(false); + const [folderPath, setFolderPath] = useState(""); + const [isAdd, setIsAdd] = useState(false); + useEffect(() => { + if (isOpen) { + setIsAdd(false); + setClickFolder({}); + setFolderList([]); + setTabList([]); + fetchFolder(); + } + }, [isOpen]); + const fetchFolder = async (folderPtr) => { + setIsLoader(true); + try { + const fetchFolder = new Parse.Query(templateCls); + if (folderPtr) { + fetchFolder.equalTo("Folder", folderPtr); + fetchFolder.equalTo("Type", "Folder"); + } else { + fetchFolder.doesNotExist("Folder"); + fetchFolder.equalTo("Type", "Folder"); + } + + const res = await fetchFolder.find(); + if (res) { + const result = JSON.parse(JSON.stringify(res)); + if (result) { + setFolderList(result); + setIsLoader(false); + } + setIsLoader(false); + } + } catch (error) { + setIsLoader(false); + } + }; + 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: templateCls, + objectId: item.objectId + }; + fetchFolder(folderPtr); + } + } else { + setTabList((tabs) => [...tabs, item]); + const folderPtr = { + __type: "Pointer", + className: templateCls, + objectId: item.objectId + }; + + fetchFolder(folderPtr); + } + }; + + const handleSubmit = () => { + let url = "Root"; + tabList.forEach((t) => { + url = url + " / " + t.Name; + }); + setFolderPath(url); + setSelectedFolder(clickFolder); + if (onSuccess) { + onSuccess(clickFolder); + } + SetIsOpen(false); + }; + const handleCancel = () => { + SetIsOpen(false); + setClickFolder({}); + setFolderList([]); + setTabList([]); + }; + + 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: templateCls, + objectId: list[_len].objectId + }; + setTabList(list); + } else { + setClickFolder({}); + setSelectedFolder({}); + setFolderList([]); + setTabList([]); + } + fetchFolder(folderPtr); + } + }; + const handleCreate = () => { + setIsAdd(!isAdd); + }; + const handleAddFolder = () => { + setFolderList([]); + if (clickFolder && clickFolder.ObjectId) { + fetchFolder({ + __type: "Pointer", + className: templateCls, + objectId: clickFolder.ObjectId + }); + } else { + fetchFolder(); + } + }; + return ( +
+
+ +
+
+
+ +
+
+
+

+ {selectFolder && selectFolder.Name ? selectFolder.Name : "Root"} +

+
SetIsOpen(true)}> + +
+
+

+ {selectFolder && selectFolder.Name ? `(${folderPath})` : ""} +

+
+
+ {isOpen && ( +
+
+
+ Select Folder +
+
+ +
+
+
+
+
+ removeTabListItem(e)} + > + Root /{" "} + + {tabList && + tabList.map((tab, i) => ( + + removeTabListItem(e, i)} + > + {tab.Name} + + {" / "} + + ))} +
+
+
+ {!isAdd && + folderList.length > 0 && + folderList.map((folder) => ( +
handleSelect(folder)} + > +
+ + {folder.Name} +
+
+ ))} + {isAdd && ( + + )} + {isLoader && ( +
+ +
+ )} +
+
+
+
+
+ {isAdd ? ( + + ) : ( + + )} +
+
+ +
+
+
+ )} +
+ ); +}; + +export default SelectFolder; diff --git a/apps/OpenSign/src/components/fields/SignersInput.js b/apps/OpenSign/src/components/fields/SignersInput.js new file mode 100644 index 000000000..0e9697965 --- /dev/null +++ b/apps/OpenSign/src/components/fields/SignersInput.js @@ -0,0 +1,190 @@ +import React, { useState, useEffect } from "react"; +import Select from "react-select"; +import AppendFormInForm from "../AppendFormInForm"; +import Modal from "react-modal"; +import Parse from "parse"; +function arrayMove(array, from, to) { + array = array.slice(); + array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]); + return array; +} + +/** + * react-sortable-hoc is depcreated not usable from react 18.x.x + * need to replace it with @dnd-kit + * code changes required + */ + +const SignersInput = (props) => { + Modal.setAppElement("body"); + const [state, setState] = useState(undefined); + // const [editFormData, setEditFormData] = useState([]); + const [selected, setSelected] = React.useState([]); + const [isModal, setIsModel] = useState(false); + const onChange = (selectedOptions) => setSelected(selectedOptions); + const [modalIsOpen, setModalIsOpen] = useState(false); + + const onSortEnd = ({ oldIndex, newIndex }) => { + const newValue = arrayMove(selected, oldIndex, newIndex); + setSelected(newValue); + }; + + const GetSelectListData = async () => { + try { + const currentUser = Parse.User.current(); + const contactbook = new Parse.Query("contracts_Contactbook"); + contactbook.equalTo( + "CreatedBy", + Parse.User.createWithoutData(currentUser.id) + ); + contactbook.notEqualTo("IsDeleted", true); + const contactRes = await contactbook.find(); + if (contactRes) { + const res = JSON.parse(JSON.stringify(contactRes)); + let list = []; + + // let _selected = []; + res.forEach((x) => { + let obj = { + label: x.Name, + value: x.objectId, + isChecked: true + }; + + list.push(obj); + }); + setState(list); + } + } catch (error) { + console.log("err", error); + } + }; + + useEffect(() => { + GetSelectListData(); + }, []); + + useEffect(() => { + if (selected && selected.length) { + let newData = []; + selected.forEach((x) => { + newData.push(x.value); + }); + if (props.onChange) { + props.onChange(newData); + } + } + + // eslint-disable-next-line + }, [selected]); + + const handleModalCloseClick = () => { + setIsModel(false); + setModalIsOpen(false); + }; + + const openModal = () => { + setModalIsOpen(true); + }; + + // `handleNewDetails` is used to set just save from quick form to selected option in dropdown + const handleNewDetails = (data) => { + setState([...state, data]); + if (selected.length > 0) { + setSelected([...selected, data]); + } else { + setSelected([data]); + } + }; + + return ( +
+ +
+
+ handleFileInput(e)} + accept="application/pdf,application/vnd.ms-excel" + required + /> + {process.env.DROPBOX_APP_KEY && ( + + )} +
+ )} +
+
+ + handleStrInput(e)} + required + /> +
+
+ + handleStrInput(e)} + /> +
+
+ + handleStrInput(e)} + /> +
+ + +
+ +
handleReset()} + > + Reset +
+
+ +
+ ); +}; + +export default TemplateForm; diff --git a/apps/OpenSign/src/routes/Form.js b/apps/OpenSign/src/routes/Form.js index fd5c67449..2c6c67709 100644 --- a/apps/OpenSign/src/routes/Form.js +++ b/apps/OpenSign/src/routes/Form.js @@ -25,6 +25,7 @@ import TreeWidget from "../components/TreeWidget"; import parse from "html-react-parser"; import Title from "../components/Title"; import { formJson } from "../json/FormJson"; +import TemplateForm from "../primitives/TemplateForm"; const widget = { TimeWidget: TimeWidget }; @@ -41,15 +42,19 @@ const fields = () => { function FormBuilderFn(props) { const { id } = useParams(); const navigate = useNavigate(); - return ( - - ); + if (id === "template") { + return ; + } else { + return ( + + ); + } } class FormBuilder extends Component { state = { diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index e42ee38e5..ee8cac810 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -17,6 +17,7 @@ import getUserDetails from './parsefunction/getUserDetails.js'; import getDocument from './parsefunction/getDocument.js'; import getDrive from './parsefunction/getDrive.js'; import getReport from './parsefunction/getReport.js'; +import TemplateAfterSave from './parsefunction/TemplateAfterSave.js'; Parse.Cloud.define('AddUserToRole', addUserToGroups); Parse.Cloud.define('UserGroups', getUserGroups); @@ -26,9 +27,6 @@ Parse.Cloud.define('googlesign', GoogleSign); Parse.Cloud.define('zohodetails', ZohoDetails); Parse.Cloud.define('usersignup', usersignup); Parse.Cloud.define('facebooksign', FacebookSign); -Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); -Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave); -Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave); Parse.Cloud.define('SendOTPMailV1', sendMailOTPv1); Parse.Cloud.define('sendmail', SendMailv1); Parse.Cloud.define('AuthLoginAsMail', AuthLoginAsMail); @@ -36,4 +34,8 @@ Parse.Cloud.define('getUserId', getUserId); Parse.Cloud.define('getUserDetails', getUserDetails); Parse.Cloud.define('getDocument', getDocument); Parse.Cloud.define('getDrive', getDrive) -Parse.Cloud.define('getReport', getReport) \ No newline at end of file +Parse.Cloud.define('getReport', getReport) +Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); +Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave); +Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave); +Parse.Cloud.afterSave("contracts_Template", TemplateAfterSave) \ No newline at end of file diff --git a/apps/OpenSignServer/cloud/parsefunction/TemplateAfterSave.js b/apps/OpenSignServer/cloud/parsefunction/TemplateAfterSave.js new file mode 100644 index 000000000..7d22ccb86 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/TemplateAfterSave.js @@ -0,0 +1,83 @@ +export default async function TemplateAfterSave(request) { + try { + if (!request.original) { + console.log('new entry is insert in contracts_Template'); + // update acl of New Document If There are signers present in array + const signers = request.object.get('Signers'); + + if (signers && signers.length > 0) { + await updateAclDoc(request.object.id); + } else { + await updateSelfDoc(request.object.id); + } + } else { + if (request.user) { + const signers = request.object.get('Signers'); + if (signers && signers.length > 0) { + await updateAclDoc(request.object.id); + } else { + await updateSelfDoc(request.object.id); + } + } + } + } catch (err) { + console.log('err in aftersave of contracts_Template'); + console.log(err); + } + + async function updateAclDoc(objId) { + // console.log("In side updateAclDoc func") + // console.log(objId) + const Query = new Parse.Query('contracts_Template'); + Query.include('Signers'); + const updateACL = await Query.get(objId, { useMasterKey: true }); + const res = JSON.parse(JSON.stringify(updateACL)); + // console.log("res"); + // console.log(JSON.stringify(res)); + const UsersPtr = res.Signers.map(item => item.UserId); + + if (res.Signers[0].ExtUserPtr) { + const ExtUserSigners = res.Signers.map(item => { + return { + __type: 'Pointer', + className: 'contracts_Users', + objectId: item.ExtUserPtr.objectId, + }; + }); + updateACL.set('Signers', ExtUserSigners); + } + + // console.log("UsersPtr") + // console.log(JSON.stringify(UsersPtr)) + const newACL = new Parse.ACL(); + newACL.setPublicReadAccess(false); + newACL.setPublicWriteAccess(false); + newACL.setReadAccess(request.user, true); + newACL.setWriteAccess(request.user, true); + + UsersPtr.forEach(x => { + newACL.setReadAccess(x.objectId, true); + newACL.setWriteAccess(x.objectId, true); + }); + + updateACL.setACL(newACL); + updateACL.save(null, { useMasterKey: true }); + } + + async function updateSelfDoc(objId) { + // console.log("Inside updateSelfDoc func") + + const Query = new Parse.Query('contracts_Template'); + const updateACL = await Query.get(objId, { useMasterKey: true }); + // const res = JSON.parse(JSON.stringify(updateACL)); + // console.log("res"); + // console.log(JSON.stringify(res)); + const newACL = new Parse.ACL(); + newACL.setPublicReadAccess(false); + newACL.setPublicWriteAccess(false); + newACL.setReadAccess(request.user, true); + newACL.setWriteAccess(request.user, true); + updateACL.setACL(newACL); + updateACL.save(null, { useMasterKey: true }); + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js index 8ad9c47df..66d743a6a 100644 --- a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js +++ b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js @@ -154,7 +154,7 @@ export default function reportJson(id, userId) { $gt: { __type: 'Date', iso: new Date().toISOString() }, }, }, - keys: ['Name', 'Note', 'Folder.Name', 'URL', 'ExtUserPtr.Name', 'Signers.Name'], + keys: ['Name', 'Folder.Name', 'URL', 'ExtUserPtr.Name', 'Signers.Name'], }; // Recent signature requests report show on dashboard case '5Go51Q7T8r': @@ -169,16 +169,7 @@ export default function reportJson(id, userId) { }, Placeholders: { $ne: null }, }, - keys: [ - 'Name', - 'Note', - 'Folder.Name', - 'URL', - 'ExtUserPtr.Name', - 'Signers.Name', - 'Signers.UserId', - 'AuditTrail', - ], + keys: ['Name', 'URL', 'ExtUserPtr.Name', 'Signers.Name', 'Signers.UserId', 'AuditTrail'], }; // Drafts report show on dashboard case 'kC5mfynCi4': diff --git a/apps/OpenSignServer/databases/migrations/20231129103946-update_menu.cjs b/apps/OpenSignServer/databases/migrations/20231129103946-update_menu.cjs index 9581be3a8..c897aefac 100644 --- a/apps/OpenSignServer/databases/migrations/20231129103946-update_menu.cjs +++ b/apps/OpenSignServer/databases/migrations/20231129103946-update_menu.cjs @@ -39,6 +39,14 @@ exports.up = async Parse => { description: '', objectId: '8mZzFxbG1z', }, + { + icon: 'fas fa-file-signature', + title: 'New template', + target: '_self', + pageType: 'form', + description: '', + objectId: 'template', + }, ], }, { diff --git a/apps/OpenSignServer/databases/migrations/20231208132950-create_template_cls.cjs b/apps/OpenSignServer/databases/migrations/20231208132950-create_template_cls.cjs new file mode 100644 index 000000000..47fe71b87 --- /dev/null +++ b/apps/OpenSignServer/databases/migrations/20231208132950-create_template_cls.cjs @@ -0,0 +1,40 @@ +/** + * + * @param {Parse} Parse + */ +exports.up = async Parse => { + const className = 'contracts_Template'; + const schema = new Parse.Schema(className); + + schema.addString('Name'); + schema.addString('URL'); + schema.addString('Note'); + schema.addString('Description'); + schema.addArray('Signers'); + schema.addBoolean('IsArchive'); + schema.addArray('Placeholders'); + schema.addPointer('Folder', 'contracts_Template'); + schema.addString('Type'); + schema.addPointer('CreatedBy', '_User'); + schema.addPointer('ExtUserPtr', 'contracts_Users'); + schema.addBoolean('EnablePhoneOTP') + schema.addBoolean('EnableEmailOTP') + schema.addBoolean('SendinOrder') + schema.addBoolean('SentToOthers') + schema.addBoolean('AutomaticReminders') + + + + return schema.save(); +}; + +/** + * + * @param {Parse} Parse + */ +exports.down = async Parse => { + const className = 'contracts_Template'; + const schema = new Parse.Schema(className); + + return schema.purge().then(() => schema.delete()); +}; diff --git a/apps/OpenSignServer/index.js b/apps/OpenSignServer/index.js index 819940954..d8944d8d3 100644 --- a/apps/OpenSignServer/index.js +++ b/apps/OpenSignServer/index.js @@ -69,7 +69,9 @@ if (process.env.SMTP_ENABLE) { export const config = { databaseURI: process.env.DATABASE_URI || process.env.MONGODB_URI || 'mongodb://localhost:27017/dev', - cloud: process.env.CLOUD || __dirname + '/cloud/main.js', + cloud: function () { + import('./cloud/main.js'); + }, appId: process.env.APP_ID || 'myAppId', masterKey: process.env.MASTER_KEY || '', //Add your master key here. Keep it secret! masterKeyIps: ['0.0.0.0/0', '::1'], // '::1'