Merge pull request #568 from OpenSignLabs/custom_logo

feat: implement custom email template of request email and completion email
This commit is contained in:
prafull-opensignlabs
2024-04-08 20:26:06 +05:30
committed by GitHub
16 changed files with 831 additions and 127 deletions
+126
View File
@@ -34,6 +34,7 @@
"react-konva": "^18.2.10",
"react-modal": "^3.16.1",
"react-pdf": "^7.7.1",
"react-quill": "^2.0.0",
"react-redux": "^9.1.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.22.3",
@@ -6209,6 +6210,14 @@
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz",
"integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg=="
},
"node_modules/@types/quill": {
"version": "1.3.10",
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
"integrity": "sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==",
"dependencies": {
"parchment": "^1.1.2"
}
},
"node_modules/@types/range-parser": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz",
@@ -9432,6 +9441,25 @@
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
"integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA=="
},
"node_modules/deep-equal": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz",
"integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==",
"dependencies": {
"is-arguments": "^1.1.1",
"is-date-object": "^1.0.5",
"is-regex": "^1.1.4",
"object-is": "^1.1.5",
"object-keys": "^1.1.1",
"regexp.prototype.flags": "^1.5.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
@@ -10990,6 +11018,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -12616,6 +12649,21 @@
"node": ">= 10"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz",
@@ -16845,6 +16893,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
@@ -17198,6 +17261,11 @@
"tslib": "^2.0.3"
}
},
"node_modules/parchment": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
"integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg=="
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -19138,6 +19206,50 @@
}
]
},
"node_modules/quill": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz",
"integrity": "sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==",
"dependencies": {
"clone": "^2.1.1",
"deep-equal": "^1.0.1",
"eventemitter3": "^2.0.3",
"extend": "^3.0.2",
"parchment": "^1.1.4",
"quill-delta": "^3.6.2"
}
},
"node_modules/quill-delta": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz",
"integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==",
"dependencies": {
"deep-equal": "^1.0.1",
"extend": "^3.0.2",
"fast-diff": "1.1.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/quill-delta/node_modules/fast-diff": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz",
"integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig=="
},
"node_modules/quill/node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/quill/node_modules/eventemitter3": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz",
"integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg=="
},
"node_modules/radix-ui": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.0.1.tgz",
@@ -19749,6 +19861,20 @@
}
}
},
"node_modules/react-quill": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz",
"integrity": "sha512-4qQtv1FtCfLgoD3PXAur5RyxuUbPXQGOHgTlFie3jtxp43mXDtzCKaOgQ3mLyZfi1PUlyjycfivKelFhy13QUg==",
"dependencies": {
"@types/quill": "^1.3.10",
"lodash": "^4.17.4",
"quill": "^1.3.7"
},
"peerDependencies": {
"react": "^16 || ^17 || ^18",
"react-dom": "^16 || ^17 || ^18"
}
},
"node_modules/react-reconciler": {
"version": "0.29.0",
"resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz",
+1
View File
@@ -29,6 +29,7 @@
"react-konva": "^18.2.10",
"react-modal": "^3.16.1",
"react-pdf": "^7.7.1",
"react-quill": "^2.0.0",
"react-redux": "^9.1.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.22.3",
+8 -6
View File
@@ -28,13 +28,15 @@ const Header = ({ showSidebar }) => {
}, []);
async function checkSubscription() {
if (isEnableSubscription) {
const applogo = await getAppLogo();
if (applogo) {
setAppLogo(applogo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
const getIsSubscribe = await checkIsSubscribed();
if (getIsSubscribe) {
const applogo = await getAppLogo();
if (applogo) {
setAppLogo(applogo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
setIsPro(getIsSubscribe);
setIsSubscribe(getIsSubscribe);
}
@@ -0,0 +1,157 @@
import React from "react";
import { Quill } from "react-quill";
// Custom Undo button icon component for Quill editor. You can import it directly
// from 'quill/assets/icons/undo.svg' but I found that a number of loaders do not
// handle them correctly
const CustomUndo = () => (
<svg viewBox="0 0 18 18">
<polygon className="ql-fill ql-stroke" points="6 10 4 12 2 10 6 10" />
<path
className="ql-stroke"
d="M8.09,13.91A4.6,4.6,0,0,0,9,14,5,5,0,1,0,4,9"
/>
</svg>
);
// Redo button icon component for Quill editor
const CustomRedo = () => (
<svg viewBox="0 0 18 18">
<polygon className="ql-fill ql-stroke" points="12 10 14 12 16 10 12 10" />
<path
className="ql-stroke"
d="M9.91,13.91A4.6,4.6,0,0,1,9,14a5,5,0,1,1,5-5"
/>
</svg>
);
// Undo and redo functions for Custom Toolbar
function undoChange() {
this.quill.history.undo();
}
function redoChange() {
this.quill.history.redo();
}
// Add sizes to whitelist and register them
const Size = Quill.import("formats/size");
Size.whitelist = ["extra-small", "small", "medium", "large"];
Quill.register(Size, true);
// Add fonts to whitelist and register them
const Font = Quill.import("formats/font");
Font.whitelist = [
"arial",
"comic-sans",
"courier-new",
"georgia",
"helvetica",
"lucida"
];
Quill.register(Font, true);
// Modules object for setting up the Quill editor
export const module1 = {
toolbar: {
container: "#toolbar1",
handlers: {
undo: undoChange,
redo: redoChange
}
},
history: {
delay: 500,
maxStack: 100,
userOnly: true
}
};
// Formats objects for setting up the Quill editor
export const formats = [
"header",
"font",
"size",
"bold",
"italic",
"underline",
"align",
"strike",
"script",
"blockquote",
"background",
"list",
"bullet",
"indent",
"link",
"image",
"color",
"code-block"
];
// Quill Toolbar component
export const QuillToolbar = ({ containerId }) => (
<div id={containerId}>
<span className="ql-formats">
<select className="ql-font" defaultValue="arial">
<option value="arial">Arial</option>
<option value="comic-sans">Comic Sans</option>
<option value="courier-new">Courier New</option>
<option value="georgia">Georgia</option>
<option value="helvetica">Helvetica</option>
<option value="lucida">Lucida</option>
</select>
<select className="ql-size" defaultValue="medium">
<option value="extra-small">Size 1</option>
<option value="small">Size 2</option>
<option value="medium">Size 3</option>
<option value="large">Size 4</option>
</select>
<select className="ql-header" defaultValue="3">
<option value="1">Heading</option>
<option value="2">Subheading</option>
<option value="3">Normal</option>
</select>
</span>
<span className="ql-formats">
<button className="ql-bold" />
<button className="ql-italic" />
<button className="ql-underline" />
<button className="ql-strike" />
</span>
<span className="ql-formats">
<button className="ql-list" value="ordered" />
<button className="ql-list" value="bullet" />
<button className="ql-indent" value="-1" />
<button className="ql-indent" value="+1" />
</span>
<span className="ql-formats">
<button className="ql-script" value="super" />
<button className="ql-script" value="sub" />
<button className="ql-blockquote" />
<button className="ql-direction" />
</span>
<span className="ql-formats">
<select className="ql-align" />
<select className="ql-color" />
<select className="ql-background" />
</span>
<span className="ql-formats">
<button className="ql-link" />
<button className="ql-image" />
<button className="ql-video" />
</span>
<span className="ql-formats">
<button className="ql-formula" />
<button className="ql-code-block" />
<button className="ql-clean" />
</span>
<span className="ql-formats">
<button className="ql-undo">
<CustomUndo />
</button>
<button className="ql-redo">
<CustomRedo />
</button>
</span>
</div>
);
export default QuillToolbar;
@@ -0,0 +1,52 @@
import React from "react";
import Tooltip from "../../primitives/Tooltip";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import EditorToolbar, { module1, formats } from "./EditorToolbar";
export function EmailBody(props) {
return (
<React.Fragment>
<div className={`flex justify-center py-1 p-2`}>
<div className=" flex flex-col">
<form>
<div className="text-lg font-normal text-black">
<div className=" m-2 md:m-10 p-3 md:p-10 shadow-md hover:shadow-lg border-[1px] border-indigo-800 rounded-md">
<label className="text-sm ml-2">
Subject <Tooltip message={"email subject"} />
</label>
<input
required
value={props.requestSubject}
onChange={(e) => props.setRequestSubject(e.target.value)}
placeholder="${senderName} has requested you to sign ${documentName}"
className="px-3 py-2 w-full border-[1px] border-gray-300 rounded focus:outline-none text-xs"
/>
<label className="text-sm ml-2 mt-3">
Body <Tooltip message={"email body"} />
</label>
<div className="px-3 py-2 w-full focus:outline-none text-xs">
<EditorToolbar containerId="toolbar1" />
<ReactQuill
theme="snow"
value={props.requestBody}
// value={typedText}
placeholder="add body of email "
// readOnly={loading}
// onChangeSelection={handleTextSelection} // Listen for text selection
ref={props.editorRef}
modules={module1}
formats={formats}
onChange={props.handleOnchangeRequest}
/>
</div>
</div>
</div>
</form>
</div>
</div>
</React.Fragment>
);
}
+64 -6
View File
@@ -1,6 +1,6 @@
import axios from "axios";
import moment from "moment";
import { themeColor } from "./const";
import { isEnableSubscription, themeColor } from "./const";
import React from "react";
import { rgb } from "pdf-lib";
import Parse from "parse";
@@ -501,14 +501,32 @@ export const signPdfFun = async (
base64Url,
documentId,
signerObjectId,
setIsAlert
setIsAlert,
objectId,
isSubscribed
) => {
let singleSign;
let singleSign,
isCustomCompletionMail = false;
//get tenant details
const tenantDetails = await getTenantDetails(objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert("User does not exist");
} else {
if (
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject &&
(!isEnableSubscription || isSubscribed)
) {
isCustomCompletionMail = true;
}
}
singleSign = {
pdfFile: base64Url,
docId: documentId,
userId: signerObjectId
userId: signerObjectId,
isCustomCompletionMail: isCustomCompletionMail
};
const response = await axios
@@ -1211,8 +1229,8 @@ export const multiSignEmbed = async (
position.type === radioButtonWidget
? 10
: position.type === "checkbox"
? 10
: newUpdateHeight;
? 10
: newUpdateHeight;
const newHeight = ind ? (ind > 0 ? widgetHeight : 0) : widgetHeight;
if (signyourself) {
@@ -1804,3 +1822,43 @@ export const getAppLogo = async () => {
return null;
}
};
export const getTenantDetails = async (objectId) => {
try {
const tenantCreditsQuery = new Parse.Query("partners_Tenant");
tenantCreditsQuery.equalTo("UserId", {
__type: "Pointer",
className: "_User",
objectId: objectId
});
const res = await tenantCreditsQuery.first();
if (res) {
const updateRes = JSON.parse(JSON.stringify(res));
return updateRes;
}
} catch (e) {
return "user does not exist!";
}
};
//function to convert variable string name to variable value of email body and subject
export function replaceMailVaribles(subject, body, variables) {
let replacedSubject = subject;
let replacedBody = body;
for (const variable in variables) {
const regex = new RegExp(`{{${variable}}}`, "g");
if (subject) {
replacedSubject = replacedSubject.replace(regex, variables[variable]);
}
if (body) {
replacedBody = replacedBody.replace(regex, variables[variable]);
}
}
const result = {
subject: replacedSubject,
body: replacedBody
};
return result;
}
+21 -7
View File
@@ -3,8 +3,8 @@ import { useNavigate, useParams } from "react-router-dom";
import "../styles/loginPage.css";
import loader from "../assets/images/loader2.gif";
import axios from "axios";
import { themeColor } from "../constant/const";
import { contractUsers } from "../constant/Utils";
import { isEnableSubscription, themeColor } from "../constant/const";
import { contractUsers, getAppLogo } from "../constant/Utils";
function GuestLogin() {
const { id, userMail, contactBookId, serverUrl } = useParams();
@@ -14,14 +14,27 @@ function GuestLogin() {
const [EnterOTP, setEnterOtp] = useState(false);
const [loading, setLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [appLogo, setAppLogo] = useState(
"https://qikinnovation.ams3.digitaloceanspaces.com/logo.png"
);
useEffect(() => {
handleServerUrl();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function generate serverUrl and parseAppId from url and save it in local storage
const handleServerUrl = () => {
const handleServerUrl = async () => {
if (isEnableSubscription) {
const applogo = await getAppLogo();
if (applogo) {
setAppLogo(applogo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
//split url in array from '&'
localStorage.clear();
const checkSplit = serverUrl.split("&");
@@ -164,11 +177,12 @@ function GuestLogin() {
}}
>
<div className="main_head">
<div className="main-logo">
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
<img
alt="sign img"
src="https://qikinnovation.ams3.digitaloceanspaces.com/logo.png"
width="100%"
src={appLogo}
loading={true}
className="object-contain h-full"
alt="logo"
/>
</div>
</div>
+7 -6
View File
@@ -58,11 +58,13 @@ function Login() {
}, []);
const saveLogo = async () => {
const logo = await getAppLogo();
if (logo) {
setImage(logo);
} else {
setImage(appInfo?.applogo || undefined);
if (isEnableSubscription) {
const logo = await getAppLogo();
if (logo) {
setImage(logo);
} else {
setImage(appInfo?.applogo || undefined);
}
}
};
const handleChange = (event) => {
@@ -1070,7 +1072,6 @@ function Login() {
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
<img
src={image}
loading={true}
className="object-contain h-full"
alt="The image displays the OpenSign logo with a stylized blue square with an open corner, accompanied by the tagline Seal the Deal, Openly."
/>
+78 -32
View File
@@ -21,7 +21,8 @@ import {
onSaveSign,
onSaveImage,
addDefaultSignatureImg,
radioButtonWidget
radioButtonWidget,
replaceMailVaribles
} from "../constant/Utils";
import Loader from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
@@ -567,14 +568,17 @@ function PdfRequestFiles() {
flag,
containerWH
);
//get ExistUserPtr object id of user class to get tenantDetails
const objectId = pdfDetails?.[0]?.ExtUserPtr?.UserId?.objectId;
//function for call to embed signature in pdf and get digital signature pdf
try {
const res = await signPdfFun(
pdfBytes,
documentId,
signerObjectId,
setIsAlert
setIsAlert,
objectId,
isSubscribed
);
if (res && res.status === "success") {
setPdfUrl(res.data);
@@ -586,10 +590,12 @@ function PdfRequestFiles() {
const index = pdfDetails?.[0].Signers.findIndex(
(x) => x.Email === jsonSender.email
);
const requestBody = pdfDetails?.[0]?.RequestBody;
const requestSubject = pdfDetails?.[0]?.RequestSubject;
const newIndex = index + 1;
const user = pdfDetails?.[0].Signers[newIndex];
if (user) {
// console.log("pdfDetails", pdfDetails);
const expireDate = pdfDetails?.[0].ExpiryDate.iso;
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString("en-US", {
@@ -597,7 +603,9 @@ function PdfRequestFiles() {
month: "long",
year: "numeric"
});
let sender = pdfDetails?.[0].ExtUserPtr.Email;
let senderEmail = pdfDetails?.[0].ExtUserPtr.Email;
let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone;
const senderName = `${pdfDetails?.[0].ExtUserPtr.Name}`;
try {
const imgPng =
@@ -625,33 +633,70 @@ function PdfRequestFiles() {
? pdfDetails[0].ExtUserPtr.Company
: "";
const themeBGcolor = themeColor;
let replaceVar;
if (
requestBody &&
requestSubject &&
(!isEnableSubscription || isSubscribed)
) {
const replacedRequestBody = requestBody.replace(
/"/g,
"'"
);
const htmlReqBody =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
replacedRequestBody +
"</body> </html>";
const variables = {
document_title: pdfDetails?.[0].Name,
sender_name: senderName,
sender_mail: senderEmail,
sender_phone: senderPhone,
receiver_name: user.Name,
receiver_email: user.Email,
receiver_phone: user.Phone,
expiry_date: localExpireDate,
company_name: orgName,
signing_url: `<a href=${signPdf}>Sign here</a>`
};
replaceVar = replaceMailVaribles(
requestSubject,
htmlReqBody,
variables
);
}
let params = {
extUserId: extUserId,
recipient: user.Email,
subject: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign ${pdfDetails?.[0].Name}`,
from: sender,
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'=> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
imgPng +
" height='50' style='padding: 20px,width:170px,height:40px' /></div> <div style=' padding: 2px;font-family: system-ui;background-color:" +
themeBGcolor +
";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " +
pdfDetails?.[0].ExtUserPtr.Name +
" has requested you to review and sign <strong> " +
pdfDetails?.[0].Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
sender +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender +
" directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
openSignUrl +
" target=_blank>here</a>.</p> </div></div></body> </html>"
subject: requestSubject
? replaceVar?.subject
: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign ${pdfDetails?.[0].Name}`,
from: senderEmail,
html: requestBody
? replaceVar?.body
: "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'=> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
imgPng +
" height='50' style='padding: 20px,width:170px,height:40px' /></div> <div style=' padding: 2px;font-family: system-ui;background-color:" +
themeBGcolor +
";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " +
pdfDetails?.[0].ExtUserPtr.Name +
" has requested you to review and sign <strong> " +
pdfDetails?.[0].Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
senderEmail +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
senderEmail +
" directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
openSignUrl +
" target=_blank>here</a>.</p> </div></div></body> </html>"
};
await axios.post(url, params, {
headers: headers
@@ -689,6 +734,7 @@ function PdfRequestFiles() {
});
}
}
//function for update TourStatus
const closeTour = async () => {
setWidgetsTour(false);
@@ -1052,9 +1098,9 @@ function PdfRequestFiles() {
isDecline.currnt === "Sure"
? "Are you sure want to decline this document ?"
: isDecline.currnt === "YouDeclined"
? "You have declined this document!"
: isDecline.currnt === "another" &&
"You cannot sign this document as it has been declined by one or more recipient(s)."
? "You have declined this document!"
: isDecline.currnt === "another" &&
"You cannot sign this document as it has been declined by one or more recipient(s)."
}
footerMessage={isDecline.currnt === "Sure"}
declineDoc={declineDoc}
+234 -48
View File
@@ -28,7 +28,9 @@ import {
textInputWidget,
textWidget,
radioButtonWidget,
color
color,
getTenantDetails,
replaceMailVaribles
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import { useNavigate } from "react-router-dom";
@@ -40,8 +42,11 @@ import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import WidgetNameModal from "../components/pdf/WidgetNameModal";
import { SaveFileSize } from "../constant/saveFileSize";
import { EmailBody } from "../components/pdf/EmailBody";
import Upgrade from "../primitives/Upgrade";
function PlaceHolderSign() {
const editorRef = useRef();
const navigate = useNavigate();
const { state } = useLocation();
const [pdfDetails, setPdfDetails] = useState([]);
@@ -83,6 +88,8 @@ function PlaceHolderSign() {
const [signKey, setSignKey] = useState();
const [tempSignerId, setTempSignerId] = useState("");
const [blockColor, setBlockColor] = useState("");
const [defaultBody, setDefaultBody] = useState("");
const [defaultSubject, setDefaultSubject] = useState("");
const [pdfLoadFail, setPdfLoadFail] = useState({
status: false,
type: "load"
@@ -106,11 +113,14 @@ function PlaceHolderSign() {
const [mailStatus, setMailStatus] = useState("");
const [isCurrUser, setIsCurrUser] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [requestSubject, setRequestSubject] = useState("");
const [requestBody, setRequestBody] = useState("");
const [isAlreadyPlace, setIsAlreadyPlace] = useState({
status: false,
message: ""
});
const [extUserId, setExtUserId] = useState("");
const [isCustomize, setIsCustomize] = useState(false);
const isMobile = window.innerWidth < 767;
const [, drop] = useDrop({
accept: "BOX",
@@ -170,6 +180,48 @@ function PlaceHolderSign() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
//function to fetch tenant Details
const fetchTenantDetails = async () => {
const user = JSON.parse(
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
)
);
if (user) {
try {
const tenantDetails = await getTenantDetails(user?.objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert("User does not exist");
} else if (tenantDetails) {
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br>`;
if (tenantDetails?.RequestBody) {
setRequestBody(tenantDetails?.RequestBody);
setRequestSubject(tenantDetails?.RequestSubject);
setDefaultBody(defaultRequestBody);
setDefaultSubject(
`{{senderName}} has requested you to sign {{documentName}}`
);
} else {
setRequestBody(defaultRequestBody);
setRequestSubject(
`{{senderName}} has requested you to sign {{documentName}}`
);
setDefaultBody(defaultRequestBody);
setDefaultSubject(
`{{senderName}} has requested you to sign {{documentName}}`
);
}
}
} catch (e) {
alert("User does not exist");
}
} else {
alert("User does not exist");
}
};
useEffect(() => {
if (divRef.current) {
const pdfWidth = pdfNewWidthFun(divRef);
@@ -204,6 +256,7 @@ function PlaceHolderSign() {
}
//function for get document details
const getDocumentDetails = async () => {
fetchTenantDetails();
//getting document details
const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) {
@@ -816,6 +869,7 @@ function PlaceHolderSign() {
};
const sendEmailToSigners = async () => {
let htmlReqBody;
setIsUiLoading(true);
const pdfUrl = await embedPrefilllData();
setIsSendAlert({});
@@ -827,12 +881,15 @@ function PlaceHolderSign() {
month: "long",
year: "numeric"
});
let sender = pdfDetails?.[0].ExtUserPtr.Email;
let senderEmail = pdfDetails?.[0]?.ExtUserPtr?.Email;
let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone;
let signerMail = signersdata.slice();
if (pdfDetails?.[0]?.SendinOrder && pdfDetails?.[0]?.SendinOrder === true) {
signerMail.splice(1);
}
for (let i = 0; i < signerMail.length; i++) {
try {
const imgPng =
@@ -857,40 +914,77 @@ function PlaceHolderSign() {
? pdfDetails[0].ExtUserPtr.Company
: "";
const themeBGcolor = themeColor;
const senderName = `${pdfDetails?.[0].ExtUserPtr.Name}`;
const documentName = `${pdfDetails?.[0].Name}`;
let replaceVar;
if (
requestBody &&
requestSubject &&
isCustomize &&
(isSubscribe || !isEnableSubscription)
) {
const replacedRequestBody = requestBody.replace(/"/g, "'");
htmlReqBody =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
replacedRequestBody +
"</body> </html>";
const variables = {
document_title: documentName,
sender_name: senderName,
sender_mail: senderEmail,
sender_phone: senderPhone,
receiver_name: signerMail[i].Name,
receiver_email: signerMail[i].Email,
receiver_phone: signerMail[i].Phone,
expiry_date: localExpireDate,
company_name: orgName,
signing_url: `<a href=${signPdf}>Sign here</a>`
};
replaceVar = replaceMailVaribles(
requestSubject,
htmlReqBody,
variables
);
}
let params = {
extUserId: extUserId,
recipient: signerMail[i].Email,
subject: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign ${pdfDetails?.[0].Name}`,
from: sender,
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'=> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
imgPng +
" height='50' style='padding: 20px,width:170px,height:40px' /></div> <div style=' padding: 2px;font-family: system-ui;background-color:" +
themeBGcolor +
";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " +
pdfDetails?.[0].ExtUserPtr.Name +
" has requested you to review and sign <strong> " +
pdfDetails?.[0].Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
sender +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expire on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
sender +
" directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
openSignUrl +
" target=_blank>here</a>.</p> </div></div></body> </html>"
subject: isCustomize
? replaceVar?.subject
: `${senderName} has requested you to sign ${documentName}`,
from: senderEmail,
html: isCustomize
? replaceVar?.body
: "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'=> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" +
imgPng +
" height='50' style='padding: 20px,width:170px,height:40px' /></div> <div style=' padding: 2px;font-family: system-ui;background-color:" +
themeBGcolor +
";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " +
pdfDetails?.[0].ExtUserPtr.Name +
" has requested you to review and sign <strong> " +
pdfDetails?.[0].Name +
"</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" +
senderEmail +
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " +
orgName +
"</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expire on</td><td> </td> <td style='color:#626363;font-weight:bold'>" +
localExpireDate +
"</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" +
signPdf +
"> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " +
senderEmail +
" directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href= " +
openSignUrl +
" target=_blank>here</a>.</p> </div></div></body> </html>"
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
console.log("error", error);
}
}
if (sendMail.data.result.status === "success") {
setMailStatus("success");
const signers = signersdata?.map((x) => {
@@ -923,21 +1017,45 @@ function PlaceHolderSign() {
try {
if (updateExpiryDate) {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
ExpiryDate: {
iso: updateExpiryDate,
__type: "Date"
}
};
if (isCustomize) {
data = {
RequestBody: htmlReqBody,
RequestSubject: requestSubject,
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
ExpiryDate: {
iso: updateExpiryDate,
__type: "Date"
}
};
} else {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
ExpiryDate: {
iso: updateExpiryDate,
__type: "Date"
}
};
}
} else {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers
};
if (isCustomize) {
data = {
RequestBody: htmlReqBody,
RequestSubject: requestSubject,
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers
};
} else {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers
};
}
}
await axios
@@ -1394,6 +1512,15 @@ function PlaceHolderSign() {
const closePopup = () => {
setIsAddUser({});
};
//function for handle ontext change and save again text in delta in Request Email flow
const handleOnchangeRequest = () => {
if (editorRef.current) {
const html = editorRef.current.editor.root.innerHTML;
setRequestBody(html);
}
};
return (
<>
<Title title={state?.title ? state.title : "New Document"} />
@@ -1475,20 +1602,78 @@ function PlaceHolderSign() {
: isSendAlert.mssg === "confirm" && "Send Mail"
}
handleClose={() => setIsSendAlert({})}
showHeaderMessage={isSendAlert.mssg === "confirm"}
>
<div style={{ height: "100%", padding: 20 }}>
<div
className="max-h-96 overflow-y-scroll scroll-hide"
style={{ padding: 20 }}
>
{isSendAlert.mssg === "sure" ? (
<p>Please add field for all recipients.</p>
<span>Please add field for all recipients.</span>
) : isSendAlert.mssg === textWidget ? (
<p>Please confirm that you have filled the text field.</p>
) : (
isSendAlert.mssg === "confirm" && (
<p>
Are you sure you want to send out this document for
signatures?
</p>
<>
<p>
Are you sure you want to send out this document for
signatures?
</p>
<>
<div
className={
"flex mt-4 items-center gap-1 mt-2 underline text-blue-700 focus:outline-none "
}
onClick={() => {
isSubscribe ||
(!isEnableSubscription &&
setIsCustomize(!isCustomize));
}}
>
<span
className={
isSubscribe || !isEnableSubscription
? "cursor-pointer"
: "opacity-30 select-none"
}
>
Cutomize Email
</span>
{!isSubscribe && isEnableSubscription && (
<Upgrade message="Upgrade to customize Email" />
)}
</div>
</>
{isCustomize &&
(!isEnableSubscription || isSubscribe) && (
<>
<EmailBody
editorRef={editorRef}
requestBody={requestBody}
requestSubject={requestSubject}
handleOnchangeRequest={handleOnchangeRequest}
setRequestSubject={setRequestSubject}
/>
<div
className={
"flex justify-end items-center gap-1 mt-2 underline text-blue-700 focus:outline-none cursor-pointer "
}
onClick={() => {
setRequestBody(defaultBody);
setRequestSubject(defaultSubject);
}}
>
<span>Reset to default</span>
</div>
</>
)}
</>
)
)}
<div
style={{
height: "1px",
@@ -1506,7 +1691,7 @@ function PlaceHolderSign() {
type="button"
className="finishBtn"
>
Yes
Send
</button>
)}
<button
@@ -1852,6 +2037,7 @@ function PlaceHolderSign() {
</button>
</div>
</ModalUi>
<LinkUserModal
handleAddUser={handleAddUser}
isAddUser={isAddUser}
+8 -11
View File
@@ -475,11 +475,13 @@ const Signup = () => {
}, []);
const saveLogo = async () => {
const logo = await getAppLogo();
if (logo) {
setImage(logo);
} else {
setImage(appInfo?.applogo || undefined);
if (isEnableSubscription) {
const logo = await getAppLogo();
if (logo) {
setImage(logo);
} else {
setImage(appInfo?.applogo || undefined);
}
}
};
@@ -529,12 +531,7 @@ const Signup = () => {
<div>
<div className="md:m-10 lg:m-16 md:p-4 lg:p-10 p-5 bg-[#ffffff] md:border-[1px] md:border-gray-400 ">
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
<img
src={image}
loading={true}
className="object-contain h-full"
alt="logo"
/>
<img src={image} className="object-contain h-full" alt="logo" />
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
<div className="">
+25 -7
View File
@@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from "react";
import { PDFDocument } from "pdf-lib";
import "../styles/signature.css";
import { themeColor } from "../constant/const";
import { isEnableSubscription, themeColor } from "../constant/const";
import axios from "axios";
import Loader from "../primitives/LoaderWithMsg";
import loader from "../assets/images/loader2.gif";
@@ -26,7 +26,9 @@ import {
contactBook,
randomId,
getDate,
textWidget
textWidget,
getTenantDetails,
checkIsSubscribed
} from "../constant/Utils";
import { useParams } from "react-router-dom";
import Tour from "reactour";
@@ -431,13 +433,13 @@ function SignYourSelf() {
Width: widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth
: dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).width
: "",
? defaultWidthHeight(dragTypeValue).width
: "",
Height: widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getHeight
: dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).height
: "",
? defaultWidthHeight(dragTypeValue).height
: "",
options: addWidgetOptions(dragTypeValue)
};
@@ -595,9 +597,25 @@ function SignYourSelf() {
// console.log("signyourself", xyPostion);
//function for get digital signature
const signPdfFun = async (base64Url, documentId) => {
let isCustomCompletionMail = false;
const getIsSubscribe = await checkIsSubscribed();
const tenantDetails = await getTenantDetails(jsonSender.objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert("User does not exist");
} else {
if (
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject &&
(!isEnableSubscription || getIsSubscribe)
) {
isCustomCompletionMail = true;
}
}
let singleSign = {
pdfFile: base64Url,
docId: documentId
docId: documentId,
isCustomCompletionMail: isCustomCompletionMail
};
await axios
+12 -2
View File
@@ -1,4 +1,6 @@
import React from "react";
import { isEnableSubscription } from "../constant/const";
import PremiumAlertHeader from "./PremiumAlertHeader";
const ModalUi = ({
children,
@@ -8,12 +10,13 @@ const ModalUi = ({
handleClose,
showHeader = true,
showClose = true,
styleClass
styleClass,
showHeaderMessage
}) => {
return (
<>
{isOpen && (
<div className="fixed z-[999] top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]">
<div className="fixed z-[999] 2 top-0 left-0 w-[100%] h-[100%] bg-black bg-opacity-[75%]">
<div
className={
styleClass
@@ -37,6 +40,13 @@ const ModalUi = ({
)}
</div>
)}
{!isEnableSubscription && showHeaderMessage && (
<PremiumAlertHeader
message={
"Cutomize Email is free in beta, this feature will incur a fee later."
}
/>
)}
<div>{children}</div>
</div>
</div>
+2 -2
View File
@@ -1,7 +1,7 @@
import React from "react";
import { useNavigate } from "react-router-dom";
function Upgrade() {
function Upgrade({ message }) {
const navigation = useNavigate();
return (
@@ -10,7 +10,7 @@ function Upgrade() {
onClick={() => navigation("/subscription")}
className="text-blue-800 text-sm cursor-pointer hover:underline"
>
Upgrade now
{message ? message : "Upgrade now"}
</span>
</sup>
);
+3
View File
@@ -968,6 +968,9 @@ option {
}
}
.scroll-hide::-webkit-scrollbar {
display: none;
}
@media screen and (max-width: 350px) and (min-width: 311px) {
.signatureCanvas {
width: 280px;
@@ -0,0 +1,33 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
// TODO: set className here
const className = 'contracts_Document';
const schema = new Parse.Schema(className);
schema.addString('RequestBody');
schema.addString('RequestSubject');
// 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_Document';
const schema = new Parse.Schema(className);
schema.deleteField('RequestBody');
schema.deleteField('RequestSubject');
// TODO: Set the schema here
// Example:
// schema.deleteField('name').deleteField('cash');
return schema.update();
};