Merge pull request #550 from OpenSignLabs/patch-1

Patch 1
This commit is contained in:
Amol
2024-04-01 20:15:21 +05:30
committed by GitHub
47 changed files with 1944 additions and 1027 deletions
+2 -1
View File
@@ -15,6 +15,7 @@ import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage";
import { isEnableSubscription } from "./constant/const";
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
@@ -113,7 +114,7 @@ function App() {
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
{process.env.REACT_APP_ENABLE_SUBSCRIPTION && (
{isEnableSubscription && (
<>
<Route
path="/pgsignup"
+2 -2
View File
@@ -56,11 +56,11 @@ const AddSigner = (props) => {
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest");
if (localStorage.getItem("TenetId")) {
if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenetId")
objectId: localStorage.getItem("TenantId")
});
}
+2 -2
View File
@@ -48,11 +48,11 @@ const AddUser = (props) => {
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_User");
if (localStorage.getItem("TenetId")) {
if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenetId")
objectId: localStorage.getItem("TenantId")
});
}
+28 -6
View File
@@ -4,20 +4,32 @@ import FullScreenButton from "./FullScreenButton";
import { useNavigate } from "react-router-dom";
import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize";
import { openInNewTab } from "../constant/Utils";
import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import { isEnableSubscription } from "../constant/const";
const Header = ({ showSidebar }) => {
const navigation = useNavigate();
const navigate = useNavigate();
const { width } = useWindowSize();
let applogo = localStorage.getItem("appLogo") || "";
let username = localStorage.getItem("username");
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true);
const toggleDropdown = () => {
setIsOpen(!isOpen);
};
useEffect(() => {
checkSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function checkSubscription() {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
}
const closeDropdown = () => {
setIsOpen(false);
Parse.User.logOut();
@@ -43,7 +55,7 @@ const Header = ({ showSidebar }) => {
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
navigation("/");
navigate("/");
};
//handle to close profile drop down menu onclick screen
@@ -83,6 +95,16 @@ const Header = ({ showSidebar }) => {
id="profile-menu"
className="flex justify-between items-center gap-x-3"
>
{!isSubscribe && (
<div>
<button
className="text-xs bg-[#002864] p-2 text-white rounded shadow"
onClick={() => navigate("/subscription")}
>
Upgrade Now
</button>
</div>
)}
<div>
<FullScreenButton />
</div>
@@ -123,7 +145,7 @@ const Header = ({ showSidebar }) => {
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
onClick={() => {
setIsOpen(false);
navigation("/profile");
navigate("/profile");
}}
>
<i className="fa-regular fa-user"></i> Profile
@@ -132,7 +154,7 @@ const Header = ({ showSidebar }) => {
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
onClick={() => {
setIsOpen(false);
navigation("/changepassword");
navigate("/changepassword");
}}
>
<i className="fa-solid fa-lock"></i> Change Password
@@ -30,7 +30,7 @@ const AddRoleModal = (props) => {
margin: "10px 0 10px 5px"
}}
>
e.g: Hr, Director, Manager, New joinee, Accountant, etc...
e.g: Customer, Hr, Director, Manager, Student, etc...
</p>
<div>
<div
@@ -1,8 +1,9 @@
import React, { useEffect, useState } from "react";
import { themeColor } from "../../constant/const";
import { isEnableSubscription, themeColor } from "../../constant/const";
import ModalUi from "../../primitives/ModalUi";
import { radioButtonWidget } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
function DropdownWidgetOption(props) {
const [dropdownOptionList, setDropdownOptionList] = useState([
"option-1",
@@ -241,36 +242,59 @@ function DropdownWidgetOption(props) {
}}
className="fa-solid fa-square-plus"
></i>
{props.type === "checkbox" && !props.isSignYourself && (
<>
<label style={{ fontSize: "13px", fontWeight: "600" }}>
Minimun check
</label>
<input
required
defaultValue={0}
value={minCount}
onChange={(e) => {
const count = handleSetMinMax(e);
setMinCount(count);
}}
className="drodown-input"
/>
<label style={{ fontSize: "13px", fontWeight: "600" }}>
Maximum check
</label>
<input
required
defaultValue={0}
value={maxCount}
onChange={(e) => {
const count = handleSetMinMax(e);
setMaxCount(count);
}}
className="drodown-input"
/>
</>
)}
<div>
{props.type === "checkbox" && !props.isSignYourself && (
<>
<label
style={{
fontSize: "13px",
fontWeight: "600",
color: !props.isSubscribe && "gray"
}}
>
Minimun check
</label>
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
<input
required
defaultValue={0}
value={minCount}
onChange={(e) => {
const count = handleSetMinMax(e);
setMinCount(count);
}}
className={
props.isSubscribe || !isEnableSubscription
? "drodown-input"
: "disabled drodown-input"
}
/>
<label
style={{
fontSize: "13px",
fontWeight: "600",
color: !props.isSubscribe && "gray"
}}
>
Maximum check
</label>
<input
required
defaultValue={0}
value={maxCount}
onChange={(e) => {
const count = handleSetMinMax(e);
setMaxCount(count);
}}
className={
props.isSubscribe || !isEnableSubscription
? "drodown-input"
: "disabled drodown-input"
}
/>
</>
)}
</div>
</div>
{["dropdown", radioButtonWidget].includes(props.type) && (
<>
@@ -378,13 +402,15 @@ function DropdownWidgetOption(props) {
</div>
)}
</div>
{props.type === "checkbox" && !props.isSignYourself && (
<PremiumAlertHeader
message={
"Field validations are free in beta, this feature will incur a fee later."
}
/>
)}
{props.type === "checkbox" &&
!props.isSignYourself &&
!isEnableSubscription && (
<PremiumAlertHeader
message={
"Field validations are free in beta, this feature will incur a fee later."
}
/>
)}
<div
className={`${
props.type === "checkbox" && !props.isSignYourself
@@ -15,7 +15,8 @@ function EmailComponent({
setSuccessEmail,
pdfName,
sender,
setIsAlert
setIsAlert,
extUserId
}) {
const [emailList, setEmailList] = useState([]);
const [emailValue, setEmailValue] = useState();
@@ -38,6 +39,7 @@ function EmailComponent({
const openSignUrl = "https://www.opensignlabs.com/contact-us";
const themeBGcolor = themeColor;
let params = {
extUserId: extUserId,
pdfName: pdfName,
url: pdfUrl,
recipient: emailList[i],
@@ -1,6 +1,7 @@
import React from "react";
import { themeColor } from "../../constant/const";
import RecipientList from "./RecipientList";
import { Tooltip } from "react-tooltip";
function SignerListPlace(props) {
return (
@@ -13,6 +14,58 @@ function SignerListPlace(props) {
>
<span className="signedStyle">
{props.title ? props.title : "Recipients"}
<span className="absolute text-xs z-[30] mt-1 ml-0.5">
{props?.title === "Roles" && (
<>
<a data-tooltip-id="my-tooltip">
<sup>
<i
className="fa-solid fa-question rounded-full"
style={{
borderColor: "white",
color: "white",
fontSize: 11,
borderWidth: 1.5,
padding: "1px 3px"
}}
></i>
</sup>
</a>
<Tooltip id="my-tooltip">
<div className="max-w-[450px]">
<p className="font-bold">What are template roles?</p>
<p>
Begin by specifying each role needed for the completion of
the document. Think about the parties involved in the
signing process and what their responsibilities are.
Common roles include HR for internal documents, Customer
for agreements or Vendor for business agreements.{" "}
</p>
<p className="font-bold">
Why pre-attach users to some roles?
</p>
<p>
For roles that consistently involve the same individual
(e.g., the CEO&apos;s signature on employee offer
letters), you can pre-attach a user to a role within the
template. This step is optional but recommended for
efficiency and consistency across documents.
</p>
<p className="font-bold">
When do i specify the user attached to each role?
</p>
<p>
When you create a document from your template, you&apos;ll
be prompted to attach users to each defined role. If a
role already has a user attached, this will be pre-filled,
but you can modify it as needed before sending out the
document.
</p>
</div>
</Tooltip>
</>
)}
</span>
</span>
</div>
<div className="signerList">
@@ -4,6 +4,8 @@ import "../../styles/AddUser.css";
import RegexParser from "regex-parser";
import { textInputWidget } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
const WidgetNameModal = (props) => {
const [formdata, setFormdata] = useState({
@@ -100,13 +102,14 @@ const WidgetNameModal = (props) => {
title={"Widget info"}
>
{(props.defaultdata?.type === textInputWidget ||
props.widgetName === textInputWidget) && (
<PremiumAlertHeader
message={
"Field validations are free in beta, this feature will incur a fee later."
}
/>
)}
props.widgetName === textInputWidget) &&
!isEnableSubscription && (
<PremiumAlertHeader
message={
"Field validations are free in beta, this feature will incur a fee later."
}
/>
)}
<form
onSubmit={handleSubmit}
className={`${
@@ -133,10 +136,20 @@ const WidgetNameModal = (props) => {
props.widgetName === textInputWidget) && (
<>
<div className="form-section">
<label htmlFor="textvalidate" style={{ fontSize: 13 }}>
<label
htmlFor="textvalidate"
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{ fontSize: 13 }}
>
Validation
</label>
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
<div
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{
display: "flex",
flexDirection: "row",
@@ -178,6 +191,7 @@ const WidgetNameModal = (props) => {
</div>
</div>
</div>
<div className="form-section">
<label htmlFor="name" style={{ fontSize: 13 }}>
Default value
@@ -196,7 +210,10 @@ const WidgetNameModal = (props) => {
}}
/>
{isValid === false && (
<div className="warning defaultvalueWarning" style={{ fontSize: 12 }}>
<div
className="warning defaultvalueWarning"
style={{ fontSize: 12 }}
>
<i
className="fas fa-exclamation-circle"
style={{ color: "#fab005", fontSize: 15 }}
+24
View File
@@ -3,6 +3,7 @@ import moment from "moment";
import { themeColor } from "./const";
import React from "react";
import { rgb } from "pdf-lib";
import Parse from "parse";
export const isMobile = window.innerWidth < 767;
export const textInputWidget = "text input";
@@ -12,6 +13,29 @@ export const openInNewTab = (url) => {
window.open(url, "_blank", "noopener,noreferrer");
};
//function to get subcripition details from Extand user class
export async function checkIsSubscribed() {
const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass);
const user = await Parse.Cloud.run("getUserDetails", {
email: jsonSender[0].Email
});
const freeplan = user?.get("Plan") && user?.get("Plan")?.plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
return false;
} else if (billingDate) {
if (billingDate > new Date()) {
return true;
} else {
return false;
}
} else {
return false;
}
}
export const color = [
"#93a3db",
"#e6c3db",
+1
View File
@@ -9,3 +9,4 @@ 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;
+39 -60
View File
@@ -2,26 +2,25 @@ import axios from "axios";
const parseAppId = localStorage.getItem("parseAppId");
const serverUrl = localStorage.getItem("baseUrl");
export const SaveFileSize = async (size, imageUrl) => {
export const SaveFileSize = async (size, imageUrl, tenantId) => {
//checking server url and save file's size
const tenantPtr = {
__type: "Pointer",
className: "partners_Tenant",
objectId: tenantId
};
const _tenantPtr = JSON.stringify(tenantPtr);
try {
const response = await axios
.get(`${serverUrl}classes/partners_TenantCredits`, {
const res = await axios.get(
`${serverUrl}classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
})
.then((result) => {
const res = result.data;
// console.log("res", res);
return res.results;
})
.catch((err) => {
console.log("axois err ", err);
});
}
);
const response = res.data.results;
let data;
// console.log("response", response);
if (response && response.length > 0) {
@@ -30,69 +29,49 @@ export const SaveFileSize = async (size, imageUrl) => {
? response[0].usedStorage + size
: size
};
await axios
.put(
`${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`,
data,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
}
)
.then(() => {
// const res = result.data;
// console.log("save res", res);
})
.catch((err) => {
console.log("axois err ", err);
});
} else {
data = {
usedStorage: size
};
await axios
.post(`${serverUrl}classes/partners_TenantCredits`, data, {
await axios.put(
`${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`,
data,
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
})
.then(() => {
// const res = result.data;
// console.log("res", res);
})
.catch((err) => {
console.log("axois err ", err);
});
}
);
} else {
data = { usedStorage: size, PartnersTenant: tenantPtr };
await axios.post(`${serverUrl}classes/partners_TenantCredits`, data, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
});
}
} catch (e) {
console.log("org app error", e);
} catch (err) {
console.log("err in save usage", err);
}
saveDataFile(size, imageUrl);
saveDataFile(size, imageUrl, tenantPtr);
};
//function for save fileUrl and file size in particular client db class partners_DataFiles
const saveDataFile = async (size, imageUrl) => {
const saveDataFile = async (size, imageUrl, tenantPtr) => {
const data = {
FileUrl: imageUrl,
FileSize: size
FileSize: size,
TenantPtr: tenantPtr
};
// console.log("data save",file, data)
await axios
.post(`${serverUrl}classes/partners_DataFiles`, data, {
try {
await axios.post(`${serverUrl}classes/partners_DataFiles`, data, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
})
.then(() => {
// const res = result.data;
// console.log("res", res);
})
.catch((err) => {
console.log("axois err ", err);
});
} catch (err) {
console.log("err in save usage ", err);
}
};
+69 -64
View File
@@ -1,65 +1,70 @@
[
{
"planName": "SELF HOSTED",
"currency": "",
"price": "Free",
"subtitle": "Free for life One click install.",
"btnText": "Visit github",
"url": "https://github.com/opensignlabs/opensign",
"target": "_blank",
"benefits": [
"Sign Unlimited Documents",
"S3 Doc Storage",
"Unlimited Guest Signers",
"Unlimited completion certificates",
"Unique Code(OTP) verification for guest signers",
"Expiring docs & reject signature support",
"PDF Template Creation(coming soon)",
"Audit Trails",
"API support",
"Add-ons support(coming soon)",
"Self hosted cloud infrastructure",
"Free for lifetime",
"Community support"
]
},
{
"planName": "THANKS PLAN",
"currency": "$",
"price": "29.99",
"subtitle": "Use code <span style='background-color: yellow;'>'FREEBETA'</span> to get this for free(First 1000 users)",
"btnText": "Subscribe",
"url": "https://subscriptions.zoho.in/subscribe/ef798486e6a0a11ea65f2bae8f2af901dbadf3175d495584fd25b9af5d2f2b9e/thanks",
"target": "_self",
"benefits": [
"Sign Unlimited Documents",
"Unlimited Secure Doc Storage with OpenSign™ Drive",
"Unlimited Guest Signers",
"Unlimited completion certificates",
"Unique Code(OTP) verification for guest signers",
"Expiring docs & reject signature support",
"PDF Template Creation(coming soon)",
"Audit Trails",
"API support",
"Add-ons support(coming soon)",
"Self hosted cloud infrastructure"
]
},
{
"planName": "ENTERPRISE",
"currency": "",
"price": "Request Price",
"subtitle": "Customization available Priority support.",
"btnText": "Contact us",
"url": "https://www.opensignlabs.com/contact-us",
"target": "_blank",
"benefits": [
"All features",
"Custom domain",
"Whitelabeling",
"Uptime SLA",
"SSO support",
"24/7 support"
]
}
]
{
"planName": "OPENSIGN™ FREE",
"currency": "",
"monthlyPrice": "Free",
"yearlyPrice": "Free",
"subtitle": "Free Unlimited E-signatures, Forever.",
"btnText": "Subscribe",
"url": "",
"target": "_blank",
"benefits": [
"Unlimited envelopes",
"Sign documents yourself",
"Request signatures from others",
"14 field types",
"Automatic e-signatures",
"Completion certificates",
"Send in order",
"Document templates",
"Import from Dropbox",
"Contact book",
"Document expiry support",
"Decline document support",
"Email notifications",
"Recipient authentication using OTP",
"And much more"
]
},
{
"planName": "OPENSIGN™ PROFESSIONAL",
"currency": "$",
"monthlyPrice": "29.99",
"yearlyPrice": "329.99",
"subtitle": "Exclusive Access to advanced features.",
"btnText": "Subscribe",
"url": "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d1a09dfa8085585cdd4ec4d7f32137f3/professional-monthly",
"yearlyUrl": "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly",
"target": "_self",
"benefits": [
"Everything in OpenSign™ professional",
"Field validations",
"Regular expression validations",
"Organize docs in OpenSign™ Drive",
"Webhooks",
"Zapier integration",
"API Access",
"100 API signatures included",
"DocumentId removal from signed docs",
"Custom email templates(coming soon)"
]
},
{
"planName": "OPENSIGN™ ENTERPRISE",
"currency": "",
"monthlyPrice": "Request Price",
"yearlyPrice": "Request Price",
"subtitle": "Scalable Features with priority support.",
"btnText": "Contact us",
"url": "https://www.opensignlabs.com/contact-us",
"target": "_blank",
"benefits": [
"All features",
"Custom domain",
"Custom branding",
"Uptime SLA",
"SSO support",
"Priority support"
]
}
]
+37 -10
View File
@@ -9,6 +9,7 @@ import { useSelector } from "react-redux";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import { useNavigate, useLocation, Outlet } from "react-router-dom";
import { isEnableSubscription } from "../constant/const";
const HomeLayout = () => {
const navigate = useNavigate();
@@ -18,6 +19,12 @@ const HomeLayout = () => {
const arr = useSelector((state) => state.TourSteps);
const [isUserValid, setIsUserValid] = useState(true);
const [isLoader, setIsLoader] = useState(true);
// reactour state
const [isCloseBtn, setIsCloseBtn] = useState(true);
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
const [tourConfigs, setTourConfigs] = useState([]);
useEffect(() => {
(async () => {
try {
@@ -27,8 +34,7 @@ const HomeLayout = () => {
sessionToken: localStorage.getItem("accesstoken")
});
if (user) {
setIsUserValid(true);
setIsLoader(false);
checkIsSubscribed();
} else {
setIsUserValid(false);
}
@@ -37,14 +43,36 @@ const HomeLayout = () => {
setIsUserValid(false);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// reactour state
const [isCloseBtn, setIsCloseBtn] = useState(true);
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
const [tourConfigs, setTourConfigs] = useState([]);
async function checkIsSubscribed() {
const currentUser = Parse.User.current();
const user = await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
});
if (isEnableSubscription) {
const freeplan = user?.get("Plan") && user?.get("Plan").plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
setIsUserValid(true);
setIsLoader(false);
} else if (billingDate) {
if (billingDate > new Date()) {
setIsUserValid(true);
setIsLoader(false);
} else {
navigate(`/subscription`);
}
} else {
navigate(`/subscription`);
}
} else {
setIsUserValid(true);
setIsLoader(false);
}
}
const showSidebar = () => {
setIsOpen((value) => !value);
};
@@ -198,7 +226,7 @@ const HomeLayout = () => {
return (
<div>
<div className="sticky top-0 z-50">
<Header showSidebar={showSidebar} />
{!isLoader && <Header showSidebar={showSidebar} />}
</div>
{isUserValid ? (
<>
@@ -223,7 +251,6 @@ const HomeLayout = () => {
<>
<div className="flex md:flex-row flex-col z-50">
<Sidebar isOpen={isOpen} closeSidebar={closeSidebar} />
<div
id="renderList"
className="relative h-screen flex flex-col justify-between w-full overflow-y-auto"
+7
View File
@@ -10,6 +10,7 @@ import SelectFolder from "../components/shared/fields/SelectFolder";
import SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
import { SaveFileSize } from "../constant/saveFileSize";
// `Form` render all type of Form on this basis of their provided in path
function Form() {
@@ -82,6 +83,7 @@ const Forms = (props) => {
const handleFileUpload = async (file) => {
setfileload(true);
const fileName = file.name;
const size = file.size;
const name = sanitizeFileName(fileName);
const pdfFile = file;
const parseFile = new Parse.File(name, pdfFile);
@@ -101,6 +103,8 @@ const Forms = (props) => {
setFileUpload(response.url());
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
} catch (error) {
@@ -113,6 +117,7 @@ const Forms = (props) => {
const dropboxSuccess = async (files) => {
setfileload(true);
const file = files[0];
const size = file.bytes;
const url = file.link;
const mb = Math.round(file.bytes / Math.pow(1024, 2));
@@ -141,6 +146,8 @@ const Forms = (props) => {
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
} catch (error) {
+75 -24
View File
@@ -1,14 +1,16 @@
import React, { useEffect, useState } from "react";
import Title from "../components/Title";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi";
import { rejectBtn, submitBtn } from "../constant/const";
import { openInNewTab } from "../constant/Utils";
import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
function GenerateToken() {
const navigation = useNavigate();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [apiToken, SetApiToken] = useState("");
@@ -17,6 +19,7 @@ function GenerateToken() {
const [isGenerate, setIsGenerate] = useState(false);
const [isErr, setIsErr] = useState(false);
const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => {
fetchToken();
@@ -25,6 +28,11 @@ function GenerateToken() {
const fetchToken = async () => {
try {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
const url = parseBaseUrl + "functions/getapitoken";
const headers = {
"Content-Type": "application/json",
@@ -35,6 +43,7 @@ function GenerateToken() {
if (res) {
SetApiToken(res.data.result.result);
}
setIsLoader(false);
} catch (err) {
SetApiToken();
@@ -90,6 +99,7 @@ function GenerateToken() {
}, 1500); // Reset copied state after 1.5 seconds
};
const handleModal = () => setIsModal(!isModal);
return (
<React.Fragment>
<Title title={"API Token"} />
@@ -117,35 +127,47 @@ function GenerateToken() {
</div>
) : (
<div className="bg-white flex flex-col justify-center shadow rounded">
<PremiumAlertHeader />
<h1 className="ml-4 mt-3 mb-2 font-semibold">
API Token{" "}
{!isEnableSubscription && <PremiumAlertHeader />}
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
OpenSign API{" "}
<Tooltip
url={"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"}
url={
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
}
isSubscribe={true}
/>
</h1>
<ul className="w-full flex flex-col p-2 text-sm">
<ul
className={
isSubscribe || !isEnableSubscription
? "w-full flex flex-col p-2 text-sm "
: "w-full flex flex-col p-2 text-sm opacity-20 pointer-events-none select-none"
}
>
<li
className={`flex justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
>
<span className="w-[40%]">Api Token:</span>{" "}
<span
id="token"
className="w-[60%] md:text-end cursor-pointer"
onClick={() => copytoclipboard(apiToken)}
<div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
<span className="">Api Token:</span>{" "}
<span
id="token"
className=" md:text-end cursor-pointer"
onClick={() => copytoclipboard(apiToken)}
>
{apiToken ? apiToken : "_____"}
</span>
</div>
<button
type="button"
onClick={apiToken ? handleModal : handleSubmit}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
>
{apiToken && apiToken}
</span>
{apiToken ? "Regenerate Token" : "Generate Token"}
</button>
</li>
</ul>
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
<button
type="button"
onClick={apiToken ? handleModal : handleSubmit}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
>
{apiToken ? "Regenerate Token" : "Generate Token"}
</button>
<div className="flex items-center justify-center ">
<button
type="button"
onClick={() =>
@@ -153,12 +175,41 @@ function GenerateToken() {
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
)
}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
className="rounded hover:bg-[#15b4e9] border-[1px] my-2 border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
>
View Docs
</button>
</div>
{!isSubscribe && isEnableSubscription && (
<>
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
Upgrade to PRO Plan
</h1>
<ul className={"w-full flex flex-col p-2 text-sm "}>
<li
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
>
<div className="w-[70%] flex-col md:flex-row flex items-center gap-3 ">
<span className="">$29.99/month:</span>{" "}
<span id="token" className=" md:text-end cursor-pointer">
Sign 100 docs using API. Only 0.15/docs after that.
</span>
</div>
{!isSubscribe && isEnableSubscription && (
<button
type="button"
onClick={() => navigation("/subscription")}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
>
Upgrade Now
</button>
)}
</li>
</ul>
</>
)}
<ModalUi
isOpen={isModal}
title={"Regenerate Token"}
+2 -1
View File
@@ -59,7 +59,8 @@ function GuestLogin() {
"X-Parse-Application-Id": parseId
};
let body = {
email: email.toString()
email: email.toString(),
docId: id
};
let Otp = await axios.post(url, body, { headers: headers });
+137 -27
View File
@@ -10,7 +10,11 @@ import { NavLink, useNavigate, useLocation } from "react-router-dom";
import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize";
import ModalUi from "../primitives/ModalUi";
import { modalCancelBtnColor, modalSubmitBtnColor } from "../constant/const";
import {
isEnableSubscription,
modalCancelBtnColor,
modalSubmitBtnColor
} from "../constant/const";
import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
@@ -209,7 +213,7 @@ function Login() {
x.TenantId.TenantName || ""
};
localStorage.setItem(
"TenetId",
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
@@ -239,10 +243,7 @@ function Login() {
element.pageType
);
setState({ ...state, loading: false });
if (
process.env
.REACT_APP_ENABLE_SUBSCRIPTION
) {
if (isEnableSubscription) {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
@@ -253,10 +254,15 @@ function Login() {
"userDetails",
JSON.stringify(LocalUserDetails)
);
const freeplan =
results[0].get("Plan") &&
results[0].get("Plan").plan_code;
const billingDate =
results[0].get("Next_billing_date") &&
results[0].get("Next_billing_date");
if (billingDate) {
if (freeplan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (billingDate > new Date()) {
localStorage.removeItem(
"userDetails"
@@ -292,9 +298,7 @@ function Login() {
element.pageType
);
setState({ ...state, loading: false });
if (
process.env.REACT_APP_ENABLE_SUBSCRIPTION
) {
if (isEnableSubscription) {
const LocalUserDetails = {
name: _user.name,
email: email,
@@ -391,6 +395,18 @@ function Login() {
const setThirdpartyLoader = (value) => {
setState({ ...state, thirdpartyLoader: value });
};
const handleFreePlan = async (id) => {
try {
const params = { userId: id };
const res = await Parse.Cloud.run("freesubscription", params);
if (res.status === "error") {
alert(res.result);
}
} catch (err) {
console.log("err in free subscribe", err.message);
alert("Somenthing went wrong, please try again later!");
}
};
const thirdpartyLoginfn = async (sessionToken, billingDate) => {
const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
@@ -400,6 +416,11 @@ function Login() {
"X-Parse-Application-Id": parseAppId
}
});
const param = new URLSearchParams(location.search);
const isFreeplan = param?.get("subscription") === "freeplan";
if (isFreeplan) {
await handleFreePlan(res.data.objectId);
}
await Parse.User.become(sessionToken).then(() => {
window.localStorage.setItem("accesstoken", sessionToken);
});
@@ -542,7 +563,7 @@ function Login() {
tenentName: x.TenantId.TenantName || ""
};
localStorage.setItem(
"TenetId",
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
@@ -571,18 +592,28 @@ function Login() {
);
setThirdpartyLoader(false);
setState({ ...state, loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
if (isEnableSubscription) {
if (billingDate) {
if (billingDate > new Date()) {
localStorage.removeItem("userDetails");
navigate(redirectUrl);
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
}
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
} else {
navigate(`/subscription`, { replace: true });
}
} else {
navigate(redirectUrl);
@@ -597,17 +628,29 @@ function Login() {
localStorage.setItem("pageType", element.pageType);
setState({ ...state, loading: false });
setThirdpartyLoader(false);
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
if (isEnableSubscription) {
if (billingDate) {
if (billingDate > new Date()) {
localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
navigate(`/subscription`, { replace: true });
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
}
} else {
navigate(`/subscription`, { replace: true });
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
}
} else {
navigate(redirectUrl);
@@ -745,6 +788,13 @@ function Login() {
// Get TenentID from Extendend Class
localStorage.setItem("extended_class", item.extended_class);
const currentUser = Parse.User.current();
const userSettings = appInfo.settings;
const setting = userSettings.find(
(x) => x.role === _currentRole
);
const redirectUrl =
location?.state?.from ||
`/${setting.pageType}/${setting.pageId}`;
await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email")
}).then(
@@ -777,22 +827,78 @@ function Login() {
tenentName: x.TenantId.TenantName || ""
};
localStorage.setItem(
"TenetId",
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
}
});
localStorage.setItem("PageLanding", item.pageId);
localStorage.setItem("defaultmenuid", item.menuId);
localStorage.setItem("pageType", item.pageType);
navigate(`/${item.pageType}/${item.pageId}`);
localStorage.setItem("PageLanding", setting.pageId);
localStorage.setItem(
"defaultmenuid",
setting.menuId
);
localStorage.setItem("pageType", setting.pageType);
if (isEnableSubscription) {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
phone: results[0].get("Phone"),
company: results[0].get("Company")
};
localStorage.setItem(
"userDetails",
JSON.stringify(LocalUserDetails)
);
const billingDate =
results[0].get("Next_billing_date") &&
results[0].get("Next_billing_date");
const freeplan =
results[0]?.get("Plan") &&
results[0]?.get("Plan").plan_code;
if (freeplan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (billingDate > new Date()) {
localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
navigate(`/subscription`);
}
} else {
navigate(`/subscription`);
}
} else {
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
}
}
} else {
localStorage.setItem("PageLanding", item.pageId);
localStorage.setItem("defaultmenuid", item.menuId);
localStorage.setItem("pageType", item.pageType);
navigate(`/${item.pageType}/${item.pageId}`);
localStorage.setItem("PageLanding", setting.pageId);
localStorage.setItem("defaultmenuid", setting.menuId);
localStorage.setItem("pageType", setting.pageType);
setState({ ...state, loading: false });
if (isEnableSubscription) {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
phone: results[0].get("Phone")
// company: results.get("Company"),
};
localStorage.setItem(
"userDetails",
JSON.stringify(LocalUserDetails)
);
const billingDate = "";
if (billingDate) {
navigate(`/subscription`);
}
} else {
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
}
}
},
(error) => {
@@ -1045,7 +1151,11 @@ function Login() {
</button>
<NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
to="/signup"
to={
location.search
? "/signup" + location.search
: "/signup"
}
style={width < 768 ? { textAlign: "center" } : {}}
>
Create Account
+6 -1
View File
@@ -5,6 +5,7 @@ import "../styles/signature.css";
import { toDataUrl } from "../constant/Utils";
import Parse from "parse";
import { appInfo } from "../constant/appinfo";
import { SaveFileSize } from "../constant/saveFileSize";
const ManageSign = () => {
const appName = appInfo.appname;
const [penColor, setPenColor] = useState("blue");
@@ -158,7 +159,11 @@ const ManageSign = () => {
try {
const parseFile = new Parse.File(file.name, file);
const response = await parseFile.save();
return response?.url();
if (response?.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(file.size, response.url(), tenantId);
return response?.url();
}
} catch (err) {
console.log("sign upload err", err);
setIsLoader(false);
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -353,7 +353,7 @@ const PgSignUp = () => {
tenentName: x.TenantId.TenantName || ""
};
localStorage.setItem(
"TenetId",
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
+36 -2
View File
@@ -3,7 +3,7 @@ import axios from "axios";
import Parse from "parse";
import "../styles/signature.css";
import { PDFDocument } from "pdf-lib";
import { themeColor } from "../constant/const";
import { isEnableSubscription, themeColor } from "../constant/const";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd";
@@ -39,6 +39,7 @@ import TourContentWithBtn from "../primitives/TourContentWithBtn";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import WidgetNameModal from "../components/pdf/WidgetNameModal";
import { SaveFileSize } from "../constant/saveFileSize";
function PlaceHolderSign() {
const navigate = useNavigate();
@@ -104,11 +105,12 @@ function PlaceHolderSign() {
const [widgetName, setWidgetName] = useState(false);
const [mailStatus, setMailStatus] = useState("");
const [isCurrUser, setIsCurrUser] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isAlreadyPlace, setIsAlreadyPlace] = useState({
status: false,
message: ""
});
const [extUserId, setExtUserId] = useState("");
const isMobile = window.innerWidth < 767;
const [, drop] = useDrop({
accept: "BOX",
@@ -180,11 +182,35 @@ function PlaceHolderSign() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current]);
async function checkIsSubscribed(email) {
const user = await Parse.Cloud.run("getUserDetails", {
email: email
});
const freeplan = user?.get("Plan") && user?.get("Plan").plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
return true;
} else if (billingDate) {
if (billingDate > new Date()) {
setIsSubscribe(true);
return true;
} else {
navigate(`/subscription`);
}
} else {
navigate(`/subscription`);
}
}
//function for get document details
const getDocumentDetails = async () => {
//getting document details
const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) {
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
if (isEnableSubscription) {
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
}
const alreadyPlaceholder = documentData[0]?.SignedUrl;
// Check if document is sent for signing
if (alreadyPlaceholder) {
@@ -736,6 +762,9 @@ function PlaceHolderSign() {
// Save the Parse File if needed
const pdfData = await pdfFile.save();
const pdfUrl = pdfData.url();
const tenantId = localStorage.getItem("TenantId");
const buffer = atob(pdfBytes);
SaveFileSize(buffer.length, pdfUrl, tenantId);
return pdfUrl;
} catch (e) {
console.log("error", e);
@@ -829,6 +858,7 @@ function PlaceHolderSign() {
: "";
const themeBGcolor = themeColor;
let params = {
extUserId: extUserId,
recipient: signerMail[i].Email,
subject: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign ${pdfDetails?.[0].Name}`,
from: sender,
@@ -1596,6 +1626,7 @@ function PlaceHolderSign() {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<DropdownWidgetOption
type="checkbox"
@@ -1606,6 +1637,7 @@ function PlaceHolderSign() {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<DropdownWidgetOption
type="dropdown"
@@ -1616,6 +1648,7 @@ function PlaceHolderSign() {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
{/* pdf header which contain funish back button */}
@@ -1831,6 +1864,7 @@ function PlaceHolderSign() {
isOpen={isNameModal}
handleClose={handleNameModal}
handleData={handleWidgetdefaultdata}
isSubscribe={isSubscribe}
/>
</div>
</>
+121 -36
View File
@@ -1,9 +1,9 @@
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router";
import { NavLink } from "react-router-dom";
import { NavLink, useNavigate } from "react-router-dom";
import checkmark from "../assets/images/checkmark.png";
import plansArr from "../json/plansArr.json";
import Title from "../components/Title";
import Parse from "parse";
const listItemStyle = {
paddingLeft: "20px", // Add padding to create space for the image
backgroundImage: `url(${checkmark})`, // Set your image as the list style image
@@ -35,15 +35,35 @@ const PlanSubscriptions = () => {
company +
phone;
useEffect(() => {
if (localStorage.getItem("accesstoken")) {
setIsLoader(false);
setYearlyVisible(false);
} else {
navigate("/", { replace: true });
}
// if (localStorage.getItem("accesstoken")) {
setIsLoader(false);
// } else {
// navigate("/", { replace: true });
// }
// eslint-disable-next-line
}, []);
const handleFreePlan = async () => {
setIsLoader(true);
try {
const params = { userId: Parse.User.current().id };
const res = await Parse.Cloud.run("freesubscription", params);
if (res.status === "success" && res.result === "already subscribed!") {
setIsLoader(false);
alert("You have already subscribed to plan!");
} else if (res.status === "success") {
setIsLoader(false);
navigate("/");
} else if (res.status === "error") {
setIsLoader(false);
alert(res.result);
}
} catch (err) {
setIsLoader(false);
console.log("err in free subscribe", err.message);
alert("Somenthing went wrong, please try again later!");
}
};
return (
<>
<Title title={"Subscriptions"} />
@@ -69,23 +89,40 @@ const PlanSubscriptions = () => {
style={{
backgroundColor: "white",
overflowY: "auto",
maxHeight: "100%",
"--theme-color": "#7952b3",
"--plan-width": 30
maxHeight: "100%"
}}
>
<div
id="monthlyPlans"
className={`${yearlyVisible ? "none" : "block my-2"}`}
>
<div className="flex justify-center w-full my-2">
<ul className=" flex flex-col md:flex-row h-full bg-white justify-center border-collapse border-[1px] border-gray-300">
<div id="monthlyPlans" className="block my-2">
<div className="flex flex-col justify-center items-center w-full">
<div className="mb-6 mt-2 flex flex-row border-[1px] p-2 border-gray-300 rounded text-sm">
<span
onClick={() => setYearlyVisible(false)}
className={`${
!yearlyVisible
? "bg-[#002862] text-white"
: "bg-white text-black"
} px-4 py-1 rounded cursor-pointer`}
>
Monthly
</span>
<span
onClick={() => setYearlyVisible(true)}
className={`${
yearlyVisible
? "bg-[#002862] text-white"
: "bg-white text-black"
} px-4 py-1 rounded cursor-pointer`}
>
Yearly (10% off)
</span>
</div>
<ul className="flex flex-col md:flex-row h-full bg-white justify-center">
{plansArr.map((item) => (
<li
className="flex flex-col md:my-0 text-center border-[1px] border-gray-300 w-[260px]"
className="flex flex-col md:my-0 text-center border-collapse border-[1px] border-gray-300 w-[260px]"
key={item.planName}
>
<div className="p-2 flex flex-col justify-center items-center">
<div className="p-2 flex flex-col justify-center items-center min-h-[320px]">
<h3 className="text-[#002862] uppercase">
{item.planName}
</h3>
@@ -99,17 +136,23 @@ const PlanSubscriptions = () => {
<div className="">
<span className="text-3xl">
{item.currency && <small>{item.currency}</small>}
{item.price}
{yearlyVisible
? item?.yearlyPrice
: item.monthlyPrice}
</span>
<p className="font-semibold pt-2 text-sm">
{yearlyVisible ? "Billed Yearly" : "Billed Monthly"}
</p>
<div
className={`${
item.subtitle.length <= 32
? "w-[150px] text-center"
? "w-[150px] h-[40px] text-center"
: ""
} text-sm text-center my-2`}
>
<p
<div
style={{
textAlign: "center",
backgroundColor: item.subtitlecolor
? item.subtitlecolor
: "white"
@@ -124,23 +167,33 @@ const PlanSubscriptions = () => {
) : (
<span>{item.subtitle}</span>
)}
</p>
</div>
</div>
</div>
<NavLink
to={
item.btnText === "Subscribe"
? item.url + details
: item.url
}
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:no-underline hover:text-white"
target={item.target}
>
{item.btnText}
</NavLink>
{item.url ? (
<NavLink
to={
item.btnText === "Subscribe"
? yearlyVisible
? item.yearlyUrl + details
: item.url + details
: item.url
}
className="bg-[#002862] w-full mt-1 text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
target={item.target}
>
{item.btnText}
</NavLink>
) : (
<button
className="bg-[#002862] w-full mt-1 text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
onClick={() => handleFreePlan()}
>
{item.btnText}
</button>
)}
</div>
<hr className="w-full bg-gray-300 p-[.5px]" />
<hr className="w-full bg-gray-300 h-[0.5px]" />
<ul className="mx-1 p-3 text-left break-words text-sm list-none">
{item.benefits.map((subitem, index) => (
<li style={listItemStyle} key={index} className="m-1">
@@ -154,6 +207,38 @@ const PlanSubscriptions = () => {
))}
</ul>
</div>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
className="text-sm"
>
<hr
className={"border-[1px] border-gray-300 w-[20%]"}
style={{ color: "grey" }}
/>
<span style={{ color: "grey" }} className="px-2 ">
or
</span>
<hr
className={"border-[1px] border-gray-300 w-[20%]"}
style={{ color: "grey" }}
/>
</div>
<div className="flex flex-col justify-center w-full items-center">
<h3 className="text-[#002862] mt-1 mb-2">
Host it yourself for free
</h3>
<NavLink
to={"https://github.com/OpenSignLabs/OpenSign"}
className="bg-[#002862] w-[200px] text-center text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
target={"_blank"}
>
Visit Github
</NavLink>
</div>
</div>
</div>
)}
+45 -14
View File
@@ -2,17 +2,19 @@ import React, { useState, useEffect } from "react";
import Parse from "parse";
import axios from "axios";
import Title from "../components/Title";
import { useNavigate, NavLink } from "react-router-dom";
import { useNavigate, NavLink, useLocation } from "react-router-dom";
import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize";
import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import { showTenant } from "../redux/reducers/ShowTenant";
import { isEnableSubscription } from "../constant/const";
const Signup = () => {
const { width } = useWindowSize();
const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
@@ -67,6 +69,19 @@ const Signup = () => {
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
};
const handleFreePlan = async (id) => {
try {
const params = { userId: id };
const res = await Parse.Cloud.run("freesubscription", params);
if (res.status === "error") {
alert(res.result);
}
} catch (err) {
console.log("err in free subscribe", err.message);
alert("Somenthing went wrong, please try again later!");
}
};
const handleSubmit = (event) => {
event.preventDefault();
if (lengthValid && caseDigitValid && specialCharValid) {
@@ -110,7 +125,13 @@ const Signup = () => {
params
);
if (usersignup) {
handleNavigation(r.getSessionToken());
const param = new URLSearchParams(location.search);
const isFreeplan =
param?.get("subscription") === "freeplan";
if (isFreeplan) {
await handleFreePlan(r.id);
}
handleNavigation(r.getSessionToken(), isFreeplan);
}
} catch (err) {
alert(err.message);
@@ -155,7 +176,7 @@ const Signup = () => {
}
};
const handleNavigation = async (sessionToken) => {
const handleNavigation = async (sessionToken, isFreeplan = false) => {
const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
const res = await axios.get(baseUrl + "users/me", {
@@ -259,9 +280,7 @@ const Signup = () => {
});
if (tenentInfo.length) {
dispatch(
showTenant(
tenentInfo[0].tenentName || ""
)
showTenant(tenentInfo[0].tenentName || "")
);
localStorage.setItem(
"TenantName",
@@ -297,7 +316,7 @@ const Signup = () => {
tenentName: x.TenantId.TenantName || ""
};
localStorage.setItem(
"TenetId",
"TenantId",
x.TenantId.objectId
);
tenentInfo.push(obj);
@@ -325,8 +344,14 @@ const Signup = () => {
element.pageType
);
setState({ loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
navigate(`/subscription`, { replace: true });
if (isEnableSubscription) {
if (isFreeplan) {
navigate(
`/${element.pageType}/${element.pageId}`
);
} else {
navigate(`/subscription`, { replace: true });
}
} else {
alert("Registered user successfully");
navigate(
@@ -343,8 +368,14 @@ const Signup = () => {
);
localStorage.setItem("pageType", element.pageType);
setState({ loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) {
navigate(`/subscription`, { replace: true });
if (isEnableSubscription) {
if (isFreeplan) {
navigate(
`/${element.pageType}/${element.pageId}`
);
} else {
navigate(`/subscription`, { replace: true });
}
} else {
navigate(
`/${element.pageType}/${element.pageId}`
@@ -626,7 +657,7 @@ const Signup = () => {
</button>
<NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase"
to="/"
to={location.search ? "/" + location.search : "/"}
style={width < 768 ? { textAlign: "center" } : {}}
>
Login
@@ -661,4 +692,4 @@ const Signup = () => {
);
};
export default Signup
export default Signup;
+7 -5
View File
@@ -99,6 +99,7 @@ function SignYourSelf() {
});
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isDontShow, setIsDontShow] = useState(false);
const [extUserId, setExtUserId] = useState("");
const divRef = useRef(null);
const nodeRef = useRef(null);
const [, drop] = useDrop({
@@ -186,7 +187,7 @@ function SignYourSelf() {
if (documentData && documentData.length > 0) {
setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
isCompleted = documentData[0].IsCompleted && documentData[0].IsCompleted;
if (isCompleted) {
const docStatus = {
@@ -433,13 +434,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)
};
@@ -1120,6 +1121,7 @@ function SignYourSelf() {
setSuccessEmail={setSuccessEmail}
sender={jsonSender}
setIsAlert={setIsAlert}
extUserId={extUserId}
/>
{/* pdf header which contain funish back button */}
<Header
+30 -2
View File
@@ -3,7 +3,7 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { useParams, useNavigate } from "react-router-dom";
import axios from "axios";
import "../styles/signature.css";
import { themeColor } from "../constant/const";
import { isEnableSubscription, themeColor } from "../constant/const";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd";
@@ -35,6 +35,7 @@ import AddRoleModal from "../components/pdf/AddRoleModal";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import Parse from "parse";
const TemplatePlaceholder = () => {
const navigate = useNavigate();
const { templateId } = useParams();
@@ -52,6 +53,7 @@ const TemplatePlaceholder = () => {
const [isSelectListId, setIsSelectId] = useState();
const [isSendAlert, setIsSendAlert] = useState(false);
const [isCreateDocModal, setIsCreateDocModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isLoading, setIsLoading] = useState({
isLoad: true,
message: "This might take some time"
@@ -168,7 +170,26 @@ const TemplatePlaceholder = () => {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current]);
async function checkIsSubscribed(email) {
const user = await Parse.Cloud.run("getUserDetails", {
email: email
});
const freeplan = user?.get("Plan") && user?.get("Plan").plan_code;
const billingDate =
user?.get("Next_billing_date") && user?.get("Next_billing_date");
if (freeplan === "freeplan") {
return true;
} else if (billingDate) {
if (billingDate > new Date()) {
setIsSubscribe(true);
return true;
} else {
navigate(`/subscription`);
}
} else {
navigate(`/subscription`);
}
}
// `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
const fetchTemplate = async () => {
try {
@@ -190,6 +211,9 @@ const TemplatePlaceholder = () => {
: [];
if (documentData && documentData.length > 0) {
if (isEnableSubscription) {
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
}
setPdfDetails(documentData);
setIsSigners(true);
if (documentData[0].Signers && documentData[0].Signers.length > 0) {
@@ -1323,6 +1347,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<DropdownWidgetOption
type="checkbox"
@@ -1333,6 +1358,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<DropdownWidgetOption
type="dropdown"
@@ -1343,6 +1369,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal}
isSubscribe={isSubscribe}
/>
<PlaceholderCopy
isPageCopy={isPageCopy}
@@ -1542,6 +1569,7 @@ const TemplatePlaceholder = () => {
isOpen={isNameModal}
handleClose={handleNameModal}
handleData={handleWidgetdefaultdata}
isSubscribe={isSubscribe}
/>
</div>
);
+43 -13
View File
@@ -8,6 +8,9 @@ import sanitizeFileName from "../primitives/sanitizeFileName";
import axios from "axios";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribed } from "../constant/Utils";
import Upgrade from "../primitives/Upgrade";
function UserProfile() {
const navigate = useNavigate();
@@ -21,15 +24,24 @@ function UserProfile() {
const [isLoader, setIsLoader] = useState(false);
const [percentage, setpercentage] = useState(0);
const [isDisableDocId, setIsDisableDocId] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => {
getUserDetail();
}, []);
const getUserDetail = async () => {
const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass);
const HeaderDocId = jsonSender[0]?.HeaderDocId;
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
if (HeaderDocId) {
setIsDisableDocId(HeaderDocId);
}
}, []);
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoader(true);
@@ -137,7 +149,8 @@ function UserProfile() {
setImage(response.url());
localStorage.setItem("profileImg", response.url());
setpercentage(0);
SaveFileSize(size, response.url());
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
} catch (error) {
@@ -155,7 +168,6 @@ function UserProfile() {
const handleDisableDocId = () => {
setIsDisableDocId((prevChecked) => !prevChecked);
};
return (
<React.Fragment>
<Title title={"Profile"} />
@@ -262,14 +274,30 @@ function UserProfile() {
</li>
<li className="border-y-[1px] border-gray-300 break-all">
<div className="flex justify-between items-center py-2">
<span className="font-semibold">
<span
className={
isSubscribe || !isEnableSubscription
? "font-semibold"
: "font-semibold text-gray-300"
}
>
Disable DocumentId :{" "}
<Tooltip url={"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"} />{" "}
<Tooltip
url={
"https://docs.opensignlabs.com/docs/help/Settings/disabledocumentid"
}
isSubscribe={isSubscribe}
/>{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>{" "}
<label
className={`${
editmode ? "cursor-pointer" : ""
} relative inline-flex items-center mb-0`}
className={
isSubscribe || !isEnableSubscription
? `${
editmode ? "cursor-pointer" : ""
} relative inline-flex items-center mb-0`
: "relative inline-flex items-center mb-0 pointer-events-none opacity-50"
}
>
<input
disabled={editmode ? false : true}
@@ -282,11 +310,13 @@ function UserProfile() {
<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>
<PremiumAlertHeader
message={
"Disable documentId is free in beta, this feature will incur a fee later."
}
/>
{!isEnableSubscription && (
<PremiumAlertHeader
message={
"Disable documentId is free in beta, this feature will incur a fee later."
}
/>
)}
</li>
</ul>
<div className="flex justify-center pt-2 pb-3 md:pt-3 md:pb-4">
+110 -54
View File
@@ -1,15 +1,17 @@
import React, { useEffect, useState } from "react";
import Title from "../components/Title";
import axios from "axios";
import { useNavigate } from "react-router-dom";
import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi";
import { rejectBtn, submitBtn } from "../constant/const";
import { openInNewTab } from "../constant/Utils";
import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import Parse from "parse";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip";
function Webhook() {
const navigation = useNavigate();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [webhook, setWebhook] = useState();
@@ -17,6 +19,8 @@ function Webhook() {
const [isGenerate, setIsGenerate] = useState(false);
const [isErr, setIsErr] = useState(false);
const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [error, setError] = useState("");
useEffect(() => {
fetchWebhook();
@@ -25,6 +29,10 @@ function Webhook() {
const fetchWebhook = async () => {
const email = Parse.User.current().getEmail();
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
const params = { email: email };
try {
const extRes = await Parse.Cloud.run("getUserDetails", params);
@@ -40,41 +48,48 @@ function Webhook() {
};
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoader(true);
setIsModal(false);
try {
const params = { url: webhook };
const url = parseBaseUrl + "functions/savewebhook";
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessiontoken: localStorage.getItem("accesstoken")
};
await axios.post(url, params, { headers: headers }).then((res) => {
if (res.data && res.data.result && res.data.result.Webhook) {
setWebhook(res.data.result.Webhook);
setIsGenerate(true);
setTimeout(() => {
setIsGenerate(false);
}, 1500);
setIsLoader(false);
} else {
console.error("Error while generating webhook");
setIsLoader(false);
setIsErr(true);
setTimeout(() => {
setIsErr(false);
}, 1500);
}
});
} catch (error) {
setIsLoader(false);
setIsErr(true);
setTimeout(() => {
setIsErr(false);
}, 1500);
if (webhook && webhook.startsWith("https://")) {
setIsLoader(true);
setIsModal(false);
try {
const params = { url: webhook };
const url = parseBaseUrl + "functions/savewebhook";
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessiontoken: localStorage.getItem("accesstoken")
};
await axios.post(url, params, { headers: headers }).then((res) => {
if (res.data && res.data.result && res.data.result.Webhook) {
setWebhook(res.data.result.Webhook);
setIsGenerate(true);
setTimeout(() => {
setIsGenerate(false);
}, 1500);
setIsLoader(false);
} else {
console.error("Error while generating webhook");
setIsLoader(false);
setIsErr(true);
setTimeout(() => {
setIsErr(false);
}, 1500);
}
});
} catch (error) {
setIsLoader(false);
setIsErr(true);
setTimeout(() => {
setIsErr(false);
}, 1500);
console.log("err", error);
console.log("err", error);
}
} else {
setError("The webhook url should always start with https://");
setTimeout(() => {
setError("");
}, 1500);
}
};
@@ -84,6 +99,7 @@ function Webhook() {
<Title title={"Webhook"} />
{isGenerate && <Alert type="success">Webhook added successfully!</Alert>}
{isErr && <Alert type="danger">Something went wrong!</Alert>}
{isLoader ? (
<div
style={{
@@ -103,31 +119,41 @@ function Webhook() {
</div>
) : (
<div className="bg-white flex flex-col justify-center shadow rounded">
<PremiumAlertHeader />
<h1 className="ml-4 mt-3 mb-2 font-semibold">
Webhook{" "}
{!isEnableSubscription && <PremiumAlertHeader />}
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
OpenSign Webhook{" "}
<Tooltip
url={"https://docs.opensignlabs.com/docs/API-docs/get-webhook"}
isSubscribe={true}
/>
</h1>
<ul className="w-full flex flex-col p-2 text-sm">
<ul
className={
isSubscribe || !isEnableSubscription
? "w-full flex flex-col p-2 text-sm "
: "w-full flex flex-col p-2 text-sm opacity-20 pointer-events-none select-none"
}
>
<li
className={`flex justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
>
<span className="w-[40%]">Webhook:</span>{" "}
<span id="token" className="w-[60%] md:text-end cursor-pointer">
{webhook && webhook}
</span>
<div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
<span className="">Webhook:</span>{" "}
<span id="token" className=" md:text-end cursor-pointer">
{webhook ? webhook : "_____"}
</span>
</div>
<button
type="button"
onClick={handleModal}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
>
{webhook ? "Update Webhook" : "Add Webhook"}
</button>
</li>
</ul>
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
<button
type="button"
onClick={handleModal}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-4 py-2 text-xs md:text-base focus:outline-none"
>
{webhook ? "Update Webhook" : "Add Webhook"}
</button>
<div className="flex items-center justify-center ">
<button
type="button"
onClick={() =>
@@ -135,17 +161,47 @@ function Webhook() {
"https://docs.opensignlabs.com/docs/API-docs/save-update-webhook"
)
}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
className="rounded hover:bg-[#15b4e9] border-[1px] my-2 border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
>
View Docs
</button>
</div>
{!isSubscribe && isEnableSubscription && (
<>
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
Upgrade to PRO Plan
</h1>
<ul className={"w-full flex flex-col p-2 text-sm "}>
<li
className={`flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2`}
>
<div className="w-[70%] flex-col md:flex-row flex items-center gap-3 ">
<span className="">$29.99/month:</span>{" "}
<span id="token" className=" md:text-end cursor-pointer">
First 100 documents included, then just $0.15 per
document.
</span>
</div>
{!isSubscribe && isEnableSubscription && (
<button
type="button"
onClick={() => navigation("/subscription")}
className="rounded hover:bg-[#15b4e9] border-[1px] border-[#15b4e9] text-[#15b4e9] hover:text-white px-11 py-2 text-xs md:text-base focus:outline-none"
>
Upgrade Now
</button>
)}
</li>
</ul>
</>
)}
<ModalUi
isOpen={isModal}
title={"Regenerate Token"}
handleClose={handleModal}
>
{error && <Alert type="danger">{error}</Alert>}
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
<label className="text-sm ml-2">Webhook</label>
+2 -2
View File
@@ -54,11 +54,11 @@ const AddUser = (props) => {
contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest");
if (localStorage.getItem("TenetId")) {
if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenetId")
objectId: localStorage.getItem("TenantId")
});
}
+10 -2
View File
@@ -1,9 +1,17 @@
import React from "react";
import { Tooltip as ReactTooltip } from "react-tooltip";
import { openInNewTab } from "../constant/Utils";
const Tooltip = ({ id, message, url, iconColor }) =>
import { isEnableSubscription } from "../constant/const";
const Tooltip = ({ id, message, url, iconColor, isSubscribe }) =>
url ? (
<button onClick={() => openInNewTab(url)} className="text-center">
<button
onClick={() => openInNewTab(url)}
className={
isSubscribe || !isEnableSubscription
? "text-center"
: "text-center opacity-20 pointer-events-none"
}
>
<sup>
<i
className="fa-solid fa-question rounded-full"
+19
View File
@@ -0,0 +1,19 @@
import React from "react";
import { useNavigate } from "react-router-dom";
function Upgrade() {
const navigation = useNavigate();
return (
<sup>
<span
onClick={() => navigation("/subscription")}
className="text-blue-800 text-sm cursor-pointer hover:underline"
>
Upgrade now
</span>
</sup>
);
}
export default Upgrade;
+2
View File
@@ -24,7 +24,9 @@ const Validate = () => {
setIsUserValid(false);
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleLoginBtn = () => {
try {
Parse.User.logOut();
+112
View File
@@ -1,3 +1,7 @@
import axios from 'axios';
const appId = process.env.APP_ID;
const serverUrl = process.env.SERVER_URL;
export function customAPIurl() {
const url = new URL(process.env.SERVER_URL);
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
@@ -38,3 +42,111 @@ export function replaceMailVaribles(subject, body, variables) {
};
return result;
}
export const saveFileUsage = async (size, imageUrl, userId) => {
//checking server url and save file's size
try {
const tenantQuery = new Parse.Query('partners_Tenant');
tenantQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
const tenant = await tenantQuery.first();
if (tenant) {
const tenantPtr = {
__type: 'Pointer',
className: 'partners_Tenant',
objectId: tenant.id,
};
const _tenantPtr = JSON.stringify(tenantPtr);
try {
const res = await axios.get(
`${serverUrl}/classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
{
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
},
}
);
const response = res.data.results;
let data;
// console.log("response", response);
if (response && response.length > 0) {
data = {
usedStorage: response[0].usedStorage ? response[0].usedStorage + size : size,
};
await axios.put(
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
data,
{
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
},
}
);
} else {
data = { usedStorage: size, PartnersTenant: tenantPtr };
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
},
});
}
} catch (err) {
console.log('err in save usage', err);
}
saveDataFile(size, imageUrl, tenantPtr);
}
} catch (err) {
console.log('err in fetch tenant Id', err);
}
};
//function for save fileUrl and file size in particular client db class partners_DataFiles
const saveDataFile = async (size, imageUrl, tenantPtr) => {
const data = {
FileUrl: imageUrl,
FileSize: size,
TenantPtr: tenantPtr,
};
// console.log("data save",file, data)
try {
await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': appId,
},
});
} catch (err) {
console.log('error in save usage ', err);
}
};
export const updateMailCount = async extUserId => {
// Update count in contracts_Users class
const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', extUserId);
try {
const contractUser = await query.first({ useMasterKey: true });
if (contractUser) {
contractUser.increment('EmailCount', 1);
await contractUser.save(null, { useMasterKey: true });
} else {
// Create new entry if not found
const ContractsUsers = Parse.Object.extend('contracts_users');
const newContractUser = new ContractsUsers();
newContractUser.set('EmailCount', 1);
await newContractUser.save(null, { useMasterKey: true });
}
} catch (error) {
console.log('Error updating EmailCount in contracts_users: ' + error.message);
}
};
@@ -284,6 +284,7 @@ export default async function createDocumentWithTemplate(request, response) {
subject: subject,
from: sender,
html: html,
extUserId: template.ExtUserPtr.objectId,
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
@@ -1,5 +1,5 @@
import axios from 'axios';
import { color, customAPIurl, replaceMailVaribles } from '../../../../Utils.js';
import { color, customAPIurl, replaceMailVaribles, saveFileUsage } from '../../../../Utils.js';
// `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) {
@@ -8,6 +8,7 @@ async function sendDoctoWebhook(doc, WebhookUrl, userId) {
event: 'created',
...doc,
};
await axios
.post(WebhookUrl, params, {
headers: { 'Content-Type': 'application/json' },
@@ -65,6 +66,7 @@ export default async function createDocumentwithCoordinate(request, response) {
if (!reqToken) {
return response.status(400).json({ error: 'Please Provide API Token' });
}
try {
const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', reqToken);
@@ -81,17 +83,22 @@ export default async function createDocumentwithCoordinate(request, response) {
if (signers && signers.length > 0) {
let fileUrl;
if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'),
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const file = new Parse.File(`${name}.pdf`, {
base64: base64File,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
@@ -242,7 +249,6 @@ export default async function createDocumentwithCoordinate(request, response) {
year: 'numeric',
});
let sender = parseExtUser.Email;
let sendMail;
const serverUrl = process.env.SERVER_URL;
const newServer = serverUrl.replaceAll('/', '%2F');
const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`;
@@ -330,9 +336,10 @@ export default async function createDocumentwithCoordinate(request, response) {
subject: subject,
from: sender,
html: html,
extUserId: extUser.id,
};
sendMail = await axios.post(url, params, { headers: headers });
await axios.post(url, params, { headers: headers });
} catch (error) {
console.log('error', error);
}
@@ -1,6 +1,4 @@
import { customAPIurl } from '../../../../Utils.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000);
import { saveFileUsage } from '../../../../Utils.js';
export default async function createTemplate(request, response) {
const name = request.body?.title;
const note = request.body?.note;
@@ -36,12 +34,17 @@ export default async function createTemplate(request, response) {
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'),
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
@@ -1,5 +1,5 @@
import axios from 'axios';
import { color, customAPIurl } from '../../../../Utils.js';
import { color, customAPIurl, saveFileUsage } from '../../../../Utils.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function createTemplatewithCoordinate(request, response) {
@@ -34,17 +34,22 @@ export default async function createTemplatewithCoordinate(request, response) {
if (signers && signers.length > 0) {
let fileUrl;
if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'),
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const file = new Parse.File(`${name}.pdf`, {
base64: base64File,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
@@ -36,17 +36,22 @@ export default async function draftDocument(request, response) {
if (signers && signers.length > 0) {
let fileUrl;
if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'),
base64: base64,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else {
const file = new Parse.File(`${name}.pdf`, {
base64: base64File,
});
await file.save({ useMasterKey: true });
fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
}
const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr);
+8 -4
View File
@@ -1,5 +1,5 @@
import PDF from './parsefunction/pdf/PDF.min.js';
import sendmail from './parsefunction/sendMail.js';
import sendmailv3 from './parsefunction/sendMailv3.js';
import GoogleSign from './parsefunction/GoogleSign.js';
import ZohoDetails from './parsefunction/ZohoDetails.js';
import usersignup from './parsefunction/usersignup.js';
@@ -10,7 +10,6 @@ import DocumentAftersave from './parsefunction/DocumentAftersave.js';
import ContactbookAftersave from './parsefunction/ContactBookAftersave.js';
import ContractUsersAftersave from './parsefunction/ContractUsersAftersave.js';
import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js';
import SendMailv1 from './parsefunction/SendMailv1.js';
import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js';
import getUserId from './parsefunction/getUserId.js';
import getUserDetails from './parsefunction/getUserDetails.js';
@@ -23,17 +22,19 @@ import TemplateAfterSave from './parsefunction/TemplateAfterSave.js';
import GetTemplate from './parsefunction/GetTemplate.js';
import savewebhook from './parsefunction/saveWebhook.js';
import callWebhook from './parsefunction/callWebhook.js';
import SubscribeFree from './parsefunction/SubscribeFree.js';
import DocumentBeforesave from './parsefunction/DocumentBeforesave.js';
import TemplateBeforeSave from './parsefunction/TemplateBeforesave.js';
Parse.Cloud.define('AddUserToRole', addUserToGroups);
Parse.Cloud.define('UserGroups', getUserGroups);
Parse.Cloud.define('signPdf', PDF);
Parse.Cloud.define('sendmailv3', sendmail);
Parse.Cloud.define('sendmailv3', sendmailv3);
Parse.Cloud.define('googlesign', GoogleSign);
Parse.Cloud.define('zohodetails', ZohoDetails);
Parse.Cloud.define('usersignup', usersignup);
Parse.Cloud.define('facebooksign', FacebookSign);
Parse.Cloud.define('SendOTPMailV1', sendMailOTPv1);
Parse.Cloud.define('sendmail', SendMailv1);
Parse.Cloud.define('AuthLoginAsMail', AuthLoginAsMail);
Parse.Cloud.define('getUserId', getUserId);
Parse.Cloud.define('getUserDetails', getUserDetails);
@@ -45,7 +46,10 @@ Parse.Cloud.define('getapitoken', getapitoken);
Parse.Cloud.define('getTemplate', GetTemplate);
Parse.Cloud.define('savewebhook', savewebhook);
Parse.Cloud.define('callwebhook', callWebhook);
Parse.Cloud.define('freesubscription', SubscribeFree);
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave);
Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave);
Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave);
Parse.Cloud.beforeSave('contracts_Document', DocumentBeforesave);
Parse.Cloud.beforeSave('contracts_Template', TemplateBeforeSave);
@@ -0,0 +1,35 @@
async function DocumentBeforesave(request) {
try {
// below code is used to update document when user sent document or self signed
const document = request.object;
const oldDocument = request.original;
// Check if SignedUrl field has been added (transition from undefined to defined)
if (!oldDocument.get('SignedUrl') && document.get('SignedUrl')) {
const SignedUrl = document.get('SignedUrl');
// Update count in contracts_Users class
const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', oldDocument.get('ExtUserPtr').id);
try {
const contractUser = await query.first({ useMasterKey: true });
if (contractUser) {
contractUser.increment('DocumentCount', 1);
await contractUser.save(null, { useMasterKey: true });
} else {
// Create new entry if not found
const ContractsUsers = Parse.Object.extend('contracts_users');
const newContractUser = new ContractsUsers();
newContractUser.set('DocumentCount', 1);
await newContractUser.save(null, { useMasterKey: true });
}
} catch (error) {
console.log('Error updating document count in contracts_users: ' + error.message);
}
}
} catch (err) {
console.log('err in document beforesave', err.message);
}
}
export default DocumentBeforesave;
@@ -1,47 +1,65 @@
import axios from 'axios'
import { updateMailCount } from '../../Utils.js';
async function getDocument(docId) {
try {
const query = new Parse.Query('contracts_Document');
query.equalTo('objectId', docId);
query.include('ExtUserPtr');
query.include('CreatedBy');
query.include('Signers');
query.include('AuditTrail.UserPtr');
query.include('Placeholders');
query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true });
const _res = res.toJSON();
return _res?.ExtUserPtr?.objectId;
} catch (err) {
console.log('err ', err);
}
}
async function sendMailOTPv1(request) {
try {
//--for elearning app side
let code = Math.floor(1000 + Math.random() * 9000);
let email = request.params.email;
var TenantId = request.params.TenantId ? request.params.TenantId : undefined;
// console.log("In tempSendOTPv2");
// console.log(JSON.stringify(request));
if (email) {
axios({
method: 'POST',
url: process.env.SERVER_URL + '/functions/sendmail',
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': process.env.APP_ID,
},
params: {
otp: code,
email: email,
TenantId: TenantId,
},
}).then(
function (httpResponse) {},
function (httpResponse) {
console.error('sms Request failed with response code ' + httpResponse.status);
return Promise.reject('sms Request failed with response code ' + httpResponse.status);
const recipient = request.params.email;
const mailsender = process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER;
try {
await Parse.Cloud.sendEmail({
from: 'Opensign™' + ' <' + mailsender + '>',
recipient: recipient,
subject: 'Your OpenSign™ OTP',
text: 'This email is a test.',
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-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for OpenSign™ verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>" +
code +
'</p></div> </div> </div></body></html>',
});
console.log('OTP sent');
if (request.params?.docId) {
const extUserId = await getDocument(request.params?.docId);
if (extUserId) {
updateMailCount(extUserId);
}
}
);
} catch (err) {
console.log('error in send OTP mail', err);
}
const tempOtp = new Parse.Query('defaultdata_Otp');
tempOtp.equalTo('Email', email);
const resultOTP = await tempOtp.first({ useMasterKey: true });
console.log('resultOTP', resultOTP);
// console.log('resultOTP', resultOTP);
if (resultOTP !== undefined) {
const updateOtpQuery = new Parse.Query('defaultdata_Otp');
const updateOtp = await updateOtpQuery.get(resultOTP.id, {
useMasterKey: true,
});
updateOtp.set('OTP', code);
const updateRes = updateOtp.save(null, { useMasterKey: true });
updateOtp.save(null, { useMasterKey: true });
// console.log("update otp Res in tempSendOtp ", updateRes);
} else {
const otpClass = Parse.Object.extend('defaultdata_Otp');
@@ -49,17 +67,17 @@ async function sendMailOTPv1(request) {
newOtpQuery.set('OTP', code);
newOtpQuery.set('Email', email);
newOtpQuery.set('TenantId', TenantId);
const newRes = await newOtpQuery.save(null, { useMasterKey: true });
await newOtpQuery.save(null, { useMasterKey: true });
// console.log("new otp Res in tempSendOtp ", newRes);
}
return 'Otp send';
} else {
return Promise.reject('Please Enter valid email');
return 'Please Enter valid email';
}
} catch (err) {
console.log('err in sendMailOTPv1');
console.log(err);
return Promise.reject(err);
return err;
}
}
export default sendMailOTPv1;
@@ -1,27 +0,0 @@
async function SendMailv1(request) {
console.log('in SendMailv1');
try {
const recipient = request.params.email;
const otp = request.params.otp;
const res = await Parse.Cloud.sendEmail({
from: 'Test user' + ' <' + process.env.SMTP_ENABLE ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER + '>',
recipient: recipient,
subject: 'Your OpenSign™ OTP',
text: 'This email is a test.',
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-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for OpenSign™ verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>" +
otp +
'</p></div> </div> </div></body></html>',
// "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body style='text-align: center;'><div style='display:flex;flex-direction:column;justify-content:center;align-item:center;margin:40px'> <p style='font-weight: bolder; font-size: large;'>Hello,</p> <span>Your OTP for LegaDaft verification . </span> <p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px'>76984</p><span>Thank You!</span> </div> </body></html>"
});
console.log('Res');
console.log(res);
return otp;
} catch (err) {
console.log('err in SendMailv1');
console.log(err);
return err;
}
}
export default SendMailv1;
@@ -0,0 +1,43 @@
export default async function SubscribeFree(request) {
const userId = request.params.userId;
const userPtr = { __type: 'Pointer', className: '_User', objectId: userId };
try {
const extQuery = new Parse.Query('contracts_Users');
extQuery.equalTo('UserId', userPtr);
const extUser = await extQuery.first({ useMasterKey: true });
if (extUser) {
if (extUser?.get('Plan')?.plan_code === 'freeplan') {
return { status: 'success', result: 'already subscribed!' };
} else if (extUser?.get('Next_billing_date') < new Date()) {
try {
const extUpdate = new Parse.Object('contracts_Users');
extUpdate.id = extUser.id;
extUpdate.set('Plan', { plan_code: 'freeplan' });
await extUpdate.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
}
} else if (extUser?.get('Next_billing_date') > new Date()) {
return { status: 'success', result: 'already subscribed!' };
} else {
try {
const extUpdate = new Parse.Object('contracts_Users');
extUpdate.id = extUser.id;
extUpdate.set('Plan', { plan_code: 'freeplan' });
await extUpdate.save(null, { useMasterKey: true });
return { status: 'success', result: 'subscribed!' };
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
}
}
} else {
return { status: 'error', result: 'User not found!' };
}
} catch (err) {
console.log('err ', err);
return { status: 'error', result: err.message };
}
}
@@ -0,0 +1,31 @@
async function TemplateBeforeSave(request) {
try {
if (!request.original) {
// below code is used to update template when user sent template or self signed
const template = request.object;
// Update count in contracts_Users class
const query = new Parse.Query('contracts_Users');
query.equalTo('objectId', template.get('ExtUserPtr').id);
try {
const contractUser = await query.first({ useMasterKey: true });
if (contractUser) {
contractUser.increment('TemplateCount', 1);
await contractUser.save(null, { useMasterKey: true });
} else {
// Create new entry if not found
const ContractsUsers = Parse.Object.extend('contracts_users');
const newContractUser = new ContractsUsers();
newContractUser.set('TemplateCount', 1);
await newContractUser.save(null, { useMasterKey: true });
}
} catch (error) {
console.log('Error updating template count in contracts_users: ' + error.message);
}
}
} catch (err) {
console.log('err in template beforesave', err.message);
}
}
export default TemplateBeforeSave;
+95 -122
View File
@@ -3,6 +3,7 @@ import fs from 'node:fs';
import axios from 'axios';
import { pdflibAddPlaceholder } from './customSignPdf/pdflibplaceholder.min.js';
import { PDFDocument } from 'pdf-lib';
import { saveFileUsage } from '../../../Utils.js';
const serverUrl = process.env.SERVER_URL,
APPID = process.env.APP_ID,
masterKEY = process.env.MASTER_KEY;
@@ -16,20 +17,20 @@ async function uploadFile(e, a) {
console.log('Err ', e), fs.unlinkSync(a);
}
}
async function updateDoc(t, s, r, i, o, n) {
async function updateDoc(t, s, r, o, i, n) {
try {
var d = {
UserPtr: { __type: 'Pointer', className: n, objectId: r },
SignedUrl: s,
Activity: 'Signed',
ipAddress: i,
ipAddress: o,
};
let e;
var l = (e = o.AuditTrail && 0 < o.AuditTrail.length ? [...o.AuditTrail, d] : [d]).filter(
var l = (e = i.AuditTrail && 0 < i.AuditTrail.length ? [...i.AuditTrail, d] : [d]).filter(
e => 'Signed' === e.Activity
);
let a = !1;
!((o.Signers && 0 < o.Signers.length && l.length !== o.Signers.length) || !(a = !0));
!((i.Signers && 0 < i.Signers.length && l.length !== i.Signers.length) || !(a = !0));
var c = { SignedUrl: s, AuditTrail: e, IsCompleted: a };
await axios.put(serverUrl + '/classes/contracts_Document/' + t, c, {
headers: {
@@ -43,39 +44,16 @@ async function updateDoc(t, s, r, i, o, n) {
return console.log('update doc err ', e), 'err';
}
}
async function sendMail(e) {
var a = e.url,
t = e.sender,
s = e.pdfName,
a = {
url: a,
from: 'OpenSign™',
recipient: e.receiver,
subject: 'You have signed the doc - ' + s,
pdfName: s,
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-color:white;'> <div><img src=https://qikinnovation.ams3.digitaloceanspaces.com/logo.png height='50' style='padding:20px'/> </div><div style='padding:2px;font-family:system-ui; background-color: #47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',> Document Copy</p></div><div><p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document " +
s +
' Standard is attached to this email. Kindly download the document from the attachment.</p></div> </div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ' +
t.Mail +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>',
};
await axios.post(serverUrl + '/functions/sendmailv3', a, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
},
});
}
async function sendCompletedMail(e) {
var a = e.url,
t = e.sender,
s = e.pdfName,
a = {
r = e.receiver,
e = {
extUserId: e.extUserId,
url: a,
from: 'OpenSign™',
recipient: e.receiver,
recipient: r,
subject: `Document ${s} has been signed by all parties`,
pdfName: s,
html:
@@ -85,7 +63,7 @@ async function sendCompletedMail(e) {
t.Mail +
' directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>',
};
await axios.post(serverUrl + '/functions/sendmailv3', a, {
await axios.post(serverUrl + '/functions/sendmailv3', e, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
@@ -155,7 +133,7 @@ async function PDF(i) {
try {
var e = i.params.docId,
a = i.params.userId,
o = await axios.get(
n = await axios.get(
serverUrl + '/classes/contracts_Document/' + e + '?include=ExtUserPtr,Signers',
{
headers: {
@@ -165,142 +143,137 @@ async function PDF(i) {
},
}
),
t = await axios.get(serverUrl + '/users/me', {
d = await axios.get(serverUrl + '/users/me', {
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken,
},
});
if (!t.data || !t.data.objectId) return { status: 'error', message: 'this user not allowed!' };
if (!d.data || !d.data.objectId) return { status: 'error', message: 'This user not allowed!' };
{
var n,
d,
var t,
s,
l,
c = JSON.stringify({ objectId: a });
let s, r;
r = a
? (n = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + c, {
let r, o;
o = a
? (t = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + c, {
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken,
},
})).data && 0 < n.data.results.length
? ((s = n), 'contracts_Contactbook')
: ((s = await axios.get(serverUrl + '/classes/contracts_Users?where=' + c, {
})).data && 0 < t.data.results.length
? ((r = t), 'contracts_Contactbook')
: ((r = await axios.get(serverUrl + '/classes/contracts_Users?where=' + c, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
})),
'contracts_Users')
: ((d = JSON.stringify({
UserId: { __type: 'Pointer', className: '_User', objectId: t.data.objectId },
: ((s = JSON.stringify({
UserId: { __type: 'Pointer', className: '_User', objectId: d.data.objectId },
})),
(l = await axios.get(serverUrl + '/classes/contracts_Users?where=' + d, {
(l = await axios.get(serverUrl + '/classes/contracts_Users?where=' + s, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
})).data && 0 < l.data.results.length
? ((s = l), 'contracts_Users')
: ((s = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + d, {
? ((r = l), 'contracts_Users')
: ((r = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + s, {
headers: {
'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken,
},
})),
'contracts_Contactbook'));
var p = s.data.results[0].Name,
m = s.data.results[0].Email;
var p = r.data.results[0].Name,
m = r.data.results[0].Email;
if (!i.params.pdfFile) return { status: 'error', message: 'pdf file not present!' };
{
let e = Buffer.from(i.params.pdfFile, 'base64');
var g = process.env.PFX_BASE64,
h = Buffer.from(g, 'base64'),
u = {
UserPtr: { __type: 'Pointer', className: r, objectId: s.data.results[0].objectId },
u = Buffer.from(g, 'base64'),
h = {
UserPtr: { __type: 'Pointer', className: o, objectId: r.data.results[0].objectId },
SignedUrl: '',
Activity: 'Signed',
ipAddress: i.headers['x-real-ip'],
};
let a;
var f = (a =
o.data.AuditTrail && 0 < o.data.AuditTrail.length
? [...o.data.AuditTrail, u]
: [u]).filter(e => 'Signed' === e.Activity);
n.data.AuditTrail && 0 < n.data.AuditTrail.length
? [...n.data.AuditTrail, h]
: [h]).filter(e => 'Signed' === e.Activity);
let t = !1;
!(
(o.data.Signers && 0 < o.data.Signers.length && f.length !== o.data.Signers.length) ||
(n.data.Signers && 0 < n.data.Signers.length && f.length !== n.data.Signers.length) ||
!(t = !0)
);
var y,
P,
var P,
y,
v,
b,
x,
w,
U,
b,
I,
S,
A = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
D = './exports/' + A,
E =
(t
? ((y = o.data.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
(e =
y && 0 < y.length
? ((P = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: P,
reason: 'Digitally signed by OpenSign for ' + y?.join(', '),
location: 'location',
signatureLength: 15e3,
}),
(v = await P.save()),
Buffer.from(v))
: ((b = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: b,
reason: 'Digitally signed by OpenSign for ' + p + ' <' + m + '>',
location: 'location',
signatureLength: 15e3,
}),
(x = await b.save()),
Buffer.from(x))),
(w = await new SignPDF(e, h).signPDF()),
fs.writeFileSync(D, w))
: fs.writeFileSync(D, e),
await uploadFile(A, D));
if (E && E.imageUrl)
S = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
x = './exports/' + S;
let s = e.length;
s = (
t
? ((P = n.data.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
(e =
P && 0 < P.length
? ((y = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: y,
reason: 'Digitally signed by OpenSign for ' + P?.join(', '),
location: 'location',
signatureLength: 15e3,
}),
(v = await y.save()),
Buffer.from(v))
: ((U = await PDFDocument.load(e)),
pdflibAddPlaceholder({
pdfDoc: U,
reason: 'Digitally signed by OpenSign for ' + p + ' <' + m + '>',
location: 'location',
signatureLength: 15e3,
}),
(b = await U.save()),
Buffer.from(b))),
(I = await new SignPDF(e, u).signPDF()),
fs.writeFileSync(x, I),
I)
: (fs.writeFileSync(x, e), e)
).length;
var A,
w,
D = await uploadFile(S, x);
if (D && D.imageUrl)
return (
(U = await updateDoc(
(A = await updateDoc(
i.params.docId,
E.imageUrl,
s.data.results[0].objectId,
D.imageUrl,
r.data.results[0].objectId,
i.headers['x-real-ip'],
o.data,
r
n.data,
o
)),
(I = {
url: E.imageUrl,
sender: { Mail: o.data.ExtUserPtr.Email, Name: o.data.ExtUserPtr.Name },
pdfName: o.data.Name,
receiver: m,
}),
o.data.IsSendMail && !1 === o.data.IsSendMail
? console.log("don't send mail")
: sendMail(I),
sendDoctoWebhook(o, E.imageUrl, 'signed', s?.data.results?.[0]),
U &&
U.isCompleted &&
((S = {
url: E.imageUrl,
sender: { Mail: o.data.ExtUserPtr.Email, Name: 'OpenSign™' },
pdfName: o.data.Name,
receiver: o.data.ExtUserPtr.Email,
sendDoctoWebhook(n, D.imageUrl, 'signed', r?.data.results?.[0]),
saveFileUsage(s, D.imageUrl, d.data.objectId),
A &&
A.isCompleted &&
((w = {
url: D.imageUrl,
sender: { Mail: n.data.ExtUserPtr.Email, Name: 'OpenSign™' },
pdfName: n.data.Name,
receiver: n.data.ExtUserPtr.Email,
extUserId: n.data.ExtUserPtr.objectId,
}),
o.data.IsSendMail && !1 === o.data.IsSendMail
n.data.IsSendMail && !1 === n.data.IsSendMail
? console.log("don't send mail")
: sendCompletedMail(S),
sendDoctoWebhook(o, E.imageUrl, 'completed')),
fs.unlinkSync(D),
console.log('New Signed PDF created called: ' + D),
'success' === U.message
? { status: 'success', data: E.imageUrl }
: sendCompletedMail(w),
sendDoctoWebhook(n, D.imageUrl, 'completed')),
fs.unlinkSync(x),
console.log('New Signed PDF created called: ' + x),
'success' === A.message
? { status: 'success', data: D.imageUrl }
: { status: 'error', message: 'please provide required parameters!' }
);
}
@@ -3,8 +3,9 @@ import https from 'https';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import { createTransport } from 'nodemailer';
import { updateMailCount } from '../../Utils.js';
async function sendmail(req) {
async function sendmailv3(req) {
try {
let transporterSMTP;
let mailgunClient;
@@ -77,6 +78,9 @@ async function sendmail(req) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return {
status: 'success',
};
@@ -85,6 +89,9 @@ async function sendmail(req) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res);
if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return {
status: 'success',
};
@@ -109,6 +116,9 @@ async function sendmail(req) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return {
status: 'success',
};
@@ -117,6 +127,9 @@ async function sendmail(req) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res);
if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return {
status: 'success',
};
@@ -124,11 +137,11 @@ async function sendmail(req) {
}
}
} catch (err) {
console.log('err ', err);
console.log('err in sendmailv3', err);
if (err) {
return { status: 'error' };
}
}
}
export default sendmail;
export default sendmailv3;
+13 -5
View File
@@ -86,7 +86,9 @@ if (process.env.SMTP_ENABLE) {
console.log('Please provide valid Mailgun credentials');
}
}
const mailsender = process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER;
export const config = {
databaseURI:
process.env.DATABASE_URI || process.env.MONGODB_URI || 'mongodb://localhost:27017/dev',
@@ -102,7 +104,7 @@ export const config = {
verifyUserEmails: isMailAdapter === true ? true : false,
publicServerURL: process.env.SERVER_URL || 'http://localhost:8080/app',
// Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'Open Sign',
appName: 'Opensign',
allowClientClassCreation: false,
allowExpiredAuthDataToken: false,
encodeParseObjectInCloudFunction: true,
@@ -112,9 +114,7 @@ export const config = {
module: 'parse-server-api-mail-adapter',
options: {
// The email address from which emails are sent.
sender: process.env.SMTP_ENABLE
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER,
sender: 'Opensign™' + ' <' + mailsender + '>',
// The email templates.
templates: {
// The template used by Parse Server to send an email for password
@@ -147,6 +147,14 @@ export const config = {
google: {
enabled: true,
},
ldap: {
enabled: true,
url: 'ldap://ldap.forumsys.com:389',
suffix: 'dc=example,dc=com',
// dn: 'ou=mathematicians, dc=example, dc=com',
groupCn: 'mathematicians',
groupFilter: '(&(uniqueMember=uid=,dc=example,dc=com)(objectClass=groupOfUniqueNames))',
},
},
};
// Client-keys like the javascript key or the .NET key are not necessary with parse-server