From 1764d7f9ba76280b623263e3bc5b10063a4a6c46 Mon Sep 17 00:00:00 2001 From: RaktimaNXG Date: Mon, 27 May 2024 21:08:05 +0530 Subject: [PATCH 01/13] feat: create public profile URLs for users and develop functionality to make the report template public --- apps/OpenSign/src/json/ReportJson.js | 8 + apps/OpenSign/src/pages/UserProfile.js | 123 +++++++++---- .../src/primitives/GetReportDisplay.js | 163 ++++++++++++++++++ apps/OpenSignServer/cloud/main.js | 4 + .../parsefunction/CreatePublicTemplate.js | 40 +++++ .../cloud/parsefunction/GetPublicUserName.js | 14 ++ ...4216-update_contracts_template_cls_cjs.cjs | 33 ++++ apps/OpenSignServer/test.pdf | Bin 164324 -> 174961 bytes 8 files changed, 349 insertions(+), 36 deletions(-) create mode 100644 apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js create mode 100644 apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js create mode 100644 apps/OpenSignServer/databases/migrations/20240527194216-update_contracts_template_cls_cjs.cjs 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 5facc82df518e5b4dbb4ddbc6f7787e9b0dbb1e3..dfe01c08824add491d69f9c7715a522a4dfde782 100644 GIT binary patch delta 24286 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�ilQcDT` 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 L_KO!a4Yd9Pz%{Qy delta 13750 zcmY+r1ymeC(>97EfdIh*1cC+<EI6yUyF+jSyUSw1-4}v;fZ*;B+=4?0?hxGFCAbqV z@B7{V-v2viX1b@kditrSdd|$L>OPsm+3>^h@kg)0peIb|CB$H%0^tY=;W#+jBVkrJ zu5;?l8Y<Wt%o=ED*dNd*5@3WFB<R2dSSAKt!Z8sBHj0Pmf2n^8Z^9AdCX`@mHIPKX z<gDNz>lhC`(L0gWcCm{3$K^z^XB2_zX&wC`3DWtj^XiNZK-EAYB)8$$h*@p1b8#-} zGD{n1J1h`NrVXFmtvFgvotHmS+(W&O++)v!hd`xi#o2C4E#J++mfe049bs=*pJFPC zy&+40AuveDx#_s*Y~U6+0s=Uk3P@{HM>B3Z5ElTDfFi`8K>@hAq5lhQt+t0N$)|YX zf*-eWTEa2-uc!+xLaj%iVVV<TzaE#2vHq$${;tOPIVSZ>LQkUIYoD<!BHBvdcV?ZU z8rS4b&s13nKh$>+E>d7=2qn(ir7cp#2lEx<lfOYywLAVT+Ea2J96l1RzS{TL7dV2V zt`;1;uQa`0VWTkyqvOU)UO8h=KX@#<#R%a3a^x^Kbl;BBU8=ySc#VeFBL7#rA@_72 z$cbTmPxFiXi{8Q?U9*N!oNttjDODh1eJAz|NzXSYujh^q>QPfR=RqNVKYVUP++>sa z%$awh#65@7*stoU(}ri!+<1fE%xSS%qaF{{Ge+K}-&amHUOD~k@M{Db1;x#|>a3fH z(JWwn6DYNR)6+S0OkmYLk@E)sH_eQ9(vR2P66;3;#z89?tE3TgIF$e1)L|KECGQ2Y zAI~hOxlopT5aty5`=JV3R_nL2*{0An1zMyuXQ{h~?j087RJiGP)G@XII_LNxlMzZd zLa(S_*m(H&>p}hcu$ylNm;~+XH9c1Kc%VP`*v&p?FmzdUD|EKNO)W@-_?u?zVr)?c z#X80tpfRc~iXN4EZyAWR1%LW;|4_f7li2<6vg&o4pE#RxW!sDLFN|LTtzOk0SPfF* z@t(iuPBCJ+AiD>LI$-kS{-aItel?AH;kMwx8W{5!qpeu^=*xP}h+IzuczDNB;SYUD zohK}5I|<z1FH2G9=U*u=Dog%w!&+W?-9C6qF+Lzypv2N@Vn3D}LUZ-Zt~tzQLx+7U z^<WS$b}Vz+j?GWyJu~^q#}d7Y7rGK{Giv+|GRORL7L0Fhx<n+1be{##q)L~dN+%`Y zVe}Huy2PX6<vWQavy<e@5?S(0<QRCz=ru$F8vU7>rN2mI5v08REn!nggcOD5jDgJn z+hd8AaD9%d(QLIG(tEj;rJwb}V%%_L6RBG576`(g=-k+tI7#+9kF4e2cG<W>qQUo% zgETL~XZNaV)KT)q#Fp51Us5?x2?7mfkueNYW~_x%`mDDWU6~Ce@kLo3d|3sB!mX_% z7<E!O)w*3%@p`{;=EZ*6P0>aP=WFA}6?eYf{P8IRVzwO?M_<`pPV(dP{#Im(x{IBx zhNE>DbLR-Um9&7vt-Y+gZnlfwu&*4eLhV<F4vtzV=J$P90XiIC*Y`_*Pzm9StT<%c zOR;gJHdpG6@5mY7c=ds&p>e<5bt)z1Z1Yo>+>NZ$V&=JoOSq1FMyJGxZ*L@~@d^g` zc$luQCPd7$HdF!)IDoJAZ{OF)k$;#XR64;}*jvC=v&$;sBKkBgTv8dNp=hbi3#TC} z`soz0TK~IYyC&4T|GJUv1Iqbh);_3~D_gSfCT#pex0PmAv-Z>$4#{Nd6!L5KnMzFW zo*L%w2n*smGlgM8wJ2UC-Cu~>mz(qrst?zasP_*|bwi@~&*U7fU^v)kze6>|{U>6| z65CAWxOHkI3XKLVYT&G0@TM3h%?J&xR|9P&+$Bl5L*JV%p3{f@(L~8r8uDv>6{abY z4amGDru>cdD}`z&i4k(;27B8J+}pUf5KHQzC8*<2y$-I98(t)Sci_kpqd*$pbqv=D zphoWeKreWKuhY@rY-^CCJv~a29*zG#Pi4{0`jOPSvx0Xs5Ur4WQR>|wL??++SBCCc zFkvyPnFgLUgA?ut{aDnGU!^|?fA9FO&XNS$$E_IZ$EFvYCO{{PY=4wdU*>D+oT?k_ zJNA`nr092k)0N>=Ad><X)poEp=F7m|dumQw&;t|jNzcW!_1XI2DVZ|u6^&q^pl<wl z->7|h*<|xTy?l(MImVl!APQiTT;`~oxqaO1TjAeRpR>|=OUO`bQk7-gWqfbO@qOtt za(r-L=7Ez%i_iCb$##!O*&K6gF{%GXzrWzB8`4M%cF<jiBkILc6tDXHhCl7;f(knt z-9E*J?N}7=5mi=aN&g=1&Ob)S^{R6m#{G-@RurkxC-_?CjS$`FbR;8Eqli_5LKLZ? z>=43jah8Vl-X{^2zX|Z|=G+q>HSUH{{LU6){7{`sc4~$z`|Devym{{GELC4Pi;hC9 zc`4S!cUcO)KIWucHpUdY%S_fe`55pn|EPZjCvNrFthlBF(2lm-T_J`S$RNd=tKpxP z6(%2){QIuTXRs!k?1vg<d)Js$SN?RUq1h+jQ=bL0sNOvk%+Hhc)q{#?WlaBjO*Oh( zwh!X^`gz8opMES9^n0;1e2$~U&Q6RnrpcMZBGn+`5<bXgU<rq=h3gF$#>MQah3E;T zDYR4PaYhs0S{t{cHGd!4aVkj%QaG_m(=U&$<!DmIC+M|k{+(cH;3)n$y;nuENoF|X zF6g0Aw{>rUGT04JUD%!rdhHd2mXJ4(x1Om+j6>IBSrb*8;=BGFAsRUpPZ4MJ!XJZq z=UY2^t61@?e(H0y^3v3Jiaholjt%zGi8!UR;Ps2ZkhmT8zPjzrOkZz~JGH;D{6D+) z__d@m`i}<gE)NNWO85UM);Lf(*k1_Vfdr35wiT%>QCNR}9tn7E{92&qcV>6xs|bK2 z=wbwhW|@Dga-na@PKQ@hoa5iVn4Z_JV;Vp@OCqb2z`W3s*}=KP;X+GrMSh_`zt0P2 zz;*Lbz>18tezUD;^;<{24aY$<bh~ELwR2CK5Oy$D*NMVz4Nvx~IiUl$_vI49@z<9N zVQm}>D1)gwr#Il;!$?EV)9KeJp}PUoX`vc7hl6fu8zZIn>2f8yb(2MzK1qr$^jE7P zS)I5Z&eZ#Ew@F^rw0}<Tc{bf%PYymq{?VS;J(rRiF)^mEQ<0jWz^hOJRq&;J3mICW z#Ba2H-$7D5iFDDCd~3(@<?i2r_`&u<f%vk3FzWo{VzQ~|!1T~}PAnIO86&^~({uY$ z_YO0k5;ng$r^vB=-x%WW)zSzWuzO9P<uF0NNm$6M{`~uOr305WXC`MT_g1GflT<3X z;~ELmlHfw&wN|cr4hOm~S$=X&)ou>?v}?CTrQ7pjuQgy{HpW@{ZmVg>&bA&gC(3E{ zH)`8;P0?W*@uhC(WFEY1pu!J*3Bm6%B8#MSA0R?)HVCrd2`G5Hm0zC85%D)Mzl9hm zDgDH(f_>F9MHcRe&RX{c;_<KJ^eeQkNo7zrEzBHAEk($J@#`y>BZh{CYWzq)s(Hw4 z*LiLv64GgK)nntr)0gPt<6_uGRzBHTq7a;4v%)D;v`;!R+;?x?6_*F>K5&-t-A=XN z(cbkn4zs6yOga(&+aGp4NrB{V;C<<)SzpRx$7i><Zb&11(e5E_#wj`%CgVDsyQmOz zYI(~x>gOj%6sP!NiYD&UM1HJ*z0cV1U|m)Nl722lnBAI-A?jg>!vF8^!E(iV9!gQ! zdU<N7Ic_FiJL;n?(NbjHhjbas_98R+RK@0mYlAghwoLNSmqFt%gDCuCc+A5;nIgy; z_PD?VkdJTTA~Zjm>|My1E=o%`&!h+eHxp9dk_P0-)KKqw=1>?MEH(wsB}(JB#_quL zkxc0v)hjyZAC|<H-7h2lmOpZPWT3o7k4AU*PivBwa;LuACZpe|d?eMmNctY6ijPoI z0qM>(XAW-JxELj1BJeF;j5>Wqv6SpqWU)YoFT30NV)y$)(;iO;b?(p04XHdg8n(eD z9u_@(Ci-iy`vY-H2fue3t>yS$MLwJu3Jaw3i4!nc=g(M=9#Q>H!E`MiGecp-_E>Em zDB#%WnB>G#l&=o?2@yEERV?~7W5v=?9UHZ6KmOL^Gn&2*fw!hrL9Mb~s1R+>tf8(% zHrqCT>{@Q8zm40eryDw4r}-7rG<fBeI}GFqYoK2_tVjBnj6aryr!>B=tA(B@u5}jG zrQloLtSlOuU*Tumhc@VHJwv&zBrqCpZz7Y27$XPjws*oCGgHk$FtRT(;U?YrM@;eM zpM@<35s`n0Xjxjx>_gtx>TyPL)`sRS?S5{W=FpwwD&W-`7-~Zuul~6spuN0hoFJ#( zPFyY%^l&~@*^x3-Vl9tS=bHWl)m+wbk*#H0bfq!{><*?<XQ@ibB%^G9`7<fb*obxc z&nuAg!kS*Xxx1wt7t3qRE6yN)yDI|2gGd?J(@vuccS*JPYOB9r_IP-BIQ>3a+{UU; zdb4zc)h5l4KEwHP9FZycqasOxf{PImUi~Yc;O6_br~FSS=0Oi`MH^Zi#=RV6>~n~U z55w=n=QE!(#R_!%qJE+hVgSw@4L|ag>~spuwf3+brQFnQ^grr(U$CEwr+8d6eS=Z_ zJT3Tm)!N(aeAhC5DI{FuYb78f!(47-bL569OoXRL^(49Hr-26KHI1OuzlHnekI8wu zg0Z`XTng|Llf@FeguMNF>TIwsP(YLO_ywC37Y=|{DyGnd@E;Uxx5;#3Te={f-+nbO zi%GB{kf2sLiF`<Gh=`o^HCU{+$740`tXQDI{HDuf43lazyW+dMKTAQ%9>O(gC#6VO zK~Sb8@m7&l*_{UUDyqPWFUf#Q_;A~OFJtU$OVKc-FeCr=Jh}&G??K~AZ-+lS*uDIA z1n<Hcr&jafuQDpE|F_960f;ylh)LGSYYsa5U1(QBnYu|2xkam_64Ord*36P%3h&W5 z-`KsuJYG|X8OdbY6&3SyC#$uiwEx3#K>J%z99uk56@?yv>&gKM5vEM+eAHU8_*X3Q zuFcl=f(}7bLaPs{x3e9|<Tx8w?mnT-z&YMhXHLV}I789CdUlP#WWsuZrEUI8kBl4} za?LsN{)YEjXUFj{L)`GY$j;e_5blF1yZ^suvi~}3nZ%i*Up@5HTJA(y#(+`3P`}Ll z&dWL|AeSJTgYuPS9QW&tM>uhYfKPktWve#re8NFOjMvW?w6^8CHt)yNhvF-n`8LzG z_LCPs|1P3hwPpvBui(@Bh0T#$(_>~!+w<W1s0bDVoE+wde!BqnrS<s^OVlT~xLwr6 zb+N*U^|H0e{KuKL{cDbV@9pk1g_Ge;eu6`VqmA@MK3@XsuFaH_QIiEK@&VyV!BDWv z*<x`M*qN!xNgX7ck~qU?xlBIa>uA;aFQNy`bAj4_Q)bwj?gjX^Q78D7pggVLMZ(MT zJE-cu4Ch~&2#fFw>a4NP(!DE4arZfQe@EP+W#YvC5t|>IDLYAeaNJ#HLH(DMjedFZ zm0w%ZTl~CyCSQTx=S{tkvS3al`u27q7Nh4Ygo*CBcEb&HA&4$OL5{~&iMEw6Bd!Su zV*7yFBJ|ThiKK(?262aqhf-(tEz7{1U>iUNfNAIjZQqNb1N&d@B8@IiGeHJ|XK!(F z_iyp~8k|DkK@8iKx)uu~)x=}7|7y@p0E8F(t`RrUuP8GuEYsBfkZaN-Hu>`6uw_5M zCA7OQH^k$p4uhQ+)C)lhM5C(O<Pkqee&|cV<O-hZAApJs3~;_9Uo%(@X3iLXu=6*8 zP~6~1sdsXx%rP%7%wThm!A!bF=+NvehFS@LoU`ibYemj&ZM4T%!dAPC|BW;x7-eF- z`%eS}3&jKd-ysl#=st@MF2caS?l{!r;>W~H=D*0*>d<K$RSF2`<Plmb17h(6-gj~g zKwo^BWA3w&<cT4NoZP&=pd?q^_)E2ym}fQQ_%(i_tTCG!GS&>MBhvY@Yn_LDdgPRo z%DHUjr@N`uz>kZmT@&q?vP-B4G=@nLu%Xj_y+O@CU!w1MEJduWnJyuple`fOqtB4@ zQjoTq{VDS!qUleVL($cvcp1+IlkK<^UWxQi=tvHPRfqZzKi*ptes>alG<nfc-T%<k zEY?e;gzijp3J_V%f;^k?fE-{_5yHPRFa{?}`_uo54MO<81y&B@Vmt%X8>#vZ^>9?T zrT^tk_tbEgg3%)UrbiBx9rTzD!|j?n?^cN)ZgaoRhg{^g;4E`0P9?GZmqT>G{~t$6 zNGHU=PtYO6piIbsV!TSQ%)}u5Z-D7P4J-+)a12%yFW3Jayh#Oy@Z(^p;-TH8(${8g zZEl`n(3jMQ1w?$q4KB$x%L?9}W;aeW*?O7sOqcnemw{0O<=>1GFG1>UrLo-Tx?f_6 zrbg`x;-a^z`BSV2A)^m|u|*fU?1QCIM#PovKZ>mi*6Cqt{EViX7WtoR98}{8*E(yu zn?;8+l_9_0(9P4`{?9}hgJUo!bQ5EcpfE)7p5J*&T8Ivr#3twwu67EtS^7VqN2`;t zlbqaL;85fzx&6tHW=nl+<)~SRxEi@`O^-Yy&fl3Bvm>@PRa^gA(0R(BSUS*uDEg67 zY^5+=m9Q|cFcBt#-{Z^&(==+(dg(OeBYi3~IH?xgpFAHi7jBV*>4i?4NOy&UOu(c? zC&B=8{ePssOz0)XAVf9c{1zcZyL$*NeePsqL+e85AN=NjfH8-%JOKj+;EfLoz$isN zcD0p%EV)`==WVsw9*+qr{5Azo#9qf>Bgc}F5&p9vXA=IBk0Mo})cmJ~b*UyJ5hYvG zoV5^#nxg&4MoH?>MC>I;e@tp3h}<Rkd5sT)CpuX^-9`dBE&A&O!%PhN|9M@&Jb?e@ z#)$Z#$O$gIp1)q9>CxoGO#fmC)q>K9usCGSFtFLNp9M(<Z}*(k3=T(1pOz(C$67s9 zpVg%mn4^;W@wX3eAFSNZR!_E<dxV$UJiSZ9^J2wT8{%)yoxTcX2q`&nT<Z0t0uq(m z^6MUY1HR$UpkzXJGcVQtI_7rE5Fi3PwDznjqtL||#LDVzrFY_Rzq_36_Bgr&sg=eH zcKaF@27yF5EsJko4v|`i7@6+t7JeyjAzWb#a8b$qq(bz~y?clvHZo7HsN>-~H`%Lj zZk0SiJvw&cxEG$(-ZN%n@LFhQfKQ*)iCsxQAJ*m7Z<I0F%(9j{0oGlAG);^(;SXEV z_|aF*PJR|Ef$L}yw3#jMD5yv}76?Qr%`Fe>ZT2JEn0jH^B6V7N@`y_^$$3R<85$iF zsNQMQb9pP{(Gb5z?u{0^i70xGtFH5Ge0GEdH?j@EM<gNfg?*xm5Jn#!QH3i=zo9(8 zrHbGA47I>kD;i5hvK6c+<=yw~JHBo7k00b(0cc+l0V%S%(LMwD7N|Lb8$4!17l`hM z4q1scc!<rv3pRSGy;ie$4u*&2j!g<(`^WmMm*w*M>lkHZp*n*<>b;=6@4vj3=bY3P zeKn+Y$cc7D;<(QV?=xQ9RFh@LNt~#ttea}=?n9BNbjB@}a*XWNLkM`%wqq@%)X&T^ z%oDYTc94qq-ekdA+s4tD-7mOqJ>u!f4)&Vmos35`tz*05DMGCX%=9f8B0Vq+K2-K2 zP{Zq%Ewv6fWHxT^q>C?-*VC2muzOXy=4hl#Ju*8;C#E0?#_JLmaIA9Tph7!0-);k{ zSEeQFx_Zp)kbH8ut%_{7oXlZ$sGTylkM2t!wv1Wj*d=B+Y*|5`WKwR*-w@5-G{vUp zMTC;wiiT3ca%Mb9hrfD$ly+TQkhNRbk>JL1<QVoGFp6lgqv<TOk5;Zuo_(x-L|f*` z7yR$EM+YZ3Au#lb0Nh|Ol#Uw+0z<gzz(4?R-5LTTgNX>h%f-w6bV7Lnyr8EVZ$eBM z#(NQjF#>7=fJ05VxsX6O7z%?SVBBC61RMh40UJXB#!y~fC>#z3!jLcsF9={_41j~U zk;VWh0t$qJfC-t#7!(Nv;TW$_+)yrF7_SKu42J-Xp<o0Of&c=LKqwHv4S)iGAY(X! ziwlC}h4UbQya*r&f#fj;A)s&wFA~XZjD+x-K#aMN2woEyw}}ZC2x$TXa|4V4KoedF z1Oxy<o@_Be{-^y(3CwL`%msi0jh}|$K|+vlBot)==YHw}<ORXFczL;vjllq<F*l3{ zWCG@a0>D6SFpmirF9-xdAb}8sF%0r#G33c)7!nRQ;YA`KPzVCb!v*2t0rG%gNE2fi z?~?)oX##kH34@q$!Hkh6CdObS1kPh@!i|JNOt_&yUND#!0pSK9c~MYfAP9s2JyFLC zhMB;5xZx%+5P%y9hjN*iJW&Dx!@*#fu?Y+a0dj$Op(b2BKrj@}4Tb&ZiO?tH5Cj4U z2R@O=%LRS{VPeb+0C6GVFa#XNjQ~U7NT4y22Lj^deZmZYf*~ePzT<*GLEJn5C=ZH< z7r_Ms@^EuK6;I~#LLs0hM*^SNfWQ&Zr(RFvLwUGi;3q!<jX_8-;>kh~01hz*f}ePT zBe-EuZW!o^QZ60@7yttC!k^dzfsH|M2p9>1@j{>oFoFjFc(RC>8x8})zzC=@nCpoQ z6PPjZnF$OIta}%U;YOIi6^ZdYAvqmG6~%(XsYu5x!N<eJ1K<L1adSZdPjGdg#NsyA zjxdCythI>^9W&AjW^QR>Z)0W4%>#jonEvOM17TzJq^AV4Leeq+_xc|Nx+je%se%mx z=4ftXO~-8c|JI&_D)u(EhGI_Umd14d$;wEXTPB2LV6dY@66!KA3ZIInKPhe$kn2B9 z_70A8Pf42ple?h<@qoDh*T>>8^@{^_U36gEzSdyfOdN5@-G@#4()Xc1S(OgrzC%(v zYXjPijIEi~Bxz=++LjBp?OkPP`a=(OwLkztQ;uI~5v|ROukdpDCufzu<nS>uEe8yf z=QT2;n3gw(lXLk>HxBr%_)PtdYGQe>S{>3891rv>^2cR|i(<E8>}tl`4((0ZOh;P& zHaj))R-P@XbK2qmtp0HpZJ&%CYF)a?mL?XZDiCF0`_2(St?!RL`JBcPK<f81jn}{) zhu$)wbgg0ifG&_of*{)WU11{sn+97cahf+FC||L(Dc0@bk1#aJ^b>RCgmm=XT;{Km zQrzCk{4csi*bzhLy>yP68pk!UuOgmx9V-T^4Fsq+;;M2GBfh@w@2JDZC1Kdjo(N%d zA=)a;rHD!hvusipa<jcYT}+vx_Wty_7VG|VO?5<tzS9UTm5@!swhgC6Qz|P>hC0B& z3<aChSf2@|+8PMyI?H=~G?ei98IR!$iVepbH~xn*D<umz@=r<}MTu46n^LgIVSi&! z8J<?k05ihD3+Qu-vr=%2{X1Uk)s7({FTm2=uapzbj6Syjk($su9LxvFG&r%z67#tk z0=s+8D<E0beH~EJW<ahkNAb!Rvkuom0yU(czUdN+MdY@XI+ipa@Q$_HOl8f>KsTJe ziR9`#w}3`}kB+)^%xkf#DZZL*=XZ@sqi}_F=OoaW9)kGNrHM-_$86TPtw3dv_Iac) zxP!e6G2b`uBiW<Jx2E>XN2A-;`i&G#*kH`8x|w4Iw`o|&(U+<ER8q}GGVj}6KU7DX z=j@|$adn7*$1Av56Z_rXT63#e%8`xs+?buqb=5<~ZehTrpw93}{Mq_Z3RaLZYR2;t zt`1Vv%|SO`C$NIBM2(;_iMTm}vA%^*csn{fw>E4vq#Lsw(-i>BTQ#l^*6t5eLMq;z zd#Mg{EC=UJyAa@K9B&kQxv0&bp+;!LUF#ZL=kx!i8~x4nt)bj<e(>Zui^#asRe~Mv z-`q&l#efthR@()87yJeVd+!JQ1~~)XO{*}J7FGsS*lY;OuQ)|fUe(+E=Tf;MHP;nU zc+~mo|CR+#P5X2lVBuR0;ae*p&+mRND_j|-rar8<ucFq^!m<q5{C}`a5)I#%4!qbo z#u|4V5<Rk<z_Q+hn|g1mE~chtC?amg&rAt63hs91hqi_`|KbxY>!%Xf>~Ec}cv4YJ zhzg>-W`=26TLkXYg@ix5I#Ew^xI_;o=_I>2oLw<>e5X_D$bh~!{b!kV-gl!{F{*el zx^%!Jp@KRsG?)sD1*e>aQ2-ycKdYZ^MBiV{=UfVJj@TH|y~j}=_$ZV7MKHa{t&sHF z?I`+hK8YlJ23YQhX^OqO8f{t&UtIXf@feb{aF|gL^8Me&R#qj=T7SyOd|$B*c)a-7 zZv69OgS^Gj=JiGmh3@B1XMCIdF`gmWGU$KU60A@dk1gVmhWZ78o1gmFs)pY;=eYCB zl>>+BV%=FJulE{%eG@=boB6iee%(x}9S5o`>rfZm?KXhMTU;6(n78f~Mx4G6{zX^f zP#nog8&D@a<f-?YNb;QgdCq=;PJZx7(Mgc&wd;t-F7E<gtDCKO3{Mgt%kaRNa9Y~L zS(X!uuUx_Z>$NDKtpM+FSZu2SiR#(ajxQ{cfL&O(-M?hgqekS$fPK_m3)j`5jhOr) z_WMd1_ra%U)XpTLTZK6aVqtAO)x^CQ20a?4MN#8P!*$OQ2a4_Fb@*%^NMRW+X)y3^ zb^UC7RSEaQS|Yo~x#s?kcU6FtOOUO1a=;P_8rkxP8$1LcKGQbha!q1#$#$QiaAvw@ zR=-xgJ0uFG$?sR4LTAn}Ie+b>2=2mymkSKs?4=2=H6gXO`pdABgk(IUu68#(B(WLU zx<BAD;~d_aPkksqO2~XL-?~`JI!IVkq{KA}po%Yauc97?e|b%Qs-Ir!w2@?B`K1=s zv&d5^%97yGpoCj^R43^%Kz2AqR?1-ikIuZ+^{P==xJBLGRXSGE9#6jbYBl@&Q7Fg! ze)fD+`0;Viho15!(s{YF;ogLj4g4&(HU{7Y@1t>^d@e}{RM45tTKzpKw=~u}HIS?u zOYH6bY;a%y8382sNF&2#x`XIwK&J-9;ID(|^a^W=Y*#vRFP0?l(nvQc{l3489Rt{< zxH-G$$hwng`u?(+uzchl+F>qN6w`H*Gm!r*F8d%K+)~0OC$C~88pmR*&B7G-?ey@$ zp4}<mh2@PkP9BEIS(0-s8}pA{whL6*Mg{BdcoA*m1*#*n)m3_FwfrwZCGpOv+|1WW z`?Ks~aam;@(JvV7{TdldiZJuG(gQsD1`)51<osga|GxKZiSIrVh(B$Rbo(+{9O7Xi zbUB{3_&z@1)x4DTKUr!&USgifQ;$0v0`fcq-qwuT`u{ngU+jb{U2N#D7*C6p4rHs( z{9_EiO5PY*WGiG#-soENptQG>C1N<0LH<87sDuTDH2VcrS#1O7SqUNj40)ZAE5w#- z6;w?li~7O`&8G40<NVe|1=}vUL%XxFn+*m_sK(~?ZBsSV(vJ_Nhf0U_AKmk-4d(hB z_zN%er#T%<9R<=dnhFNU(}(|B%^Q?=c+x&7HJM}~N*z2UgY;7hP#H=$?I!%IF4qZ< zw;%HzM1o`1ebX_+kOrOX^(WFqRcEwxWKF>mf4>pNoAP(0mu0X8{UaKqX77j`xO10g zlTUfn5DqKMJxDf-ibo)4Pe=z`X1A;>hUQ(3E~^fU!!8#!YfWj6RIRFkU+cd-+h=QU zi<&a5ge)1^Dg67@iVEtY%}y#NEA6g&J5`oXV?Z(SLw3H3X*#G4SjEJ@*;9>PRuXw6 zMfopuGqyh7yNu#M&adg@-&|C~pREw@g0e7s;3YMmmXmhZPu<R+%U2akcaAG%)j|iX zj<;x2f8Q1@!;qi31`S^!>3Iw1=PHE^kF->+b=r&!wIyAeb5OfITH>E|s(<oUnj&ZM zBR+~K{Qc|m`%W7f(dzf9I{tH*=(cP;6H;Ai#85CX!RqJ!s9~$Q>DiA_r$iwS4|l#t zg%-~nX9stC#xDZSl9AO~i_(V&s~Mj??^ljhTOh(hZLaKwxPNhWvq}5Ng!%Z&Z47Jd z=N`P8=FT6kmT6IV0<9Y-qJociJ+#U+C|AAtF4p0C3yL)^>FHU{1KyB*$GrBBT(i>O zsYiN3hhmw>e_Qi5wHgS-H*&C(<&}k5V8qboFrgap$UoD_lAMl>{SE&^Z1v(Wb^Nlf z|6ZzSnQK%yl|buLc&tsqCuh-=Hg8W43(H4|CBvwUhs`L|qnk&O502r8gDs(Zk3fdY zB^|%bMKg}Qhzc2Ykiq%QuTHbgoYO=C`xwuaBbHxge|&k=9_FaZ_`Z$mZ7<%m6(_ZS zqkqK|3m98I9UT7##Bq0Ka_fjvJ{59vey^N6crZ-f8U*E!CHL;LH{0xLk~i~m2Bj-! z?VMfiZP+`YPRj0f#%!0s?=SO88q<S^g(=Cm`?eN)#ClS|1&2;4-^_Hcy`0Za@2gFv zQOq{M3JIZH+h11aregyhteT!x^CO%8Cg#VzOlXTHu-Kk9{(O|;R(jnsUa*ImcyeBn zPQa7Ro<$&RxCtTgw-qWkx;unlOx}Ots*^lh+lcc=y(yqPxUz-Mw`&<b{0oa!ObOPL z?-yxLK}`&}a@8mnE&oLKn9;=$o_kBOhKLqxc$OfPy;%)%d}uo2XMVigpl$xN^!fWh ztV>tXqw7UuDz<;~QLFH*=X|MlM=XWf(*4zX9aBNa`x$AQt*-moKp`7p5;a_R_gnF+ zXugUglt}shLQvGQ)trM$nW96lc11!2!DkqSfd=DX*yMoNBa`Nb9E(%dLQ6Rujyx_Y zpp06HLXsRJ@yjX@?L}UxCIdF0mtVAxUv8bK#9kdLB5B@MCn;E)_(ix@=2jpLDIDf2 z0laK^#dlr{UotmSx@atTNqlYb+K9yR!ixC|>MwY1`ke2=8W-><sDFa~E}$xt-TseA z@Ra@!J3TdxPc_t1RU*pHyIGlPxO0v(Q&kghZKilaN2jn5FHF-M>FS}{eytxV+n;l; zui#fy_P<G6NnO231J+l!p&d?tnQf`{KbuSBcZ13juSw+R*g7?`x|Yl0652V}hUZX= zqnv6#9C{uz;$_(|Ms#0#vHn=0irKFQngAF-8S0JaO|&xt@kICcJod3@^*NxIeN*ph z{=m=775vnpKt0ECDN5iASA+1Avif*R_N~@D!c>ZWt{v{U_}E?PY5j7=E!8QLV@UYB zpLV%|+O(fIUAXH<qx>kpoN<_^j42)dKyjC{P8S<{06V>@uzr}0^=bg#LM4`a-lbH& z{bDctOzV_zu<(|6EmPm}C)i(l4DWhmsc7pDxxM++q}^yVzwnDZJv)SqO1Wh~pVy#n zzPkImC&ed9-F<2PL}Q1|VI(Ckk4)=id(@7Cai%}s&u{?t$D9kP9(^g{Lh_uSqbRVL zoil|?@-5$`t^Ri3b=8$%ZlYQhZHmJuv!cB7&-AK-$p?3KdxQ#SjVg)uW_V4UIb|^a zJ+%r~Htln?;-6#^6_|}0rlsS4@jQZ&UvV@Q6Gx|bl0Oef@Keae<Of>0spHQ(6l(N& z?Yy>Bo-+NU(waF|A_VaUycOIi*+*S_OK8_<o00DSh=YZ5xV_?6_Y2~<K!|0ejMQS= z(3dE$WKcVq7fqRHh%%1NPcb>FU6sy~Ng2uG39r(~Nm4EZcLr}tSu*&mMfiOt4TK#y zshd%3sSXet>^k7Jh=0*j_46Ab62)sEjE@7v@4?w?Nu$V&s$>Ze&;C&%%2C}t%;*Yi zG?Nb8n!wCOnZ;}!Cw~n>+2d-0$|N5P53c<8>!Hq#lXHkr2>~yBnvNnraj+uJ9HNqa zpC`OwHnoQ+Vt5YdZ&=JLk~TiZGsic<heyaa`aP=X0vcdR!9yk^)p;ycDCaiVKQZ&h zFS&8-&xVRqxdml~8C%v*Ba}~&f9q|f{}R0Fm#89wQvBp)nGtP-&oF~|(r_9fy3<Io zO^GuVo(NS#$xJv+vf?W$Irq*zpCCZE-*bIw%l?hozbh&xZQ~=}Ozp*gu7-X~6EcEL z!>ws%M9=emmFDPqu5!E!9-miGz7;K^KA5tITILoIWeC50E;v%Y0YssG(u&nEVx9Lh z{iWkZ3&mM=vt%>T9a60&OQpq-+kF`IND$@rUvMD$5}M8E<4)mof^$;od$CSff)C16 z^-}h0fd3?mV&a2DzV)hR!H4~dBnCW-iPcpfXP07oN!J~_i@qHMT`8>yRFy72QL3lC zkMMb`nbB(yxb?wD!3(9(Wiso}daV^m^`T<I8}MPCn{uC==}^zk1<mG2lv3eS>NCrH z#dOy<TsSr&<f&%VVdI$bUrS9hNum^ekd?dW^1Jc`OYWs;z<}ro^2l&kF6;36JuVX} zPLl=Y$hV0H48t^_S`{C`DA)?tJaZx;UUFFR2?IpNVndorS{c;~>2Af(cX{y~-Jkq^ z=S5KL$@7AII~h7ExgvYv_)3d?1p?5!7umjB$6!H!U^Y&3txdp@pVf7*|C`wp@!=WS zLruk&t)S+OnX^TYmgth<g9|D43mWC1G@rri96ky97nV$$*<<5kpzw7q+Pw(0;jx+x z70@?Tg1+#Vm^`SYikD~kP!f~(SIYiupJ9d+XyH3mapsseqru-Wm^8a0+|q*U1$J#H z&P9yHYS8|*pD1VxW)rQ85UNrg;^%L^BVNP%z4YDQ5W9<Kf=&nQ-od|=(`<g+teA-= zlMRH9{}6psx%8)?B*&jDpB!y2k|Rw0ZPotzj=N{(TX#Ct+}G{i1-&sTTJa7Q?((nV z@+&dz>VcovKiV~{71-~Dv>acwt}Qq*s`&OEs8Hz(t<H+b1n=<b3Wj&z?N!*%DyKJt zC3xId1_`5SL??IJIdfi?xK>Bu`9rvgGa4c83n$vY82t*8pn;-scmg2Rg+FKnOmh?^ z=Fu*;1Z0Y+UrOjN4umx4b2|JD{bM?UZF*Lx;iJdN0Gxqu7)nGV`>~i9bn2^{Jny#F zA{N#n_6{O$X%L`R2z|c=u0}$Bwl;rR70h=-1Vr%g=wNeRtorEiFqYY;H~KCU!}Ui1 zdgjoQE1MJ{hZfryVI1KYAx0#2H4HN|d_y+imjxAyGH~@6=QGw<sA!Tga!S*;&vxMX zkzip-$BaY*4vTMsYl)4&XfCUuU;9v&Lg`g@ud9%nc@@F>jtafB_ScsnSHsFG^hjPR zG)C;j6-xMTH9VGIGwPh3twjA-4JtDJZ^cW1O=hPXV(26%9IWVr4CvH?7_#xU8n2zd zQsxGra>>H8Uv^6pM}y#F<N=ui_+QC&R!eM`mcS!|{aikBnQ5;1!4Fs39-2JQeY%M4 zV{jQyMevlqNQ*`yC@tR6Jh#<gvC|+Gv1joKrlBp&cBR<w{7yQ-r=v<6ihgXz{Ss@v z*w`SB6<f-a?$1XW+uBJzs;cL4$)4&W!&o~nP}>Sta`xEtp)|}i|Nh<+<xs>hjJ}h% zSRwVXw(MNIBK5T2XhLshOnDGN`3C}}sQ}E&9@%IGAAr9r0t-X9k}Eo7Nbr^-b60fq z1$*b#X?kHY-zlRteX4gTV#=S<^v1srqIzFnIQn)-JTV^LZ4_!{d@sT%0*;yZrjrSj zK%M*JDOJS^Wf!Q3ZTatC0Zr7h7kaxL==JD2nMhUGpXPZYJ0=HV7j^H#=)*f>HF3p! z>EuGYv1f1z<TEcb@?;U3uX2RT(#@|AM?J7f^$80{d^3sasPIU;mI>{%qU#~jKsL*7 zTs9Nrr2jHhbx$lC`clz4qwC&zV#pJ}L#;G%k_VNXiBNGf(J->48g<{ihQ-iL#n4~+ z(i%Iq<btv`#<axH@2MSEI%MS}eocnFyc(613$P$6Si3%^0j(Ia<2SE%AUY0zO)TjW zSEwR%&!1<Jf*8dh^g_ysvZo|3TSFCE%}>NDhyDfcSf>9~&pvEZay}m4dVVz`ha!_y zgJ|DuZ2x56FgIC=)RA8*@>o9MdDD2!cktB^cmo_L-|k)O8?3)Fs5h~N-Y*ZZX;?`} zf3kNSU95ST+S|}>DA)J4_qf=oL4g>;)h{(VBNGGP?4aWky8z#Pb8_=TC&c6lgeV;c zambaMy$6W_lu1Z~+1PHZ714VoQI5IP43O`^tMYGq_s~5Tr9$#`I}2ZkqH`umGkh$2 z)Sa^nl6HBG!+PM;DMi$Dp|b^uVOm;`s@IvM8;(%{U}0QHr3R`W$V&8%$n1498&Z5y zXCP<2T-1Lbq#lq+CY^t)?U79Bj^W9^YN31{j9|n2VrxXE)J9PeXhiaP0)-w2*f~}? zS)utxAY&;17KB3_JNT8Bxcb)neZt#qme*1uWWk2`R>v`5A^~ju%+DXLU+C`!x-j{@ zwG!T830hN%6O#FiSD7#EAx%~0U-dGxz(3e`qlY1ywYtomjzv~-D?3@-Ly#uq-Ywi8 zhv=`uRFKvskKV?QSNQthH>FT9<8m_V!J1Mr?JE%-TILk*M#U#yDF@NCK7$3uYXMp1 zlI3hD($6{Y%#gHHH-JhNG$TdI*o2LumD5fzT|VQ&%ke)_FOSijEUp#rL82kmzV~VL zX>pz;pulJL)1eFzZg=QYC$jw1TLxU?a(^a;u^rAmb;I38f<8ytcpalIhAyI5ADqR* zN@l49V~|vnEql2NPMp#@dHim`@~?9Q26~I$wa!+8lOLTBO%m9P;tY?>iJlh`&TXbe z8iw;lxobx;I~uOFWQ_-IH_c&M{CMMyF<*v+>s;2lRh+ei6GpttE>%V^4N{n=hF;S3 zJA0{tD0=gsaXB=-gwLVKVi)vX=7y^*EnG)5YxcjS%X`gg)zh5XM6=A2NSf|*1UpH0 z*1Q%IVo$ql9^xn4xM?i6`tdQPaHAz}>9S=r+23{kr+{I|H*vCG({a=6S#kypt{yE% zMX`%i1K#N=nEEcEmdomAOBsu4bda*hUMoIa%#w_|*t14c<+&u(gmvBOu5UQpgP~l3 zlD~Pb)jIwa!+0e?P;2RD!vZ!U|E+v=v4)K-dm;O)u|Hn=JH%_#rp~j9l>+nDDbeni zKMr@Xy@@jCX~uuXeUbr3hU}CYyF?nuCQIHHODzcaSJdm7Y7LML)yrFTkvP|`<@2Gw zQM|8fi>fsMdWAU1*IGVa^v&i}55!)ylUW;bd^zFVjytPz^B4m_u1#;GAGSCjE{`~S zW(;eGoP}?Wwg_%~Hj0lhL5nR|-rnDj@5mknb?*&lV|)GooB6|l@ct(c&yD&&X$>CW z|CiO^2LG=uHmQ@}+Ht;$JS`YGBYCV;S`V#%K~MfZO-$pIkXqr5PB75^mHVCMhaFP* z7T4JW@8hptLYiEO4RI+#n2*da0<lhRy9;k-TM)Utz7n{phL@!C&P1acL+Hnyyw0K; zs1s`(KMf>UKg9^l`G+A#e9PH0k+?ctHDypv)yryI<gq4JOZMif1z6T;+M8C4W9l1` z!X1&8E@!z&jIl-q|1|D-bM!L04$WnH6ASCqf$6{Ykw5_d*H#;Kb9W>i1elOtgz=7< z`EqygBLrkxa69~-es8YKoQ00LV>9I$Szrj7*y#E5>RUX0dlou_|Ln<mlknjO1_=fb zoWTDBg8~Hs(i!}(d=GGpUkO8%yzAtzXU^9ZgotP63IuOuW&Sfeo4$|w90jnlD!Y<S zs?;Df%oe?@ozzSroP9WzD#cLgUqeGzi;2m4WlCB~I~!pAg_HKC)`!g36<adM7^QYK zb(YOLgC>Onn4t|TsqXW+=zcsx6tKp0m&%r;a1gUc{P5e9&wz3qpY26SiwNbf^`d_I z<MXT*iG<Zj+Bnj=++xRwpJpj#<H6RWiOyzXO*L2q>-QG|&C?HFjk4-|nR>@_vUWV) zlYLgMe^}-0r5~3qu$H{WmIAIgYOYouG(0kBMmrY51=p96H_JCGCG}^w#F~OW%M)Qd kw;Z0#DG%u73v6xwvwA|W7y}m<#0vpHa6WvHRF?XG09OLyM*si- From 662049d8788018df7b61fa94607263603d1fcd81 Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Tue, 28 May 2024 09:50:38 +0530 Subject: [PATCH 02/13] fix: remove console --- apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js | 1 - apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js | 2 -- 2 files changed, 3 deletions(-) diff --git a/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js index e43d4450a..0da7fb0b7 100644 --- a/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js +++ b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js @@ -30,7 +30,6 @@ export default async function CreatePublicTemplate(request) { }; } } catch (err) { - console.log('err', err); if (err.code == 209) { return { error: 'Invalid session token' }; } else { diff --git a/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js index 6ee4eadd9..3397739fc 100644 --- a/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js +++ b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js @@ -4,10 +4,8 @@ async function GetPublicUserName(request) { 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; } } From ae464274b0993b0193a64862009c8bee39b63eb0 Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Wed, 29 May 2024 15:55:07 +0530 Subject: [PATCH 03/13] fix: update ReportJson file --- apps/OpenSign/src/json/ReportJson.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/OpenSign/src/json/ReportJson.js b/apps/OpenSign/src/json/ReportJson.js index fe21c8d55..f16efca1c 100644 --- a/apps/OpenSign/src/json/ReportJson.js +++ b/apps/OpenSign/src/json/ReportJson.js @@ -368,14 +368,6 @@ 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", From d85074b127812a81f2cdc96a34c53de966afe580 Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Wed, 29 May 2024 18:17:07 +0530 Subject: [PATCH 04/13] fix: username issue to get details from GetPublicUserNmae cloud function --- apps/OpenSign/src/constant/const.js | 3 +- apps/OpenSign/src/pages/PdfRequestFiles.js | 5 +- apps/OpenSign/src/pages/UserProfile.js | 93 +++-- .../src/primitives/GetReportDisplay.js | 344 ++++++++++++------ apps/OpenSign/src/primitives/ModalUi.js | 4 +- apps/OpenSignServer/cloud/main.js | 7 +- .../parsefunction/CreatePublicTemplate.js | 69 ++-- .../cloud/parsefunction/GetPublicTemplate.js | 29 ++ .../cloud/parsefunction/GetPublicUserName.js | 4 +- .../cloud/parsefunction/reportsJson.js | 1 + 10 files changed, 366 insertions(+), 193 deletions(-) create mode 100644 apps/OpenSignServer/cloud/parsefunction/GetPublicTemplate.js diff --git a/apps/OpenSign/src/constant/const.js b/apps/OpenSign/src/constant/const.js index a046b4ae1..abfcce0ae 100644 --- a/apps/OpenSign/src/constant/const.js +++ b/apps/OpenSign/src/constant/const.js @@ -9,4 +9,5 @@ export const modalSubmitBtnColor = "#17a2b8"; export const modalCancelBtnColor = "white"; export const themeColor = "#47a3ad"; export const iconColor = "#686968"; -export const isEnableSubscription = process.env.REACT_APP_ENABLE_SUBSCRIPTION; +export const isEnableSubscription = false; +// process.env.REACT_APP_ENABLE_SUBSCRIPTION; diff --git a/apps/OpenSign/src/pages/PdfRequestFiles.js b/apps/OpenSign/src/pages/PdfRequestFiles.js index b0be26095..3c17139fd 100644 --- a/apps/OpenSign/src/pages/PdfRequestFiles.js +++ b/apps/OpenSign/src/pages/PdfRequestFiles.js @@ -1578,7 +1578,10 @@ function PdfRequestFiles() { handleClose={() => { setIsCompleted((prev) => ({ ...prev, isModal: false })); }} - reduceWidth={!isCompleted?.message} + reduceWidth={ + !isCompleted?.message && + "md:min-w-[440px] md:max-w-[400px]" + } > <div style={{ height: "100%", padding: 20 }}> {isCompleted?.message ? ( diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index a3211f357..3803e43cf 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Navigate, useNavigate } from "react-router-dom"; import Parse from "parse"; import { SaveFileSize } from "../constant/saveFileSize"; @@ -28,8 +28,9 @@ function UserProfile() { const [isDisableDocId, setIsDisableDocId] = useState(false); const [isSubscribe, setIsSubscribe] = useState(false); const [publicUserName, setPublicUserName] = useState( - (extendUser && extendUser?.[0]?.UserName) || "" + extendUser && extendUser?.[0]?.UserName ); + const previousStateValueRef = useRef(publicUserName); const [company, setCompany] = useState( extendUser && extendUser?.[0]?.Company ); @@ -81,8 +82,8 @@ function UserProfile() { const handleCheckPublicUserName = async () => { try { - const res = await Parse.Cloud.run("getPublicUserName", { - userName: publicUserName + const res = await Parse.Cloud.run("getpublicusername", { + username: publicUserName }); if (res) { setIsLoader(false); @@ -98,15 +99,19 @@ function UserProfile() { }; const handleSubmit = async (e) => { e.preventDefault(); - setIsLoader(true); - let phn = Phone; - const res = await handleCheckPublicUserName(); + let phn = Phone, + res; + + if (previousStateValueRef.current !== publicUserName) { + res = await handleCheckPublicUserName(); + } + if (!res) { + setIsLoader(true); try { - const tour = Parse.Object.extend("_User"); - const query = new Parse.Query(tour); - + const userQuery = Parse.Object.extend("_User"); + const query = new Parse.Query(userQuery); await query.get(UserProfile.objectId).then((object) => { object.set("name", name); object.set("ProfilePic", Image); @@ -148,15 +153,26 @@ function UserProfile() { const extClass = localStorage.getItem("extended_class"); const extData = JSON.parse(localStorage.getItem("Extand_Class")); const ExtUserId = extData[0].objectId; + let body; + if (publicUserName) { + body = { + Phone: obj.Phone, + Name: obj.Name, + HeaderDocId: isDisableDocId, + JobTitle: jobTitle, + Company: company, + UserName: publicUserName + }; + } else { + body = { + Phone: obj.Phone, + Name: obj.Name, + HeaderDocId: isDisableDocId, + JobTitle: jobTitle, + Company: company + }; + } - const body = { - Phone: obj.Phone, - Name: obj.Name, - HeaderDocId: isDisableDocId, - JobTitle: jobTitle, - Company: company, - UserName: publicUserName - }; await axios.put( parseBaseUrl + "classes/" + extClass + "/" + ExtUserId, body, @@ -267,7 +283,15 @@ function UserProfile() { alert("OTP sent on you email"); }; const handlePublicUrl = (e) => { - setPublicUserName(e.target.value); + const value = e.target.value; + if (value.length > 6 && !isSubscribe) { + setUserNameError("Please upgrade to allow more than 6 characters."); + setTimeout(() => { + setUserNameError(""); + }, 2000); + } else { + setPublicUserName(e.target.value); + } }; return ( <React.Fragment> @@ -291,10 +315,9 @@ function UserProfile() { </div> ) : ( <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]`} + className={`z-[1000] fixed top-[50%] transform border-[1px] text-sm border-[#f0a8a8] bg-[#f4bebe] text-[#c42121] rounded py-[.75rem] px-[1.25rem]`} > {userNameError} </div> @@ -426,20 +449,22 @@ 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> + {!isEnableSubscription && ( + <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> + <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"> diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 34a81763b..8cf06e6b0 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -4,14 +4,18 @@ import { useNavigate } from "react-router-dom"; import axios from "axios"; import ModalUi from "./ModalUi"; import AddSigner from "../components/AddSigner"; -import { modalSubmitBtnColor, modalCancelBtnColor } from "../constant/const"; +import { + modalSubmitBtnColor, + modalCancelBtnColor, + isEnableSubscription +} from "../constant/const"; import Alert from "./Alert"; import Tooltip from "./Tooltip"; import { RWebShare } from "react-web-share"; import Tour from "reactour"; import Parse from "parse"; import { saveAs } from "file-saver"; -import { replaceMailVaribles } from "../constant/Utils"; +import { copytoData, replaceMailVaribles } from "../constant/Utils"; import Confetti from "react-confetti"; import EditorToolbar, { module1, @@ -48,7 +52,9 @@ const ReportTable = (props) => { const [role, setRole] = useState([]); const [selectedPublicRole, setSelectedPublicRole] = useState(""); const [isCelebration, setIsCelebration] = useState(false); - + const [isPublic, setIsPublic] = useState({}); + const [isPublicProfile, setIsPublicProfile] = useState({}); + const [publicUserName, setIsPublicUserName] = useState("raktima"); const [isViewShare, setIsViewShare] = useState({}); const startIndex = (currentPage - 1) * props.docPerPage; const { isMoreDocs, setIsNextRecord } = props; @@ -235,17 +241,6 @@ 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 @@ -318,6 +313,9 @@ const ReportTable = (props) => { setIsRevoke({}); setIsDeleteModal({}); setIsMakePublic({}); + setSelectedPublicRole(""); + setIsPublic({}); + setIsPublicProfile({}); }; const handleShare = (item) => { @@ -711,50 +709,35 @@ const ReportTable = (props) => { 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); + const res = await Parse.Cloud.run("createpublictemplate", { + templateid: item.objectId, + ispublic: isPublic[item.objectId], + publicrole: [selectedPublicRole] + }); + + if (res.status === "success") { + setIsAlert(true); + setTimeout(() => setIsAlert(false), 1500); + if (isPublic[item.objectId]) { setAlertMsg({ type: "success", message: "You have successfully made the template public." }); - setTimeout(() => setIsAlert(false), 1500); setIsCelebration(true); setTimeout(() => { setIsCelebration(false); }, 5000); + setIsPublicProfile({ [item.objectId]: isPublic[item.objectId] }); + } else { + setAlertMsg({ + type: "success", + message: "You have successfully made the template private." + }); + setSelectedPublicRole(""); - setActLoader({}); } + setActLoader({}); } } catch (e) { console.log("save public template error", e); @@ -768,10 +751,50 @@ const ReportTable = (props) => { setTimeout(() => setIsAlert(false), 1500); } }; - const handleViewSigners = (item) => { setIsViewShare({ [item.objectId]: true }); }; + const handlePublicChange = async (e, item) => { + const getPlaceholder = item?.Placeholders; + if (getPlaceholder && getPlaceholder.length > 0) { + let extendUser = JSON.parse(localStorage.getItem("Extand_Class")); + const userName = extendUser[0]?.UserName; + setIsPublicUserName(extendUser[0]?.UserName); + if (userName) { + setIsPublic({ [item.objectId]: e.target.checked }); + const getRole = []; + getPlaceholder.map((data) => { + getRole.push(data.Role); + }); + if (getRole.length === 1) { + setSelectedPublicRole(getRole[0]); + } + setRole(getRole); + setIsMakePublic({ [item.objectId]: true }); + } else { + setIsPublicProfile({ [item.objectId]: true }); + } + } else { + setIsAlert(true); + setAlertMsg({ + type: "danger", + message: "Please assign at least one role to make this template public." + }); + setTimeout(() => setIsAlert(false), 3000); + } + }; + + const copytoProfileLink = () => { + const url = `https://opensign.me/${publicUserName}`; + copytoData(url); + setIsAlert(true); + setAlertMsg({ + type: "success", + message: "Copied." + }); + setTimeout(() => setIsAlert(false), 1500); + }; + return ( <div className="relative"> {Object.keys(actLoader)?.length > 0 && ( @@ -834,6 +857,9 @@ const ReportTable = (props) => { {props.actions?.length > 0 && ( <th className="px-4 py-2 font-thin">Action</th> )} + {props.ReportName === "Templates" && !isEnableSubscription && ( + <th className="px-4 py-2 font-thin">Public</th> + )} </tr> </thead> <tbody className="text-[12px]"> @@ -1272,84 +1298,168 @@ 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 ( + </td> + {!isEnableSubscription && ( + <td className=" pl-[20px] py-2 "> + {props.ReportName === "Templates" && ( + <div className=" flex flex-row-"> + <label + className={ + "cursor-pointer relative inline-flex items-center mb-0" + } + > + <input + checked={ + (isPublic && isPublic[item.objectId]) || + item?.IsPublic || + false + } + onChange={(e) => handlePublicChange(e, item)} + type="checkbox" + value="" + className="sr-only peer" + /> + <div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-1 peer-focus:ring-black rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-black peer-checked:bg-blue-600"></div> + </label> + </div> + )} + {isMakePublic[item.objectId] && ( + <ModalUi + isOpen + title={"Make template public"} + handleClose={() => { + setIsMakePublic({}); + setSelectedPublicRole(""); + setIsPublic({}); + }} + > + <div className="m-[20px]"> + <div className="font-normal text-black"> + <p className="text-lg"> + {" "} + {isPublic[item.objectId] + ? `Are you sure you want to make this template + public ?` + : `Are you sure you want to make this template + private ?`} + </p> + {isPublic[item.objectId] && ( + <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" }} - key={ind} - value={data} + value="" > - {data} + Select... </option> - ); - })} - </select> - ) : ( - <div className="w-[60%] md:w-[70%] border-[1px] border-gray-200 rounded-sm p-[2px]"> - {role} + {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> - </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> + <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> - </div> - </ModalUi> - )} - </td> + </ModalUi> + )} + {isPublicProfile[item.objectId] && ( + <ModalUi + isOpen + title={"Public url"} + handleClose={() => { + setIsPublicProfile({}); + }} + reduceWidth={"md:min-w-[440px] md:max-w-[400px]"} + > + <div className="m-[20px]"> + {publicUserName ? ( + <div className="font-normal text-black"> + <p> + Here’s your public URL. Copy or share it + with the signer, and you will be able to + see all your publicly set templates. + </p> + <div className=" flex items-center gap-5 mt-2 p-[2px] w-[75%]"> + <span className="font-bold"> + Public Url :{" "} + </span> + <span + onClick={() => copytoProfileLink()} + className="underline underline-offset-2 cursor-pointer" + >{`https://opensign.me/${publicUserName}`}</span> + </div> + </div> + ) : ( + <div className="font-normal text-black"> + <p> + Please add your public URL, and you will + be able to make a public template. + </p> + <button + className="px-4 py-1.5 mt-3 text-white rounded shadow-md text-center focus:outline-none " + style={{ + backgroundColor: modalSubmitBtnColor + }} + onClick={() => navigate("/profile")} + > + Add + </button> + </div> + )} + </div> + </ModalUi> + )} + </td> + )} </tr> ) )} diff --git a/apps/OpenSign/src/primitives/ModalUi.js b/apps/OpenSign/src/primitives/ModalUi.js index 778d483d1..b0413b430 100644 --- a/apps/OpenSign/src/primitives/ModalUi.js +++ b/apps/OpenSign/src/primitives/ModalUi.js @@ -19,9 +19,7 @@ const ModalUi = ({ <div className="fixed z-[999] top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]"> <div className={`${ - reduceWidth - ? "md:min-w-[440px] md:max-w-[400px]" - : "md:min-w-[500px]" + reduceWidth || "md:min-w-[500px]" } fixed z-[1000] top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-sm bg-white rounded shadow-md max-h-90 min-w-[90%] overflow-y-auto hide-scrollbar `} > {showHeader && ( diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index 283e5eb3d..6de2c181a 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -40,6 +40,8 @@ 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'; +import GetPublicTemplate from './parsefunction/GetPublicTemplate.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); @@ -89,5 +91,6 @@ 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); +Parse.Cloud.define('createpublictemplate', CreatePublicTemplate); +Parse.Cloud.define('getpublicusername', GetPublicUserName); +Parse.Cloud.define('getpublictemplate', GetPublicTemplate); diff --git a/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js index 0da7fb0b7..b7b5fb0b8 100644 --- a/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js +++ b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js @@ -1,39 +1,42 @@ -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; - + const templateid = request.params.templateid; + const ispublic = request.params.ispublic; + const publicrole = request.params.publicrole; 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', - }; + console.log('session token', request); + if (!request?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } else { + const userId = request?.user && request?.user?.id; + if (userId) { + const updateTemplate = new Parse.Object('contracts_Template'); + updateTemplate.id = templateid; + if (ispublic) { + updateTemplate.set('PublicRole', publicrole); + } + updateTemplate.set('IsPublic', ispublic); + const Acl = new Parse.ACL(); + if (ispublic) { + Acl.setPublicReadAccess(true); + } + Acl.setReadAccess(userId, true); + Acl.setWriteAccess(userId, true); + updateTemplate.setACL(Acl); + const savedObject = await updateTemplate.save(null, { useMasterKey: true }); + const res = savedObject.toJSON(); + if (res) { + return { + status: 'success', + }; + } + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User does not exist.'); + } } } catch (err) { - if (err.code == 209) { - return { error: 'Invalid session token' }; - } else { - return { error: "You don't have access of this document!" }; - } + const code = err.code || 400; + const msg = err.message; + const error = new Parse.Error(code, msg); + throw error; } } diff --git a/apps/OpenSignServer/cloud/parsefunction/GetPublicTemplate.js b/apps/OpenSignServer/cloud/parsefunction/GetPublicTemplate.js new file mode 100644 index 000000000..33c2a1050 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/GetPublicTemplate.js @@ -0,0 +1,29 @@ +async function GetPublicTemplate(request) { + try { + const username = request.params.username; + if (username) { + const extUserQuery = new Parse.Query('contracts_Users'); + extUserQuery.equalTo('UserName', username); + const res = await extUserQuery.first({ useMasterKey: true }); + const userId = res.get('UserId').id; + if (userId) { + const templatQuery = new Parse.Query('contracts_Template'); + templatQuery.equalTo('CreatedBy', { + __type: 'Pointer', + className: '_User', + objectId: userId, + }); + templatQuery.equalTo('IsPublic', true); + const getTemplate = await templatQuery.find({ useMasterKey: true }); + return getTemplate; + } + return { error: "You don't have access of this document!" }; + } else { + return { error: 'Please provide username!' }; + } + } catch (err) { + console.log('error', err); + return { error: "You don't have access of this document!" }; + } +} +export default GetPublicTemplate; diff --git a/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js index 3397739fc..eb927521c 100644 --- a/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js +++ b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js @@ -1,8 +1,8 @@ async function GetPublicUserName(request) { try { - const userName = request.params.userName; + const username = request.params.username; const userQuery = new Parse.Query('contracts_Users'); - userQuery.equalTo('UserName', userName); + userQuery.equalTo('UserName', username); const res = await userQuery.first({ useMasterKey: true }); return res; } catch (err) { diff --git a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js index 64c5cf522..37b53b95a 100644 --- a/apps/OpenSignServer/cloud/parsefunction/reportsJson.js +++ b/apps/OpenSignServer/cloud/parsefunction/reportsJson.js @@ -334,6 +334,7 @@ export default function reportJson(id, userId) { 'Signers.Email', 'Signers.Phone', 'Placeholders', + 'IsPublic', ], }; default: From 48fd9e52b3d7dddff7fd64b3147966a88c6cdd3c Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Wed, 29 May 2024 18:20:16 +0530 Subject: [PATCH 05/13] fix: update isEnableSuscription variable nname --- apps/OpenSign/src/constant/const.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/OpenSign/src/constant/const.js b/apps/OpenSign/src/constant/const.js index abfcce0ae..a046b4ae1 100644 --- a/apps/OpenSign/src/constant/const.js +++ b/apps/OpenSign/src/constant/const.js @@ -9,5 +9,4 @@ export const modalSubmitBtnColor = "#17a2b8"; export const modalCancelBtnColor = "white"; export const themeColor = "#47a3ad"; export const iconColor = "#686968"; -export const isEnableSubscription = false; -// process.env.REACT_APP_ENABLE_SUBSCRIPTION; +export const isEnableSubscription = process.env.REACT_APP_ENABLE_SUBSCRIPTION; From 33b0111ed0e8d5cdebe69683072d1249f37c224b Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Wed, 29 May 2024 18:43:24 +0530 Subject: [PATCH 06/13] fix: remove console --- apps/OpenSign/src/pages/UserProfile.js | 30 +- .../src/primitives/GetReportDisplay.js | 313 +++++++++--------- .../parsefunction/CreatePublicTemplate.js | 1 - 3 files changed, 171 insertions(+), 173 deletions(-) diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index 3803e43cf..625e103be 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -449,22 +449,22 @@ function UserProfile() { )} </span> </li> - {!isEnableSubscription && ( - <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> + {/* {!isEnableSubscription && ( */} + <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> - )} + <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"> diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 8cf06e6b0..7fb1a0b7f 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -6,8 +6,8 @@ import ModalUi from "./ModalUi"; import AddSigner from "../components/AddSigner"; import { modalSubmitBtnColor, - modalCancelBtnColor, - isEnableSubscription + modalCancelBtnColor + // isEnableSubscription } from "../constant/const"; import Alert from "./Alert"; import Tooltip from "./Tooltip"; @@ -857,7 +857,8 @@ const ReportTable = (props) => { {props.actions?.length > 0 && ( <th className="px-4 py-2 font-thin">Action</th> )} - {props.ReportName === "Templates" && !isEnableSubscription && ( + {props.ReportName === "Templates" && ( + // && !isEnableSubscription <th className="px-4 py-2 font-thin">Public</th> )} </tr> @@ -1299,167 +1300,165 @@ const ReportTable = (props) => { </ModalUi> )} </td> - {!isEnableSubscription && ( - <td className=" pl-[20px] py-2 "> - {props.ReportName === "Templates" && ( - <div className=" flex flex-row-"> - <label - className={ - "cursor-pointer relative inline-flex items-center mb-0" + {/* {!isEnableSubscription && ( */} + <td className=" pl-[20px] py-2 "> + {props.ReportName === "Templates" && ( + <div className=" flex flex-row-"> + <label + className={ + "cursor-pointer relative inline-flex items-center mb-0" + } + > + <input + checked={ + (isPublic && isPublic[item.objectId]) || + item?.IsPublic || + false } - > - <input - checked={ - (isPublic && isPublic[item.objectId]) || - item?.IsPublic || - false - } - onChange={(e) => handlePublicChange(e, item)} - type="checkbox" - value="" - className="sr-only peer" - /> - <div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-1 peer-focus:ring-black rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-black peer-checked:bg-blue-600"></div> - </label> - </div> - )} - {isMakePublic[item.objectId] && ( - <ModalUi - isOpen - title={"Make template public"} - handleClose={() => { - setIsMakePublic({}); - setSelectedPublicRole(""); - setIsPublic({}); - }} - > - <div className="m-[20px]"> - <div className="font-normal text-black"> - <p className="text-lg"> - {" "} - {isPublic[item.objectId] - ? `Are you sure you want to make this template + onChange={(e) => handlePublicChange(e, item)} + type="checkbox" + value="" + className="sr-only peer" + /> + <div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-1 peer-focus:ring-black rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-black peer-checked:bg-blue-600"></div> + </label> + </div> + )} + {isMakePublic[item.objectId] && ( + <ModalUi + isOpen + title={"Make template public"} + handleClose={() => { + setIsMakePublic({}); + setSelectedPublicRole(""); + setIsPublic({}); + }} + > + <div className="m-[20px]"> + <div className="font-normal text-black"> + <p className="text-lg"> + {" "} + {isPublic[item.objectId] + ? `Are you sure you want to make this template public ?` - : `Are you sure you want to make this template + : `Are you sure you want to make this template private ?`} - </p> - {isPublic[item.objectId] && ( - <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 - ) - } + </p> + {isPublic[item.objectId] && ( + <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="" > - <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> - )} - {isPublicProfile[item.objectId] && ( - <ModalUi - isOpen - title={"Public url"} - handleClose={() => { - setIsPublicProfile({}); - }} - reduceWidth={"md:min-w-[440px] md:max-w-[400px]"} - > - <div className="m-[20px]"> - {publicUserName ? ( - <div className="font-normal text-black"> - <p> - Here’s your public URL. Copy or share it - with the signer, and you will be able to - see all your publicly set templates. - </p> - <div className=" flex items-center gap-5 mt-2 p-[2px] w-[75%]"> - <span className="font-bold"> - Public Url :{" "} - </span> - <span - onClick={() => copytoProfileLink()} - className="underline underline-offset-2 cursor-pointer" - >{`https://opensign.me/${publicUserName}`}</span> - </div> - </div> - ) : ( - <div className="font-normal text-black"> - <p> - Please add your public URL, and you will - be able to make a public template. - </p> - <button - className="px-4 py-1.5 mt-3 text-white rounded shadow-md text-center focus:outline-none " - style={{ - backgroundColor: modalSubmitBtnColor - }} - onClick={() => navigate("/profile")} - > - Add - </button> + 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> - </ModalUi> - )} - </td> - )} + + <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> + )} + {isPublicProfile[item.objectId] && ( + <ModalUi + isOpen + title={"Public url"} + handleClose={() => { + setIsPublicProfile({}); + }} + reduceWidth={"md:min-w-[440px] md:max-w-[400px]"} + > + <div className="m-[20px]"> + {publicUserName ? ( + <div className="font-normal text-black"> + <p> + Here’s your public URL. Copy or share it + with the signer, and you will be able to see + all your publicly set templates. + </p> + <div className=" flex items-center gap-5 mt-2 p-[2px] w-[75%]"> + <span className="font-bold"> + Public Url :{" "} + </span> + <span + onClick={() => copytoProfileLink()} + className="underline underline-offset-2 cursor-pointer" + >{`https://opensign.me/${publicUserName}`}</span> + </div> + </div> + ) : ( + <div className="font-normal text-black"> + <p> + Please add your public URL, and you will be + able to make a public template. + </p> + <button + className="px-4 py-1.5 mt-3 text-white rounded shadow-md text-center focus:outline-none " + style={{ + backgroundColor: modalSubmitBtnColor + }} + onClick={() => navigate("/profile")} + > + Add + </button> + </div> + )} + </div> + </ModalUi> + )} + </td> + {/* )} */} </tr> ) )} diff --git a/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js index b7b5fb0b8..c99b878c2 100644 --- a/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js +++ b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js @@ -3,7 +3,6 @@ export default async function CreatePublicTemplate(request) { const ispublic = request.params.ispublic; const publicrole = request.params.publicrole; try { - console.log('session token', request); if (!request?.user) { throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); } else { From 6b47f6a27b158e1cee9919609fcc72843524f58a Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Wed, 29 May 2024 18:56:17 +0530 Subject: [PATCH 07/13] fix: add comment --- apps/OpenSign/src/pages/UserProfile.js | 9 +++------ apps/OpenSign/src/primitives/GetReportDisplay.js | 5 +++++ .../cloud/parsefunction/GetPublicUserName.js | 1 + 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index 625e103be..41937a862 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -30,7 +30,7 @@ function UserProfile() { const [publicUserName, setPublicUserName] = useState( extendUser && extendUser?.[0]?.UserName ); - const previousStateValueRef = useRef(publicUserName); + const previousPublicUserName = useRef(publicUserName); const [company, setCompany] = useState( extendUser && extendUser?.[0]?.Company ); @@ -79,7 +79,7 @@ function UserProfile() { } } }; - + //function to check public username already exist const handleCheckPublicUserName = async () => { try { const res = await Parse.Cloud.run("getpublicusername", { @@ -99,14 +99,11 @@ function UserProfile() { }; const handleSubmit = async (e) => { e.preventDefault(); - let phn = Phone, res; - - if (previousStateValueRef.current !== publicUserName) { + if (previousPublicUserName.current !== publicUserName) { res = await handleCheckPublicUserName(); } - if (!res) { setIsLoader(true); try { diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 7fb1a0b7f..b4dac2d5b 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -754,18 +754,22 @@ const ReportTable = (props) => { const handleViewSigners = (item) => { setIsViewShare({ [item.objectId]: true }); }; + //function to handle change template status is public or private const handlePublicChange = async (e, item) => { const getPlaceholder = item?.Placeholders; + //condiiton to check role is exist or not if (getPlaceholder && getPlaceholder.length > 0) { let extendUser = JSON.parse(localStorage.getItem("Extand_Class")); const userName = extendUser[0]?.UserName; setIsPublicUserName(extendUser[0]?.UserName); + //condition to check user have public url or not if (userName) { setIsPublic({ [item.objectId]: e.target.checked }); const getRole = []; getPlaceholder.map((data) => { getRole.push(data.Role); }); + //condiiton to handle role length if only one then set it default selected role if (getRole.length === 1) { setSelectedPublicRole(getRole[0]); } @@ -784,6 +788,7 @@ const ReportTable = (props) => { } }; + //function to copy public profile link const copytoProfileLink = () => { const url = `https://opensign.me/${publicUserName}`; copytoData(url); diff --git a/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js index eb927521c..720027084 100644 --- a/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js +++ b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js @@ -4,6 +4,7 @@ async function GetPublicUserName(request) { const userQuery = new Parse.Query('contracts_Users'); userQuery.equalTo('UserName', username); const res = await userQuery.first({ useMasterKey: true }); + return res; } catch (err) { return err; From c6be81923b9712865e5989b26faba9746f2a8cc3 Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Wed, 29 May 2024 19:24:04 +0530 Subject: [PATCH 08/13] fix: conflict resolved in userProfile --- apps/OpenSign/src/pages/UserProfile.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index 41937a862..fc1d6f43b 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -112,7 +112,7 @@ function UserProfile() { await query.get(UserProfile.objectId).then((object) => { object.set("name", name); object.set("ProfilePic", Image); - object.set("phone", phn); + object.set("phone", phn || ""); object.save().then( async (response) => { @@ -121,15 +121,18 @@ function UserProfile() { let rr = JSON.stringify(res); localStorage.setItem("UserInformation", rr); SetName(res.name); - SetPhone(res.phone); + SetPhone(res?.phone || ""); setImage(res.ProfilePic); localStorage.setItem("username", res.name); localStorage.setItem("profileImg", res.ProfilePic); - await updateExtUser({ Name: res.name, Phone: res.phone }); + await updateExtUser({ + Name: res.name, + Phone: res?.phone || "" + }); alert("Profile updated successfully."); setEditMode(false); setIsLoader(false); - // navigate("/dashboard/35KBoSgoAK"); + //navigate("/dashboard/35KBoSgoAK"); } }, (error) => { @@ -169,7 +172,6 @@ function UserProfile() { Company: company }; } - await axios.put( parseBaseUrl + "classes/" + extClass + "/" + ExtUserId, body, @@ -188,6 +190,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) => { @@ -279,7 +282,8 @@ function UserProfile() { setOtpLoader(false); alert("OTP sent on you email"); }; - const handlePublicUrl = (e) => { + //function to handle onchange user name nad restict 6 characters username for free users + const handleOnchangeUserName = (e) => { const value = e.target.value; if (value.length > 6 && !isSubscribe) { setUserNameError("Please upgrade to allow more than 6 characters."); @@ -453,7 +457,7 @@ function UserProfile() { <span>opensign.me/</span> <input - onChange={handlePublicUrl} + onChange={handleOnchangeUserName} value={publicUserName} disabled={!editmode} placeholder="enter user name" @@ -462,7 +466,6 @@ function UserProfile() { </div> </li> {/* )} */} - <li className="border-y-[1px] border-gray-300 break-all"> <div className="flex justify-between items-center py-2"> <span From 3e2c822bbcc0092efc673d863199ae3d558bfdfd Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Wed, 29 May 2024 19:39:27 +0530 Subject: [PATCH 09/13] fix: handle condition to save username --- apps/OpenSign/src/pages/UserProfile.js | 28 ++++++++------------------ 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index 72c75ba97..8cff09052 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -152,26 +152,14 @@ function UserProfile() { const extClass = localStorage.getItem("extended_class"); const extData = JSON.parse(localStorage.getItem("Extand_Class")); const ExtUserId = extData[0].objectId; - let body; - if (publicUserName) { - body = { - Phone: obj?.Phone || "", - Name: obj.Name, - HeaderDocId: isDisableDocId, - JobTitle: jobTitle, - Company: company, - UserName: publicUserName - }; - } else { - body = { - Phone: obj?.Phone || "", - Name: obj.Name, - HeaderDocId: isDisableDocId, - JobTitle: jobTitle, - Company: company - }; - } - + const body = { + Phone: obj?.Phone || "", + Name: obj.Name, + HeaderDocId: isDisableDocId, + JobTitle: jobTitle, + Company: company, + UserName: publicUserName + }; await axios.put( parseBaseUrl + "classes/" + extClass + "/" + ExtUserId, body, From dfc6da388c7b50e050d8be99a05ddab680d23d5b Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Wed, 29 May 2024 19:44:17 +0530 Subject: [PATCH 10/13] fix: add comment --- apps/OpenSign/src/pages/UserProfile.js | 28 ++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index 8cff09052..330e84ec9 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -101,6 +101,7 @@ function UserProfile() { e.preventDefault(); let phn = Phone, res; + //condition to call cloud function when user change publicUserName if (previousPublicUserName.current !== publicUserName) { res = await handleCheckPublicUserName(); } @@ -152,14 +153,25 @@ function UserProfile() { const extClass = localStorage.getItem("extended_class"); const extData = JSON.parse(localStorage.getItem("Extand_Class")); const ExtUserId = extData[0].objectId; - const body = { - Phone: obj?.Phone || "", - Name: obj.Name, - HeaderDocId: isDisableDocId, - JobTitle: jobTitle, - Company: company, - UserName: publicUserName - }; + let body; + if (publicUserName) { + body = { + Phone: obj?.Phone || "", + Name: obj.Name, + HeaderDocId: isDisableDocId, + JobTitle: jobTitle, + Company: company, + UserName: publicUserName + }; + } else { + body = { + Phone: obj?.Phone || "", + Name: obj.Name, + HeaderDocId: isDisableDocId, + JobTitle: jobTitle, + Company: company + }; + } await axios.put( parseBaseUrl + "classes/" + extClass + "/" + ExtUserId, body, From 83fe88911f9825a4b7a927813004cd286bb70b1d Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Thu, 30 May 2024 15:55:16 +0530 Subject: [PATCH 11/13] fix: public toggle issue,cloud function error handling --- apps/OpenSign/src/pages/UserProfile.js | 90 +++-- .../src/primitives/GetReportDisplay.js | 355 +++++++++--------- .../parsefunction/CreatePublicTemplate.js | 4 +- .../cloud/parsefunction/GetPublicTemplate.js | 11 +- .../cloud/parsefunction/GetPublicUserName.js | 24 +- 5 files changed, 264 insertions(+), 220 deletions(-) diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index 330e84ec9..dd684b129 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -94,7 +94,7 @@ function UserProfile() { return res; } } catch (e) { - console.log("extend error"); + console.log("error in getpublicusername cloud function"); } }; const handleSubmit = async (e) => { @@ -153,25 +153,15 @@ function UserProfile() { const extClass = localStorage.getItem("extended_class"); const extData = JSON.parse(localStorage.getItem("Extand_Class")); const ExtUserId = extData[0].objectId; - let body; - if (publicUserName) { - body = { - Phone: obj?.Phone || "", - Name: obj.Name, - HeaderDocId: isDisableDocId, - JobTitle: jobTitle, - Company: company, - UserName: publicUserName - }; - } else { - body = { - Phone: obj?.Phone || "", - Name: obj.Name, - HeaderDocId: isDisableDocId, - JobTitle: jobTitle, - Company: company - }; - } + const body = { + Phone: obj?.Phone || "", + Name: obj.Name, + HeaderDocId: isDisableDocId, + JobTitle: jobTitle, + Company: company, + UserName: publicUserName || "" + }; + await axios.put( parseBaseUrl + "classes/" + extClass + "/" + ExtUserId, body, @@ -282,7 +272,7 @@ function UserProfile() { setOtpLoader(false); alert("OTP sent on you email"); }; - //function to handle onchange user name nad restict 6 characters username for free users + //function to handle onchange username and restrict 6-characters username for free users const handleOnchangeUserName = (e) => { const value = e.target.value; if (value.length > 6 && !isSubscribe) { @@ -294,6 +284,15 @@ function UserProfile() { setPublicUserName(e.target.value); } }; + const handleCancel = () => { + setEditMode(false); + SetName(localStorage.getItem("username")); + SetPhone(UserProfile && UserProfile.phone); + setImage(localStorage.getItem("profileImg")); + setPublicUserName(extendUser && extendUser?.[0]?.UserName); + setCompany(extendUser && extendUser?.[0]?.Company); + setJobTitle(extendUser?.[0]?.JobTitle); + }; return ( <React.Fragment> <Title title={"Profile"} /> @@ -450,22 +449,37 @@ function UserProfile() { )} </span> </li> - {/* {!isEnableSubscription && ( */} - <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={handleOnchangeUserName} - value={publicUserName} - disabled={!editmode} - placeholder="enter user name" - className="border-[1px] border-gray-200 rounded-[3px]" - /> - </div> - </li> - {/* )} */} + {!isEnableSubscription && ( + <li className="flex justify-between items-center border-t-[1px] border-gray-300 py-2 break-all"> + <span className="font-semibold"> + Public profile :{" "} + <Tooltip + message={`this is your public URL. Copy or share it + with the signer, and you will be able to see + all your publicly set templates.`} + /> + </span> + <div className="flex items-center"> + <span>opensign.me/</span> + {editmode ? ( + <input + onChange={handleOnchangeUserName} + value={publicUserName} + disabled={!editmode} + placeholder="enter user name" + className="border-[1px] border-gray-200 rounded-[3px]" + /> + ) : ( + <input + value={extendUser?.[0]?.UserName} + disabled + 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 @@ -536,7 +550,7 @@ function UserProfile() { type="button" onClick={() => { if (editmode) { - setEditMode(false); + handleCancel(); } else { navigate("/changepassword"); } diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 9bf42c1e9..b42903dc4 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -6,8 +6,8 @@ import ModalUi from "./ModalUi"; import AddSigner from "../components/AddSigner"; import { modalSubmitBtnColor, - modalCancelBtnColor - // isEnableSubscription + modalCancelBtnColor, + isEnableSubscription } from "../constant/const"; import Alert from "./Alert"; import Tooltip from "./Tooltip"; @@ -49,12 +49,12 @@ 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 [currentLists, setCurrentLists] = useState([]); const [isPublic, setIsPublic] = useState({}); const [isPublicProfile, setIsPublicProfile] = useState({}); - const [publicUserName, setIsPublicUserName] = useState("raktima"); + const [publicUserName, setIsPublicUserName] = useState(""); const [isViewShare, setIsViewShare] = useState({}); const startIndex = (currentPage - 1) * props.docPerPage; const { isMoreDocs, setIsNextRecord } = props; @@ -246,8 +246,19 @@ const ReportTable = (props) => { // Get current list const indexOfLastDoc = currentPage * props.docPerPage; const indexOfFirstDoc = indexOfLastDoc - props.docPerPage; - // `currentLists` is total record render on current page - const currentLists = props.List?.slice(indexOfFirstDoc, indexOfLastDoc); + useEffect(() => { + // `currentLists` is total record render on current page + const currentList = props.List?.slice(indexOfFirstDoc, indexOfLastDoc); + //check public template and save in a object to show public and private template + setIsPublic( + currentList.reduce((acc, item) => { + acc[item.objectId] = item?.IsPublic || false; + return acc; + }, {}) + ); + setCurrentLists(currentList); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [indexOfLastDoc, indexOfFirstDoc]); // Change page const paginateFront = () => setCurrentPage(currentPage + 1); @@ -309,12 +320,15 @@ const ReportTable = (props) => { setActLoader({}); } }; - const handleClose = () => { + const handleClose = (item) => { setIsRevoke({}); setIsDeleteModal({}); setIsMakePublic({}); setSelectedPublicRole(""); - setIsPublic({}); + setIsPublic((prevStates) => ({ + ...prevStates, + [item.objectId]: !prevStates[item.objectId] + })); setIsPublicProfile({}); }; @@ -706,7 +720,7 @@ const ReportTable = (props) => { //function to make template public and set public role const handlePublicTemplate = async (item) => { - if (selectedPublicRole) { + if (selectedPublicRole || !isPublic[item.objectId]) { setActLoader({ [item.objectId]: true }); setIsMakePublic(false); try { @@ -740,7 +754,7 @@ const ReportTable = (props) => { setActLoader({}); } } catch (e) { - console.log("save public template error", e); + console.log("error in createpublictemplate", e); } } else { setIsAlert(true); @@ -764,16 +778,15 @@ const ReportTable = (props) => { setIsPublicUserName(extendUser[0]?.UserName); //condition to check user have public url or not if (userName) { - setIsPublic({ [item.objectId]: e.target.checked }); - const getRole = []; - getPlaceholder.map((data) => { - getRole.push(data.Role); - }); - //condiiton to handle role length if only one then set it default selected role - if (getRole.length === 1) { - setSelectedPublicRole(getRole[0]); + setIsPublic((prevStates) => ({ + ...prevStates, + [item.objectId]: e.target.checked + })); + const getPlaceholder = item?.Placeholders; + if (getPlaceholder.length === 1) { + setSelectedPublicRole(getPlaceholder[0]?.Role); } - setRole(getRole); + setIsMakePublic({ [item.objectId]: true }); } else { setIsPublicProfile({ [item.objectId]: true }); @@ -788,7 +801,7 @@ const ReportTable = (props) => { } }; - //function to copy public profile link + //function to copy public profile links const copytoProfileLink = () => { const url = `https://opensign.me/${publicUserName}`; copytoData(url); @@ -862,8 +875,7 @@ const ReportTable = (props) => { {props.actions?.length > 0 && ( <th className="px-4 py-2 font-thin">Action</th> )} - {props.ReportName === "Templates" && ( - // && !isEnableSubscription + {props.ReportName === "Templates" && !isEnableSubscription && ( <th className="px-4 py-2 font-thin">Public</th> )} </tr> @@ -1305,165 +1317,170 @@ const ReportTable = (props) => { </ModalUi> )} </td> - {/* {!isEnableSubscription && ( */} - <td className=" pl-[20px] py-2 "> - {props.ReportName === "Templates" && ( - <div className=" flex flex-row-"> - <label - className={ - "cursor-pointer relative inline-flex items-center mb-0" - } - > - <input - checked={ - (isPublic && isPublic[item.objectId]) || - item?.IsPublic || - false + {!isEnableSubscription && ( + <td className=" pl-[20px] py-2 "> + {props.ReportName === "Templates" && ( + <div className=" flex flex-row-"> + <label + className={ + "cursor-pointer relative inline-flex items-center mb-0" } - onChange={(e) => handlePublicChange(e, item)} - type="checkbox" - value="" - className="sr-only peer" - /> - <div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-1 peer-focus:ring-black rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-black peer-checked:bg-blue-600"></div> - </label> - </div> - )} - {isMakePublic[item.objectId] && ( - <ModalUi - isOpen - title={"Make template public"} - handleClose={() => { - setIsMakePublic({}); - setSelectedPublicRole(""); - setIsPublic({}); - }} - > - <div className="m-[20px]"> - <div className="font-normal text-black"> - <p className="text-lg"> - {" "} - {isPublic[item.objectId] - ? `Are you sure you want to make this template - public ?` - : `Are you sure you want to make this template - private ?`} - </p> - {isPublic[item.objectId] && ( - <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> + > + <input + checked={isPublic[item.objectId]} + onChange={(e) => handlePublicChange(e, item)} + type="checkbox" + value="" + className="sr-only peer" + /> + <div className="w-9 h-5 bg-gray-200 peer-focus:outline-none peer-focus:ring-1 peer-focus:ring-black rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-black peer-checked:bg-blue-600"></div> + </label> </div> - </ModalUi> - )} - {isPublicProfile[item.objectId] && ( - <ModalUi - isOpen - title={"Public url"} - handleClose={() => { - setIsPublicProfile({}); - }} - reduceWidth={"md:min-w-[440px] md:max-w-[400px]"} - > - <div className="m-[20px]"> - {publicUserName ? ( + )} + {isMakePublic[item.objectId] && ( + <ModalUi + isOpen + title={ + isPublic[item.objectId] + ? "Make template public" + : "Make template private" + } + handleClose={() => { + setIsMakePublic({}); + setSelectedPublicRole(""); + setIsPublic((prevStates) => ({ + ...prevStates, + [item.objectId]: !prevStates[item.objectId] + })); + }} + > + <div className="m-[20px]"> <div className="font-normal text-black"> - <p> - Here’s your public URL. Copy or share it - with the signer, and you will be able to see - all your publicly set templates. + <p className="text-lg"> + {" "} + {isPublic[item.objectId] + ? `Are you sure you want tof make this templat public ?` + : `Are you sure you want to make this template private? This will remove it from your public profile ?`} </p> - <div className=" flex items-center gap-5 mt-2 p-[2px] w-[75%]"> - <span className="font-bold"> - Public Url :{" "} - </span> - <span - onClick={() => copytoProfileLink()} - className="underline underline-offset-2 cursor-pointer" - >{`https://opensign.me/${publicUserName}`}</span> - </div> + {isPublic[item.objectId] && ( + <div className="flex mt-2 gap-2 md:items-center"> + <p className="text-[15px]"> + Public role :{" "} + </p> + {item?.Placeholders?.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> + {item?.Placeholders.map( + (data, ind) => { + return ( + <option + style={{ fontSize: "13px" }} + key={ind} + value={data?.Role} + > + {data?.Role} + </option> + ); + } + )} + </select> + ) : ( + <div className="w-[60%] md:w-[70%] border-[1px] border-gray-200 rounded-sm p-[2px]"> + {item?.Placeholders[0]?.Role} + </div> + )} + </div> + )} </div> - ) : ( - <div className="font-normal text-black"> - <p> - Please add your public URL, and you will be - able to make a public template. - </p> + + <hr className="bg-[#ccc] mt-2 " /> + <div className="flex items-center mt-3 gap-2 text-white"> <button - className="px-4 py-1.5 mt-3 text-white rounded shadow-md text-center focus:outline-none " + onClick={() => handlePublicTemplate(item)} + className="px-4 py-1.5 text-white rounded shadow-md text-center focus:outline-none " style={{ backgroundColor: modalSubmitBtnColor }} - onClick={() => navigate("/profile")} > - Add + Submit + </button> + <button + onClick={() => handleClose(item)} + 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> - {/* )} */} + </div> + </ModalUi> + )} + {isPublicProfile[item.objectId] && ( + <ModalUi + isOpen + title={"Public URL"} + handleClose={() => { + setIsPublicProfile({}); + }} + reduceWidth={"md:min-w-[440px] md:max-w-[400px]"} + > + <div className="m-[20px]"> + {publicUserName ? ( + <div className="font-normal text-black"> + <p> + Here’s your public URL. Copy or share it + with the signer, and you will be able to + see all your publicly set templates. + </p> + <div className=" flex items-center gap-5 mt-2 p-[2px] w-[75%]"> + <span className="font-bold"> + Public URL :{" "} + </span> + <span + onClick={() => copytoProfileLink()} + className="underline underline-offset-2 cursor-pointer" + >{`https://opensign.me/${publicUserName}`}</span> + </div> + </div> + ) : ( + <div className="font-normal text-black"> + <p> + Please add your public URL, and you will + be able to make a public template. + </p> + <button + className="px-4 py-1.5 mt-3 text-white rounded shadow-md text-center focus:outline-none " + style={{ + backgroundColor: modalSubmitBtnColor + }} + onClick={() => navigate("/profile")} + > + Add + </button> + </div> + )} + </div> + </ModalUi> + )} + </td> + )} </tr> ) )} diff --git a/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js index c99b878c2..1831b9f47 100644 --- a/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js +++ b/apps/OpenSignServer/cloud/parsefunction/CreatePublicTemplate.js @@ -7,7 +7,7 @@ export default async function CreatePublicTemplate(request) { throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); } else { const userId = request?.user && request?.user?.id; - if (userId) { + if (templateid) { const updateTemplate = new Parse.Object('contracts_Template'); updateTemplate.id = templateid; if (ispublic) { @@ -29,7 +29,7 @@ export default async function CreatePublicTemplate(request) { }; } } else { - throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User does not exist.'); + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Please provide required parameters!'); } } } catch (err) { diff --git a/apps/OpenSignServer/cloud/parsefunction/GetPublicTemplate.js b/apps/OpenSignServer/cloud/parsefunction/GetPublicTemplate.js index 33c2a1050..2b356bd32 100644 --- a/apps/OpenSignServer/cloud/parsefunction/GetPublicTemplate.js +++ b/apps/OpenSignServer/cloud/parsefunction/GetPublicTemplate.js @@ -16,14 +16,17 @@ async function GetPublicTemplate(request) { templatQuery.equalTo('IsPublic', true); const getTemplate = await templatQuery.find({ useMasterKey: true }); return getTemplate; + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Template not found'); } - return { error: "You don't have access of this document!" }; } else { - return { error: 'Please provide username!' }; + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Please provide required parameters!'); } } catch (err) { - console.log('error', err); - return { error: "You don't have access of this document!" }; + const code = err.code || 400; + const msg = err.message; + const error = new Parse.Error(code, msg); + throw error; } } export default GetPublicTemplate; diff --git a/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js index 720027084..94b08a330 100644 --- a/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js +++ b/apps/OpenSignServer/cloud/parsefunction/GetPublicUserName.js @@ -1,13 +1,23 @@ 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 }); - - return res; + if (!request?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } else { + const username = request.params.username; + if (username) { + const userQuery = new Parse.Query('contracts_Users'); + userQuery.equalTo('UserName', username); + const res = await userQuery.first({ useMasterKey: true }); + return res; + } else { + throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Please provide required parameters!'); + } + } } catch (err) { - return err; + const code = err.code || 400; + const msg = err.message; + const error = new Parse.Error(code, msg); + throw error; } } export default GetPublicUserName; From aeead23c864a110624c0c923b264273f01fcb5d7 Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Thu, 30 May 2024 15:57:16 +0530 Subject: [PATCH 12/13] fix: change public profile url --- apps/OpenSign/src/pages/UserProfile.js | 2 +- apps/OpenSign/src/primitives/GetReportDisplay.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/OpenSign/src/pages/UserProfile.js b/apps/OpenSign/src/pages/UserProfile.js index dd684b129..fe27836eb 100644 --- a/apps/OpenSign/src/pages/UserProfile.js +++ b/apps/OpenSign/src/pages/UserProfile.js @@ -460,7 +460,7 @@ function UserProfile() { /> </span> <div className="flex items-center"> - <span>opensign.me/</span> + <span>opensign-me.vercel.app/</span> {editmode ? ( <input onChange={handleOnchangeUserName} diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index b42903dc4..8b53afd56 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -803,7 +803,7 @@ const ReportTable = (props) => { //function to copy public profile links const copytoProfileLink = () => { - const url = `https://opensign.me/${publicUserName}`; + const url = `https://opensign-me.vercel.app/${publicUserName}`; copytoData(url); setIsAlert(true); setAlertMsg({ From c00406e40119ab713980044f8585d432a3e2765a Mon Sep 17 00:00:00 2001 From: RaktimaNXG <raktima.c@nxglabs.com> Date: Thu, 30 May 2024 17:24:33 +0530 Subject: [PATCH 13/13] fix: template list status issue on pagination --- .../src/primitives/GetReportDisplay.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/OpenSign/src/primitives/GetReportDisplay.js b/apps/OpenSign/src/primitives/GetReportDisplay.js index 8b53afd56..a20f72c55 100644 --- a/apps/OpenSign/src/primitives/GetReportDisplay.js +++ b/apps/OpenSign/src/primitives/GetReportDisplay.js @@ -748,13 +748,28 @@ const ReportTable = (props) => { type: "success", message: "You have successfully made the template private." }); - setSelectedPublicRole(""); } + const updateList = props.List.map((x) => + x.objectId === item.objectId + ? { ...x, IsPublic: isPublic[item.objectId] } + : x + ); + props.setList(updateList); setActLoader({}); } } catch (e) { console.log("error in createpublictemplate", e); + setIsAlert(true); + setAlertMsg({ + type: "danger", + message: "Something went wrong, Please try again later!" + }); + setTimeout(() => setIsAlert(false), 1500); + setIsPublic((prevStates) => ({ + ...prevStates, + [item.objectId]: !prevStates[item.objectId] + })); } } else { setIsAlert(true); @@ -765,6 +780,7 @@ const ReportTable = (props) => { setTimeout(() => setIsAlert(false), 1500); } }; + const handleViewSigners = (item) => { setIsViewShare({ [item.objectId]: true }); }; @@ -1327,7 +1343,7 @@ const ReportTable = (props) => { } > <input - checked={isPublic[item.objectId]} + checked={isPublic?.[item.objectId]} onChange={(e) => handlePublicChange(e, item)} type="checkbox" value=""