diff --git a/apps/OpenSign/src/App.js b/apps/OpenSign/src/App.js index 70e707b01..40b6801b6 100644 --- a/apps/OpenSign/src/App.js +++ b/apps/OpenSign/src/App.js @@ -18,6 +18,8 @@ import LazyPage from "./primitives/LazyPage"; import { isEnableSubscription } from "./constant/const"; import SSOVerify from "./pages/SSOVerify"; import Loader from "./primitives/Loader"; +import TeamList from "./pages/TeamList"; +import UserList from "./pages/UserList"; const DebugPdf = lazy(() => import("./pages/DebugPdf")); const ForgetPassword = lazy(() => import("./pages/ForgetPassword")); const GuestLogin = lazy(() => import("./pages/GuestLogin")); @@ -120,6 +122,8 @@ function App() { )} }> + } /> + } /> } diff --git a/apps/OpenSign/src/components/AddDepartment.js b/apps/OpenSign/src/components/AddDepartment.js new file mode 100644 index 000000000..6af89e6f0 --- /dev/null +++ b/apps/OpenSign/src/components/AddDepartment.js @@ -0,0 +1,237 @@ +import React, { useEffect, useState } from "react"; +import Parse from "parse"; +import Title from "./Title"; +import Alert from "../primitives/Alert"; +import Loader from "../primitives/Loader"; + +const AddDepartment = (props) => { + const [formdata, setFormdata] = useState({ + name: "", + department: "" + }); + const [isLoader, setIsLoader] = useState(false); + const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); + const [departmentList, setDepartmentList] = useState([]); + useEffect(() => { + getDepartmentList(); + }, []); + + const getDepartmentList = async () => { + try { + const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + const department = new Parse.Query("contracts_Departments"); + department.equalTo("OrganizationId", { + __type: "Pointer", + className: "contracts_Organizations", + objectId: extUser.OrganizationId.objectId + }); + department.equalTo("IsActive", true); + const departmentRes = await department.find(); + if (departmentRes.length > 0) { + const _departmentRes = JSON.parse(JSON.stringify(departmentRes)); + setDepartmentList(_departmentRes); + } + } catch (err) { + console.log("Err in fetch top level departmentlist", err); + } + }; + + const handleDropdown = (e) => { + setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value })); + }; + // Define a function to handle form submission + const handleSubmit = async (e) => { + e.preventDefault(); + e.stopPropagation(); + // Extracting values except for the 'name' key + let updatedAncestors = []; + if (formdata.department) { + const Ancestors = departmentList.find( + (x) => x.objectId === formdata.department + )?.Ancestors; + if (Ancestors && Ancestors.length > 0) { + updatedAncestors = Ancestors.map((x) => ({ + __type: "Pointer", + className: "contracts_Departments", + objectId: x.objectId + })); + } else { + const AllUser = departmentList.find((x) => x.objectId === "All Users"); + updatedAncestors = [ + AllUser, + { + __type: "Pointer", + className: "contracts_Departments", + objectId: formdata.department + } + ]; + } + } + try { + const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + setIsLoader(true); + const department = new Parse.Query("contracts_Departments"); + department.equalTo("Name", formdata.name); + if (updatedAncestors.length > 0) { + const ParentId = updatedAncestors[updatedAncestors.length - 1]; + department.equalTo("ParentId", ParentId); + } + if (localUser && localUser.OrganizationId) { + department.equalTo("OrganizationId", { + __type: "Pointer", + className: "contracts_Organizations", + objectId: localUser.OrganizationId.objectId + }); + } + const isDepartment = await department.first(); + if (isDepartment) { + setIsAlert({ type: "info", msg: "Department already exists." }); + setIsLoader(false); + } else { + const newDepartment = new Parse.Object("contracts_Departments"); + newDepartment.set("Name", formdata.name); + if (updatedAncestors.length > 0) { + const ParentId = updatedAncestors[updatedAncestors.length - 1]; + newDepartment.set("ParentId", ParentId); + newDepartment.set("Ancestors", updatedAncestors); + } + if (localUser && localUser.OrganizationId) { + newDepartment.set("OrganizationId", { + __type: "Pointer", + className: "contracts_Organizations", + objectId: localUser.OrganizationId.objectId + }); + } + const newdepartmentRes = await newDepartment.save(); + if (updatedAncestors.length > 0) { + const ParentId = departmentList.find( + (x) => x.objectId === formdata.department + ); + props.handleTeamInfo({ + objectId: newdepartmentRes.id, + Name: formdata.name, + ParentId: ParentId, + Ancestors: updatedAncestors, + IsActive: true + }); + } else { + props.handleTeamInfo({ + objectId: newdepartmentRes.id, + Name: formdata.name, + ParentId: "", + Ancestors: "", + IsActive: true + }); + } + + if (props.closePopup) { + props.closePopup(); + } + setFormdata({ + name: "", + department: { name: "", objectId: "" } + }); + setIsAlert({ + type: "success", + msg: "Department created successfully." + }); + } + } catch (err) { + console.log("err in save department", err); + setIsAlert({ type: "danger", msg: "Something went wrong." }); + } finally { + setTimeout(() => { + setIsAlert({ type: "success", msg: "" }); + }, 1500); + setIsLoader(false); + } + }; + + // Define a function to handle the "add yourself" checkbox + const handleReset = () => { + setFormdata({ + name: "", + department: { name: "", objectId: "" } + }); + }; + const handleChange = (e) => { + setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value })); + }; + + return ( +
+ + {isAlert.msg && ( + <Alert type={isAlert.type}> + <div className="ml-3">{isAlert.msg}</div> + </Alert> + )} + {isLoader && ( + <div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50 rounded-box"> + <Loader /> + </div> + )} + <div className="w-full mx-auto"> + <form onSubmit={handleSubmit}> + {/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */} + <div className="mb-3"> + <label + htmlFor="name" + className="block text-xs text-gray-700 font-semibold" + > + Name + <span className="text-[red] text-[13px]"> *</span> + </label> + <input + type="text" + name="name" + value={formdata.name} + onChange={(e) => handleChange(e)} + required + className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" + /> + </div> + + <div className="mb-3"> + <label + htmlFor="phone" + className="block text-xs text-gray-700 font-semibold" + > + Parent Team + </label> + <select + value={formdata.department} + onChange={(e) => handleDropdown(e)} + name="department" + className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs" + > + <option defaultValue={""} value={""}> + select + </option> + {departmentList.length > 0 && + departmentList.map((x) => ( + <option key={x.objectId} value={x.objectId}> + {x.Name} + </option> + ))} + </select> + </div> + <div className="flex items-center mt-3 gap-2 text-white"> + <button type="submit" className="op-btn op-btn-primary"> + Submit + </button> + <div + type="button" + onClick={() => handleReset()} + className="op-btn op-btn-secondary" + > + Reset + </div> + </div> + </form> + </div> + </div> + ); +}; + +export default AddDepartment; diff --git a/apps/OpenSign/src/components/AddUser.js b/apps/OpenSign/src/components/AddUser.js index 088e9e786..38e3de2ea 100644 --- a/apps/OpenSign/src/components/AddUser.js +++ b/apps/OpenSign/src/components/AddUser.js @@ -1,24 +1,62 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import Parse from "parse"; import axios from "axios"; import Title from "./Title"; import Alert from "../primitives/Alert"; import Loader from "../primitives/Loader"; +import { copytoData } from "../constant/Utils"; +function generatePassword(length) { + const characters = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + const charactersLength = characters.length; + + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} const AddUser = (props) => { - const [name, setName] = useState(""); - const [phone, setPhone] = useState(""); - const [email, setEmail] = useState(""); + const [formdata, setFormdata] = useState({ + name: "", + phone: "", + email: "", + department: "", + password: "", + role: "" + }); const [isLoader, setIsLoader] = useState(false); - const [isUserExist, setIsUserExist] = useState(false); + const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); + const [departmentList, setDepartmentList] = useState([]); + const role = ["OrgAdmin", "Manager", "User"]; const parseBaseUrl = localStorage.getItem("baseUrl"); const parseAppId = localStorage.getItem("parseAppId"); + useEffect(() => { + getDepartmentList(); + }, []); + + const getDepartmentList = async () => { + setFormdata((prev) => ({ ...prev, password: generatePassword(12) })); + const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + const department = new Parse.Query("contracts_Departments"); + department.equalTo("OrganizationId", { + __type: "Pointer", + className: "contracts_Organizations", + objectId: extUser.OrganizationId.objectId + }); + department.equalTo("IsActive", true); + const departmentRes = await department.find(); + if (departmentRes.length > 0) { + const _departmentRes = JSON.parse(JSON.stringify(departmentRes)); + setDepartmentList(_departmentRes); + } + }; const checkUserExist = async () => { try { const res = await Parse.Cloud.run("getUserDetails", { - email: email, - userId: Parse.User.current().id + email: formdata.email }); if (res) { return true; @@ -33,24 +71,43 @@ const AddUser = (props) => { const handleSubmit = async (e) => { e.preventDefault(); e.stopPropagation(); + const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; setIsLoader(true); const res = await checkUserExist(); if (res) { - setIsUserExist(true); + setIsAlert({ type: "danger", msg: "User already exist." }); setIsLoader(false); setTimeout(() => { - setIsUserExist(false); + setIsAlert({ type: "success", msg: "" }); }, 1000); } else { try { - const contactQuery = new Parse.Object("contracts_Users"); - contactQuery.set("Name", name); - contactQuery.set("Phone", phone); - contactQuery.set("Email", email); - contactQuery.set("UserRole", "contracts_User"); + const extUser = new Parse.Object("contracts_Users"); + extUser.set("Name", formdata.name); + if (formdata.phone) { + extUser.set("Phone", formdata.phone); + } + extUser.set("Email", formdata.email); + extUser.set("UserRole", `contracts_${formdata.role}`); + if (formdata?.department) { + extUser.set("DepartmentIds", [ + { + __type: "Pointer", + className: "contracts_Departments", + objectId: formdata.department + } + ]); + } + if (localUser && localUser.OrganizationId) { + extUser.set("OrganizationId", { + __type: "Pointer", + className: "contracts_Organizations", + objectId: localUser.OrganizationId.objectId + }); + } if (localStorage.getItem("TenantId")) { - contactQuery.set("TenantId", { + extUser.set("TenantId", { __type: "Pointer", className: "partners_Tenant", objectId: localStorage.getItem("TenantId") @@ -60,12 +117,12 @@ const AddUser = (props) => { try { const _users = Parse.Object.extend("User"); const _user = new _users(); - _user.set("name", name); - _user.set("username", email); - _user.set("email", email); - _user.set("password", email); - if (phone) { - _user.set("phone", phone); + _user.set("name", formdata.name); + _user.set("username", formdata.email); + _user.set("email", formdata.email); + _user.set("password", formdata.password); + if (formdata.phone) { + _user.set("phone", formdata.phone); } const user = await _user.save(); @@ -77,52 +134,58 @@ const AddUser = (props) => { sessionToken: localStorage.getItem("accesstoken") }; const body = { - appName: localStorage.getItem("_appName"), - roleName: "contracts_User", + appName: "contracts", + roleName: "contracts_" + formdata.role, userId: user.id }; await axios.post(roleurl, body, { headers: headers }); const currentUser = Parse.User.current(); - contactQuery.set( + extUser.set( "CreatedBy", Parse.User.createWithoutData(currentUser.id) ); - contactQuery.set("UserId", user); + extUser.set("UserId", user); const acl = new Parse.ACL(); acl.setPublicReadAccess(true); acl.setPublicWriteAccess(true); acl.setReadAccess(currentUser.id, true); acl.setWriteAccess(currentUser.id, true); - contactQuery.setACL(acl); + extUser.setACL(acl); - const res = await contactQuery.save(); + const res = await extUser.save(); const parseData = JSON.parse(JSON.stringify(res)); - if (props.details) { - props.details({ - value: parseData[props.valueKey], - label: parseData[props.displayKey] - }); - } + if (props.closePopup) { props.closePopup(); } if (props.handleUserData) { + if (formdata?.department) { + const department = departmentList.find( + (x) => x.objectId === formdata.department + ); + parseData.DepartmentIds = parseData.DepartmentIds.map((y) => + y.objectId === department.objectId ? department : y + ); + } props.handleUserData(parseData); } setIsLoader(false); - - setName(""); - setPhone(""); - setEmail(""); + setFormdata({ + name: "", + email: "", + phone: "", + department: "", + role: "" + }); } } catch (err) { console.log("err ", err); if (err.code === 202) { - const params = { email: email }; + const params = { email: formdata.email }; const userRes = await Parse.Cloud.run("getUserId", params); const roleurl = `${parseBaseUrl}functions/AddUserToRole`; const headers = { @@ -131,18 +194,18 @@ const AddUser = (props) => { sessionToken: localStorage.getItem("accesstoken") }; const body = { - appName: localStorage.getItem("_appName"), - roleName: "contracts_User", + appName: "contracts", + roleName: "contracts_" + formdata.role, userId: userRes.id }; await axios.post(roleurl, body, { headers: headers }); const currentUser = Parse.User.current(); - contactQuery.set( + extUser.set( "CreatedBy", Parse.User.createWithoutData(currentUser.id) ); - contactQuery.set("UserId", { + extUser.set("UserId", { __type: "Pointer", className: "_User", objectId: userRes.id @@ -153,55 +216,85 @@ const AddUser = (props) => { acl.setReadAccess(currentUser.id, true); acl.setWriteAccess(currentUser.id, true); - contactQuery.setACL(acl); - const res = await contactQuery.save(); + extUser.setACL(acl); + const res = await extUser.save(); const parseData = JSON.parse(JSON.stringify(res)); - if (props.details) { - props.details({ - value: parseData[props.valueKey], - label: parseData[props.displayKey] - }); - } if (props.closePopup) { props.closePopup(); } if (props.handleUserData) { + if (formdata?.department) { + const department = departmentList.find( + (x) => x.objectId === formdata.department + ); + parseData.DepartmentIds = parseData.DepartmentIds.map((y) => + y.objectId === department.objectId ? department : y + ); + } + props.handleUserData(parseData); } setIsLoader(false); - setName(""); - setPhone(""); - setEmail(""); + setFormdata({ + name: "", + email: "", + phone: "", + department: "", + role: "" + }); } } } catch (err) { - // console.log("err", err); + console.log("err", err); setIsLoader(false); - alert("something went wrong!"); + setIsAlert({ type: "danger", msg: "something went wrong." }); + } finally { + setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500); } } }; // Define a function to handle the "add yourself" checkbox const handleReset = () => { - setName(""); - setPhone(""); - setEmail(""); + setFormdata({ + name: "", + email: "", + phone: "", + department: "", + role: "" + }); + if (props.closePopup) { + props.closePopup(); + } + }; + const handleChange = (e) => { + setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value })); }; + const copytoclipboard = (text) => { + copytoData(text); + setIsAlert({ type: "success", msg: "Copied" }); + setTimeout(() => { + setIsAlert({ type: "success", msg: "" }); + }, 1500); // Reset copied state after 1.5 seconds + }; return ( - <div className="shadow-md rounded my-2 p-3 bg-[#ffffff] md:border-[1px] md:border-gray-600/50"> + <div className="shadow-md rounded-box my-[1px] p-3 bg-[#ffffff]"> <Title title={"Add User"} /> - {isUserExist && <Alert type="danger">User already exists!</Alert>} + {isAlert.msg && ( + <Alert type={isAlert.type}> + <div className="ml-3">{isAlert.msg}</div> + </Alert> + )} {isLoader && ( - <div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50"> + <div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30 z-50 rounded-box"> <Loader /> </div> )} <div className="w-full mx-auto"> <form onSubmit={handleSubmit}> - <h1 className="text-[20px] font-semibold mb-4">Add User</h1> + {/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */} <div className="mb-3"> <label htmlFor="name" @@ -212,9 +305,9 @@ const AddUser = (props) => { </label> <input type="text" - id="name" - value={name} - onChange={(e) => setName(e.target.value)} + name="name" + value={formdata.name} + onChange={(e) => handleChange(e)} required className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" /> @@ -229,44 +322,108 @@ const AddUser = (props) => { </label> <input type="email" - id="email" - value={email} - onChange={(e) => setEmail(e.target.value)} + name="email" + value={formdata.email} + onChange={(e) => handleChange(e)} required className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" /> </div> + <div className="mb-3"> + <label + htmlFor="email" + className="block text-xs text-gray-700 font-semibold" + > + Password + </label> + <div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]"> + <div className="break-all">{formdata?.password}</div> + <i + onClick={() => copytoclipboard(formdata?.password)} + className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px]" + ></i> + </div> + <div className="text-[12px] ml-2 mb-0 text-[red]"> + Password will only be generated once; make sure to copy it. + </div> + </div> + <div className="mb-3"> + <label + htmlFor="phone" + className="block text-xs text-gray-700 font-semibold" + > + Phone + {/* <span className="text-[red] text-[13px]"> *</span> */} + </label> + <input + type="text" + name="phone" + placeholder="optional" + value={formdata.phone} + onChange={(e) => handleChange(e)} + // required + className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" + /> + </div> <div className="mb-3"> <label htmlFor="phone" className="block text-xs text-gray-700 font-semibold" > - Phone - <span className="text-[red] text-[13px]"> *</span> + Team </label> - <input - type="text" - id="phone" - value={phone} - onChange={(e) => setPhone(e.target.value)} + <select + value={formdata.department} + onChange={(e) => handleChange(e)} + name="department" + className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs" required - className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" - /> - </div> - - <div className="flex items-center mt-3 gap-2 text-white"> - <button - type="submit" - className="bg-[#1ab6ce] rounded-sm shadow-md text-[13px] font-semibold uppercase text-white py-1.5 px-2.5 focus:outline-none" > + <option defaultValue={""} value={""}> + select + </option> + {departmentList.length > 0 && + departmentList.map((x) => ( + <option key={x.objectId} value={x.objectId}> + {x.Name} + </option> + ))} + </select> + </div> + <div className="mb-3"> + <label + htmlFor="phone" + className="block text-xs text-gray-700 font-semibold" + > + Role + </label> + <select + value={formdata.role} + onChange={(e) => handleChange(e)} + name="role" + className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs" + > + <option defaultValue={""} value={""}> + select + </option> + {role.length > 0 && + role.map((x) => ( + <option key={x} value={x}> + {x} + </option> + ))} + </select> + </div> + <div className="flex items-center mt-3 gap-2 text-white"> + <button type="submit" className="op-btn op-btn-primary"> Submit </button> <div type="button" onClick={() => handleReset()} - 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="op-btn op-btn-secondary" > - Reset + Cancel </div> </div> </form> diff --git a/apps/OpenSign/src/components/pdf/PdfHeader.js b/apps/OpenSign/src/components/pdf/PdfHeader.js index c90521eb2..4223194b9 100644 --- a/apps/OpenSign/src/components/pdf/PdfHeader.js +++ b/apps/OpenSign/src/components/pdf/PdfHeader.js @@ -298,6 +298,17 @@ function Header({ ) : isPdfRequestFiles ? ( alreadySign ? ( <div className="flex flex-row"> + <button + onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)} + type="button" + className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow" + > + <i + className="fa-light fa-print py-[3px]" + aria-hidden="true" + ></i> + <span className="hidden lg:block">Print</span> + </button> {isCompleted && ( <button type="button" @@ -313,19 +324,6 @@ function Header({ <span className="hidden lg:block">Certificate</span> </button> )} - - <button - onClick={(e) => handleToPrint(e, pdfUrl, setIsDownloading)} - type="button" - className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow" - > - <i - className="fa-light fa-print py-[3px]" - aria-hidden="true" - ></i> - <span className="hidden lg:block">Print</span> - </button> - <button type="button" className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow" diff --git a/apps/OpenSign/src/components/sidebar/Sidebar.js b/apps/OpenSign/src/components/sidebar/Sidebar.js index 8e01910df..23fb34fb6 100644 --- a/apps/OpenSign/src/components/sidebar/Sidebar.js +++ b/apps/OpenSign/src/components/sidebar/Sidebar.js @@ -23,29 +23,57 @@ const Sidebar = ({ isOpen, closeSidebar }) => { const menuItem = async () => { try { if (localStorage.getItem("defaultmenuid")) { - const menuId = localStorage.getItem("defaultmenuid") !== "VPh91h0ZHk"; - if (menuId) { - setmenuList(sidebarList); - } else { - const addUserForm = { - icon: "fa-light fa-user", - title: "Add User", - target: "_self", - pageType: "form", - description: "", - objectId: "lM0xRnM3iE" - }; + const Extand_Class = localStorage.getItem("Extand_Class"); + const extClass = Extand_Class && JSON.parse(Extand_Class); + // console.log("extClass ", extClass); + let userRole = "contracts_Users"; + if (extClass && extClass.length > 0) { + userRole = extClass[0].UserRole; + } + if ( + userRole === "contracts_Admin" || + userRole === "contracts_OrgAdmin" + ) { + // const addUserForm = { + // icon: "fa-light fa-user", + // title: "Add User", + // target: "_self", + // pageType: "form", + // description: "", + // objectId: "lM0xRnM3iE" + // }; const newSidebarList = sidebarList.map((item) => { if (item.title === "Settings") { // Make a shallow copy of the item const newItem = { ...item }; + newItem.children = [ + ...newItem.children, + { + icon: "fa-light fa-building-memo", + title: "Teams", + target: "_self", + pageType: "", + description: "", + objectId: "teams" + }, + { + icon: "fa-light fa-users fa-fw", + title: "Users", + target: "_self", + pageType: "", + description: "", + objectId: "users" + } + ]; // Insert addUserForm at the second position - newItem.children.splice(1, 0, addUserForm); + // newItem.children.splice(1, 0, addUserForm); return newItem; } return item; }); setmenuList(newSidebarList); + } else { + setmenuList(sidebarList); } } } catch (e) { @@ -63,7 +91,7 @@ const Sidebar = ({ isOpen, closeSidebar }) => { }; return ( <aside - className={`absolute lg:relative bg-base-100 h-screen overflow-y-auto transition-all z-[999] shadow-lg hide-scrollbar + className={`absolute lg:relative bg-base-100 h-screen overflow-y-auto transition-all z-[500] shadow-lg hide-scrollbar ${isOpen ? "w-full md:w-[300px]" : "w-0"}`} > <div className="flex px-2 py-3 gap-2 items-center shadow-md"> diff --git a/apps/OpenSign/src/constant/Utils.js b/apps/OpenSign/src/constant/Utils.js index 3976d8b1c..beefad8e6 100644 --- a/apps/OpenSign/src/constant/Utils.js +++ b/apps/OpenSign/src/constant/Utils.js @@ -63,7 +63,7 @@ export async function fetchSubscription( return { plan: "", billingDate: "" }; } } -//function to get subcripition details from Extand user class +//function to get subcripition details from subscription class export async function checkIsSubscribed() { try { const res = await fetchSubscription(); @@ -84,6 +84,28 @@ export async function checkIsSubscribed() { } } +//function to get subcripition details from subscription class +export async function checkIsSubscribedTeam() { + try { + const res = await fetchSubscription(); + if (res.plan === "freeplan") { + return false; + } else if (res.billingDate) { + const plan = res.plan === "team-weekly" || res.plan === "team-yearly"; + if (plan && new Date(res.billingDate) > new Date()) { + return true; + } else { + return false; + } + } else { + return false; + } + } catch (err) { + console.log("Err in fetch subscription", err); + return false; + } +} + export const color = [ "#93a3db", "#e6c3db", diff --git a/apps/OpenSign/src/constant/appinfo.js b/apps/OpenSign/src/constant/appinfo.js index 5467b55b9..474501e20 100644 --- a/apps/OpenSign/src/constant/appinfo.js +++ b/apps/OpenSign/src/constant/appinfo.js @@ -21,15 +21,29 @@ export const appInfo = { objectId: "aIPmIvMzGM", settings: [ { - role: "contracts_User", + role: "contracts_Admin", + menuId: "VPh91h0ZHk", + pageType: "dashboard", + pageId: "35KBoSgoAK", + extended_class: "contracts_Users" + }, + { + role: "contracts_OrgAdmin", + menuId: "VPh91h0ZHk", + pageType: "dashboard", + pageId: "35KBoSgoAK", + extended_class: "contracts_Users" + }, + { + role: "contracts_Manager", menuId: "H9vRfEYKhT", pageType: "dashboard", pageId: "35KBoSgoAK", extended_class: "contracts_Users" }, { - role: "contracts_Admin", - menuId: "VPh91h0ZHk", + role: "contracts_User", + menuId: "H9vRfEYKhT", pageType: "dashboard", pageId: "35KBoSgoAK", extended_class: "contracts_Users" diff --git a/apps/OpenSign/src/json/ReportJson.js b/apps/OpenSign/src/json/ReportJson.js index bd35f9312..a33990b46 100644 --- a/apps/OpenSign/src/json/ReportJson.js +++ b/apps/OpenSign/src/json/ReportJson.js @@ -346,6 +346,14 @@ export default function reportJson(id) { message: "This menu reveals more options such as Edit & Delete. Use the 'Edit' button to add signer roles, modify fields, and update your template. Changes will apply to all future documents created from this template but won’t affect existing documents.Use the Delete button you can delete template. ", subaction: [ + { + btnId: "1873", + btnLabel: "Share-with", + hoverLabel: "Share-with", + btnIcon: "fa-light fa-share-nodes", + redirectUrl: "", + action: "sharewith" + }, { btnId: "2434", btnLabel: "Edit", diff --git a/apps/OpenSign/src/json/plansArr.js b/apps/OpenSign/src/json/plansArr.js index 3cc7b1696..5178f2d27 100644 --- a/apps/OpenSign/src/json/plansArr.js +++ b/apps/OpenSign/src/json/plansArr.js @@ -30,7 +30,7 @@ const plans = [ planName: "OPENSIGN™ PROFESSIONAL", currency: "$", monthlyPrice: "29.99", - yearlyPrice: "329.99", + yearlyPrice: "10 <sup>/month/user</sup>", subtitle: "Exclusive Access to advanced features.", btnText: "Subscribe", url: @@ -56,6 +56,36 @@ const plans = [ "Auto reminders" ] }, + { + planName: "OPENSIGN™ TEAMS", + currency: "$", + monthlyPrice: "40", + yearlyPrice: "20 <sup>/month/user</sup>", + subtitle: "Exclusive Access to advanced features.", + btnText: "Subscribe", + url: + window.location.origin === "http://localhost:3001" + ? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/team-weekly" + : "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d1a09dfa8085585cdd4ec4d7f32137f3/professional-monthly", + yearlyUrl: + window.location.origin === "http://localhost:3001" + ? "https://billing.zoho.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/team-weekly" + : "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly", + target: "_self", + benefits: [ + "Everything in OpenSign™ free, 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", + "Auto reminders" + ] + }, { planName: "OPENSIGN™ ENTERPRISE", currency: "", diff --git a/apps/OpenSign/src/layout/HomeLayout.js b/apps/OpenSign/src/layout/HomeLayout.js index 188d991b7..7c3073048 100644 --- a/apps/OpenSign/src/layout/HomeLayout.js +++ b/apps/OpenSign/src/layout/HomeLayout.js @@ -251,7 +251,7 @@ const HomeLayout = () => { }; return ( <div> - <div className="sticky top-0 z-[101]"> + <div className="sticky top-0 z-[501]"> {!isLoader && <Header showSidebar={showSidebar} />} </div> {isUserValid ? ( diff --git a/apps/OpenSign/src/pages/Form.js b/apps/OpenSign/src/pages/Form.js index e05dfcac7..d35d1cd23 100644 --- a/apps/OpenSign/src/pages/Form.js +++ b/apps/OpenSign/src/pages/Form.js @@ -645,8 +645,8 @@ const Forms = (props) => { </label> {fileupload.length > 0 ? ( <div className="flex gap-2 justify-center items-center"> - <div className="flex justify-between items-center op-input op-input-bordered op-input-sm w-full h-full text-x text-[13px]"> - <div className="break-all "> + <div className="flex justify-between items-center op-input op-input-bordered op-input-sm w-full h-full text-[13px]"> + <div className="break-all"> file selected: {getFileName(fileupload)} </div> <div diff --git a/apps/OpenSign/src/pages/Login.js b/apps/OpenSign/src/pages/Login.js index d788dbc07..8a41d5c86 100644 --- a/apps/OpenSign/src/pages/Login.js +++ b/apps/OpenSign/src/pages/Login.js @@ -110,68 +110,49 @@ function Login() { "X-Parse-Application-Id": parseAppId, sessionToken: _user.sessionToken }; - - let body = { - appname: localStorage.getItem("_appName") - }; + const body = { appname: "contracts" }; await axios .post(url, JSON.stringify(body), { headers: headers }) - .then((axiosRes) => { + .then(async (axiosRes) => { const roles = axiosRes.data.result; - if (roles) { - userRoles = roles; - let _currentRole = ""; - const valuesToExclude = [ - "contracts_Guest", - `${localStorage.getItem("_appName")}_appeditor` - ]; - if (userRoles.length > 1) { - const rolesfiltered = userRoles.filter( - (x) => !valuesToExclude.includes(x) - ); - if (rolesfiltered.length > 0) { - _currentRole = rolesfiltered[0]; - } - } else { - const rolesfiltered = userRoles.filter( - (x) => !valuesToExclude.includes(x) - ); - if (rolesfiltered.length > 0) { - _currentRole = userRoles[0]; - } else { - _currentRole = ""; - } - } - if ( - _currentRole && - _currentRole !== - `${localStorage.getItem("_appName")}_appeditor` - ) { - userSettings.forEach(async (element) => { - const redirectUrl = - location?.state?.from || - `/${element.pageType}/${element.pageId}`; - 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( - async (result) => { + const currentUser = Parse.User.current(); + await Parse.Cloud.run("getUserDetails", { + email: currentUser.get("email") + }) + .then((extUser) => { + if (roles) { + userRoles = roles; + let _currentRole = ""; + const valuesToExclude = [ + "contracts_Guest", + "contracts_appeditor" + ]; + const filterRoles = userRoles.filter( + (x) => !valuesToExclude.includes(x) + ); + if (filterRoles.length > 0) { + _currentRole = filterRoles?.[0]; + userSettings.forEach(async (element) => { + const redirectUrl = + location?.state?.from || + `/${element.pageType}/${element.pageId}`; + if (element.role === _currentRole) { + let _role = _currentRole.replace( + "contracts_", + "" + ); + localStorage.setItem("_user_role", _role); + // Get TenentID from Extendend Class + localStorage.setItem( + "extended_class", + element.extended_class + ); let tenentInfo = []; - const results = [result]; + const results = [extUser]; if (results) { let extendedInfo_stringify = JSON.stringify(results); + localStorage.setItem( "Extand_Class", extendedInfo_stringify @@ -332,28 +313,21 @@ function Login() { navigate(redirectUrl); } } - }, - (error) => { - const payload = { - sessionToken: user.getSessionToken() - }; - handleSubmitbtn(payload); - console.error( - "Error while fetching Follow", - error - ); } - ); + }); } - }); - } else { - setState({ ...state, loading: false }); - setIsModal(true); - } - } else { - setState({ ...state, loading: false }); - setIsModal(true); - } + } else { + setState({ ...state, loading: false }); + setIsModal(true); + } + }) + .catch((error) => { + const payload = { + sessionToken: user.getSessionToken() + }; + handleSubmitbtn(payload); + console.error("Error while fetching Follow", error); + }); }) .catch((err) => { console.log("err", err); @@ -474,7 +448,7 @@ function Login() { let _currentRole = ""; const valuesToExclude = [ "contracts_Guest", - `${localStorage.getItem("_appName")}_appeditor` + "contracts_appeditor" ]; if (userRoles.length > 1) { const rolesfiltered = userRoles.filter( @@ -493,20 +467,13 @@ function Login() { _currentRole = ""; } } - if ( - _currentRole && - _currentRole !== - `${localStorage.getItem("_appName")}_appeditor` - ) { + if (_currentRole && _currentRole !== "contracts_appeditor") { userSettings.forEach(async (element) => { const redirectUrl = location?.state?.from || `/${element.pageType}/${element.pageId}`; if (element.role === _currentRole) { - let _role = _currentRole.replace( - `${localStorage.getItem("_appName")}_`, - "" - ); + let _role = _currentRole.replace("contracts_", ""); localStorage.setItem("_user_role", _role); // Get TenentID from Extendend Class @@ -732,185 +699,126 @@ function Login() { setState({ ...state, loading: true }); try { const user = await Parse.User.become(localStorage.getItem("accesstoken")); - let _usss = user.toJSON(); - localStorage.setItem("UserInformation", JSON.stringify(_usss)); - localStorage.setItem("username", _usss.name); - localStorage.setItem("accesstoken", _usss.sessionToken); + let _user = user.toJSON(); + localStorage.setItem("UserInformation", JSON.stringify(_user)); + localStorage.setItem("username", _user.name); + localStorage.setItem("accesstoken", _user.sessionToken); localStorage.setItem("scriptId", true); - if (_usss.ProfilePic) { - localStorage.setItem("profileImg", _usss.ProfilePic); + if (_user.ProfilePic) { + localStorage.setItem("profileImg", _user.ProfilePic); } else { localStorage.setItem("profileImg", ""); } let userRoles = []; + let userSettings = appInfo.settings; - if (localStorage.getItem("userSettings")) { - let userSettings = localStorage.getItem("userSettings"); + //Get Current user roles + let url = `${localStorage.getItem("baseUrl")}functions/UserGroups`; + const headers1 = { + "Content-Type": "application/json", + "X-Parse-Application-Id": localStorage.getItem("parseAppId"), + sessionToken: _user.sessionToken + }; + const body = { appname: "contracts" }; - //Get Current user roles - let url = `${localStorage.getItem("baseUrl")}functions/UserGroups`; - const headers1 = { - "Content-Type": "application/json", - "X-Parse-Application-Id": localStorage.getItem("parseAppId"), - sessionToken: _usss.sessionToken - }; - - let body = { - appname: localStorage.getItem("_appName") - }; - - await axios - .post(url, JSON.stringify(body), { headers: headers1 }) - .then((axiosres) => { - const roles = axiosres.data.result; - if (roles) { - userRoles = roles; - let _currentRole = ""; - if (userRoles.length > 1) { - if ( - userRoles[0] === - `${localStorage.getItem("_appName")}_appeditor` - ) { - _currentRole = userRoles[1]; - } else { - const rolesfiltered = userRoles.filter( - (x) => x !== "contracts_Guest" + await axios + .post(url, JSON.stringify(body), { headers: headers1 }) + .then((axiosres) => { + const roles = axiosres.data.result; + if (roles && roles.length > 0) { + userRoles = roles; + let _currentRole = ""; + const valuesToExclude = ["contracts_Guest", "contracts_appeditor"]; + const filterRoles = userRoles.filter( + (x) => !valuesToExclude.includes(x) + ); + if (filterRoles.length > 0) { + _currentRole = filterRoles?.[0]; + let SettingsUser = 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 ); - if (rolesfiltered.length > 0) { - _currentRole = rolesfiltered[0]; - } else { - setThirdpartyLoader(false); - setState({ - ...state, - loading: false, - alertType: "danger", - alertMsg: - "Does not have permissions to access this application!" - }); - setTimeout(function () { - setState({ - ...state, - loading: false, - alertType: "danger", - alertMsg: "" - }); - }, 2000); - } - // _currentRole = userRoles[0]; - } - } else { - _currentRole = userRoles[0]; - } - 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( - async (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") + const redirectUrl = + location?.state?.from || + `/${setting.pageType}/${setting.pageId}`; + await Parse.Cloud.run("getUserDetails", { + email: currentUser.get("email") + }).then( + async (result) => { + let tenentInfo = []; + const results = [result]; + if (results) { + let extendedInfo_stringify = JSON.stringify(results); + localStorage.setItem( + "Extand_Class", + extendedInfo_stringify + ); + 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( - "userDetails", - JSON.stringify(LocalUserDetails) + "TenantId", + x.TenantId.objectId ); - const res = await fetchSubscription(); - const billingDate = res.billingDate; - const freeplan = res.plan; - if (freeplan === "freeplan") { - navigate(redirectUrl); - } else if (billingDate) { - if (new Date(billingDate) > new Date()) { - localStorage.removeItem("userDetails"); - // Redirect to the appropriate URL after successful login - navigate(redirectUrl); - } else { - navigate(`/subscription`); - } - } else { - navigate(`/subscription`); - } - } else { - // Redirect to the appropriate URL after successful login - navigate(redirectUrl); + tenentInfo.push(obj); } - } - } 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"), + phone: results[0]?.get("Phone") || "", + company: results[0].get("Company") }; localStorage.setItem( "userDetails", JSON.stringify(LocalUserDetails) ); - const billingDate = ""; - if (billingDate) { + const res = await fetchSubscription(); + const billingDate = res.billingDate; + const freeplan = res.plan; + if (freeplan === "freeplan") { + navigate(redirectUrl); + } else if (billingDate) { + if (new Date(billingDate) > new Date()) { + localStorage.removeItem("userDetails"); + // Redirect to the appropriate URL after successful login + navigate(redirectUrl); + } else { + navigate(`/subscription`); + } + } else { navigate(`/subscription`); } } else { @@ -918,41 +826,95 @@ function Login() { navigate(redirectUrl); } } - }, - (error) => { + } 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); + } + } + }, + (error) => { + setState({ + ...state, + loading: false, + alertType: "danger", + alertMsg: "You don`t have access to this application!" + }); + setTimeout(function () { setState({ ...state, loading: false, alertType: "danger", - alertMsg: "You don`t have access to this application!" + alertMsg: "" }); - 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(); - } + }, 2000); + localStorage.setItem("accesstoken", null); + console.error("Error while fetching Follow", error); + } + ); + } + }); } else { - console.log("User Role Not Found."); + setState({ ...state, loading: false }); + handleCloseModal(); + setThirdpartyLoader(false); + setState({ + ...state, + loading: false, + alertType: "danger", + alertMsg: + "Does not have permissions to access this application!" + }); + setTimeout(function () { + setState({ + ...state, + loading: false, + alertType: "danger", + alertMsg: "" + }); + }, 2000); } - }) - .catch((err) => { - setState({ ...state, loading: false }); - console.log("err", err); - }); - } + } else { + console.log("User Role Not Found."); + setThirdpartyLoader(false); + setState({ + ...state, + loading: false, + alertType: "danger", + alertMsg: "Does not have permissions to access this application!" + }); + setTimeout(function () { + setState({ + ...state, + alertType: "danger", + alertMsg: "" + }); + }, 2000); + } + }) + .catch((err) => { + setState({ ...state, loading: false }); + console.log("err", err); + }); } catch (error) { setState({ ...state, loading: false }); console.log("err", error); diff --git a/apps/OpenSign/src/pages/Managesign.js b/apps/OpenSign/src/pages/Managesign.js index 187254b35..8b90fb14e 100644 --- a/apps/OpenSign/src/pages/Managesign.js +++ b/apps/OpenSign/src/pages/Managesign.js @@ -265,7 +265,7 @@ const ManageSign = () => { <div className="relative"> <div> {image ? ( - <div className="signatureCanvas relative border-[2px] border-[#888] rounded-box"> + <div className="signatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden"> <img alt="signature" src={image} diff --git a/apps/OpenSign/src/pages/PdfRequestFiles.js b/apps/OpenSign/src/pages/PdfRequestFiles.js index 6fd6db379..0c9b5b812 100644 --- a/apps/OpenSign/src/pages/PdfRequestFiles.js +++ b/apps/OpenSign/src/pages/PdfRequestFiles.js @@ -1582,6 +1582,19 @@ function PdfRequestFiles() { )} {!isCompleted?.message && ( <div className="flex mt-4 gap-1 px-[15px]"> + <button + onClick={(e) => + handleToPrint(e, pdfUrl, setIsDownloading) + } + type="button" + className="font-[500] text-[13px] mr-[5px] op-btn op-btn-neutral" + > + <i + className="fa-light fa-print" + aria-hidden="true" + ></i> + <span className="hidden lg:block">Print</span> + </button> <button type="button" onClick={() => @@ -1600,19 +1613,6 @@ function PdfRequestFiles() { Certificate </span> </button> - <button - onClick={(e) => - handleToPrint(e, pdfUrl, setIsDownloading) - } - type="button" - className="font-[500] text-[13px] mr-[5px] op-btn op-btn-neutral" - > - <i - className="fa-light fa-print" - aria-hidden="true" - ></i> - <span className="hidden lg:block">Print</span> - </button> <button type="button" className="font-[500] text-[13px] mr-[5px] op-btn op-btn-primary" diff --git a/apps/OpenSign/src/pages/PlanSubscriptions.js b/apps/OpenSign/src/pages/PlanSubscriptions.js index 70e5e138a..a45f525e1 100644 --- a/apps/OpenSign/src/pages/PlanSubscriptions.js +++ b/apps/OpenSign/src/pages/PlanSubscriptions.js @@ -16,7 +16,7 @@ const listItemStyle = { const PlanSubscriptions = () => { const navigate = useNavigate(); - const [yearlyVisible, setYearlyVisible] = useState(false); + const [yearlyVisible, setYearlyVisible] = useState(true); const [isLoader, setIsLoader] = useState(true); const extUser = localStorage.getItem("Extand_Class") && @@ -132,9 +132,22 @@ const PlanSubscriptions = () => { <div> <span className="text-3xl"> {item.currency && <small>{item.currency}</small>} - {yearlyVisible - ? item?.yearlyPrice - : item.monthlyPrice} + {yearlyVisible ? ( + <> + {item?.yearlyPrice.includes("<") ? ( + <div + className="inline-block" + dangerouslySetInnerHTML={{ + __html: item?.yearlyPrice + }} + /> + ) : ( + <span>{item?.yearlyPrice}</span> + )} + </> + ) : ( + item.monthlyPrice + )} </span> <p className="font-semibold pt-2 text-sm"> {yearlyVisible ? "Billed Yearly" : "Billed Monthly"} diff --git a/apps/OpenSign/src/pages/TeamList.js b/apps/OpenSign/src/pages/TeamList.js new file mode 100644 index 000000000..72269d8eb --- /dev/null +++ b/apps/OpenSign/src/pages/TeamList.js @@ -0,0 +1,365 @@ +import React, { useEffect, useState } from "react"; +import Parse from "parse"; +import Alert from "../primitives/Alert"; +import Loader from "../primitives/Loader"; +import { useLocation } from "react-router-dom"; +import Tooltip from "../primitives/Tooltip"; +import ModalUi from "../primitives/ModalUi"; +import pad from "../assets/images/pad.svg"; +import AddDepartment from "../components/AddDepartment"; +import { isEnableSubscription } from "../constant/const"; +import { checkIsSubscribedTeam } from "../constant/Utils"; +import SubscribeCard from "../primitives/SubscribeCard"; + +const heading = ["Sr.No", "Name", "Parent Team", "Active"]; +// const actions = [ +// { +// btnId: "1231", +// hoverLabel: "Edit", +// btnColor: "op-btn-primary", +// btnIcon: "fa-light fa-pen", +// redirectUrl: "draftDocument", +// action: "redirect" +// } +// ]; + +const TeamList = () => { + const recordperPage = 10; + const [teamList, setTeamList] = useState([]); + const [isLoader, setIsLoader] = useState(false); + const [isModal, setIsModal] = useState(false); + const location = useLocation(); + const isDashboard = + location?.pathname === "/dashboard/35KBoSgoAK" ? true : false; + const [currentPage, setCurrentPage] = useState(1); + const [isActiveModal, setIsActiveModal] = useState(false); + const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); + const [isActLoader, setIsActLoader] = useState({}); + const [isSubscribe, setIsSubscribe] = useState(false); + const startIndex = (currentPage - 1) * recordperPage; // user per page + + const getPaginationRange = () => { + const totalPageNumbers = 7; // Adjust this value to show more/less page numbers + const pages = []; + const totalPages = Math.ceil(teamList.length / recordperPage); + if (totalPages <= totalPageNumbers) { + for (let i = 1; i <= totalPages; i++) { + pages.push(i); + } + } else { + const leftSiblingIndex = Math.max(currentPage - 1, 1); + const rightSiblingIndex = Math.min(currentPage + 1, totalPages); + + const showLeftDots = leftSiblingIndex > 2; + const showRightDots = rightSiblingIndex < totalPages - 2; + + const firstPageIndex = 1; + const lastPageIndex = totalPages; + + if (!showLeftDots && showRightDots) { + let leftItemCount = 3; + let leftRange = Array.from({ length: leftItemCount }, (_, i) => i + 1); + + pages.push(...leftRange); + pages.push("..."); + pages.push(totalPages); + } else if (showLeftDots && !showRightDots) { + let rightItemCount = 3; + let rightRange = Array.from( + { length: rightItemCount }, + (_, i) => totalPages - rightItemCount + i + 1 + ); + + pages.push(firstPageIndex); + pages.push("..."); + pages.push(...rightRange); + } else if (showLeftDots && showRightDots) { + let middleRange = Array.from( + { length: 3 }, + (_, i) => leftSiblingIndex + i + ); + + pages.push(firstPageIndex); + pages.push("..."); + pages.push(...middleRange); + pages.push("..."); + pages.push(lastPageIndex); + } + } + + return pages; + }; + const pageNumbers = getPaginationRange(); + useEffect(() => { + fetchTeamList(); + }, []); + async function fetchTeamList() { + try { + setIsLoader(true); + if (isEnableSubscription) { + const getIsSubscribe = await checkIsSubscribedTeam(); + setIsSubscribe(getIsSubscribe); + } + const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + const teamCls = new Parse.Query("contracts_Departments"); + teamCls.equalTo("OrganizationId", { + __type: "Pointer", + className: "contracts_Organizations", + objectId: extUser.OrganizationId.objectId + }); + teamCls.descending("createdAt"); + const teamRes = await teamCls.find(); + if (teamCls.length > 0) { + const _teamRes = JSON.parse(JSON.stringify(teamRes)); + setTeamList(_teamRes); + } + } catch (err) { + console.log("Err in fetch teamlist", err); + setIsAlert({ type: "danger", msg: "Something went wrong." }); + } finally { + setTimeout(() => { + setIsAlert({ type: "success", msg: "" }); + }, 1500); + setIsLoader(false); + } + } + const handleFormModal = () => { + setIsModal(!isModal); + }; + // Get current list + const indexOfLastDoc = currentPage * recordperPage; + const indexOfFirstDoc = indexOfLastDoc - recordperPage; + const currentList = teamList?.slice(indexOfFirstDoc, indexOfLastDoc); + + const handleClose = () => { + setIsActiveModal({}); + }; + + // Change page + const paginateFront = () => setCurrentPage(currentPage + 1); + const paginateBack = () => setCurrentPage(currentPage - 1); + // const handleActionBtn = (act, item) => { + // // if (act.action === "delete") { + // // setIsDeleteModal({ [item.objectId]: true }); + // // } + // }; + const handleToggleBtn = (team) => { + setIsActiveModal({ [team.objectId]: true }); + }; + const handleToggleSubmit = async (team) => { + const index = teamList.findIndex((obj) => obj.objectId === team.objectId); + if (index !== -1) { + setIsActiveModal({}); + setIsActLoader({ [team.objectId]: true }); + const newArray = [...teamList]; + const IsActive = newArray[index].IsActive; + newArray[index] = { ...newArray[index], IsActive: !IsActive }; + setTeamList(newArray); + try { + const teamCls = new Parse.Object("contracts_Departments"); + teamCls.id = team.objectId; + teamCls.set("IsActive", !IsActive); + await teamCls.save(); + setIsAlert({ + type: "success", + msg: "Team disabled successfully." + }); + } catch (err) { + setIsAlert({ type: "danger", msg: "something went wrong." }); + console.log("err in disable team", err); + } finally { + setIsActLoader({}); + setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500); + } + } + }; + const handleTeamInfo = (team) => { + setTeamList((prev) => [team, ...prev]); + }; + return ( + <div className="relative"> + {isLoader && ( + <div className="absolute w-full h-[300px] md:h-[400px] flex justify-center items-center z-30 rounded-box"> + <Loader /> + </div> + )} + {Object.keys(isActLoader)?.length > 0 && ( + <div className="absolute w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-30 rounded-box"> + <Loader /> + </div> + )} + {isSubscribe && isEnableSubscription && !isLoader && ( + <div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg"> + {isAlert.msg && ( + <Alert type={isAlert.type}> + <div className="ml-3">{isAlert.msg}</div> + </Alert> + )} + <div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]"> + <div className="font-light"> + Teams{" "} + <span className="text-xs md:text-[13px] font-normal"> + <Tooltip message={"Teams"} /> + </span> + </div> + <div className="cursor-pointer" onClick={() => handleFormModal()}> + <i className="fa-light fa-square-plus text-accent text-[40px]"></i> + </div> + </div> + <div className={` overflow-x-auto w-full`}> + <table className="op-table border-collapse w-full"> + <thead className="text-[14px]"> + <tr className="border-y-[1px]"> + {heading?.map((item, index) => ( + <React.Fragment key={index}> + <th className="px-4 py-2">{item}</th> + </React.Fragment> + ))} + </tr> + </thead> + <tbody className="text-[12px]"> + {teamList?.length > 0 && ( + <> + {currentList.map((item, index) => ( + <tr className="border-y-[1px]" key={index}> + {heading.includes("Sr.No") && ( + <th className="px-4 py-2"> + {startIndex + index + 1} + </th> + )} + <td className="px-4 py-2 font-semibold"> + {item?.Name} + </td> + <td className="px-4 py-2 font-semibold"> + {item?.ParentId?.Name || "-"} + </td> + {item?.Name !== "All Users" && ( + <td className="px-4 py-2 font-semibold"> + <label className="cursor-pointer relative block items-center mb-0"> + <input + type="checkbox" + className="op-toggle transition-all op-toggle-secondary" + checked={item?.IsActive} + onChange={() => handleToggleBtn(item)} + /> + </label> + {isActiveModal[item.objectId] && ( + <ModalUi + isOpen + title={"Team status"} + handleClose={handleClose} + > + <div className="m-[20px]"> + <div className="text-lg font-normal text-black"> + Are you sure you want to disable this team? + </div> + <hr className="bg-[#ccc] mt-4 " /> + <div className="flex items-center mt-3 gap-2 text-white"> + <button + onClick={() => handleToggleSubmit(item)} + className="op-btn op-btn-primary" + > + Yes + </button> + <button + onClick={handleClose} + className="op-btn op-btn-secondary" + > + No + </button> + </div> + </div> + </ModalUi> + )} + </td> + )} + {/* <td className="px-3 py-2 text-white flex flex-wrap gap-1"> + {actions?.length > 0 && + actions.map((act, index) => ( + <button + key={index} + onClick={() => handleActionBtn(act, item)} + title={act.hoverLabel} + className={`${ + act?.btnColor ? act.btnColor : "" + } op-btn op-btn-sm w-[50px]`} + > + <i className={act.btnIcon}></i> + </button> + ))} + </td> */} + </tr> + ))} + </> + )} + </tbody> + </table> + </div> + <div className="op-join flex flex-wrap items-center p-2"> + {teamList.length > recordperPage && ( + <button + onClick={() => paginateBack()} + className="op-join-item op-btn op-btn-sm" + > + Prev + </button> + )} + {pageNumbers.map((x, i) => ( + <button + key={i} + onClick={() => setCurrentPage(x)} + disabled={x === "..."} + className={`${ + x === currentPage ? "op-btn-active" : "" + } op-join-item op-btn op-btn-sm`} + > + {x} + </button> + ))} + {teamList.length > recordperPage && ( + <button + onClick={() => paginateFront()} + className="op-join-item op-btn op-btn-sm" + > + Next + </button> + )} + </div> + {teamList?.length <= 0 && ( + <div + className={`${ + isDashboard ? "h-[317px]" : "" + } flex flex-col items-center justify-center w-ful bg-base-100 text-base-content rounded-xl py-4`} + > + <div className="w-[60px] h-[60px] overflow-hidden"> + <img + className="w-full h-full object-contain" + src={pad} + alt="img" + /> + </div> + <div className="text-sm font-semibold">No Data Available</div> + </div> + )} + <ModalUi + title={"Add Team"} + isOpen={isModal} + handleClose={handleFormModal} + > + <AddDepartment + handleTeamInfo={handleTeamInfo} + closePopup={handleFormModal} + /> + </ModalUi> + </div> + )} + {!isSubscribe && isEnableSubscription && !isLoader && ( + <div data-tut="apisubscribe"> + <SubscribeCard plan={"TEAMS"} price={"20"} /> + </div> + )} + </div> + ); +}; + +export default TeamList; diff --git a/apps/OpenSign/src/pages/UserList.js b/apps/OpenSign/src/pages/UserList.js new file mode 100644 index 000000000..7c38bec57 --- /dev/null +++ b/apps/OpenSign/src/pages/UserList.js @@ -0,0 +1,387 @@ +import React, { useEffect, useState } from "react"; +import Parse from "parse"; +import Alert from "../primitives/Alert"; +import Loader from "../primitives/Loader"; +import { useLocation } from "react-router-dom"; +import ModalUi from "../primitives/ModalUi"; +import pad from "../assets/images/pad.svg"; +import Tooltip from "../primitives/Tooltip"; +import AddUser from "../components/AddUser"; +import SubscribeCard from "../primitives/SubscribeCard"; +import { isEnableSubscription } from "../constant/const"; +import { checkIsSubscribedTeam } from "../constant/Utils"; +const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"]; +// const actions = []; +const UserList = () => { + const [userList, setUserList] = useState([]); + const [isLoader, setIsLoader] = useState(false); + const [isModal, setIsModal] = useState(false); + const location = useLocation(); + const isDashboard = + location?.pathname === "/dashboard/35KBoSgoAK" ? true : false; + const [currentPage, setCurrentPage] = useState(1); + const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); + const [isActiveModal, setIsActiveModal] = useState({}); + const [isActLoader, setIsActLoader] = useState({}); + const [isSubscribe, setIsSubscribe] = useState(false); + const recordperPage = 10; + const startIndex = (currentPage - 1) * recordperPage; // user per page + + const getPaginationRange = () => { + const totalPageNumbers = 7; // Adjust this value to show more/less page numbers + const pages = []; + const totalPages = Math.ceil(userList.length / recordperPage); + if (totalPages <= totalPageNumbers) { + for (let i = 1; i <= totalPages; i++) { + pages.push(i); + } + } else { + const leftSiblingIndex = Math.max(currentPage - 1, 1); + const rightSiblingIndex = Math.min(currentPage + 1, totalPages); + + const showLeftDots = leftSiblingIndex > 2; + const showRightDots = rightSiblingIndex < totalPages - 2; + + const firstPageIndex = 1; + const lastPageIndex = totalPages; + + if (!showLeftDots && showRightDots) { + let leftItemCount = 3; + let leftRange = Array.from({ length: leftItemCount }, (_, i) => i + 1); + + pages.push(...leftRange); + pages.push("..."); + pages.push(totalPages); + } else if (showLeftDots && !showRightDots) { + let rightItemCount = 3; + let rightRange = Array.from( + { length: rightItemCount }, + (_, i) => totalPages - rightItemCount + i + 1 + ); + + pages.push(firstPageIndex); + pages.push("..."); + pages.push(...rightRange); + } else if (showLeftDots && showRightDots) { + let middleRange = Array.from( + { length: 3 }, + (_, i) => leftSiblingIndex + i + ); + + pages.push(firstPageIndex); + pages.push("..."); + pages.push(...middleRange); + pages.push("..."); + pages.push(lastPageIndex); + } + } + + return pages; + }; + const pageNumbers = getPaginationRange(); + useEffect(() => { + fetchUserList(); + }, []); + async function fetchUserList() { + try { + setIsLoader(true); + if (isEnableSubscription) { + const getIsSubscribe = await checkIsSubscribedTeam(); + setIsSubscribe(getIsSubscribe); + } + const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + const res = await Parse.Cloud.run("getuserlistbyorg", { + organizationId: extUser.OrganizationId.objectId + }); + const _userRes = JSON.parse(JSON.stringify(res)); + setUserList(_userRes); + } catch (err) { + console.log("Err in fetch userlist", err); + setIsAlert({ type: "danger", msg: "Something went wrong." }); + } finally { + setTimeout(() => { + setIsAlert({ type: "success", msg: "" }); + }, 1500); + setIsLoader(false); + } + } + const handleFormModal = () => { + setIsModal(!isModal); + }; + + // Change page + const paginateFront = () => setCurrentPage(currentPage + 1); + const paginateBack = () => setCurrentPage(currentPage - 1); + // const handleActionBtn = (act, item) => { + // if (act.action === "delete") { + // setIsDeleteModal({ [item.objectId]: true }); + // } + // }; + const handleUserData = (userData) => { + setUserList((prev) => [userData, ...prev]); + }; + // `formatRow` is used to show data in poper manner like + // if data is of array type then it will join array items with "," + // if data is of object type then it Name values will be show in row + // if no data available it will show hyphen "-" + const formatRow = (row) => { + if (Array.isArray(row)) { + let updateArr = row.map((x) => x.Name); + return updateArr.join(", "); + } else if (typeof row === "object" && row !== null) { + return row?.Name || "-"; + } else { + return "-"; + } + }; + const handleClose = () => { + setIsActiveModal({}); + }; + const handleToggleSubmit = async (user) => { + const index = userList.findIndex((obj) => obj.objectId === user.objectId); + if (index !== -1) { + setIsActiveModal({}); + setIsActLoader({ [user.objectId]: true }); + const newArray = [...userList]; + const IsDisabled = newArray[index]?.IsDisabled; + newArray[index] = { ...newArray[index], IsDisabled: !IsDisabled }; + setUserList(newArray); + try { + const extUser = new Parse.Object("contracts_Users"); + extUser.id = user.objectId; + extUser.set("IsDisabled", !IsDisabled); + await extUser.save(); + setIsAlert({ + type: "success", + msg: "User disabled successfully." + }); + } catch (err) { + setIsAlert({ type: "danger", msg: "something went wrong." }); + console.log("err in disable team", err); + } finally { + setIsActLoader({}); + setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500); + } + } + }; + const handleToggleBtn = (user) => { + setIsActiveModal({ [user.objectId]: true }); + }; + return ( + <div className="relative"> + {isLoader && ( + <div className="absolute w-full h-[300px] md:h-[400px] flex justify-center items-center z-30 rounded-box"> + <Loader /> + </div> + )} + {Object.keys(isActLoader)?.length > 0 && ( + <div className="absolute w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-30 rounded-box"> + <Loader /> + </div> + )} + {isSubscribe && isEnableSubscription && !isLoader && ( + <div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg"> + {isAlert.msg && ( + <Alert type={isAlert.type}> + <div className="ml-3">{isAlert.msg}</div> + </Alert> + )} + <div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]"> + <div className="font-light"> + Users{" "} + <span className="text-xs md:text-[13px] font-normal"> + <Tooltip message={"users from Teams"} /> + </span> + </div> + <div className="cursor-pointer" onClick={() => handleFormModal()}> + <i className="fa-light fa-square-plus text-accent text-[40px]"></i> + </div> + </div> + <div className={` overflow-x-auto w-full`}> + <table className="op-table border-collapse w-full"> + <thead className="text-[14px]"> + <tr className="border-y-[1px]"> + {heading?.map((item, index) => ( + <React.Fragment key={index}> + <th className="px-4 py-2">{item}</th> + </React.Fragment> + ))} + </tr> + </thead> + <tbody className="text-[12px]"> + {userList?.length > 0 && ( + <> + {userList.map((item, index) => ( + <tr className="border-y-[1px]" key={index}> + {heading.includes("Sr.No") && ( + <th className="px-4 py-2"> + {startIndex + index + 1} + </th> + )} + <td className="px-4 py-2 font-semibold"> + {item?.Name}{" "} + </td> + <td className="px-4 py-2 ">{item?.Email || "-"}</td> + <td className="px-4 py-2">{item?.Phone || "-"}</td> + <td className="px-4 py-2"> + {item?.UserRole?.split("_").pop() || "-"} + </td> + <td className="px-4 py-2"> + {formatRow(item.DepartmentIds)} + </td> + <td className="px-4 py-2 font-semibold"> + <label className="cursor-pointer relative block items-center mb-0"> + <input + type="checkbox" + className="op-toggle transition-all op-toggle-secondary" + checked={item?.IsDisabled !== true} + onChange={() => handleToggleBtn(item)} + /> + </label> + {isActiveModal[item.objectId] && ( + <ModalUi + isOpen + title={"User status"} + handleClose={handleClose} + > + <div className="m-[20px]"> + <div className="text-lg font-normal text-black"> + Are you sure you want to deactivate this user? + </div> + <hr className="bg-[#ccc] mt-4 " /> + <div className="flex items-center mt-3 gap-2 text-white"> + <button + onClick={() => handleToggleSubmit(item)} + className="op-btn op-btn-primary" + > + Yes + </button> + <button + onClick={handleClose} + className="op-btn op-btn-secondary" + > + No + </button> + </div> + </div> + </ModalUi> + )} + </td> + {/* <td className="px-3 py-2 text-white grid grid-cols-2"> + {actions?.length > 0 && + actions.map((act, index) => ( + <button + key={index} + onClick={() => handleActionBtn(act, item)} + title={act.hoverLabel} + className={`${ + act?.btnColor ? act.btnColor : "" + } op-btn op-btn-sm`} + > + <i className={act.btnIcon}></i> + </button> + ))} + {isDeleteModal[item.objectId] && ( + <ModalUi + isOpen + title={"Delete User"} + handleClose={handleClose} + > + <div className="m-[20px]"> + <div className="text-lg font-normal text-black"> + Are you sure you want to delete this user? + </div> + <hr className="bg-[#ccc] mt-4 " /> + <div className="flex items-center mt-3 gap-2 text-white"> + <button + onClick={() => handleDelete(item)} + className="op-btn op-btn-primary" + > + Yes + </button> + <button + onClick={handleClose} + className="op-btn op-btn-secondary" + > + No + </button> + </div> + </div> + </ModalUi> + )} + </td> */} + </tr> + ))} + </> + )} + </tbody> + </table> + </div> + <div className="op-join flex flex-wrap items-center p-2"> + {userList.length > recordperPage && ( + <button + onClick={() => paginateBack()} + className="op-join-item op-btn op-btn-sm" + > + Prev + </button> + )} + {pageNumbers.map((x, i) => ( + <button + key={i} + onClick={() => setCurrentPage(x)} + disabled={x === "..."} + className={`${ + x === currentPage ? "op-btn-active" : "" + } op-join-item op-btn op-btn-sm`} + > + {x} + </button> + ))} + {userList.length > recordperPage && ( + <button + onClick={() => paginateFront()} + className="op-join-item op-btn op-btn-sm" + > + Next + </button> + )} + </div> + {userList?.length <= 0 && ( + <div + className={`${ + isDashboard ? "h-[317px]" : "" + } flex flex-col items-center justify-center w-ful bg-base-100 text-base-content rounded-xl py-4`} + > + <div className="w-[60px] h-[60px] overflow-hidden"> + <img + className="w-full h-full object-contain" + src={pad} + alt="img" + /> + </div> + <div className="text-sm font-semibold">No Data Available</div> + </div> + )} + <ModalUi + title={"Add User"} + isOpen={isModal} + handleClose={handleFormModal} + > + <AddUser + handleUserData={handleUserData} + closePopup={handleFormModal} + /> + </ModalUi> + </div> + )} + {!isSubscribe && isEnableSubscription && !isLoader && ( + <div data-tut="apisubscribe"> + <SubscribeCard plan={"TEAMS"} price={"20"} /> + </div> + )} + </div> + ); +}; + +export default UserList; diff --git a/apps/OpenSign/src/primitives/DotLottieReact.js b/apps/OpenSign/src/primitives/DotLottieReact.js index 66ca3b8b3..79811cff9 100644 --- a/apps/OpenSign/src/primitives/DotLottieReact.js +++ b/apps/OpenSign/src/primitives/DotLottieReact.js @@ -30,8 +30,7 @@ const LottieWithLoader = () => { return ( <div> {!isLoaded && !hasError && ( - <div className="loader"> - {" "} + <div className="w-[120px] h-[120px] mx-auto"> <Loader /> </div> )} @@ -39,9 +38,8 @@ const LottieWithLoader = () => { {isLoaded && animationSrc && ( <DotLottieReact src={animationSrc} - loop autoplay - style={{ display: "block" }} + className="w-[120px] h-[120px] md:w-[200px] md:h-[200px] mx-auto" /> )} </div> diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 259bad27c..46f832210 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -12,6 +12,7 @@ import Tour from "reactour"; import Parse from "parse"; import { saveAs } from "file-saver"; import { + checkIsSubscribedTeam, // copytoData, replaceMailVaribles } from "../constant/Utils"; @@ -24,6 +25,9 @@ import ReactQuill from "react-quill"; import "react-quill/dist/quill.snow.css"; import BulkSendUi from "../components/BulkSendUi"; import Loader from "./Loader"; +import Select from "react-select"; +import { isEnableSubscription } from "../constant/const"; +import SubscribeCard from "./SubscribeCard"; const ReportTable = (props) => { const navigate = useNavigate(); @@ -52,6 +56,10 @@ const ReportTable = (props) => { const [templateDeatils, setTemplateDetails] = useState({}); const [placeholders, setPlaceholders] = useState([]); const [isLoader, setIsLoader] = useState({}); + const [isShareWith, setIsShareWith] = useState({}); + const [departmentList, setDepartmentList] = useState([]); + const [selectedDepartments, setSelectedDepartments] = useState([]); + const onChange = (selectedOptions) => setSelectedDepartments(selectedOptions); // const [selectedPublicRole, setSelectedPublicRole] = useState(""); // const [isCelebration, setIsCelebration] = useState(false); // const [currentLists, setCurrentLists] = useState([]); @@ -59,6 +67,9 @@ const ReportTable = (props) => { // const [isPublicProfile, setIsPublicProfile] = useState({}); // const [publicUserName, setIsPublicUserName] = useState(""); const [isViewShare, setIsViewShare] = useState({}); + const [isSubscribe, setIsSubscribe] = useState(true); + const Extand_Class = localStorage.getItem("Extand_Class"); + const extClass = Extand_Class && JSON.parse(Extand_Class); const startIndex = (currentPage - 1) * props.docPerPage; const { isMoreDocs, setIsNextRecord } = props; // For loop is used to calculate page numbers visible below table @@ -125,9 +136,37 @@ const ReportTable = (props) => { // below useEffect reset currenpage to 1 if user change route useEffect(() => { checkTourStatus(); + fetchDepartmentList(); return () => setCurrentPage(1); + // eslint-disable-next-line }, []); + // `fetchDepartmentList` is used to fetch department list for share with functionality + const fetchDepartmentList = async () => { + if (props.ReportName === "Templates") { + try { + const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + const department = new Parse.Query("contracts_Departments"); + department.equalTo("OrganizationId", { + __type: "Pointer", + className: "contracts_Organizations", + objectId: extUser.OrganizationId.objectId + }); + department.equalTo("IsActive", true); + const departmentRes = await department.find(); + if (departmentRes.length > 0) { + const _departmentRes = JSON.parse(JSON.stringify(departmentRes)); + const formatedList = _departmentRes.map((x) => ({ + label: x.Name, + value: x.objectId + })); + setDepartmentList(formatedList); + } + } catch (err) { + console.log("Err in fetch top level departmentlist", err); + } + } + }; // `formatRow` is used to show data in poper manner like // if data is of array type then it will join array items with "," // if data is of object type then it Name values will be show in row @@ -158,9 +197,7 @@ const ReportTable = (props) => { } else { setActLoader({ [`${item.objectId}_${act.btnId}`]: true }); try { - const params = { - templateId: item.objectId - }; + const params = { templateId: item.objectId }; const templateDeatils = await axios.post( `${localStorage.getItem("baseUrl")}functions/getTemplate`, params, @@ -191,6 +228,19 @@ const ReportTable = (props) => { }); } + // console.log("extClass ", extClass); + let extUserId = Doc.ExtUserPtr.objectId; + let creatorId = Doc.CreatedBy.objectId; + if (extClass && extClass.length > 0) { + if (Doc.ExtUserPtr?.objectId !== extClass[0].objectId) { + const Extand_Class = localStorage.getItem("Extand_Class"); + const extClass = Extand_Class && JSON.parse(Extand_Class); + if (extClass && extClass.length > 0) { + extUserId = extClass[0].objectId; + creatorId = extClass[0]?.UserId.objectId; + } + } + } let placeholdersArr = []; if (Doc.Placeholders?.length > 0) { placeholdersArr = Doc.Placeholders; @@ -205,12 +255,12 @@ const ReportTable = (props) => { ExtUserPtr: { __type: "Pointer", className: "contracts_Users", - objectId: Doc.ExtUserPtr.objectId + objectId: extUserId }, CreatedBy: { __type: "Pointer", className: "_User", - objectId: Doc.CreatedBy.objectId + objectId: creatorId }, Signers: signers, SendinOrder: Doc?.SendinOrder || false, @@ -221,7 +271,7 @@ const ReportTable = (props) => { const res = await axios.post( `${localStorage.getItem( "baseUrl" - )}classes/${localStorage.getItem("_appName")}_Document`, + )}classes/contracts_Document`, data, { headers: { @@ -282,7 +332,7 @@ const ReportTable = (props) => { } }; - const handleActionBtn = (act, item) => { + const handleActionBtn = async (act, item) => { if (act.action === "redirect") { handleURL(item, act); } else if (act.action === "delete") { @@ -297,6 +347,20 @@ const ReportTable = (props) => { setIsResendMail({ [item.objectId]: true }); } else if (act.action === "bulksend") { handleBulkSend(item); + } else if (act.action === "sharewith") { + if (isEnableSubscription) { + const getIsSubscribe = await checkIsSubscribedTeam(); + setIsSubscribe(getIsSubscribe); + } + if (item?.SharedWith && item?.SharedWith.length > 0) { + // below code is used to get existing sharewith departments and formated them as per react-select + const formatedList = item?.SharedWith.map((x) => ({ + label: x.Name, + value: x.objectId + })); + setSelectedDepartments(formatedList); + } + setIsShareWith({ [item.objectId]: true }); } }; // Get current list @@ -915,6 +979,41 @@ const ReportTable = (props) => { // setTimeout(() => setIsAlert(false), 1500); // }; + // `handleShareWith` is used to save departments in sharedWith field + const handleShareWith = async (e, template) => { + e.preventDefault(); + e.stopPropagation(); + setIsShareWith({}); + setActLoader({ [template.objectId]: true }); + try { + const templateCls = new Parse.Object("contracts_Template"); + templateCls.id = template.objectId; + const departmentArr = selectedDepartments.map((x) => ({ + __type: "Pointer", + className: "contracts_Departments", + objectId: x.value + })); + templateCls.set("SharedWith", departmentArr); + const res = await templateCls.save(); + if (res) { + setIsAlert(true); + setAlertMsg({ + type: "success", + message: "Template Shared successfully." + }); + } + } catch (err) { + setIsAlert(true); + setAlertMsg({ + type: "danger", + message: "Something went wrong, Please try again later." + }); + } finally { + setActLoader({}); + setTimeout(() => setIsAlert(false), 1500); + } + }; + return ( <div className="relative"> {Object.keys(actLoader)?.length > 0 && ( @@ -1253,55 +1352,132 @@ const ReportTable = (props) => { <td className="px-2 py-2"> <div className="text-base-content flex flex-row gap-x-2 gap-y-1 justify-start items-center"> {props.actions?.length > 0 && - props.actions.map((act, index) => ( - <div - role="button" - data-tut={act?.selector} - key={index} - onClick={() => handleActionBtn(act, item)} - title={act.hoverLabel} - className={ - act.action !== "option" - ? `${ - act?.btnColor ? act.btnColor : "" - } op-btn op-btn-sm mr-1` - : "text-base-content focus:outline-none text-lg mr-2 relative" - } - > - <i className={act.btnIcon}></i> - {act.btnLabel && ( - <span className="uppercase font-medium"> - {act.btnLabel} - </span> - )} - {isOption[item.objectId] && - act.action === "option" && ( - <ul className="absolute -right-2 top-6 z-[20] op-dropdown-content op-menu shadow bg-base-100 text-base-content rounded-box "> - {act.subaction?.map((subact) => ( - <li - key={subact.btnId} - onClick={() => - handleActionBtn(subact, item) - } - title={subact.hoverLabel} - > - <span> - <i - className={`${subact.btnIcon} mr-1.5`} - ></i> - {subact.btnLabel && ( - <span className="text-[13px] capitalize font-medium"> - {subact.btnLabel} + props.actions.map( + (act, index) => + (item.ExtUserPtr?.objectId === + extClass?.[0]?.objectId || + act.btnLabel === "Use") && ( + <div + role="button" + data-tut={act?.selector} + key={index} + onClick={() => handleActionBtn(act, item)} + title={act.hoverLabel} + className={ + act.action !== "option" + ? `${ + act?.btnColor ? act.btnColor : "" + } op-btn op-btn-sm mr-1` + : "text-base-content focus:outline-none text-lg mr-2 relative" + } + > + <i className={act.btnIcon}></i> + {act.btnLabel && ( + <span className="uppercase font-medium"> + {act.btnLabel} + </span> + )} + {isOption[item.objectId] && + act.action === "option" && ( + <ul className="fixed right-14 top-auto z-[70] w-max op-dropdown-content op-menu shadow bg-base-100 text-base-content rounded-box"> + {act.subaction?.map((subact) => ( + <li + key={subact.btnId} + onClick={() => + handleActionBtn(subact, item) + } + title={subact.hoverLabel} + > + <span> + <i + className={`${subact.btnIcon} mr-1.5`} + ></i> + {subact.btnLabel && ( + <span className="text-[13px] capitalize font-medium"> + {subact.btnLabel} + </span> + )} </span> - )} - </span> - </li> - ))} - </ul> - )} - </div> - ))} + </li> + ))} + </ul> + )} + </div> + ) + )} </div> + {isShareWith[item.objectId] && ( + <div className="op-modal op-modal-open"> + <div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative"> + {isSubscribe && isEnableSubscription && ( + <> + <h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]"> + Share with + </h3> + <div + className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40" + onClick={() => setIsShareWith({})} + > + ✕ + </div> + <form + className="h-full w-full z-[1300] px-2 mt-3" + onSubmit={(e) => handleShareWith(e, item)} + > + <Select + // onSortEnd={onSortEnd} + distance={4} + isMulti + options={departmentList} + value={selectedDepartments} + onChange={onChange} + closeMenuOnSelect + required={true} + noOptionsMessage={() => + "Departments not found" + } + unstyled + classNames={{ + control: () => + "op-input op-input-bordered op-input-sm border-gray-400 focus:outline-none hover:border-base-content w-full h-full text-[11px]", + valueContainer: () => + "flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]", + multiValue: () => + "op-badge op-badge-primary h-full text-[11px]", + multiValueLabel: () => "mb-[2px]", + menu: () => + "mt-1 shadow-md rounded-lg bg-base-200 text-base-content", + menuList: () => + "shadow-md rounded-lg overflow-hidden", + option: () => + "bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2", + noOptionsMessage: () => + "p-2 bg-base-200 rounded-lg m-1 p-2" + }} + /> + <button className="op-btn op-btn-primary ml-[10px] my-3"> + Submit + </button> + </form> + </> + )} + {!isSubscribe && isEnableSubscription && ( + <> + <div + className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-primary-content absolute right-2 top-2 z-40" + onClick={() => setIsShareWith({})} + > + ✕ + </div> + <SubscribeCard + plan={"TEAMS"} + price={"20"} + /> + </> + )} + </div> + </div> + )} {isViewShare[item.objectId] && ( <ModalUi isOpen diff --git a/apps/OpenSign/src/primitives/SubscribeCard.js b/apps/OpenSign/src/primitives/SubscribeCard.js index 84fdcc163..b0d3537f4 100644 --- a/apps/OpenSign/src/primitives/SubscribeCard.js +++ b/apps/OpenSign/src/primitives/SubscribeCard.js @@ -1,13 +1,16 @@ import React from "react"; import { useNavigate } from "react-router-dom"; -const SubscribeCard = () => { +const SubscribeCard = ({ plan, price }) => { const navigate = useNavigate(); return ( <div className="op-card op-bg-primary text-primary-content w-full shadow-lg"> <div className="op-card-body"> - <h2 className="op-card-title">Upgrade to PRO Plan</h2> - <p>$29.99/month: Sign 100 docs using API. Only $0.15/doc after that.</p> + <h2 className="op-card-title">Upgrade to {plan ? plan : "PRO"} Plan</h2> + <p> + ${price ? price : "29.99"}/month: Sign 100 docs using API. Only + $0.15/doc after that. + </p> <div className="op-card-actions justify-end"> <button onClick={() => navigate("/subscription")} diff --git a/apps/OpenSignServer/Utils.js b/apps/OpenSignServer/Utils.js index 3076085c3..dcfa1b707 100644 --- a/apps/OpenSignServer/Utils.js +++ b/apps/OpenSignServer/Utils.js @@ -143,13 +143,13 @@ export const updateMailCount = async extUserId => { await contractUser.save(null, { useMasterKey: true }); } else { // Create new entry if not found - const ContractsUsers = Parse.Object.extend('contracts_users'); + 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); + console.log('Error updating EmailCount in contracts_Users: ' + error.message); } }; diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index 59a903b1b..af4165b12 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -44,13 +44,18 @@ import GetPublicUserName from './parsefunction/GetPublicUserName.js'; import GetPublicTemplate from './parsefunction/GetPublicTemplate.js'; import ssoSignin from './parsefunction/ssoSignin.js'; import isextenduser from './parsefunction/isextenduser.js'; +import getUserByOrg from './parsefunction/getUserByOrg.js'; +import getUserListByOrg from './parsefunction/getUserListByOrg.js'; +import DepartmentsAftersave from './parsefunction/DepartmentsAftersave.js'; +import SubscriptionAftersave from './parsefunction/SubscriptionAftersave.js'; // This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic. Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave); Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave); Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave); - +Parse.Cloud.afterSave('contracts_Departments', DepartmentsAftersave); +Parse.Cloud.afterSave('contracts_Subscriptions', SubscriptionAftersave); // This beforeSave function triggers before an object is added or updated in the specified class, allowing for validation or modification. Parse.Cloud.beforeSave('contracts_Document', DocumentBeforesave); Parse.Cloud.beforeSave('contracts_Template', TemplateBeforeSave); @@ -98,3 +103,5 @@ Parse.Cloud.define('getpublicusername', GetPublicUserName); Parse.Cloud.define('getpublictemplate', GetPublicTemplate); Parse.Cloud.define('ssosign', ssoSignin); Parse.Cloud.define('isextenduser', isextenduser); +Parse.Cloud.define('getuserbyorg', getUserByOrg); +Parse.Cloud.define('getuserlistbyorg', getUserListByOrg); diff --git a/apps/OpenSignServer/cloud/parsefunction/AddUserToRole.js b/apps/OpenSignServer/cloud/parsefunction/AddUserToRole.js index 38012b1a4..6a488ca73 100755 --- a/apps/OpenSignServer/cloud/parsefunction/AddUserToRole.js +++ b/apps/OpenSignServer/cloud/parsefunction/AddUserToRole.js @@ -3,7 +3,9 @@ //-- Export Modules import 'dotenv/config.js'; import axios from 'axios'; - +const appId = process.env.APP_ID; +const masterKey = process.env.MASTER_KEY; +const serverUrl = process.env.SERVER_URL; export async function addUserToGroups(request) { try { var roleName = request.params.roleName; @@ -36,11 +38,11 @@ export async function addUserToGroups(request) { function getAccessType(request) { return new Promise(function (resolve, reject) { const options = { - url: process.env.SERVER_URL + '/classes/w_appinfo?where={"appname":"' + appName + '"}', + url: serverUrl + '/classes/w_appinfo?where={"appname":"' + appName + '"}', method: 'get', headers: { - 'X-Parse-Application-Id': process.env.APP_ID, - 'X-Parse-Master-Key': process.env.MASTER_KEY, + 'X-Parse-Application-Id': appId, + 'X-Parse-Master-Key': masterKey, }, }; @@ -77,33 +79,37 @@ export async function addUserToGroups(request) { } else { //--function to get the userid from session token function getuserid(request) { - return new Promise(function (resolve, reject) { - const options = { - url: process.env.SERVER_URL + '/users/me', - method: 'get', - headers: { - 'X-Parse-Application-Id': process.env.APP_ID, - 'X-Parse-Session-Token': request.headers['sessiontoken'], - }, - }; + try { + return new Promise(function (resolve, reject) { + const options = { + url: serverUrl + '/users/me', + method: 'get', + headers: { + 'X-Parse-Application-Id': appId, + 'X-Parse-Session-Token': request.headers['sessiontoken'], + }, + }; - axios(options) - .then(x => { - const body = x.data; - var error = body == '' ? true : false; - if (error) { - reject('result not found!'); - } else { - resolve(body); - } - }) - .catch(err => { - if (err) { - console.error(err); - return; - } - }); - }); + axios(options) + .then(x => { + const body = x.data; + var error = body == '' ? true : false; + if (error) { + reject('result not found!'); + } else { + resolve(body); + } + }) + .catch(err => { + if (err) { + console.error(err); + return; + } + }); + }); + } catch (err) { + console.log('err ', err); + } } var userData = await getuserid(request); if (userData.objectId == undefined) { @@ -112,7 +118,7 @@ export async function addUserToGroups(request) { var chkuserid = userData.objectId; //console.log("chkuserid "+chkuserid); var url = - process.env.SERVER_URL + + serverUrl + '/roles?where={"users":{"__type":"Pointer","className":"_User","objectId":"' + chkuserid + '"},"name": {"$regex": "' + @@ -126,7 +132,7 @@ export async function addUserToGroups(request) { url: url, method: 'get', headers: { - 'X-Parse-Application-Id': process.env.APP_ID, + 'X-Parse-Application-Id': appId, }, }; @@ -174,26 +180,26 @@ export async function addUserToGroups(request) { //--after validation call adduserToRole function async function adduserToRole() { - var roleNam = roleName; - var roleid = await getroleobjId(roleNam); - console.log('roleid'); - console.log(roleid); - var response = await adduserid(roleid); - /*process.stdin.resume(); - // listen to the event - process.on('SIGTERM', () => { - process.emit('cleanup'); - })*/ - return response; + try { + var roleNam = roleName; + var roleid = await getroleobjId(roleNam); + console.log('roleid'); + console.log(roleid); + var response = await adduserid(roleid); + + return response; + } catch (err) { + console.log('err in addusertorole', err); + } } //--function to get the role objId function getroleobjId(roleNam) { return new Promise(function (resolve, reject) { const options = { - url: process.env.SERVER_URL + '/roles?where={"name":"' + roleNam + '"}', + url: serverUrl + '/roles?where={"name":"' + roleNam + '"}', method: 'get', headers: { - 'X-Parse-Application-Id': process.env.APP_ID, + 'X-Parse-Application-Id': appId, }, }; axios(options) @@ -225,11 +231,11 @@ export async function addUserToGroups(request) { function adduserid(roleid) { return new Promise(function (resolve, reject) { const options = { - url: process.env.SERVER_URL + '/roles/' + roleid, + url: serverUrl + '/roles/' + roleid, method: 'PUT', headers: { - 'X-Parse-Application-Id': process.env.APP_ID, - 'X-Parse-Master-Key': process.env.MASTER_KEY, + 'X-Parse-Application-Id': appId, + 'X-Parse-Master-Key': masterKey, 'Content-Type': 'application/json', }, data: user, diff --git a/apps/OpenSignServer/cloud/parsefunction/DepartmentsAftersave.js b/apps/OpenSignServer/cloud/parsefunction/DepartmentsAftersave.js new file mode 100644 index 000000000..3feb37466 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/DepartmentsAftersave.js @@ -0,0 +1,30 @@ +export default async function DepartmentsAftersave(req) { + if (!req.original) { + try { + const Ancestors = req.object.get('Ancestors'); + let updatedAncestors; + if (Ancestors && Ancestors.length > 0) { + updatedAncestors = [ + ...Ancestors, + { + __type: 'Pointer', + className: 'contracts_Departments', + objectId: req.object.id, + }, + ]; + } else { + updatedAncestors = [ + { + __type: 'Pointer', + className: 'contracts_Departments', + objectId: req.object.id, + }, + ]; + } + req.object.set('Ancestors', updatedAncestors); + await req.object.save(null, { useMasterKey: true }); + } catch (err) { + console.log('Err in department aftersave', err); + } + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/DocumentBeforesave.js b/apps/OpenSignServer/cloud/parsefunction/DocumentBeforesave.js index e7f251ca4..3d9f85454 100644 --- a/apps/OpenSignServer/cloud/parsefunction/DocumentBeforesave.js +++ b/apps/OpenSignServer/cloud/parsefunction/DocumentBeforesave.js @@ -17,13 +17,13 @@ async function DocumentBeforesave(request) { await contractUser.save(null, { useMasterKey: true }); } else { // Create new entry if not found - const ContractsUsers = Parse.Object.extend('contracts_users'); + 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); + console.log('Error updating document count in contracts_Users: ' + error.message); } if (document?.get('Signers') && document.get('Signers').length > 0) { document.set('DocSentAt', new Date()); diff --git a/apps/OpenSignServer/cloud/parsefunction/GetTemplate.js b/apps/OpenSignServer/cloud/parsefunction/GetTemplate.js index 9d7998697..c3ff9e910 100644 --- a/apps/OpenSignServer/cloud/parsefunction/GetTemplate.js +++ b/apps/OpenSignServer/cloud/parsefunction/GetTemplate.js @@ -16,22 +16,45 @@ export default async function GetTemplate(request) { // console.log("userId ",userId) if (templateId && userId) { try { - const template = new Parse.Query('contracts_Template'); + let template = new Parse.Query('contracts_Template'); template.equalTo('objectId', templateId); template.include('ExtUserPtr'); template.include('Signers'); - template.include('CreateBy'); + template.include('CreatedBy'); + + const extUserQuery = new Parse.Query('contracts_Users'); + extUserQuery.equalTo('Email', userRes.data.email); + extUserQuery.include('DepartmentIds'); + const extUser = await extUserQuery.first({ useMasterKey: true }); + if (extUser) { + const _extUser = JSON.parse(JSON.stringify(extUser)); + if (_extUser?.DepartmentIds && _extUser.DepartmentIds?.length > 0) { + let departmentArr = []; + _extUser?.DepartmentIds?.forEach( + x => (departmentArr = [...departmentArr, ...x.Ancestors]) + ); + // Create the first query + const sharedWithQuery = new Parse.Query('contracts_Template'); + sharedWithQuery.containedIn('SharedWith', departmentArr); + + // Create the second query + const createdByQuery = new Parse.Query('contracts_Template'); + createdByQuery.equalTo('ExtUserPtr', { + __type: 'Pointer', + className: 'contracts_Users', + objectId: extUser.id, + }); + template = Parse.Query.or(sharedWithQuery, createdByQuery); + template.equalTo('objectId', templateId); + template.include('ExtUserPtr'); + template.include('Signers'); + template.include('CreatedBy'); + } + } const res = await template.first({ useMasterKey: true }); - // console.log("res ", res) if (res) { // console.log("res ",res) - const acl = res.getACL(); - // console.log("acl", acl.getReadAccess(userId)) - if (acl && acl.getReadAccess(userId)) { - return res; - } else { - return { error: "You don't have access of this document!" }; - } + return res; } else { return { error: "You don't have access of this document!" }; } diff --git a/apps/OpenSignServer/cloud/parsefunction/SubscriptionAftersave.js b/apps/OpenSignServer/cloud/parsefunction/SubscriptionAftersave.js new file mode 100644 index 000000000..ff9420dc7 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/SubscriptionAftersave.js @@ -0,0 +1,82 @@ +async function addDepartmentAndOrg(extUser) { + try { + const orgCls = new Parse.Object('contracts_Organizations'); + orgCls.set('Name', extUser.Company); + orgCls.set('IsActive', true); + const orgRes = await orgCls.save(null, { useMasterKey: true }); + const departmentCls = new Parse.Object('contracts_Departments'); + departmentCls.set('Name', 'All Users'); + departmentCls.set('OrganizationId', { + __type: 'Pointer', + className: 'contracts_Organizations', + objectId: orgRes.id, + }); + departmentCls.set('IsActive', true); + const departmentRes = await departmentCls.save(null, { useMasterKey: true }); + const updateUser = new Parse.Object('contracts_Users'); + updateUser.id = extUser.objectId; + updateUser.set('UserRole', 'contracts_Admin'); + updateUser.set('OrganizationId', { + __type: 'Pointer', + className: 'contracts_Organizations', + objectId: orgRes.id, + }); + updateUser.set('DepartmentIds', [ + { + __type: 'Pointer', + className: 'contracts_Departments', + objectId: departmentRes.id, + }, + ]); + const extUserRes = await updateUser.save(null, { useMasterKey: true }); + } catch (err) { + console.log('err in add department, role, org', err); + } +} + +export default async function SubscriptionAftersave(request) { + const oldObj = request.original; + if (!oldObj) { + try { + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.include('CreatedBy'); + const res = await subscription.get(request.object.id, { useMasterKey: true }); + const _res = JSON.parse(JSON.stringify(res)); + const user = _res.CreatedBy?.email; + if (user) { + const extUserQuery = new Parse.Query('contracts_Users'); + extUserQuery.equalTo('Email', user); + const extUserRes = await extUserQuery.first({ useMasterKey: true }); + if (extUserRes) { + const extUser = JSON.parse(JSON.stringify(extUserRes)); + if (extUser?.UserRole !== 'contracts_Admin') { + await addDepartmentAndOrg(extUser); + } + } + } + } catch (err) { + console.log('Err in subscriptionaftersave', err); + } + } else { + try { + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.include('CreatedBy'); + const res = await subscription.get(request.object.id, { useMasterKey: true }); + const _res = JSON.parse(JSON.stringify(res)); + const user = _res.CreatedBy?.email; + if (user) { + const extUserQuery = new Parse.Query('contracts_Users'); + extUserQuery.equalTo('Email', user); + const extUserRes = await extUserQuery.first({ useMasterKey: true }); + if (extUserRes) { + const extUser = JSON.parse(JSON.stringify(extUserRes)); + if (extUser?.UserRole !== 'contracts_Admin') { + await addDepartmentAndOrg(extUser); + } + } + } + } catch (err) { + console.log('Err in subscriptionaftersave', err); + } + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/TemplateBeforesave.js b/apps/OpenSignServer/cloud/parsefunction/TemplateBeforesave.js index b2ba6e3d3..79ac1c209 100644 --- a/apps/OpenSignServer/cloud/parsefunction/TemplateBeforesave.js +++ b/apps/OpenSignServer/cloud/parsefunction/TemplateBeforesave.js @@ -15,13 +15,13 @@ async function TemplateBeforeSave(request) { await contractUser.save(null, { useMasterKey: true }); } else { // Create new entry if not found - const ContractsUsers = Parse.Object.extend('contracts_users'); + 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); + console.log('Error updating template count in contracts_Users: ' + error.message); } } } catch (err) { diff --git a/apps/OpenSignServer/cloud/parsefunction/getReport.js b/apps/OpenSignServer/cloud/parsefunction/getReport.js index cd101f7c2..2d8fe2aa9 100644 --- a/apps/OpenSignServer/cloud/parsefunction/getReport.js +++ b/apps/OpenSignServer/cloud/parsefunction/getReport.js @@ -8,7 +8,7 @@ export default async function getReport(request) { const serverUrl = process.env.SERVER_URL; const appId = process.env.APP_ID; - + const masterKey = process.env.MASTER_KEY; try { const userRes = await axios.get(serverUrl + '/users/me', { headers: { @@ -23,12 +23,49 @@ export default async function getReport(request) { if (json) { const { params, keys } = json; const orderBy = '-updatedAt'; - const strParams = JSON.stringify(params); const strKeys = keys.join(); + let strParams = JSON.stringify(params); + if (reportId == '6TeaPr321t') { + const extUserQuery = new Parse.Query('contracts_Users'); + extUserQuery.equalTo('Email', userRes.data.email); + extUserQuery.include('DepartmentIds'); + const extUser = await extUserQuery.first({ useMasterKey: true }); + if (extUser) { + const _extUser = JSON.parse(JSON.stringify(extUser)); + if (_extUser?.DepartmentIds && _extUser.DepartmentIds?.length > 0) { + let departmentArr = []; + _extUser?.DepartmentIds?.forEach( + x => (departmentArr = [...departmentArr, ...x.Ancestors]) + ); + strParams = JSON.stringify({ + ...params, + $or: [ + { SharedWith: { $in: departmentArr } }, + { + ExtUserPtr: { + __type: 'Pointer', + className: 'contracts_Users', + objectId: extUser.id, + }, + }, + ], + }); + } else { + strParams = JSON.stringify({ + ...params, + CreatedBy: { + __type: 'Pointer', + className: '_User', + objectId: userId, + }, + }); + } + } + } const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': appId, - 'X-Parse-Master-Key': process.env.MASTER_KEY, + 'X-Parse-Master-Key': masterKey, }; const url = `${serverUrl}/classes/${clsName}?where=${strParams}&keys=${strKeys}&order=${orderBy}&skip=${skip}&limit=${limit}&include=AuditTrail.UserPtr,Placeholders.signerPtr`; const res = await axios.get(url, { headers: headers }); diff --git a/apps/OpenSignServer/cloud/parsefunction/getUserByOrg.js b/apps/OpenSignServer/cloud/parsefunction/getUserByOrg.js new file mode 100644 index 000000000..060c68134 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/getUserByOrg.js @@ -0,0 +1,27 @@ +export default async function getUserByOrg(req) { + const OrganizationId = req.params.organizationId; + const orgPtr = { + __type: 'Pointer', + className: 'contracts_Organizations', + objectId: OrganizationId, + }; + if (!req?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } else { + try { + const extUser = new Parse.Query('contracts_Users'); + extUser.include('DepartmentIds'); + extUser.equalTo('OrganizationId', orgPtr); + const userRes = await extUser.first({ useMasterKey: true }); + if (userRes.length > 0) { + const _userRes = JSON.parse(JSON.stringify(userRes)); + return _userRes; + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.'); + } + } catch (err) { + console.log('err in getuserlist', err); + throw err; + } + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/getUserListByOrg.js b/apps/OpenSignServer/cloud/parsefunction/getUserListByOrg.js new file mode 100644 index 000000000..b5fa1a6ab --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/getUserListByOrg.js @@ -0,0 +1,28 @@ +export default async function getUserListByOrg(req) { + const OrganizationId = req.params.organizationId; + const orgPtr = { + __type: 'Pointer', + className: 'contracts_Organizations', + objectId: OrganizationId, + }; + if (!req?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } else { + try { + const extUser = new Parse.Query('contracts_Users'); + extUser.equalTo('OrganizationId', orgPtr); + extUser.include('DepartmentIds'); + extUser.descending('createdAt'); + const userRes = await extUser.find({ useMasterKey: true }); + if (userRes.length > 0) { + const _userRes = JSON.parse(JSON.stringify(userRes)); + return _userRes; + } else { + return []; + } + } catch (err) { + console.log('err in getuserlist', err); + throw err; + } + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/getapitoken.js b/apps/OpenSignServer/cloud/parsefunction/getapitoken.js index 570679f57..192a1d0fd 100644 --- a/apps/OpenSignServer/cloud/parsefunction/getapitoken.js +++ b/apps/OpenSignServer/cloud/parsefunction/getapitoken.js @@ -18,8 +18,7 @@ export default async function getapitoken(request) { } } } catch (err) { - console.log('Err', err); - console.log('err', err); + console.log('Err in getapitoken', err); if (err.code == 209) { return { error: 'Invalid session token' }; } else { diff --git a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js index 37b53b95a..7954df977 100644 --- a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js +++ b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js @@ -318,11 +318,6 @@ export default function reportJson(id, userId) { params: { Type: { $ne: 'Folder' }, IsArchive: { $ne: true }, - CreatedBy: { - __type: 'Pointer', - className: '_User', - objectId: currentUserId, - }, }, keys: [ 'Name', @@ -335,6 +330,7 @@ export default function reportJson(id, userId) { 'Signers.Phone', 'Placeholders', 'IsPublic', + 'SharedWith.Name', ], }; default: diff --git a/apps/OpenSignServer/cloud/parsefunction/usersignup.js b/apps/OpenSignServer/cloud/parsefunction/usersignup.js index 2d4338a5f..81f0e8cc6 100644 --- a/apps/OpenSignServer/cloud/parsefunction/usersignup.js +++ b/apps/OpenSignServer/cloud/parsefunction/usersignup.js @@ -82,7 +82,7 @@ export default async function usersignup(request) { if (userDetails?.phone) { partnerQuery.set('ContactNumber', userDetails.phone); } - partnerQuery.set('TenantName', userDetails.name); + partnerQuery.set('TenantName', userDetails.company); partnerQuery.set('EmailAddress', userDetails.email); partnerQuery.set('IsActive', true); partnerQuery.set('CreatedBy', { diff --git a/apps/OpenSignServer/index.js b/apps/OpenSignServer/index.js index 9fccf17cd..670b7d64c 100644 --- a/apps/OpenSignServer/index.js +++ b/apps/OpenSignServer/index.js @@ -204,10 +204,7 @@ app.use('/v1', v1); // Parse Server plays nicely with the rest of your web routes app.get('/', function (req, res) { - // res.statusCode = 200; - // res.setHeader('Content-Type', 'text/plain'); - // res.end('I dream of being a website. Please star the parse-server repo on GitHub!'); - res.status(200).send('open-sign-server is running !!!'); + res.status(200).send('opensign-server is running !!!'); }); if (!process.env.TESTING) { @@ -217,7 +214,7 @@ if (!process.env.TESTING) { httpServer.keepAliveTimeout = 100000; // in milliseconds httpServer.headersTimeout = 100000; // in milliseconds httpServer.listen(port, '0.0.0.0', function () { - console.log('parse-server-example running on port ' + port + '.'); + console.log('opensign-server running on port ' + port + '.'); const isWindows = process.platform === 'win32'; // console.log('isWindows', isWindows); diff --git a/apps/OpenSignServer/package-lock.json b/apps/OpenSignServer/package-lock.json index e5d13f7d4..7a812dd2c 100644 --- a/apps/OpenSignServer/package-lock.json +++ b/apps/OpenSignServer/package-lock.json @@ -29,8 +29,8 @@ "multer": "^1.4.5-lts.1", "multer-s3": "^3.0.1", "node-forge": "^1.3.1", - "nodemailer": "^6.9.13", - "parse": "^5.1.0", + "nodemailer": "^6.9.14", + "parse": "^5.2.0", "parse-dbtool": "^1.2.0", "parse-server": "^7.1.0", "parse-server-api-mail-adapter": "^3.1.1", @@ -39,13 +39,12 @@ }, "devDependencies": { "@babel/eslint-parser": "^7.24.7", - "eslint": "^9.3.0", + "eslint": "^9.6.0", "jasmine": "^5.1.0", "mongodb-runner": "^5.6.2", - "nodemon": "^3.1.3", + "nodemon": "^3.1.4", "nyc": "15.1.0", - "prettier": "^3.3.2", - "uglify-js": "^3.17.4" + "prettier": "^3.3.2" }, "engines": { "node": ">=12.22.10 <21" @@ -1637,6 +1636,19 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", + "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", @@ -1671,9 +1683,17 @@ } }, "node_modules/@eslint/js": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.3.0.tgz", - "integrity": "sha512-niBqk8iwv96+yuTwjM6bWg8ovzAPF9qkICsGtcoa5/dmqcEMfdwNAX7+/OHcJHc7wj7XqPxH98oAHytFYlw6Sw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", + "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -2072,19 +2092,6 @@ "node": ">=12" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -2100,7 +2107,8 @@ "node_modules/@humanwhocodes/object-schema": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "peer": true }, "node_modules/@humanwhocodes/retry": { "version": "0.3.0", @@ -4111,9 +4119,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "bin": { "acorn": "bin/acorn" }, @@ -5463,15 +5471,15 @@ } }, "node_modules/eslint": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.3.0.tgz", - "integrity": "sha512-5Iv4CsZW030lpUqHBapdPo3MJetAPtejVW8B84GIcIIv8+ohFaddXsrn1Gn8uD9ijDb+kcYKFUVmC8qG8B2ORQ==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", + "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", + "@eslint/config-array": "^0.17.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.3.0", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint/js": "9.6.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", @@ -5482,8 +5490,8 @@ "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.0.1", "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1", - "esquery": "^1.4.2", + "espree": "^10.1.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -5509,7 +5517,7 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" } }, "node_modules/eslint-scope": { @@ -5656,11 +5664,11 @@ } }, "node_modules/espree": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", - "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "dependencies": { - "acorn": "^8.11.3", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.0.0" }, @@ -7139,10 +7147,27 @@ "resolved": "https://registry.npmjs.org/intersect/-/intersect-1.0.1.tgz", "integrity": "sha512-qsc720yevCO+4NydrJWgEWKccAQwTOvj2m73O/VBA6iUL2HGZJ9XqBiyraNrBXX/W1IAjdpXdRZk24sq8TzBRg==" }, - "node_modules/ip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", - "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -8745,17 +8770,17 @@ "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==" }, "node_modules/nodemailer": { - "version": "6.9.13", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", - "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", + "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", "engines": { "node": ">=6.0.0" } }, "node_modules/nodemon": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz", - "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", "dev": true, "dependencies": { "chokidar": "^3.5.2", @@ -9144,15 +9169,15 @@ } }, "node_modules/parse": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-5.1.0.tgz", - "integrity": "sha512-46gVRe1JHsh21Ht0/Ko6PeMDl6wELLMYxnZPFD6iZm2EWsWnzi2txNGE6PvnIv+G7yOufZIOD0BCZLYOFl3toA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse/-/parse-5.2.0.tgz", + "integrity": "sha512-FoD3kcLAQCw/2J1984sl3GUBzbHE2tA9mUcyw/EBWZ46WVZTzV+kjnA5tttXyzN4uodt21wSluzjbGnyLqreKw==", "dependencies": { - "@babel/runtime-corejs3": "7.23.2", + "@babel/runtime-corejs3": "7.24.6", "idb-keyval": "6.2.1", "react-native-crypto-js": "1.0.0", - "uuid": "9.0.1", - "ws": "8.16.0", + "uuid": "10.0.0", + "ws": "8.17.1", "xmlhttprequest": "1.8.0" }, "engines": { @@ -9491,18 +9516,6 @@ "semver": "bin/semver.js" } }, - "node_modules/parse-server/node_modules/@babel/runtime-corejs3": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.6.tgz", - "integrity": "sha512-tbC3o8uHK9xMgMsvUm9qGqxVpbv6yborMBLbDteHIc7JDNHsTV0vDMQ5j1O1NXvO+BDELtL9KgoWYaUVIVGt8w==", - "dependencies": { - "core-js-pure": "^3.30.2", - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/parse-server/node_modules/@eslint/eslintrc": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", @@ -9638,12 +9651,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "peer": true }, - "node_modules/parse-server/node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", - "optional": true - }, "node_modules/parse-server/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -9875,47 +9882,11 @@ } } }, - "node_modules/parse-server/node_modules/parse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse/-/parse-5.2.0.tgz", - "integrity": "sha512-FoD3kcLAQCw/2J1984sl3GUBzbHE2tA9mUcyw/EBWZ46WVZTzV+kjnA5tttXyzN4uodt21wSluzjbGnyLqreKw==", - "dependencies": { - "@babel/runtime-corejs3": "7.24.6", - "idb-keyval": "6.2.1", - "react-native-crypto-js": "1.0.0", - "uuid": "10.0.0", - "ws": "8.17.1", - "xmlhttprequest": "1.8.0" - }, - "engines": { - "node": ">=18 <21" - }, - "optionalDependencies": { - "crypto-js": "4.2.0" - } - }, - "node_modules/parse-server/node_modules/parse/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/parse-server/node_modules/path-to-regexp": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" }, - "node_modules/parse-server/node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - }, "node_modules/parse-server/node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -10002,16 +9973,33 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/parse/node_modules/@babel/runtime-corejs3": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.24.6.tgz", + "integrity": "sha512-tbC3o8uHK9xMgMsvUm9qGqxVpbv6yborMBLbDteHIc7JDNHsTV0vDMQ5j1O1NXvO+BDELtL9KgoWYaUVIVGt8w==", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/parse/node_modules/crypto-js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", "optional": true }, + "node_modules/parse/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/parse/node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" @@ -10020,6 +10008,26 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/parse/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -11242,15 +11250,15 @@ } }, "node_modules/socks": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", - "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, @@ -11941,18 +11949,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/unbzip2-stream": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", diff --git a/apps/OpenSignServer/package.json b/apps/OpenSignServer/package.json index 2948704ce..b6313da76 100644 --- a/apps/OpenSignServer/package.json +++ b/apps/OpenSignServer/package.json @@ -38,8 +38,8 @@ "multer": "^1.4.5-lts.1", "multer-s3": "^3.0.1", "node-forge": "^1.3.1", - "nodemailer": "^6.9.13", - "parse": "^5.1.0", + "nodemailer": "^6.9.14", + "parse": "^5.2.0", "parse-dbtool": "^1.2.0", "parse-server": "^7.1.0", "parse-server-api-mail-adapter": "^3.1.1", @@ -49,13 +49,12 @@ "type": "module", "devDependencies": { "@babel/eslint-parser": "^7.24.7", - "eslint": "^9.3.0", + "eslint": "^9.6.0", "jasmine": "^5.1.0", "mongodb-runner": "^5.6.2", - "nodemon": "^3.1.3", + "nodemon": "^3.1.4", "nyc": "15.1.0", - "prettier": "^3.3.2", - "uglify-js": "^3.17.4" + "prettier": "^3.3.2" }, "engines": { "node": ">=12.22.10 <21"