feat: create public profile URLs for users and develop functionality to make the report template public

This commit is contained in:
RaktimaNXG
2024-05-27 21:08:05 +05:30
parent f5e216fedc
commit 1764d7f9ba
8 changed files with 349 additions and 36 deletions
+8
View File
@@ -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",
+87 -36
View File
@@ -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>
)
+4
View File
@@ -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;
@@ -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.