diff --git a/apps/OpenSign/src/json/ReportJson.js b/apps/OpenSign/src/json/ReportJson.js index f16efca1c..fe21c8d55 100644 --- a/apps/OpenSign/src/json/ReportJson.js +++ b/apps/OpenSign/src/json/ReportJson.js @@ -368,6 +368,14 @@ export default function reportJson(id) { redirectUrl: "", action: "bulksend" }, + { + btnId: "1835", + btnLabel: "Make public", + hoverLabel: "Make public", + btnIcon: "fa-solid fa-users-line", + redirectUrl: "", + action: "public" + }, { btnId: "1834", btnLabel: "Delete", diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index 813d8ab52..a3211f357 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -27,6 +27,9 @@ function UserProfile() { const [percentage, setpercentage] = useState(0); const [isDisableDocId, setIsDisableDocId] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false); + const [publicUserName, setPublicUserName] = useState( + (extendUser && extendUser?.[0]?.UserName) || "" + ); const [company, setCompany] = useState( extendUser && extendUser?.[0]?.Company ); @@ -37,6 +40,7 @@ function UserProfile() { const [otp, setOtp] = useState(""); const [otpLoader, setOtpLoader] = useState(false); const [isEmailVerified, setIsEmailVerified] = useState(false); + const [userNameError, setUserNameError] = useState(""); useEffect(() => { getUserDetail(); }, []); @@ -74,46 +78,68 @@ function UserProfile() { } } }; + + const handleCheckPublicUserName = async () => { + try { + const res = await Parse.Cloud.run("getPublicUserName", { + userName: publicUserName + }); + if (res) { + setIsLoader(false); + setUserNameError("user name already exist"); + setTimeout(() => { + setUserNameError(""); + }, 3000); + return res; + } + } catch (e) { + console.log("extend error"); + } + }; const handleSubmit = async (e) => { e.preventDefault(); setIsLoader(true); let phn = Phone; - try { - const tour = Parse.Object.extend("_User"); - const query = new Parse.Query(tour); + const res = await handleCheckPublicUserName(); + if (!res) { + try { + const tour = Parse.Object.extend("_User"); + const query = new Parse.Query(tour); - await query.get(UserProfile.objectId).then((object) => { - object.set("name", name); - object.set("ProfilePic", Image); - object.set("phone", phn); + await query.get(UserProfile.objectId).then((object) => { + object.set("name", name); + object.set("ProfilePic", Image); + object.set("phone", phn); - object.save().then( - async (response) => { - if (response) { - let res = response.toJSON(); - let rr = JSON.stringify(res); - localStorage.setItem("UserInformation", rr); - SetName(res.name); - SetPhone(res.phone); - setImage(res.ProfilePic); - localStorage.setItem("username", res.name); - localStorage.setItem("profileImg", res.ProfilePic); - await updateExtUser({ Name: res.name, Phone: res.phone }); - alert("Profile updated successfully."); - setEditMode(false); - navigate("/dashboard/35KBoSgoAK"); + object.save().then( + async (response) => { + if (response) { + let res = response.toJSON(); + let rr = JSON.stringify(res); + localStorage.setItem("UserInformation", rr); + SetName(res.name); + SetPhone(res.phone); + setImage(res.ProfilePic); + localStorage.setItem("username", res.name); + localStorage.setItem("profileImg", res.ProfilePic); + await updateExtUser({ Name: res.name, Phone: res.phone }); + alert("Profile updated successfully."); + setEditMode(false); + setIsLoader(false); + // navigate("/dashboard/35KBoSgoAK"); + } + }, + (error) => { + alert("Something went wrong."); + console.error("Error while updating tour", error); + setIsLoader(false); } - }, - (error) => { - alert("Something went wrong."); - console.error("Error while updating tour", error); - setIsLoader(false); - } - ); - }); - } catch (error) { - console.log("err", error); + ); + }); + } catch (error) { + console.log("err", error); + } } }; @@ -128,7 +154,8 @@ function UserProfile() { Name: obj.Name, HeaderDocId: isDisableDocId, JobTitle: jobTitle, - Company: company + Company: company, + UserName: publicUserName }; await axios.put( parseBaseUrl + "classes/" + extClass + "/" + ExtUserId, @@ -147,9 +174,7 @@ function UserProfile() { const json = JSON.parse(JSON.stringify([res])); const extRes = JSON.stringify(json); - localStorage.setItem("Extand_Class", extRes); - // console.log("updateRes ", updateRes); }; // file upload function const fileUpload = async (file) => { @@ -241,6 +266,9 @@ function UserProfile() { setOtpLoader(false); alert("OTP sent on you email"); }; + const handlePublicUrl = (e) => { + setPublicUserName(e.target.value); + }; return ( @@ -262,7 +290,15 @@ function UserProfile() { ></div> </div> ) : ( - <div className="flex justify-center items-center w-full"> + <div className="flex justify-center items-center w-full relative"> + {/* <Alert type={alertMsg.type}>{alertMsg.message}</Alert> */} + {userNameError && ( + <div + className={`z-[1000] fixed top-20 transform border-[1px] text-sm border-[#f0a8a8] bg-[#f4bebe] text-[#c42121] rounded py-[.75rem] px-[1.25rem]`} + > + {userNameError} + </div> + )} <div className="bg-white flex flex-col justify-center shadow rounded w-[450px]"> <div className="flex flex-col justify-center items-center my-4"> <div className="w-[200px] h-[200px] overflow-hidden rounded-full"> @@ -390,6 +426,21 @@ function UserProfile() { )} </span> </li> + <li className="flex justify-between items-center border-t-[1px] border-gray-300 py-2 break-all"> + <span className="font-semibold">Public profile:</span> + <div className="flex items-center"> + <span>opensign.me/</span> + + <input + onChange={handlePublicUrl} + value={publicUserName} + disabled={!editmode} + placeholder="enter user name" + className="border-[1px] border-gray-200 rounded-[3px]" + /> + </div> + </li> + <li className="border-y-[1px] border-gray-300 break-all"> <div className="flex justify-between items-center py-2"> <span diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 9e5b01a63..4ca135e24 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 { replaceMailVaribles } from "../constant/Utils"; +import Confetti from "react-confetti"; import EditorToolbar, { module1, formats @@ -37,6 +38,7 @@ const ReportTable = (props) => { const [isTour, setIsTour] = useState(false); const [tourStatusArr, setTourStatusArr] = useState([]); const [isResendMail, setIsResendMail] = useState({}); + const [isMakePublic, setIsMakePublic] = useState({}); const [mail, setMail] = useState({ subject: "", body: "" }); const [userDetails, setUserDetails] = useState({}); const [isNextStep, setIsNextStep] = useState({}); @@ -44,6 +46,9 @@ const ReportTable = (props) => { const [templateDeatils, setTemplateDetails] = useState({}); const [placeholders, setPlaceholders] = useState([]); const [isLoader, setIsLoader] = useState({}); + const [role, setRole] = useState([]); + const [selectedPublicRole, setSelectedPublicRole] = useState(""); + const [isCelebration, setIsCelebration] = useState(false); const startIndex = (currentPage - 1) * props.docPerPage; const { isMoreDocs, setIsNextRecord } = props; @@ -231,6 +236,17 @@ const ReportTable = (props) => { setIsResendMail({ [item.objectId]: true }); } else if (act.action === "bulksend") { handleBulkSend(item); + } else if (act.action === "public") { + const getRole = []; + const getPlaceholder = item.Placeholders; + getPlaceholder.map((data) => { + getRole.push(data.Role); + }); + if (getRole.length === 1) { + setSelectedPublicRole(getRole[0]); + } + setRole(getRole); + setIsMakePublic({ [item.objectId]: true }); } }; // Get current list @@ -302,6 +318,7 @@ const ReportTable = (props) => { const handleClose = () => { setIsRevoke({}); setIsDeleteModal({}); + setIsMakePublic({}); }; const handleShare = (item) => { @@ -685,6 +702,70 @@ const ReportTable = (props) => { setTimeout(() => setIsAlert(false), 1500); } }; + + //function to make template public and set public role + const handlePublicTemplate = async (item) => { + if (selectedPublicRole) { + setActLoader({ [item.objectId]: true }); + setIsMakePublic(false); + let params = { + templateId: item.objectId + }; + let url = `${localStorage.getItem( + "baseUrl" + )}functions/createPublicTemplate/`; + const headers = { + "Content-Type": "application/json", + "X-Parse-Application-Id": localStorage.getItem("parseAppId"), + sessionToken: localStorage.getItem("accesstoken") + }; + try { + const res = await axios.post(url, params, { headers: headers }); + if (res.data.result.status === "success") { + const data = { + PublicRole: [selectedPublicRole] + }; + const res = await axios.put( + `${localStorage.getItem("baseUrl")}classes/contracts_Template/${ + item.objectId + }`, + data, + { + headers: { + "Content-Type": "application/json", + "X-Parse-Application-Id": localStorage.getItem("parseAppId"), + "X-Parse-Session-Token": localStorage.getItem("accesstoken") + } + } + ); + if (res) { + setIsAlert(true); + setAlertMsg({ + type: "success", + message: "You have successfully made the template public." + }); + setTimeout(() => setIsAlert(false), 1500); + setIsCelebration(true); + setTimeout(() => { + setIsCelebration(false); + }, 5000); + setSelectedPublicRole(""); + setActLoader({}); + } + } + } catch (e) { + console.log("save public template error", e); + } + } else { + setIsAlert(true); + setAlertMsg({ + type: "danger", + message: "You need to select a role for the public signers." + }); + setTimeout(() => setIsAlert(false), 1500); + } + }; + return ( <div className="relative"> {Object.keys(actLoader)?.length > 0 && ( @@ -696,6 +777,11 @@ const ReportTable = (props) => { </div> )} <div className="p-2 overflow-x-scroll w-full bg-white rounded-md"> + {isCelebration && ( + <div style={{ position: "relative", zIndex: "1000" }}> + <Confetti width={window.innerWidth} height={window.innerHeight} /> + </div> + )} {isAlert && <Alert type={alertMsg.type}>{alertMsg.message}</Alert>} {props.tourData && props.ReportName === "Templates" && ( <Tour @@ -1134,6 +1220,83 @@ const ReportTable = (props) => { </div> </ModalUi> )} + {isMakePublic[item.objectId] && ( + <ModalUi + isOpen + title={"Make template public"} + handleClose={() => { + setIsMakePublic({}); + }} + > + <div className="m-[20px]"> + <div className="font-normal text-black"> + <p className="text-lg"> + {" "} + Are you sure you want to make this template + public? + </p> + <div className="flex mt-2 gap-2 md:items-center"> + <p className="text-[15px]">Public role : </p> + {role.length > 1 ? ( + <select + className="w-[60%] md:w-[70%] border-[1px] border-gray-200 rounded-sm p-[2px]" + name="textvalidate" + value={selectedPublicRole} + onChange={(e) => + setSelectedPublicRole(e.target.value) + } + > + <option + disabled + style={{ fontSize: "13px" }} + value="" + > + Select... + </option> + {role.map((data, ind) => { + return ( + <option + style={{ fontSize: "13px" }} + key={ind} + value={data} + > + {data} + </option> + ); + })} + </select> + ) : ( + <div className="w-[60%] md:w-[70%] border-[1px] border-gray-200 rounded-sm p-[2px]"> + {role} + </div> + )} + </div> + </div> + + <hr className="bg-[#ccc] mt-2 " /> + <div className="flex items-center mt-3 gap-2 text-white"> + <button + onClick={() => handlePublicTemplate(item)} + className="px-4 py-1.5 text-white rounded shadow-md text-center focus:outline-none " + style={{ + backgroundColor: modalSubmitBtnColor + }} + > + Submit + </button> + <button + onClick={handleClose} + className="px-4 py-1.5 text-black border-[1px] border-[#ccc] shadow-md rounded focus:outline-none" + style={{ + backgroundColor: modalCancelBtnColor + }} + > + No + </button> + </div> + </div> + </ModalUi> + )} </td> </tr> ) diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index 32a047a3f..e2d0c6189 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -39,6 +39,8 @@ import encryptedpdf from './parsefunction/encryptedPdf.js'; import { getSignedUrl } from './parsefunction/getSignedUrl.js'; import createBatchDocs from './parsefunction/createBatchDocs.js'; import linkContactToDoc from './parsefunction/linkContactToDoc.js'; +import CreatePublicTemplate from './parsefunction/CreatePublicTemplate.js'; +import GetPublicUserName from './parsefunction/getPublicUserName.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); @@ -88,3 +90,5 @@ Parse.Cloud.define('encryptedpdf', encryptedpdf); Parse.Cloud.define('getsignedurl', getSignedUrl); Parse.Cloud.define('batchdocuments', createBatchDocs); Parse.Cloud.define('linkcontacttodoc', linkContactToDoc); +Parse.Cloud.define('createPublicTemplate', CreatePublicTemplate); +Parse.Cloud.define('getPublicUserName', GetPublicUserName); diff --git a/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js new file mode 100644 index 000000000..e43d4450a --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js @@ -0,0 +1,40 @@ +import axios from 'axios'; +export default async function CreatePublicTemplate(request) { + const templateId = request.params.templateId; + const serverUrl = process.env.SERVER_URL; + const appId = process.env.APP_ID; + + try { + const userRes = await axios.get(serverUrl + '/users/me', { + headers: { + 'X-Parse-Application-Id': appId, + 'X-Parse-Session-Token': request.headers['sessiontoken'], + }, + }); + const userId = userRes.data && userRes.data.objectId; + if (userId) { + const Query = new Parse.Query('contracts_Template'); + Query.equalTo('CreatedBy', { + __type: 'Pointer', + className: '_User', + objectId: userId, + }); + const updateACL = await Query.get(templateId, { useMasterKey: true }); + const newACL = new Parse.ACL(); + newACL.setPublicReadAccess(true); + newACL.setPublicWriteAccess(true); + updateACL.setACL(newACL); + updateACL.save(null, { useMasterKey: true }); + return { + status: 'success', + }; + } + } catch (err) { + console.log('err', err); + if (err.code == 209) { + return { error: 'Invalid session token' }; + } else { + return { error: "You don't have access of this document!" }; + } + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js new file mode 100644 index 000000000..6ee4eadd9 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js @@ -0,0 +1,14 @@ +async function GetPublicUserName(request) { + try { + const userName = request.params.userName; + const userQuery = new Parse.Query('contracts_Users'); + userQuery.equalTo('UserName', userName); + const res = await userQuery.first({ useMasterKey: true }); + console.log('res', res); + return res; + } catch (err) { + console.log('Err ', err); + return err; + } +} +export default GetPublicUserName; diff --git a/apps/OpenSignServer/databases/migrations/20240527194216-update_contracts_template_cls_cjs.cjs b/apps/OpenSignServer/databases/migrations/20240527194216-update_contracts_template_cls_cjs.cjs new file mode 100644 index 000000000..b0221be6a --- /dev/null +++ b/apps/OpenSignServer/databases/migrations/20240527194216-update_contracts_template_cls_cjs.cjs @@ -0,0 +1,33 @@ +/** + * + * @param {Parse} Parse + */ +exports.up = async Parse => { + // TODO: set className here + const className = 'contracts_Template'; + const schema = new Parse.Schema(className); + schema.addArray('PublicRole'); + + // TODO: Set the schema here + // Example: + // schema.addString('name').addNumber('cash'); + + return schema.update(); +}; + +/** + * + * @param {Parse} Parse + */ +exports.down = async Parse => { + // TODO: set className here + const className = 'contracts_Template'; + const schema = new Parse.Schema(className); + schema.deleteField('PublicRole'); + + // TODO: Set the schema here + // Example: + // schema.deleteField('name').deleteField('cash'); + + return schema.update(); +}; diff --git a/apps/OpenSignServer/test.pdf b/apps/OpenSignServer/test.pdf index 5facc82df..dfe01c088 100644 Binary files a/apps/OpenSignServer/test.pdf and b/apps/OpenSignServer/test.pdf differ