mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-08-25 09:02:33 +02:00
feat: create public profile URLs for users and develop functionality to make the report template public
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<React.Fragment>
|
||||
<Title title={"Profile"} />
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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!" };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
+33
@@ -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();
|
||||
};
|
||||
Binary file not shown.
Reference in New Issue
Block a user