Compare commits

...
32 Commits
Author SHA1 Message Date
AmolandGitHub 97bd375de3 Merge pull request #550 from OpenSignLabs/patch-1
Patch 1
2024-04-01 20:15:21 +05:30
prafull-opensignlabs e2d450f9aa fix: correct grammatical mistake in report help button 2024-04-01 20:13:56 +05:30
prafull-opensignlabs add01cb02a feat: auto assign free plan in google signup flow 2024-04-01 20:08:02 +05:30
prafull-opensignlabs cea1653805 feat: auto assign free plan to new signup 2024-04-01 19:51:11 +05:30
prafull-opensignlabs 79e3762b36 feat: change subscription plans 2024-04-01 18:47:03 +05:30
RaktimaNXG aa04a957ed feat: change webhook & API token UI 2024-04-01 17:48:12 +05:30
prafull-opensignlabs d31910aefa fix: visibility of upgrade now button in header 2024-04-01 17:25:23 +05:30
prafull-opensignlabs 96a4a2e7fa feat: add help button for Role in template 2024-04-01 17:17:58 +05:30
prafull-opensignlabs e8aaab99f9 fix: change message of already subscribed 2024-04-01 16:54:26 +05:30
prafull-opensignlabs 0ee85f53da refactor: handle updgrade now visibililty in header 2024-04-01 14:31:14 +05:30
AmolandGitHub 748e2196da fix: typo in help text 2024-04-01 14:02:46 +05:30
RaktimaNXG 58199a934a fix: change upgrade button redirect ul, disable documentId issue 2024-04-01 13:27:43 +05:30
prafull-opensignlabs 7eec822a65 refactor: correct grammatical mistake in report help message 2024-03-31 14:16:23 +05:30
prafull-opensignlabs 6c21028523 feat: add upgrade now button in header 2024-03-31 14:11:26 +05:30
prafull-opensignlabs 099a3cc46d refactor: update Emailcount in OTP mail 2024-03-31 13:21:04 +05:30
prafull-opensignlabs 5adaffc684 feat: add emailcount of user 2024-03-31 12:42:08 +05:30
prafull-opensignlabs d588caf6d4 fix: not able to subscribed free plan & stop sending individual mail 2024-03-31 09:28:35 +05:30
prafull-opensignlabs 5930fd940d refactor: add loader in for free subscription 2024-03-31 09:08:27 +05:30
prafull-opensignlabs 741f66b019 feat: add documentCount and templateCount for user 2024-03-31 08:28:17 +05:30
RaktimaNXG de26c2d719 fix: handle condition of free validation warning message 2024-03-29 17:51:59 +05:30
prafull-opensignlabs 881ac12b03 feat: savefileusage in server side 2024-03-29 17:26:33 +05:30
RaktimaNXG 1932dda6d6 feat: disbale validation feature, documentId,webhook,API token for free users and add upgrade button to subscribe 2024-03-29 17:06:09 +05:30
prafull-opensignlabs 46159b7214 fix: freeplan navigation 2024-03-29 13:11:13 +05:30
prafull-opensignlabs c57c15a4cb feat: save tenant usage 2024-03-28 18:57:43 +05:30
prafull-opensignlabs 724fa7c27e Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into patch-1 2024-03-28 16:51:42 +05:30
prafull-opensignlabs 2762c3e170 feat: add validation of free plan 2024-03-28 13:57:37 +05:30
prafull-opensignlabs 74bfd39abc feat: restrict guest route 2024-03-27 16:07:26 +05:30
prafull-opensignlabs 3115ff3e2a fix: restrict user from logging in when next renewal date is expired even when route is entered directly 2024-03-27 13:11:20 +05:30
prafull-opensignlabsandGitHub adbf1fe1e0 Merge pull request #539 from OpenSignLabs/staging
sync branch
2024-03-27 11:35:13 +05:30
prafull-opensignlabsandGitHub fe2f3c5f7c Merge pull request #538 from OpenSignLabs/staging
sync branch
2024-03-27 11:09:58 +05:30
prafull-opensignlabsandGitHub 8a01efee4c Merge pull request #516 from OpenSignLabs/staging
sync branch
2024-03-20 15:27:47 +05:30
prafull-opensignlabs eafb26d462 feat: restrict route if user subscription is expired 2024-03-20 15:26:38 +05:30
48 changed files with 1945 additions and 1028 deletions
+2 -1
View File
@@ -15,6 +15,7 @@ import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign"; import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles"; import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage"; import LazyPage from "./primitives/LazyPage";
import { isEnableSubscription } from "./constant/const";
const DebugPdf = lazy(() => import("./pages/DebugPdf")); const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword")); const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin")); const GuestLogin = lazy(() => import("./pages/GuestLogin"));
@@ -113,7 +114,7 @@ function App() {
path="/forgetpassword" path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />} element={<LazyPage Page={ForgetPassword} />}
/> />
{process.env.REACT_APP_ENABLE_SUBSCRIPTION && ( {isEnableSubscription && (
<> <>
<Route <Route
path="/pgsignup" path="/pgsignup"
+2 -2
View File
@@ -56,11 +56,11 @@ const AddSigner = (props) => {
contactQuery.set("Email", email); contactQuery.set("Email", email);
contactQuery.set("UserRole", "contracts_Guest"); contactQuery.set("UserRole", "contracts_Guest");
if (localStorage.getItem("TenetId")) { if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", { contactQuery.set("TenantId", {
__type: "Pointer", __type: "Pointer",
className: "partners_Tenant", 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("Email", email);
contactQuery.set("UserRole", "contracts_User"); contactQuery.set("UserRole", "contracts_User");
if (localStorage.getItem("TenetId")) { if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", { contactQuery.set("TenantId", {
__type: "Pointer", __type: "Pointer",
className: "partners_Tenant", 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 { useNavigate } from "react-router-dom";
import Parse from "parse"; import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize"; 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 Header = ({ showSidebar }) => {
const navigation = useNavigate(); const navigate = useNavigate();
const { width } = useWindowSize(); const { width } = useWindowSize();
let applogo = localStorage.getItem("appLogo") || ""; let applogo = localStorage.getItem("appLogo") || "";
let username = localStorage.getItem("username"); let username = localStorage.getItem("username");
const image = localStorage.getItem("profileImg") || dp; const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true);
const toggleDropdown = () => { const toggleDropdown = () => {
setIsOpen(!isOpen); 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 = () => { const closeDropdown = () => {
setIsOpen(false); setIsOpen(false);
Parse.User.logOut(); Parse.User.logOut();
@@ -43,7 +55,7 @@ const Header = ({ showSidebar }) => {
localStorage.setItem("baseUrl", baseUrl); localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid); localStorage.setItem("parseAppId", appid);
navigation("/"); navigate("/");
}; };
//handle to close profile drop down menu onclick screen //handle to close profile drop down menu onclick screen
@@ -83,6 +95,16 @@ const Header = ({ showSidebar }) => {
id="profile-menu" id="profile-menu"
className="flex justify-between items-center gap-x-3" 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> <div>
<FullScreenButton /> <FullScreenButton />
</div> </div>
@@ -123,7 +145,7 @@ const Header = ({ showSidebar }) => {
className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal" className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
onClick={() => { onClick={() => {
setIsOpen(false); setIsOpen(false);
navigation("/profile"); navigate("/profile");
}} }}
> >
<i className="fa-regular fa-user"></i> 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" className="hover:bg-gray-100 py-1 px-2 cursor-pointer font-normal"
onClick={() => { onClick={() => {
setIsOpen(false); setIsOpen(false);
navigation("/changepassword"); navigate("/changepassword");
}} }}
> >
<i className="fa-solid fa-lock"></i> Change Password <i className="fa-solid fa-lock"></i> Change Password
@@ -30,7 +30,7 @@ const AddRoleModal = (props) => {
margin: "10px 0 10px 5px" margin: "10px 0 10px 5px"
}} }}
> >
e.g: Hr, Director, Manager, New joinee, Accountant, etc... e.g: Customer, Hr, Director, Manager, Student, etc...
</p> </p>
<div> <div>
<div <div
@@ -1,8 +1,9 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { themeColor } from "../../constant/const"; import { isEnableSubscription, themeColor } from "../../constant/const";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
import { radioButtonWidget } from "../../constant/Utils"; import { radioButtonWidget } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader"; import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
function DropdownWidgetOption(props) { function DropdownWidgetOption(props) {
const [dropdownOptionList, setDropdownOptionList] = useState([ const [dropdownOptionList, setDropdownOptionList] = useState([
"option-1", "option-1",
@@ -241,36 +242,59 @@ function DropdownWidgetOption(props) {
}} }}
className="fa-solid fa-square-plus" className="fa-solid fa-square-plus"
></i> ></i>
{props.type === "checkbox" && !props.isSignYourself && ( <div>
<> {props.type === "checkbox" && !props.isSignYourself && (
<label style={{ fontSize: "13px", fontWeight: "600" }}> <>
Minimun check <label
</label> style={{
<input fontSize: "13px",
required fontWeight: "600",
defaultValue={0} color: !props.isSubscribe && "gray"
value={minCount} }}
onChange={(e) => { >
const count = handleSetMinMax(e); Minimun check
setMinCount(count); </label>
}} {!props.isSubscribe && isEnableSubscription && <Upgrade />}
className="drodown-input" <input
/> required
<label style={{ fontSize: "13px", fontWeight: "600" }}> defaultValue={0}
Maximum check value={minCount}
</label> onChange={(e) => {
<input const count = handleSetMinMax(e);
required setMinCount(count);
defaultValue={0} }}
value={maxCount} className={
onChange={(e) => { props.isSubscribe || !isEnableSubscription
const count = handleSetMinMax(e); ? "drodown-input"
setMaxCount(count); : "disabled drodown-input"
}} }
className="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> </div>
{["dropdown", radioButtonWidget].includes(props.type) && ( {["dropdown", radioButtonWidget].includes(props.type) && (
<> <>
@@ -378,13 +402,15 @@ function DropdownWidgetOption(props) {
</div> </div>
)} )}
</div> </div>
{props.type === "checkbox" && !props.isSignYourself && ( {props.type === "checkbox" &&
<PremiumAlertHeader !props.isSignYourself &&
message={ !isEnableSubscription && (
"Field validations are free in beta, this feature will incur a fee later." <PremiumAlertHeader
} message={
/> "Field validations are free in beta, this feature will incur a fee later."
)} }
/>
)}
<div <div
className={`${ className={`${
props.type === "checkbox" && !props.isSignYourself props.type === "checkbox" && !props.isSignYourself
@@ -15,7 +15,8 @@ function EmailComponent({
setSuccessEmail, setSuccessEmail,
pdfName, pdfName,
sender, sender,
setIsAlert setIsAlert,
extUserId
}) { }) {
const [emailList, setEmailList] = useState([]); const [emailList, setEmailList] = useState([]);
const [emailValue, setEmailValue] = useState(); const [emailValue, setEmailValue] = useState();
@@ -38,6 +39,7 @@ function EmailComponent({
const openSignUrl = "https://www.opensignlabs.com/contact-us"; const openSignUrl = "https://www.opensignlabs.com/contact-us";
const themeBGcolor = themeColor; const themeBGcolor = themeColor;
let params = { let params = {
extUserId: extUserId,
pdfName: pdfName, pdfName: pdfName,
url: pdfUrl, url: pdfUrl,
recipient: emailList[i], recipient: emailList[i],
@@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { themeColor } from "../../constant/const"; import { themeColor } from "../../constant/const";
import RecipientList from "./RecipientList"; import RecipientList from "./RecipientList";
import { Tooltip } from "react-tooltip";
function SignerListPlace(props) { function SignerListPlace(props) {
return ( return (
@@ -13,6 +14,58 @@ function SignerListPlace(props) {
> >
<span className="signedStyle"> <span className="signedStyle">
{props.title ? props.title : "Recipients"} {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> </span>
</div> </div>
<div className="signerList"> <div className="signerList">
@@ -4,6 +4,8 @@ import "../../styles/AddUser.css";
import RegexParser from "regex-parser"; import RegexParser from "regex-parser";
import { textInputWidget } from "../../constant/Utils"; import { textInputWidget } from "../../constant/Utils";
import PremiumAlertHeader from "../../primitives/PremiumAlertHeader"; import PremiumAlertHeader from "../../primitives/PremiumAlertHeader";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
const WidgetNameModal = (props) => { const WidgetNameModal = (props) => {
const [formdata, setFormdata] = useState({ const [formdata, setFormdata] = useState({
@@ -100,13 +102,14 @@ const WidgetNameModal = (props) => {
title={"Widget info"} title={"Widget info"}
> >
{(props.defaultdata?.type === textInputWidget || {(props.defaultdata?.type === textInputWidget ||
props.widgetName === textInputWidget) && ( props.widgetName === textInputWidget) &&
<PremiumAlertHeader !isEnableSubscription && (
message={ <PremiumAlertHeader
"Field validations are free in beta, this feature will incur a fee later." message={
} "Field validations are free in beta, this feature will incur a fee later."
/> }
)} />
)}
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className={`${ className={`${
@@ -133,10 +136,20 @@ const WidgetNameModal = (props) => {
props.widgetName === textInputWidget) && ( props.widgetName === textInputWidget) && (
<> <>
<div className="form-section"> <div className="form-section">
<label htmlFor="textvalidate" style={{ fontSize: 13 }}> <label
htmlFor="textvalidate"
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{ fontSize: 13 }}
>
Validation Validation
</label> </label>
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
<div <div
className={
!props.isSubscribe && isEnableSubscription && "disabled"
}
style={{ style={{
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
@@ -178,6 +191,7 @@ const WidgetNameModal = (props) => {
</div> </div>
</div> </div>
</div> </div>
<div className="form-section"> <div className="form-section">
<label htmlFor="name" style={{ fontSize: 13 }}> <label htmlFor="name" style={{ fontSize: 13 }}>
Default value Default value
@@ -196,7 +210,10 @@ const WidgetNameModal = (props) => {
}} }}
/> />
{isValid === false && ( {isValid === false && (
<div className="warning defaultvalueWarning" style={{ fontSize: 12 }}> <div
className="warning defaultvalueWarning"
style={{ fontSize: 12 }}
>
<i <i
className="fas fa-exclamation-circle" className="fas fa-exclamation-circle"
style={{ color: "#fab005", fontSize: 15 }} style={{ color: "#fab005", fontSize: 15 }}
+24
View File
@@ -3,6 +3,7 @@ import moment from "moment";
import { themeColor } from "./const"; import { themeColor } from "./const";
import React from "react"; import React from "react";
import { rgb } from "pdf-lib"; import { rgb } from "pdf-lib";
import Parse from "parse";
export const isMobile = window.innerWidth < 767; export const isMobile = window.innerWidth < 767;
export const textInputWidget = "text input"; export const textInputWidget = "text input";
@@ -12,6 +13,29 @@ export const openInNewTab = (url) => {
window.open(url, "_blank", "noopener,noreferrer"); 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 = [ export const color = [
"#93a3db", "#93a3db",
"#e6c3db", "#e6c3db",
+1
View File
@@ -9,3 +9,4 @@ export const modalSubmitBtnColor = "#17a2b8";
export const modalCancelBtnColor = "white"; export const modalCancelBtnColor = "white";
export const themeColor = "#47a3ad"; export const themeColor = "#47a3ad";
export const iconColor = "#686968"; 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 parseAppId = localStorage.getItem("parseAppId");
const serverUrl = localStorage.getItem("baseUrl"); 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 //checking server url and save file's size
const tenantPtr = {
__type: "Pointer",
className: "partners_Tenant",
objectId: tenantId
};
const _tenantPtr = JSON.stringify(tenantPtr);
try { try {
const response = await axios const res = await axios.get(
.get(`${serverUrl}classes/partners_TenantCredits`, { `${serverUrl}classes/partners_TenantCredits?where={"PartnersTenant":${_tenantPtr}}`,
{
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId "X-Parse-Application-Id": parseAppId
} }
}) }
.then((result) => { );
const res = result.data; const response = res.data.results;
// console.log("res", res);
return res.results;
})
.catch((err) => {
console.log("axois err ", err);
});
let data; let data;
// console.log("response", response); // console.log("response", response);
if (response && response.length > 0) { if (response && response.length > 0) {
@@ -30,69 +29,49 @@ export const SaveFileSize = async (size, imageUrl) => {
? response[0].usedStorage + size ? response[0].usedStorage + size
: size : size
}; };
await axios await axios.put(
.put( `${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`,
`${serverUrl}classes/partners_TenantCredits/${response[0].objectId}`, data,
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, {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId "X-Parse-Application-Id": parseAppId
} }
}) }
.then(() => { );
// const res = result.data; } else {
// console.log("res", res); data = { usedStorage: size, PartnersTenant: tenantPtr };
}) await axios.post(`${serverUrl}classes/partners_TenantCredits`, data, {
.catch((err) => { headers: {
console.log("axois err ", err); "Content-Type": "application/json",
}); "X-Parse-Application-Id": parseAppId
}
});
} }
} catch (e) { } catch (err) {
console.log("org app error", e); 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 //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 = { const data = {
FileUrl: imageUrl, FileUrl: imageUrl,
FileSize: size FileSize: size,
TenantPtr: tenantPtr
}; };
// console.log("data save",file, data) // console.log("data save",file, data)
await axios try {
.post(`${serverUrl}classes/partners_DataFiles`, data, { await axios.post(`${serverUrl}classes/partners_DataFiles`, data, {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId "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);
}
}; };
+1 -1
View File
@@ -19,7 +19,7 @@ export default function reportJson(id) {
redirectUrl: "draftDocument" redirectUrl: "draftDocument"
} }
], ],
helpMsg: "This are documents you have started but have not finalized for sending." helpMsg: "These are documents you have started but have not finalized for sending."
}; };
// Need your sign report // Need your sign report
case "4Hhwbp482K": case "4Hhwbp482K":
+68 -63
View File
@@ -1,65 +1,70 @@
[ [
{ {
"planName": "SELF HOSTED", "planName": "OPENSIGN™ FREE",
"currency": "", "currency": "",
"price": "Free", "monthlyPrice": "Free",
"subtitle": "Free for life One click install.", "yearlyPrice": "Free",
"btnText": "Visit github", "subtitle": "Free Unlimited E-signatures, Forever.",
"url": "https://github.com/opensignlabs/opensign", "btnText": "Subscribe",
"target": "_blank", "url": "",
"benefits": [ "target": "_blank",
"Sign Unlimited Documents", "benefits": [
"S3 Doc Storage", "Unlimited envelopes",
"Unlimited Guest Signers", "Sign documents yourself",
"Unlimited completion certificates", "Request signatures from others",
"Unique Code(OTP) verification for guest signers", "14 field types",
"Expiring docs & reject signature support", "Automatic e-signatures",
"PDF Template Creation(coming soon)", "Completion certificates",
"Audit Trails", "Send in order",
"API support", "Document templates",
"Add-ons support(coming soon)", "Import from Dropbox",
"Self hosted cloud infrastructure", "Contact book",
"Free for lifetime", "Document expiry support",
"Community support" "Decline document support",
] "Email notifications",
}, "Recipient authentication using OTP",
{ "And much more"
"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)", "planName": "OPENSIGN™ PROFESSIONAL",
"btnText": "Subscribe", "currency": "$",
"url": "https://subscriptions.zoho.in/subscribe/ef798486e6a0a11ea65f2bae8f2af901dbadf3175d495584fd25b9af5d2f2b9e/thanks", "monthlyPrice": "29.99",
"target": "_self", "yearlyPrice": "329.99",
"benefits": [ "subtitle": "Exclusive Access to advanced features.",
"Sign Unlimited Documents", "btnText": "Subscribe",
"Unlimited Secure Doc Storage with OpenSign™ Drive", "url": "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d1a09dfa8085585cdd4ec4d7f32137f3/professional-monthly",
"Unlimited Guest Signers", "yearlyUrl": "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly",
"Unlimited completion certificates", "target": "_self",
"Unique Code(OTP) verification for guest signers", "benefits": [
"Expiring docs & reject signature support", "Everything in OpenSign™ professional",
"PDF Template Creation(coming soon)", "Field validations",
"Audit Trails", "Regular expression validations",
"API support", "Organize docs in OpenSign™ Drive",
"Add-ons support(coming soon)", "Webhooks",
"Self hosted cloud infrastructure" "Zapier integration",
] "API Access",
}, "100 API signatures included",
{ "DocumentId removal from signed docs",
"planName": "ENTERPRISE", "Custom email templates(coming soon)"
"currency": "", ]
"price": "Request Price", },
"subtitle": "Customization available Priority support.", {
"btnText": "Contact us", "planName": "OPENSIGN™ ENTERPRISE",
"url": "https://www.opensignlabs.com/contact-us", "currency": "",
"target": "_blank", "monthlyPrice": "Request Price",
"benefits": [ "yearlyPrice": "Request Price",
"All features", "subtitle": "Scalable Features with priority support.",
"Custom domain", "btnText": "Contact us",
"Whitelabeling", "url": "https://www.opensignlabs.com/contact-us",
"Uptime SLA", "target": "_blank",
"SSO support", "benefits": [
"24/7 support" "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 Parse from "parse";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import { useNavigate, useLocation, Outlet } from "react-router-dom"; import { useNavigate, useLocation, Outlet } from "react-router-dom";
import { isEnableSubscription } from "../constant/const";
const HomeLayout = () => { const HomeLayout = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -18,6 +19,12 @@ const HomeLayout = () => {
const arr = useSelector((state) => state.TourSteps); const arr = useSelector((state) => state.TourSteps);
const [isUserValid, setIsUserValid] = useState(true); const [isUserValid, setIsUserValid] = useState(true);
const [isLoader, setIsLoader] = 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(() => { useEffect(() => {
(async () => { (async () => {
try { try {
@@ -27,8 +34,7 @@ const HomeLayout = () => {
sessionToken: localStorage.getItem("accesstoken") sessionToken: localStorage.getItem("accesstoken")
}); });
if (user) { if (user) {
setIsUserValid(true); checkIsSubscribed();
setIsLoader(false);
} else { } else {
setIsUserValid(false); setIsUserValid(false);
} }
@@ -37,14 +43,36 @@ const HomeLayout = () => {
setIsUserValid(false); setIsUserValid(false);
} }
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
// reactour state async function checkIsSubscribed() {
const [isCloseBtn, setIsCloseBtn] = useState(true); const currentUser = Parse.User.current();
const [isTour, setIsTour] = useState(false); const user = await Parse.Cloud.run("getUserDetails", {
const [tourStatusArr, setTourStatusArr] = useState([]); email: currentUser.get("email")
const [tourConfigs, setTourConfigs] = useState([]); });
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 = () => { const showSidebar = () => {
setIsOpen((value) => !value); setIsOpen((value) => !value);
}; };
@@ -198,7 +226,7 @@ const HomeLayout = () => {
return ( return (
<div> <div>
<div className="sticky top-0 z-50"> <div className="sticky top-0 z-50">
<Header showSidebar={showSidebar} /> {!isLoader && <Header showSidebar={showSidebar} />}
</div> </div>
{isUserValid ? ( {isUserValid ? (
<> <>
@@ -223,7 +251,6 @@ const HomeLayout = () => {
<> <>
<div className="flex md:flex-row flex-col z-50"> <div className="flex md:flex-row flex-col z-50">
<Sidebar isOpen={isOpen} closeSidebar={closeSidebar} /> <Sidebar isOpen={isOpen} closeSidebar={closeSidebar} />
<div <div
id="renderList" id="renderList"
className="relative h-screen flex flex-col justify-between w-full overflow-y-auto" 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 SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title"; import Title from "../components/Title";
import PageNotFound from "./PageNotFound"; import PageNotFound from "./PageNotFound";
import { SaveFileSize } from "../constant/saveFileSize";
// `Form` render all type of Form on this basis of their provided in path // `Form` render all type of Form on this basis of their provided in path
function Form() { function Form() {
@@ -82,6 +83,7 @@ const Forms = (props) => {
const handleFileUpload = async (file) => { const handleFileUpload = async (file) => {
setfileload(true); setfileload(true);
const fileName = file.name; const fileName = file.name;
const size = file.size;
const name = sanitizeFileName(fileName); const name = sanitizeFileName(fileName);
const pdfFile = file; const pdfFile = file;
const parseFile = new Parse.File(name, pdfFile); const parseFile = new Parse.File(name, pdfFile);
@@ -101,6 +103,8 @@ const Forms = (props) => {
setFileUpload(response.url()); setFileUpload(response.url());
setfileload(false); setfileload(false);
if (response.url()) { if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url(); return response.url();
} }
} catch (error) { } catch (error) {
@@ -113,6 +117,7 @@ const Forms = (props) => {
const dropboxSuccess = async (files) => { const dropboxSuccess = async (files) => {
setfileload(true); setfileload(true);
const file = files[0]; const file = files[0];
const size = file.bytes;
const url = file.link; const url = file.link;
const mb = Math.round(file.bytes / Math.pow(1024, 2)); const mb = Math.round(file.bytes / Math.pow(1024, 2));
@@ -141,6 +146,8 @@ const Forms = (props) => {
setfileload(false); setfileload(false);
if (response.url()) { if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url(); return response.url();
} }
} catch (error) { } catch (error) {
+75 -24
View File
@@ -1,14 +1,16 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import Title from "../components/Title"; import Title from "../components/Title";
import axios from "axios"; import axios from "axios";
import { useNavigate } from "react-router-dom";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import { rejectBtn, submitBtn } from "../constant/const"; import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { openInNewTab } from "../constant/Utils"; import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader"; import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip"; import Tooltip from "../primitives/Tooltip";
function GenerateToken() { function GenerateToken() {
const navigation = useNavigate();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl")); const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId")); const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [apiToken, SetApiToken] = useState(""); const [apiToken, SetApiToken] = useState("");
@@ -17,6 +19,7 @@ function GenerateToken() {
const [isGenerate, setIsGenerate] = useState(false); const [isGenerate, setIsGenerate] = useState(false);
const [isErr, setIsErr] = useState(false); const [isErr, setIsErr] = useState(false);
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => { useEffect(() => {
fetchToken(); fetchToken();
@@ -25,6 +28,11 @@ function GenerateToken() {
const fetchToken = async () => { const fetchToken = async () => {
try { try {
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
const url = parseBaseUrl + "functions/getapitoken"; const url = parseBaseUrl + "functions/getapitoken";
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -35,6 +43,7 @@ function GenerateToken() {
if (res) { if (res) {
SetApiToken(res.data.result.result); SetApiToken(res.data.result.result);
} }
setIsLoader(false); setIsLoader(false);
} catch (err) { } catch (err) {
SetApiToken(); SetApiToken();
@@ -90,6 +99,7 @@ function GenerateToken() {
}, 1500); // Reset copied state after 1.5 seconds }, 1500); // Reset copied state after 1.5 seconds
}; };
const handleModal = () => setIsModal(!isModal); const handleModal = () => setIsModal(!isModal);
return ( return (
<React.Fragment> <React.Fragment>
<Title title={"API Token"} /> <Title title={"API Token"} />
@@ -117,35 +127,47 @@ function GenerateToken() {
</div> </div>
) : ( ) : (
<div className="bg-white flex flex-col justify-center shadow rounded"> <div className="bg-white flex flex-col justify-center shadow rounded">
<PremiumAlertHeader /> {!isEnableSubscription && <PremiumAlertHeader />}
<h1 className="ml-4 mt-3 mb-2 font-semibold"> <h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
API Token{" "} OpenSign API{" "}
<Tooltip <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> </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 <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>{" "} <div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
<span <span className="">Api Token:</span>{" "}
id="token" <span
className="w-[60%] md:text-end cursor-pointer" id="token"
onClick={() => copytoclipboard(apiToken)} 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} {apiToken ? "Regenerate Token" : "Generate Token"}
</span> </button>
</li> </li>
</ul> </ul>
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
<button <div className="flex items-center justify-center ">
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>
<button <button
type="button" type="button"
onClick={() => onClick={() =>
@@ -153,12 +175,41 @@ function GenerateToken() {
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1" "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 View Docs
</button> </button>
</div> </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 <ModalUi
isOpen={isModal} isOpen={isModal}
title={"Regenerate Token"} title={"Regenerate Token"}
+2 -1
View File
@@ -59,7 +59,8 @@ function GuestLogin() {
"X-Parse-Application-Id": parseId "X-Parse-Application-Id": parseId
}; };
let body = { let body = {
email: email.toString() email: email.toString(),
docId: id
}; };
let Otp = await axios.post(url, body, { headers: headers }); 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 login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize"; import { useWindowSize } from "../hook/useWindowSize";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import { modalCancelBtnColor, modalSubmitBtnColor } from "../constant/const"; import {
isEnableSubscription,
modalCancelBtnColor,
modalSubmitBtnColor
} from "../constant/const";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo"; import { appInfo } from "../constant/appinfo";
import { fetchAppInfo } from "../redux/reducers/infoReducer"; import { fetchAppInfo } from "../redux/reducers/infoReducer";
@@ -209,7 +213,7 @@ function Login() {
x.TenantId.TenantName || "" x.TenantId.TenantName || ""
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "TenantId",
x.TenantId.objectId x.TenantId.objectId
); );
tenentInfo.push(obj); tenentInfo.push(obj);
@@ -239,10 +243,7 @@ function Login() {
element.pageType element.pageType
); );
setState({ ...state, loading: false }); setState({ ...state, loading: false });
if ( if (isEnableSubscription) {
process.env
.REACT_APP_ENABLE_SUBSCRIPTION
) {
const LocalUserDetails = { const LocalUserDetails = {
name: results[0].get("Name"), name: results[0].get("Name"),
email: results[0].get("Email"), email: results[0].get("Email"),
@@ -253,10 +254,15 @@ function Login() {
"userDetails", "userDetails",
JSON.stringify(LocalUserDetails) JSON.stringify(LocalUserDetails)
); );
const freeplan =
results[0].get("Plan") &&
results[0].get("Plan").plan_code;
const billingDate = const billingDate =
results[0].get("Next_billing_date") && results[0].get("Next_billing_date") &&
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()) { if (billingDate > new Date()) {
localStorage.removeItem( localStorage.removeItem(
"userDetails" "userDetails"
@@ -292,9 +298,7 @@ function Login() {
element.pageType element.pageType
); );
setState({ ...state, loading: false }); setState({ ...state, loading: false });
if ( if (isEnableSubscription) {
process.env.REACT_APP_ENABLE_SUBSCRIPTION
) {
const LocalUserDetails = { const LocalUserDetails = {
name: _user.name, name: _user.name,
email: email, email: email,
@@ -391,6 +395,18 @@ function Login() {
const setThirdpartyLoader = (value) => { const setThirdpartyLoader = (value) => {
setState({ ...state, thirdpartyLoader: 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 thirdpartyLoginfn = async (sessionToken, billingDate) => {
const baseUrl = localStorage.getItem("baseUrl"); const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId"); const parseAppId = localStorage.getItem("parseAppId");
@@ -400,6 +416,11 @@ function Login() {
"X-Parse-Application-Id": parseAppId "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(() => { await Parse.User.become(sessionToken).then(() => {
window.localStorage.setItem("accesstoken", sessionToken); window.localStorage.setItem("accesstoken", sessionToken);
}); });
@@ -542,7 +563,7 @@ function Login() {
tenentName: x.TenantId.TenantName || "" tenentName: x.TenantId.TenantName || ""
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "TenantId",
x.TenantId.objectId x.TenantId.objectId
); );
tenentInfo.push(obj); tenentInfo.push(obj);
@@ -571,18 +592,28 @@ function Login() {
); );
setThirdpartyLoader(false); setThirdpartyLoader(false);
setState({ ...state, loading: false }); setState({ ...state, loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) { if (isEnableSubscription) {
if (billingDate) { if (billingDate) {
if (billingDate > new Date()) { if (billingDate > new Date()) {
localStorage.removeItem("userDetails"); localStorage.removeItem("userDetails");
navigate(redirectUrl); navigate(redirectUrl);
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
}
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else { } else {
navigate(`/subscription`, { navigate(`/subscription`, {
replace: true replace: true
}); });
} }
} else {
navigate(`/subscription`, { replace: true });
} }
} else { } else {
navigate(redirectUrl); navigate(redirectUrl);
@@ -597,17 +628,29 @@ function Login() {
localStorage.setItem("pageType", element.pageType); localStorage.setItem("pageType", element.pageType);
setState({ ...state, loading: false }); setState({ ...state, loading: false });
setThirdpartyLoader(false); setThirdpartyLoader(false);
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) { if (isEnableSubscription) {
if (billingDate) { if (billingDate) {
if (billingDate > new Date()) { if (billingDate > new Date()) {
localStorage.removeItem("userDetails"); localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login // Redirect to the appropriate URL after successful login
navigate(redirectUrl); navigate(redirectUrl);
} else { } else {
navigate(`/subscription`, { replace: true }); if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
} }
} else { } else {
navigate(`/subscription`, { replace: true }); if (isFreeplan) {
navigate(redirectUrl);
} else {
navigate(`/subscription`, {
replace: true
});
}
} }
} else { } else {
navigate(redirectUrl); navigate(redirectUrl);
@@ -745,6 +788,13 @@ function Login() {
// Get TenentID from Extendend Class // Get TenentID from Extendend Class
localStorage.setItem("extended_class", item.extended_class); localStorage.setItem("extended_class", item.extended_class);
const currentUser = Parse.User.current(); 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", { await Parse.Cloud.run("getUserDetails", {
email: currentUser.get("email") email: currentUser.get("email")
}).then( }).then(
@@ -777,22 +827,78 @@ function Login() {
tenentName: x.TenantId.TenantName || "" tenentName: x.TenantId.TenantName || ""
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "TenantId",
x.TenantId.objectId x.TenantId.objectId
); );
tenentInfo.push(obj); tenentInfo.push(obj);
} }
}); });
localStorage.setItem("PageLanding", item.pageId); localStorage.setItem("PageLanding", setting.pageId);
localStorage.setItem("defaultmenuid", item.menuId); localStorage.setItem(
localStorage.setItem("pageType", item.pageType); "defaultmenuid",
navigate(`/${item.pageType}/${item.pageId}`); 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 { } else {
localStorage.setItem("PageLanding", item.pageId); localStorage.setItem("PageLanding", setting.pageId);
localStorage.setItem("defaultmenuid", item.menuId); localStorage.setItem("defaultmenuid", setting.menuId);
localStorage.setItem("pageType", item.pageType); localStorage.setItem("pageType", setting.pageType);
navigate(`/${item.pageType}/${item.pageId}`); 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) => { (error) => {
@@ -1045,7 +1151,11 @@ function Login() {
</button> </button>
<NavLink <NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase" 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" } : {}} style={width < 768 ? { textAlign: "center" } : {}}
> >
Create Account Create Account
+6 -1
View File
@@ -5,6 +5,7 @@ import "../styles/signature.css";
import { toDataUrl } from "../constant/Utils"; import { toDataUrl } from "../constant/Utils";
import Parse from "parse"; import Parse from "parse";
import { appInfo } from "../constant/appinfo"; import { appInfo } from "../constant/appinfo";
import { SaveFileSize } from "../constant/saveFileSize";
const ManageSign = () => { const ManageSign = () => {
const appName = appInfo.appname; const appName = appInfo.appname;
const [penColor, setPenColor] = useState("blue"); const [penColor, setPenColor] = useState("blue");
@@ -158,7 +159,11 @@ const ManageSign = () => {
try { try {
const parseFile = new Parse.File(file.name, file); const parseFile = new Parse.File(file.name, file);
const response = await parseFile.save(); 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) { } catch (err) {
console.log("sign upload err", err); console.log("sign upload err", err);
setIsLoader(false); 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 || "" tenentName: x.TenantId.TenantName || ""
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "TenantId",
x.TenantId.objectId x.TenantId.objectId
); );
tenentInfo.push(obj); tenentInfo.push(obj);
+36 -2
View File
@@ -3,7 +3,7 @@ import axios from "axios";
import Parse from "parse"; import Parse from "parse";
import "../styles/signature.css"; import "../styles/signature.css";
import { PDFDocument } from "pdf-lib"; import { PDFDocument } from "pdf-lib";
import { themeColor } from "../constant/const"; import { isEnableSubscription, themeColor } from "../constant/const";
import { DndProvider } from "react-dnd"; import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend"; import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd"; import { useDrag, useDrop } from "react-dnd";
@@ -39,6 +39,7 @@ import TourContentWithBtn from "../primitives/TourContentWithBtn";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption"; import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import WidgetNameModal from "../components/pdf/WidgetNameModal"; import WidgetNameModal from "../components/pdf/WidgetNameModal";
import { SaveFileSize } from "../constant/saveFileSize";
function PlaceHolderSign() { function PlaceHolderSign() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -104,11 +105,12 @@ function PlaceHolderSign() {
const [widgetName, setWidgetName] = useState(false); const [widgetName, setWidgetName] = useState(false);
const [mailStatus, setMailStatus] = useState(""); const [mailStatus, setMailStatus] = useState("");
const [isCurrUser, setIsCurrUser] = useState(false); const [isCurrUser, setIsCurrUser] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isAlreadyPlace, setIsAlreadyPlace] = useState({ const [isAlreadyPlace, setIsAlreadyPlace] = useState({
status: false, status: false,
message: "" message: ""
}); });
const [extUserId, setExtUserId] = useState("");
const isMobile = window.innerWidth < 767; const isMobile = window.innerWidth < 767;
const [, drop] = useDrop({ const [, drop] = useDrop({
accept: "BOX", accept: "BOX",
@@ -180,11 +182,35 @@ function PlaceHolderSign() {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current]); }, [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 //function for get document details
const getDocumentDetails = async () => { const getDocumentDetails = async () => {
//getting document details //getting document details
const documentData = await contractDocument(documentId); const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) { if (documentData && documentData.length > 0) {
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
if (isEnableSubscription) {
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
}
const alreadyPlaceholder = documentData[0]?.SignedUrl; const alreadyPlaceholder = documentData[0]?.SignedUrl;
// Check if document is sent for signing // Check if document is sent for signing
if (alreadyPlaceholder) { if (alreadyPlaceholder) {
@@ -736,6 +762,9 @@ function PlaceHolderSign() {
// Save the Parse File if needed // Save the Parse File if needed
const pdfData = await pdfFile.save(); const pdfData = await pdfFile.save();
const pdfUrl = pdfData.url(); const pdfUrl = pdfData.url();
const tenantId = localStorage.getItem("TenantId");
const buffer = atob(pdfBytes);
SaveFileSize(buffer.length, pdfUrl, tenantId);
return pdfUrl; return pdfUrl;
} catch (e) { } catch (e) {
console.log("error", e); console.log("error", e);
@@ -829,6 +858,7 @@ function PlaceHolderSign() {
: ""; : "";
const themeBGcolor = themeColor; const themeBGcolor = themeColor;
let params = { let params = {
extUserId: extUserId,
recipient: signerMail[i].Email, recipient: signerMail[i].Email,
subject: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign ${pdfDetails?.[0].Name}`, subject: `${pdfDetails?.[0].ExtUserPtr.Name} has requested you to sign ${pdfDetails?.[0].Name}`,
from: sender, from: sender,
@@ -1596,6 +1626,7 @@ function PlaceHolderSign() {
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe}
/> />
<DropdownWidgetOption <DropdownWidgetOption
type="checkbox" type="checkbox"
@@ -1606,6 +1637,7 @@ function PlaceHolderSign() {
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe}
/> />
<DropdownWidgetOption <DropdownWidgetOption
type="dropdown" type="dropdown"
@@ -1616,6 +1648,7 @@ function PlaceHolderSign() {
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe}
/> />
{/* pdf header which contain funish back button */} {/* pdf header which contain funish back button */}
@@ -1831,6 +1864,7 @@ function PlaceHolderSign() {
isOpen={isNameModal} isOpen={isNameModal}
handleClose={handleNameModal} handleClose={handleNameModal}
handleData={handleWidgetdefaultdata} handleData={handleWidgetdefaultdata}
isSubscribe={isSubscribe}
/> />
</div> </div>
</> </>
+121 -36
View File
@@ -1,9 +1,9 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router"; import { NavLink, useNavigate } from "react-router-dom";
import { NavLink } from "react-router-dom";
import checkmark from "../assets/images/checkmark.png"; import checkmark from "../assets/images/checkmark.png";
import plansArr from "../json/plansArr.json"; import plansArr from "../json/plansArr.json";
import Title from "../components/Title"; import Title from "../components/Title";
import Parse from "parse";
const listItemStyle = { const listItemStyle = {
paddingLeft: "20px", // Add padding to create space for the image paddingLeft: "20px", // Add padding to create space for the image
backgroundImage: `url(${checkmark})`, // Set your image as the list style image backgroundImage: `url(${checkmark})`, // Set your image as the list style image
@@ -35,15 +35,35 @@ const PlanSubscriptions = () => {
company + company +
phone; phone;
useEffect(() => { useEffect(() => {
if (localStorage.getItem("accesstoken")) { // if (localStorage.getItem("accesstoken")) {
setIsLoader(false); setIsLoader(false);
setYearlyVisible(false); // } else {
} else { // navigate("/", { replace: true });
navigate("/", { replace: true }); // }
}
// eslint-disable-next-line // 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 ( return (
<> <>
<Title title={"Subscriptions"} /> <Title title={"Subscriptions"} />
@@ -69,23 +89,40 @@ const PlanSubscriptions = () => {
style={{ style={{
backgroundColor: "white", backgroundColor: "white",
overflowY: "auto", overflowY: "auto",
maxHeight: "100%", maxHeight: "100%"
"--theme-color": "#7952b3",
"--plan-width": 30
}} }}
> >
<div <div id="monthlyPlans" className="block my-2">
id="monthlyPlans" <div className="flex flex-col justify-center items-center w-full">
className={`${yearlyVisible ? "none" : "block my-2"}`} <div className="mb-6 mt-2 flex flex-row border-[1px] p-2 border-gray-300 rounded text-sm">
> <span
<div className="flex justify-center w-full my-2"> onClick={() => setYearlyVisible(false)}
<ul className=" flex flex-col md:flex-row h-full bg-white justify-center border-collapse border-[1px] border-gray-300"> 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) => ( {plansArr.map((item) => (
<li <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} 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"> <h3 className="text-[#002862] uppercase">
{item.planName} {item.planName}
</h3> </h3>
@@ -99,17 +136,23 @@ const PlanSubscriptions = () => {
<div className=""> <div className="">
<span className="text-3xl"> <span className="text-3xl">
{item.currency && <small>{item.currency}</small>} {item.currency && <small>{item.currency}</small>}
{item.price} {yearlyVisible
? item?.yearlyPrice
: item.monthlyPrice}
</span> </span>
<p className="font-semibold pt-2 text-sm">
{yearlyVisible ? "Billed Yearly" : "Billed Monthly"}
</p>
<div <div
className={`${ className={`${
item.subtitle.length <= 32 item.subtitle.length <= 32
? "w-[150px] text-center" ? "w-[150px] h-[40px] text-center"
: "" : ""
} text-sm text-center my-2`} } text-sm text-center my-2`}
> >
<p <div
style={{ style={{
textAlign: "center",
backgroundColor: item.subtitlecolor backgroundColor: item.subtitlecolor
? item.subtitlecolor ? item.subtitlecolor
: "white" : "white"
@@ -124,23 +167,33 @@ const PlanSubscriptions = () => {
) : ( ) : (
<span>{item.subtitle}</span> <span>{item.subtitle}</span>
)} )}
</p> </div>
</div> </div>
</div> </div>
{item.url ? (
<NavLink <NavLink
to={ to={
item.btnText === "Subscribe" item.btnText === "Subscribe"
? item.url + details ? yearlyVisible
: item.url ? item.yearlyUrl + details
} : item.url + details
className="bg-[#002862] w-full text-white py-2 rounded uppercase hover:no-underline hover:text-white" : item.url
target={item.target} }
> className="bg-[#002862] w-full mt-1 text-white py-2 rounded uppercase hover:no-underline hover:text-white cursor-pointer"
{item.btnText} target={item.target}
</NavLink> >
{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> </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"> <ul className="mx-1 p-3 text-left break-words text-sm list-none">
{item.benefits.map((subitem, index) => ( {item.benefits.map((subitem, index) => (
<li style={listItemStyle} key={index} className="m-1"> <li style={listItemStyle} key={index} className="m-1">
@@ -154,6 +207,38 @@ const PlanSubscriptions = () => {
))} ))}
</ul> </ul>
</div> </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>
</div> </div>
)} )}
+45 -14
View File
@@ -2,17 +2,19 @@ import React, { useState, useEffect } from "react";
import Parse from "parse"; import Parse from "parse";
import axios from "axios"; import axios from "axios";
import Title from "../components/Title"; 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 login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize"; import { useWindowSize } from "../hook/useWindowSize";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo"; import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
import { fetchAppInfo } from "../redux/reducers/infoReducer"; 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 Signup = () => {
const { width } = useWindowSize(); const { width } = useWindowSize();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const [name, setName] = useState(""); const [name, setName] = useState("");
const [phone, setPhone] = useState(""); const [phone, setPhone] = useState("");
@@ -67,6 +69,19 @@ const Signup = () => {
localStorage.setItem("baseUrl", baseUrl); localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid); 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) => { const handleSubmit = (event) => {
event.preventDefault(); event.preventDefault();
if (lengthValid && caseDigitValid && specialCharValid) { if (lengthValid && caseDigitValid && specialCharValid) {
@@ -110,7 +125,13 @@ const Signup = () => {
params params
); );
if (usersignup) { 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) { } catch (err) {
alert(err.message); 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 baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId"); const parseAppId = localStorage.getItem("parseAppId");
const res = await axios.get(baseUrl + "users/me", { const res = await axios.get(baseUrl + "users/me", {
@@ -259,9 +280,7 @@ const Signup = () => {
}); });
if (tenentInfo.length) { if (tenentInfo.length) {
dispatch( dispatch(
showTenant( showTenant(tenentInfo[0].tenentName || "")
tenentInfo[0].tenentName || ""
)
); );
localStorage.setItem( localStorage.setItem(
"TenantName", "TenantName",
@@ -297,7 +316,7 @@ const Signup = () => {
tenentName: x.TenantId.TenantName || "" tenentName: x.TenantId.TenantName || ""
}; };
localStorage.setItem( localStorage.setItem(
"TenetId", "TenantId",
x.TenantId.objectId x.TenantId.objectId
); );
tenentInfo.push(obj); tenentInfo.push(obj);
@@ -325,8 +344,14 @@ const Signup = () => {
element.pageType element.pageType
); );
setState({ loading: false }); setState({ loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) { if (isEnableSubscription) {
navigate(`/subscription`, { replace: true }); if (isFreeplan) {
navigate(
`/${element.pageType}/${element.pageId}`
);
} else {
navigate(`/subscription`, { replace: true });
}
} else { } else {
alert("Registered user successfully"); alert("Registered user successfully");
navigate( navigate(
@@ -343,8 +368,14 @@ const Signup = () => {
); );
localStorage.setItem("pageType", element.pageType); localStorage.setItem("pageType", element.pageType);
setState({ loading: false }); setState({ loading: false });
if (process.env.REACT_APP_ENABLE_SUBSCRIPTION) { if (isEnableSubscription) {
navigate(`/subscription`, { replace: true }); if (isFreeplan) {
navigate(
`/${element.pageType}/${element.pageId}`
);
} else {
navigate(`/subscription`, { replace: true });
}
} else { } else {
navigate( navigate(
`/${element.pageType}/${element.pageId}` `/${element.pageType}/${element.pageId}`
@@ -626,7 +657,7 @@ const Signup = () => {
</button> </button>
<NavLink <NavLink
className="rounded-sm cursor-pointer bg-white border-[1px] border-[#15b4e9] text-[#15b4e9] w-full py-3 shadow uppercase" 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" } : {}} style={width < 768 ? { textAlign: "center" } : {}}
> >
Login 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 [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isDontShow, setIsDontShow] = useState(false); const [isDontShow, setIsDontShow] = useState(false);
const [extUserId, setExtUserId] = useState("");
const divRef = useRef(null); const divRef = useRef(null);
const nodeRef = useRef(null); const nodeRef = useRef(null);
const [, drop] = useDrop({ const [, drop] = useDrop({
@@ -186,7 +187,7 @@ function SignYourSelf() {
if (documentData && documentData.length > 0) { if (documentData && documentData.length > 0) {
setPdfDetails(documentData); setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
isCompleted = documentData[0].IsCompleted && documentData[0].IsCompleted; isCompleted = documentData[0].IsCompleted && documentData[0].IsCompleted;
if (isCompleted) { if (isCompleted) {
const docStatus = { const docStatus = {
@@ -433,13 +434,13 @@ function SignYourSelf() {
Width: widgetTypeExist Width: widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth ? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth
: dragTypeValue === "initials" : dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).width ? defaultWidthHeight(dragTypeValue).width
: "", : "",
Height: widgetTypeExist Height: widgetTypeExist
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getHeight ? calculateInitialWidthHeight(dragTypeValue, widgetValue).getHeight
: dragTypeValue === "initials" : dragTypeValue === "initials"
? defaultWidthHeight(dragTypeValue).height ? defaultWidthHeight(dragTypeValue).height
: "", : "",
options: addWidgetOptions(dragTypeValue) options: addWidgetOptions(dragTypeValue)
}; };
@@ -1120,6 +1121,7 @@ function SignYourSelf() {
setSuccessEmail={setSuccessEmail} setSuccessEmail={setSuccessEmail}
sender={jsonSender} sender={jsonSender}
setIsAlert={setIsAlert} setIsAlert={setIsAlert}
extUserId={extUserId}
/> />
{/* pdf header which contain funish back button */} {/* pdf header which contain funish back button */}
<Header <Header
+30 -2
View File
@@ -3,7 +3,7 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { useParams, useNavigate } from "react-router-dom"; import { useParams, useNavigate } from "react-router-dom";
import axios from "axios"; import axios from "axios";
import "../styles/signature.css"; import "../styles/signature.css";
import { themeColor } from "../constant/const"; import { isEnableSubscription, themeColor } from "../constant/const";
import { DndProvider } from "react-dnd"; import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend"; import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd"; import { useDrag, useDrop } from "react-dnd";
@@ -35,6 +35,7 @@ import AddRoleModal from "../components/pdf/AddRoleModal";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy"; import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn"; import TourContentWithBtn from "../primitives/TourContentWithBtn";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption"; import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import Parse from "parse";
const TemplatePlaceholder = () => { const TemplatePlaceholder = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { templateId } = useParams(); const { templateId } = useParams();
@@ -52,6 +53,7 @@ const TemplatePlaceholder = () => {
const [isSelectListId, setIsSelectId] = useState(); const [isSelectListId, setIsSelectId] = useState();
const [isSendAlert, setIsSendAlert] = useState(false); const [isSendAlert, setIsSendAlert] = useState(false);
const [isCreateDocModal, setIsCreateDocModal] = useState(false); const [isCreateDocModal, setIsCreateDocModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isLoading, setIsLoading] = useState({ const [isLoading, setIsLoading] = useState({
isLoad: true, isLoad: true,
message: "This might take some time" message: "This might take some time"
@@ -168,7 +170,26 @@ const TemplatePlaceholder = () => {
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current]); }, [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 // `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
const fetchTemplate = async () => { const fetchTemplate = async () => {
try { try {
@@ -190,6 +211,9 @@ const TemplatePlaceholder = () => {
: []; : [];
if (documentData && documentData.length > 0) { if (documentData && documentData.length > 0) {
if (isEnableSubscription) {
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
}
setPdfDetails(documentData); setPdfDetails(documentData);
setIsSigners(true); setIsSigners(true);
if (documentData[0].Signers && documentData[0].Signers.length > 0) { if (documentData[0].Signers && documentData[0].Signers.length > 0) {
@@ -1323,6 +1347,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe}
/> />
<DropdownWidgetOption <DropdownWidgetOption
type="checkbox" type="checkbox"
@@ -1333,6 +1358,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe}
/> />
<DropdownWidgetOption <DropdownWidgetOption
type="dropdown" type="dropdown"
@@ -1343,6 +1369,7 @@ const TemplatePlaceholder = () => {
currWidgetsDetails={currWidgetsDetails} currWidgetsDetails={currWidgetsDetails}
setCurrWidgetsDetails={setCurrWidgetsDetails} setCurrWidgetsDetails={setCurrWidgetsDetails}
handleClose={handleNameModal} handleClose={handleNameModal}
isSubscribe={isSubscribe}
/> />
<PlaceholderCopy <PlaceholderCopy
isPageCopy={isPageCopy} isPageCopy={isPageCopy}
@@ -1542,6 +1569,7 @@ const TemplatePlaceholder = () => {
isOpen={isNameModal} isOpen={isNameModal}
handleClose={handleNameModal} handleClose={handleNameModal}
handleData={handleWidgetdefaultdata} handleData={handleWidgetdefaultdata}
isSubscribe={isSubscribe}
/> />
</div> </div>
); );
+43 -13
View File
@@ -8,6 +8,9 @@ import sanitizeFileName from "../primitives/sanitizeFileName";
import axios from "axios"; import axios from "axios";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader"; import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip"; import Tooltip from "../primitives/Tooltip";
import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribed } from "../constant/Utils";
import Upgrade from "../primitives/Upgrade";
function UserProfile() { function UserProfile() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -21,15 +24,24 @@ function UserProfile() {
const [isLoader, setIsLoader] = useState(false); const [isLoader, setIsLoader] = useState(false);
const [percentage, setpercentage] = useState(0); const [percentage, setpercentage] = useState(0);
const [isDisableDocId, setIsDisableDocId] = useState(false); const [isDisableDocId, setIsDisableDocId] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => { useEffect(() => {
getUserDetail();
}, []);
const getUserDetail = async () => {
const extClass = localStorage.getItem("Extand_Class"); const extClass = localStorage.getItem("Extand_Class");
const jsonSender = JSON.parse(extClass); const jsonSender = JSON.parse(extClass);
const HeaderDocId = jsonSender[0]?.HeaderDocId; const HeaderDocId = jsonSender[0]?.HeaderDocId;
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
if (HeaderDocId) { if (HeaderDocId) {
setIsDisableDocId(HeaderDocId); setIsDisableDocId(HeaderDocId);
} }
}, []); };
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
setIsLoader(true); setIsLoader(true);
@@ -137,7 +149,8 @@ function UserProfile() {
setImage(response.url()); setImage(response.url());
localStorage.setItem("profileImg", response.url()); localStorage.setItem("profileImg", response.url());
setpercentage(0); setpercentage(0);
SaveFileSize(size, response.url()); const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, response.url(), tenantId);
return response.url(); return response.url();
} }
} catch (error) { } catch (error) {
@@ -155,7 +168,6 @@ function UserProfile() {
const handleDisableDocId = () => { const handleDisableDocId = () => {
setIsDisableDocId((prevChecked) => !prevChecked); setIsDisableDocId((prevChecked) => !prevChecked);
}; };
return ( return (
<React.Fragment> <React.Fragment>
<Title title={"Profile"} /> <Title title={"Profile"} />
@@ -262,14 +274,30 @@ function UserProfile() {
</li> </li>
<li className="border-y-[1px] border-gray-300 break-all"> <li className="border-y-[1px] border-gray-300 break-all">
<div className="flex justify-between items-center py-2"> <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 :{" "} 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>{" "} </span>{" "}
<label <label
className={`${ className={
editmode ? "cursor-pointer" : "" isSubscribe || !isEnableSubscription
} relative inline-flex items-center mb-0`} ? `${
editmode ? "cursor-pointer" : ""
} relative inline-flex items-center mb-0`
: "relative inline-flex items-center mb-0 pointer-events-none opacity-50"
}
> >
<input <input
disabled={editmode ? false : true} 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> <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> </label>
</div> </div>
<PremiumAlertHeader {!isEnableSubscription && (
message={ <PremiumAlertHeader
"Disable documentId is free in beta, this feature will incur a fee later." message={
} "Disable documentId is free in beta, this feature will incur a fee later."
/> }
/>
)}
</li> </li>
</ul> </ul>
<div className="flex justify-center pt-2 pb-3 md:pt-3 md:pb-4"> <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 React, { useEffect, useState } from "react";
import Title from "../components/Title"; import Title from "../components/Title";
import axios from "axios"; import axios from "axios";
import { useNavigate } from "react-router-dom";
import Alert from "../primitives/Alert"; import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi"; import ModalUi from "../primitives/ModalUi";
import { rejectBtn, submitBtn } from "../constant/const"; import { isEnableSubscription, rejectBtn, submitBtn } from "../constant/const";
import { openInNewTab } from "../constant/Utils"; import { checkIsSubscribed, openInNewTab } from "../constant/Utils";
import Parse from "parse"; import Parse from "parse";
import PremiumAlertHeader from "../primitives/PremiumAlertHeader"; import PremiumAlertHeader from "../primitives/PremiumAlertHeader";
import Tooltip from "../primitives/Tooltip"; import Tooltip from "../primitives/Tooltip";
function Webhook() { function Webhook() {
const navigation = useNavigate();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl")); const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId")); const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [webhook, setWebhook] = useState(); const [webhook, setWebhook] = useState();
@@ -17,6 +19,8 @@ function Webhook() {
const [isGenerate, setIsGenerate] = useState(false); const [isGenerate, setIsGenerate] = useState(false);
const [isErr, setIsErr] = useState(false); const [isErr, setIsErr] = useState(false);
const [isModal, setIsModal] = useState(false); const [isModal, setIsModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [error, setError] = useState("");
useEffect(() => { useEffect(() => {
fetchWebhook(); fetchWebhook();
@@ -25,6 +29,10 @@ function Webhook() {
const fetchWebhook = async () => { const fetchWebhook = async () => {
const email = Parse.User.current().getEmail(); const email = Parse.User.current().getEmail();
if (isEnableSubscription) {
const getIsSubscribe = await checkIsSubscribed();
setIsSubscribe(getIsSubscribe);
}
const params = { email: email }; const params = { email: email };
try { try {
const extRes = await Parse.Cloud.run("getUserDetails", params); const extRes = await Parse.Cloud.run("getUserDetails", params);
@@ -40,41 +48,48 @@ function Webhook() {
}; };
const handleSubmit = async (e) => { const handleSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
setIsLoader(true); if (webhook && webhook.startsWith("https://")) {
setIsModal(false); setIsLoader(true);
try { setIsModal(false);
const params = { url: webhook }; try {
const url = parseBaseUrl + "functions/savewebhook"; const params = { url: webhook };
const headers = { const url = parseBaseUrl + "functions/savewebhook";
"Content-Type": "application/json", const headers = {
"X-Parse-Application-Id": parseAppId, "Content-Type": "application/json",
sessiontoken: localStorage.getItem("accesstoken") "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) { await axios.post(url, params, { headers: headers }).then((res) => {
setWebhook(res.data.result.Webhook); if (res.data && res.data.result && res.data.result.Webhook) {
setIsGenerate(true); setWebhook(res.data.result.Webhook);
setTimeout(() => { setIsGenerate(true);
setIsGenerate(false); setTimeout(() => {
}, 1500); setIsGenerate(false);
setIsLoader(false); }, 1500);
} else { setIsLoader(false);
console.error("Error while generating webhook"); } else {
setIsLoader(false); console.error("Error while generating webhook");
setIsErr(true); setIsLoader(false);
setTimeout(() => { setIsErr(true);
setIsErr(false); setTimeout(() => {
}, 1500); setIsErr(false);
} }, 1500);
}); }
} catch (error) { });
setIsLoader(false); } catch (error) {
setIsErr(true); setIsLoader(false);
setTimeout(() => { setIsErr(true);
setIsErr(false); setTimeout(() => {
}, 1500); 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"} /> <Title title={"Webhook"} />
{isGenerate && <Alert type="success">Webhook added successfully!</Alert>} {isGenerate && <Alert type="success">Webhook added successfully!</Alert>}
{isErr && <Alert type="danger">Something went wrong!</Alert>} {isErr && <Alert type="danger">Something went wrong!</Alert>}
{isLoader ? ( {isLoader ? (
<div <div
style={{ style={{
@@ -103,31 +119,41 @@ function Webhook() {
</div> </div>
) : ( ) : (
<div className="bg-white flex flex-col justify-center shadow rounded"> <div className="bg-white flex flex-col justify-center shadow rounded">
<PremiumAlertHeader /> {!isEnableSubscription && <PremiumAlertHeader />}
<h1 className="ml-4 mt-3 mb-2 font-semibold"> <h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
Webhook{" "} OpenSign Webhook{" "}
<Tooltip <Tooltip
url={"https://docs.opensignlabs.com/docs/API-docs/get-webhook"} url={"https://docs.opensignlabs.com/docs/API-docs/get-webhook"}
isSubscribe={true}
/> />
</h1> </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 <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>{" "} <div className="w-[70%] flex-col md:flex-row flex items-center gap-5 ">
<span id="token" className="w-[60%] md:text-end cursor-pointer"> <span className="">Webhook:</span>{" "}
{webhook && webhook} <span id="token" className=" md:text-end cursor-pointer">
</span> {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> </li>
</ul> </ul>
<div className="flex flex-col md:flex-row items-center justify-center gap-2 pb-4">
<button <div className="flex items-center justify-center ">
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>
<button <button
type="button" type="button"
onClick={() => onClick={() =>
@@ -135,17 +161,47 @@ function Webhook() {
"https://docs.opensignlabs.com/docs/API-docs/save-update-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 View Docs
</button> </button>
</div> </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 <ModalUi
isOpen={isModal} isOpen={isModal}
title={"Regenerate Token"} title={"Regenerate Token"}
handleClose={handleModal} handleClose={handleModal}
> >
{error && <Alert type="danger">{error}</Alert>}
<div className="m-[20px]"> <div className="m-[20px]">
<div className="text-lg font-normal text-black"> <div className="text-lg font-normal text-black">
<label className="text-sm ml-2">Webhook</label> <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("Email", email);
contactQuery.set("UserRole", "contracts_Guest"); contactQuery.set("UserRole", "contracts_Guest");
if (localStorage.getItem("TenetId")) { if (localStorage.getItem("TenantId")) {
contactQuery.set("TenantId", { contactQuery.set("TenantId", {
__type: "Pointer", __type: "Pointer",
className: "partners_Tenant", className: "partners_Tenant",
objectId: localStorage.getItem("TenetId") objectId: localStorage.getItem("TenantId")
}); });
} }
+10 -2
View File
@@ -1,9 +1,17 @@
import React from "react"; import React from "react";
import { Tooltip as ReactTooltip } from "react-tooltip"; import { Tooltip as ReactTooltip } from "react-tooltip";
import { openInNewTab } from "../constant/Utils"; import { openInNewTab } from "../constant/Utils";
const Tooltip = ({ id, message, url, iconColor }) => import { isEnableSubscription } from "../constant/const";
const Tooltip = ({ id, message, url, iconColor, isSubscribe }) =>
url ? ( 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> <sup>
<i <i
className="fa-solid fa-question rounded-full" 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); setIsUserValid(false);
} }
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const handleLoginBtn = () => { const handleLoginBtn = () => {
try { try {
Parse.User.logOut(); 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() { export function customAPIurl() {
const url = new URL(process.env.SERVER_URL); const url = new URL(process.env.SERVER_URL);
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin; return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
@@ -38,3 +42,111 @@ export function replaceMailVaribles(subject, body, variables) {
}; };
return result; 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, subject: subject,
from: sender, from: sender,
html: html, html: html,
extUserId: template.ExtUserPtr.objectId,
}; };
sendMail = await axios.post(url, params, { headers: headers }); sendMail = await axios.post(url, params, { headers: headers });
} catch (error) { } catch (error) {
@@ -1,5 +1,5 @@
import axios from 'axios'; 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 // `sendDoctoWebhook` is used to send res data of document on webhook
async function sendDoctoWebhook(doc, WebhookUrl, userId) { async function sendDoctoWebhook(doc, WebhookUrl, userId) {
@@ -8,6 +8,7 @@ async function sendDoctoWebhook(doc, WebhookUrl, userId) {
event: 'created', event: 'created',
...doc, ...doc,
}; };
await axios await axios
.post(WebhookUrl, params, { .post(WebhookUrl, params, {
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@@ -65,6 +66,7 @@ export default async function createDocumentwithCoordinate(request, response) {
if (!reqToken) { if (!reqToken) {
return response.status(400).json({ error: 'Please Provide API Token' }); return response.status(400).json({ error: 'Please Provide API Token' });
} }
try { try {
const tokenQuery = new Parse.Query('appToken'); const tokenQuery = new Parse.Query('appToken');
tokenQuery.equalTo('token', reqToken); tokenQuery.equalTo('token', reqToken);
@@ -81,17 +83,22 @@ export default async function createDocumentwithCoordinate(request, response) {
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let fileUrl; let fileUrl;
if (request.files?.[0]) { if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, { const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'), base64: base64,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const file = new Parse.File(`${name}.pdf`, { const file = new Parse.File(`${name}.pdf`, {
base64: base64File, base64: base64File,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users'); const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr); contractsUser.equalTo('UserId', userPtr);
@@ -242,7 +249,6 @@ export default async function createDocumentwithCoordinate(request, response) {
year: 'numeric', year: 'numeric',
}); });
let sender = parseExtUser.Email; let sender = parseExtUser.Email;
let sendMail;
const serverUrl = process.env.SERVER_URL; const serverUrl = process.env.SERVER_URL;
const newServer = serverUrl.replaceAll('/', '%2F'); const newServer = serverUrl.replaceAll('/', '%2F');
const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`; const serverParams = `${newServer}%2F&${process.env.APP_ID}&contracts`;
@@ -330,9 +336,10 @@ export default async function createDocumentwithCoordinate(request, response) {
subject: subject, subject: subject,
from: sender, from: sender,
html: html, html: html,
extUserId: extUser.id,
}; };
sendMail = await axios.post(url, params, { headers: headers }); await axios.post(url, params, { headers: headers });
} catch (error) { } catch (error) {
console.log('error', error); console.log('error', error);
} }
@@ -1,6 +1,4 @@
import { customAPIurl } from '../../../../Utils.js'; import { saveFileUsage } from '../../../../Utils.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function createTemplate(request, response) { export default async function createTemplate(request, response) {
const name = request.body?.title; const name = request.body?.title;
const note = request.body?.note; const note = request.body?.note;
@@ -36,12 +34,17 @@ export default async function createTemplate(request, response) {
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, { const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'), base64: base64,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users'); const contractsUser = new Parse.Query('contracts_Users');
@@ -1,5 +1,5 @@
import axios from 'axios'; 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); const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function createTemplatewithCoordinate(request, response) { export default async function createTemplatewithCoordinate(request, response) {
@@ -34,17 +34,22 @@ export default async function createTemplatewithCoordinate(request, response) {
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let fileUrl; let fileUrl;
if (request.files?.[0]) { if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, { const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'), base64: base64,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const file = new Parse.File(`${name}.pdf`, { const file = new Parse.File(`${name}.pdf`, {
base64: base64File, base64: base64File,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users'); const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr); contractsUser.equalTo('UserId', userPtr);
@@ -36,17 +36,22 @@ export default async function draftDocument(request, response) {
if (signers && signers.length > 0) { if (signers && signers.length > 0) {
let fileUrl; let fileUrl;
if (request.files?.[0]) { if (request.files?.[0]) {
const base64 = fileData?.toString('base64');
const file = new Parse.File(request.files?.[0]?.originalname, { const file = new Parse.File(request.files?.[0]?.originalname, {
base64: fileData?.toString('base64'), base64: base64,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} else { } else {
const file = new Parse.File(`${name}.pdf`, { const file = new Parse.File(`${name}.pdf`, {
base64: base64File, base64: base64File,
}); });
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
fileUrl = file.url(); fileUrl = file.url();
const buffer = Buffer.from(base64File, 'base64');
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
} }
const contractsUser = new Parse.Query('contracts_Users'); const contractsUser = new Parse.Query('contracts_Users');
contractsUser.equalTo('UserId', userPtr); contractsUser.equalTo('UserId', userPtr);
+8 -4
View File
@@ -1,5 +1,5 @@
import PDF from './parsefunction/pdf/PDF.min.js'; 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 GoogleSign from './parsefunction/GoogleSign.js';
import ZohoDetails from './parsefunction/ZohoDetails.js'; import ZohoDetails from './parsefunction/ZohoDetails.js';
import usersignup from './parsefunction/usersignup.js'; import usersignup from './parsefunction/usersignup.js';
@@ -10,7 +10,6 @@ import DocumentAftersave from './parsefunction/DocumentAftersave.js';
import ContactbookAftersave from './parsefunction/ContactBookAftersave.js'; import ContactbookAftersave from './parsefunction/ContactBookAftersave.js';
import ContractUsersAftersave from './parsefunction/ContractUsersAftersave.js'; import ContractUsersAftersave from './parsefunction/ContractUsersAftersave.js';
import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js'; import sendMailOTPv1 from './parsefunction/SendMailOTPv1.js';
import SendMailv1 from './parsefunction/SendMailv1.js';
import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js'; import AuthLoginAsMail from './parsefunction/AuthLoginAsMail.js';
import getUserId from './parsefunction/getUserId.js'; import getUserId from './parsefunction/getUserId.js';
import getUserDetails from './parsefunction/getUserDetails.js'; import getUserDetails from './parsefunction/getUserDetails.js';
@@ -23,17 +22,19 @@ import TemplateAfterSave from './parsefunction/TemplateAfterSave.js';
import GetTemplate from './parsefunction/GetTemplate.js'; import GetTemplate from './parsefunction/GetTemplate.js';
import savewebhook from './parsefunction/saveWebhook.js'; import savewebhook from './parsefunction/saveWebhook.js';
import callWebhook from './parsefunction/callWebhook.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('AddUserToRole', addUserToGroups);
Parse.Cloud.define('UserGroups', getUserGroups); Parse.Cloud.define('UserGroups', getUserGroups);
Parse.Cloud.define('signPdf', PDF); Parse.Cloud.define('signPdf', PDF);
Parse.Cloud.define('sendmailv3', sendmail); Parse.Cloud.define('sendmailv3', sendmailv3);
Parse.Cloud.define('googlesign', GoogleSign); Parse.Cloud.define('googlesign', GoogleSign);
Parse.Cloud.define('zohodetails', ZohoDetails); Parse.Cloud.define('zohodetails', ZohoDetails);
Parse.Cloud.define('usersignup', usersignup); Parse.Cloud.define('usersignup', usersignup);
Parse.Cloud.define('facebooksign', FacebookSign); Parse.Cloud.define('facebooksign', FacebookSign);
Parse.Cloud.define('SendOTPMailV1', sendMailOTPv1); Parse.Cloud.define('SendOTPMailV1', sendMailOTPv1);
Parse.Cloud.define('sendmail', SendMailv1);
Parse.Cloud.define('AuthLoginAsMail', AuthLoginAsMail); Parse.Cloud.define('AuthLoginAsMail', AuthLoginAsMail);
Parse.Cloud.define('getUserId', getUserId); Parse.Cloud.define('getUserId', getUserId);
Parse.Cloud.define('getUserDetails', getUserDetails); Parse.Cloud.define('getUserDetails', getUserDetails);
@@ -45,7 +46,10 @@ Parse.Cloud.define('getapitoken', getapitoken);
Parse.Cloud.define('getTemplate', GetTemplate); Parse.Cloud.define('getTemplate', GetTemplate);
Parse.Cloud.define('savewebhook', savewebhook); Parse.Cloud.define('savewebhook', savewebhook);
Parse.Cloud.define('callwebhook', callWebhook); Parse.Cloud.define('callwebhook', callWebhook);
Parse.Cloud.define('freesubscription', SubscribeFree);
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave); Parse.Cloud.afterSave('contracts_Contactbook', ContactbookAftersave);
Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave); Parse.Cloud.afterSave('contracts_Users', ContractUsersAftersave);
Parse.Cloud.afterSave('contracts_Template', TemplateAfterSave); 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) { async function sendMailOTPv1(request) {
try { try {
//--for elearning app side //--for elearning app side
let code = Math.floor(1000 + Math.random() * 9000); let code = Math.floor(1000 + Math.random() * 9000);
let email = request.params.email; let email = request.params.email;
var TenantId = request.params.TenantId ? request.params.TenantId : undefined; var TenantId = request.params.TenantId ? request.params.TenantId : undefined;
// console.log("In tempSendOTPv2");
// console.log(JSON.stringify(request));
if (email) { if (email) {
axios({ const recipient = request.params.email;
method: 'POST', const mailsender = process.env.SMTP_ENABLE
url: process.env.SERVER_URL + '/functions/sendmail', ? process.env.SMTP_USER_EMAIL
headers: { : process.env.MAILGUN_SENDER;
'Content-Type': 'application/json', try {
'X-Parse-Application-Id': process.env.APP_ID, await Parse.Cloud.sendEmail({
}, from: 'Opensign™' + ' <' + mailsender + '>',
params: { recipient: recipient,
otp: code, subject: 'Your OpenSign™ OTP',
email: email, text: 'This email is a test.',
TenantId: TenantId, 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'>" +
}).then( code +
function (httpResponse) {}, '</p></div> </div> </div></body></html>',
function (httpResponse) { });
console.error('sms Request failed with response code ' + httpResponse.status); console.log('OTP sent');
return Promise.reject('sms Request failed with response code ' + httpResponse.status); 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'); const tempOtp = new Parse.Query('defaultdata_Otp');
tempOtp.equalTo('Email', email); tempOtp.equalTo('Email', email);
const resultOTP = await tempOtp.first({ useMasterKey: true }); const resultOTP = await tempOtp.first({ useMasterKey: true });
console.log('resultOTP', resultOTP); // console.log('resultOTP', resultOTP);
if (resultOTP !== undefined) { if (resultOTP !== undefined) {
const updateOtpQuery = new Parse.Query('defaultdata_Otp'); const updateOtpQuery = new Parse.Query('defaultdata_Otp');
const updateOtp = await updateOtpQuery.get(resultOTP.id, { const updateOtp = await updateOtpQuery.get(resultOTP.id, {
useMasterKey: true, useMasterKey: true,
}); });
updateOtp.set('OTP', code); updateOtp.set('OTP', code);
const updateRes = updateOtp.save(null, { useMasterKey: true }); updateOtp.save(null, { useMasterKey: true });
// console.log("update otp Res in tempSendOtp ", updateRes); // console.log("update otp Res in tempSendOtp ", updateRes);
} else { } else {
const otpClass = Parse.Object.extend('defaultdata_Otp'); const otpClass = Parse.Object.extend('defaultdata_Otp');
@@ -49,17 +67,17 @@ async function sendMailOTPv1(request) {
newOtpQuery.set('OTP', code); newOtpQuery.set('OTP', code);
newOtpQuery.set('Email', email); newOtpQuery.set('Email', email);
newOtpQuery.set('TenantId', TenantId); 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); // console.log("new otp Res in tempSendOtp ", newRes);
} }
return 'Otp send'; return 'Otp send';
} else { } else {
return Promise.reject('Please Enter valid email'); return 'Please Enter valid email';
} }
} catch (err) { } catch (err) {
console.log('err in sendMailOTPv1'); console.log('err in sendMailOTPv1');
console.log(err); console.log(err);
return Promise.reject(err); return err;
} }
} }
export default sendMailOTPv1; 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 axios from 'axios';
import { pdflibAddPlaceholder } from './customSignPdf/pdflibplaceholder.min.js'; import { pdflibAddPlaceholder } from './customSignPdf/pdflibplaceholder.min.js';
import { PDFDocument } from 'pdf-lib'; import { PDFDocument } from 'pdf-lib';
import { saveFileUsage } from '../../../Utils.js';
const serverUrl = process.env.SERVER_URL, const serverUrl = process.env.SERVER_URL,
APPID = process.env.APP_ID, APPID = process.env.APP_ID,
masterKEY = process.env.MASTER_KEY; masterKEY = process.env.MASTER_KEY;
@@ -16,20 +17,20 @@ async function uploadFile(e, a) {
console.log('Err ', e), fs.unlinkSync(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 { try {
var d = { var d = {
UserPtr: { __type: 'Pointer', className: n, objectId: r }, UserPtr: { __type: 'Pointer', className: n, objectId: r },
SignedUrl: s, SignedUrl: s,
Activity: 'Signed', Activity: 'Signed',
ipAddress: i, ipAddress: o,
}; };
let e; 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 e => 'Signed' === e.Activity
); );
let a = !1; 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 }; var c = { SignedUrl: s, AuditTrail: e, IsCompleted: a };
await axios.put(serverUrl + '/classes/contracts_Document/' + t, c, { await axios.put(serverUrl + '/classes/contracts_Document/' + t, c, {
headers: { headers: {
@@ -43,39 +44,16 @@ async function updateDoc(t, s, r, i, o, n) {
return console.log('update doc err ', e), 'err'; 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) { async function sendCompletedMail(e) {
var a = e.url, var a = e.url,
t = e.sender, t = e.sender,
s = e.pdfName, s = e.pdfName,
a = { r = e.receiver,
e = {
extUserId: e.extUserId,
url: a, url: a,
from: 'OpenSign™', from: 'OpenSign™',
recipient: e.receiver, recipient: r,
subject: `Document ${s} has been signed by all parties`, subject: `Document ${s} has been signed by all parties`,
pdfName: s, pdfName: s,
html: html:
@@ -85,7 +63,7 @@ async function sendCompletedMail(e) {
t.Mail + 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>', ' 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: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID, 'X-Parse-Application-Id': APPID,
@@ -155,7 +133,7 @@ async function PDF(i) {
try { try {
var e = i.params.docId, var e = i.params.docId,
a = i.params.userId, a = i.params.userId,
o = await axios.get( n = await axios.get(
serverUrl + '/classes/contracts_Document/' + e + '?include=ExtUserPtr,Signers', serverUrl + '/classes/contracts_Document/' + e + '?include=ExtUserPtr,Signers',
{ {
headers: { headers: {
@@ -165,142 +143,137 @@ async function PDF(i) {
}, },
} }
), ),
t = await axios.get(serverUrl + '/users/me', { d = await axios.get(serverUrl + '/users/me', {
headers: { headers: {
'X-Parse-Application-Id': APPID, 'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken, '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, var t,
d, s,
l, l,
c = JSON.stringify({ objectId: a }); c = JSON.stringify({ objectId: a });
let s, r; let r, o;
r = a o = a
? (n = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + c, { ? (t = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + c, {
headers: { headers: {
'X-Parse-Application-Id': APPID, 'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken, 'X-Parse-Session-Token': i.headers.sessiontoken,
}, },
})).data && 0 < n.data.results.length })).data && 0 < t.data.results.length
? ((s = n), 'contracts_Contactbook') ? ((r = t), 'contracts_Contactbook')
: ((s = await axios.get(serverUrl + '/classes/contracts_Users?where=' + c, { : ((r = await axios.get(serverUrl + '/classes/contracts_Users?where=' + c, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY }, headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
})), })),
'contracts_Users') 'contracts_Users')
: ((d = JSON.stringify({ : ((s = JSON.stringify({
UserId: { __type: 'Pointer', className: '_User', objectId: t.data.objectId }, 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 }, headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
})).data && 0 < l.data.results.length })).data && 0 < l.data.results.length
? ((s = l), 'contracts_Users') ? ((r = l), 'contracts_Users')
: ((s = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + d, { : ((r = await axios.get(serverUrl + '/classes/contracts_Contactbook?where=' + s, {
headers: { headers: {
'X-Parse-Application-Id': APPID, 'X-Parse-Application-Id': APPID,
'X-Parse-Session-Token': i.headers.sessiontoken, 'X-Parse-Session-Token': i.headers.sessiontoken,
}, },
})), })),
'contracts_Contactbook')); 'contracts_Contactbook'));
var p = s.data.results[0].Name, var p = r.data.results[0].Name,
m = s.data.results[0].Email; m = r.data.results[0].Email;
if (!i.params.pdfFile) return { status: 'error', message: 'pdf file not present!' }; if (!i.params.pdfFile) return { status: 'error', message: 'pdf file not present!' };
{ {
let e = Buffer.from(i.params.pdfFile, 'base64'); let e = Buffer.from(i.params.pdfFile, 'base64');
var g = process.env.PFX_BASE64, var g = process.env.PFX_BASE64,
h = Buffer.from(g, 'base64'), u = Buffer.from(g, 'base64'),
u = { h = {
UserPtr: { __type: 'Pointer', className: r, objectId: s.data.results[0].objectId }, UserPtr: { __type: 'Pointer', className: o, objectId: r.data.results[0].objectId },
SignedUrl: '', SignedUrl: '',
Activity: 'Signed', Activity: 'Signed',
ipAddress: i.headers['x-real-ip'], ipAddress: i.headers['x-real-ip'],
}; };
let a; let a;
var f = (a = var f = (a =
o.data.AuditTrail && 0 < o.data.AuditTrail.length n.data.AuditTrail && 0 < n.data.AuditTrail.length
? [...o.data.AuditTrail, u] ? [...n.data.AuditTrail, h]
: [u]).filter(e => 'Signed' === e.Activity); : [h]).filter(e => 'Signed' === e.Activity);
let t = !1; 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) !(t = !0)
); );
var y, var P,
P, y,
v, v,
b,
x,
w,
U, U,
b,
I, I,
S, S = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`,
A = `exported_file_${Math.floor(5e3 * Math.random())}.pdf`, x = './exports/' + S;
D = './exports/' + A, let s = e.length;
E = s = (
(t t
? ((y = o.data.Signers?.map(e => e.Name + ' <' + e.Email + '>')), ? ((P = n.data.Signers?.map(e => e.Name + ' <' + e.Email + '>')),
(e = (e =
y && 0 < y.length P && 0 < P.length
? ((P = await PDFDocument.load(e)), ? ((y = await PDFDocument.load(e)),
pdflibAddPlaceholder({ pdflibAddPlaceholder({
pdfDoc: P, pdfDoc: y,
reason: 'Digitally signed by OpenSign for ' + y?.join(', '), reason: 'Digitally signed by OpenSign for ' + P?.join(', '),
location: 'location', location: 'location',
signatureLength: 15e3, signatureLength: 15e3,
}), }),
(v = await P.save()), (v = await y.save()),
Buffer.from(v)) Buffer.from(v))
: ((b = await PDFDocument.load(e)), : ((U = await PDFDocument.load(e)),
pdflibAddPlaceholder({ pdflibAddPlaceholder({
pdfDoc: b, pdfDoc: U,
reason: 'Digitally signed by OpenSign for ' + p + ' <' + m + '>', reason: 'Digitally signed by OpenSign for ' + p + ' <' + m + '>',
location: 'location', location: 'location',
signatureLength: 15e3, signatureLength: 15e3,
}), }),
(x = await b.save()), (b = await U.save()),
Buffer.from(x))), Buffer.from(b))),
(w = await new SignPDF(e, h).signPDF()), (I = await new SignPDF(e, u).signPDF()),
fs.writeFileSync(D, w)) fs.writeFileSync(x, I),
: fs.writeFileSync(D, e), I)
await uploadFile(A, D)); : (fs.writeFileSync(x, e), e)
if (E && E.imageUrl) ).length;
var A,
w,
D = await uploadFile(S, x);
if (D && D.imageUrl)
return ( return (
(U = await updateDoc( (A = await updateDoc(
i.params.docId, i.params.docId,
E.imageUrl, D.imageUrl,
s.data.results[0].objectId, r.data.results[0].objectId,
i.headers['x-real-ip'], i.headers['x-real-ip'],
o.data, n.data,
r o
)), )),
(I = { sendDoctoWebhook(n, D.imageUrl, 'signed', r?.data.results?.[0]),
url: E.imageUrl, saveFileUsage(s, D.imageUrl, d.data.objectId),
sender: { Mail: o.data.ExtUserPtr.Email, Name: o.data.ExtUserPtr.Name }, A &&
pdfName: o.data.Name, A.isCompleted &&
receiver: m, ((w = {
}), url: D.imageUrl,
o.data.IsSendMail && !1 === o.data.IsSendMail sender: { Mail: n.data.ExtUserPtr.Email, Name: 'OpenSign™' },
? console.log("don't send mail") pdfName: n.data.Name,
: sendMail(I), receiver: n.data.ExtUserPtr.Email,
sendDoctoWebhook(o, E.imageUrl, 'signed', s?.data.results?.[0]), extUserId: n.data.ExtUserPtr.objectId,
U &&
U.isCompleted &&
((S = {
url: E.imageUrl,
sender: { Mail: o.data.ExtUserPtr.Email, Name: 'OpenSign™' },
pdfName: o.data.Name,
receiver: o.data.ExtUserPtr.Email,
}), }),
o.data.IsSendMail && !1 === o.data.IsSendMail n.data.IsSendMail && !1 === n.data.IsSendMail
? console.log("don't send mail") ? console.log("don't send mail")
: sendCompletedMail(S), : sendCompletedMail(w),
sendDoctoWebhook(o, E.imageUrl, 'completed')), sendDoctoWebhook(n, D.imageUrl, 'completed')),
fs.unlinkSync(D), fs.unlinkSync(x),
console.log('New Signed PDF created called: ' + D), console.log('New Signed PDF created called: ' + x),
'success' === U.message 'success' === A.message
? { status: 'success', data: E.imageUrl } ? { status: 'success', data: D.imageUrl }
: { status: 'error', message: 'please provide required parameters!' } : { status: 'error', message: 'please provide required parameters!' }
); );
} }
@@ -3,8 +3,9 @@ import https from 'https';
import formData from 'form-data'; import formData from 'form-data';
import Mailgun from 'mailgun.js'; import Mailgun from 'mailgun.js';
import { createTransport } from 'nodemailer'; import { createTransport } from 'nodemailer';
import { updateMailCount } from '../../Utils.js';
async function sendmail(req) { async function sendmailv3(req) {
try { try {
let transporterSMTP; let transporterSMTP;
let mailgunClient; let mailgunClient;
@@ -77,6 +78,9 @@ async function sendmail(req) {
const res = await transporterSMTP.sendMail(messageParams); const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res); console.log('Res ', res);
if (!res.err) { if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return { return {
status: 'success', status: 'success',
}; };
@@ -85,6 +89,9 @@ async function sendmail(req) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams); const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res); console.log('Res ', res);
if (res.status === 200) { if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return { return {
status: 'success', status: 'success',
}; };
@@ -109,6 +116,9 @@ async function sendmail(req) {
const res = await transporterSMTP.sendMail(messageParams); const res = await transporterSMTP.sendMail(messageParams);
console.log('Res ', res); console.log('Res ', res);
if (!res.err) { if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return { return {
status: 'success', status: 'success',
}; };
@@ -117,6 +127,9 @@ async function sendmail(req) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams); const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('Res ', res); console.log('Res ', res);
if (res.status === 200) { if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId);
}
return { return {
status: 'success', status: 'success',
}; };
@@ -124,11 +137,11 @@ async function sendmail(req) {
} }
} }
} catch (err) { } catch (err) {
console.log('err ', err); console.log('err in sendmailv3', err);
if (err) { if (err) {
return { status: 'error' }; 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'); 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 = { export const config = {
databaseURI: databaseURI:
process.env.DATABASE_URI || process.env.MONGODB_URI || 'mongodb://localhost:27017/dev', process.env.DATABASE_URI || process.env.MONGODB_URI || 'mongodb://localhost:27017/dev',
@@ -102,7 +104,7 @@ export const config = {
verifyUserEmails: isMailAdapter === true ? true : false, verifyUserEmails: isMailAdapter === true ? true : false,
publicServerURL: process.env.SERVER_URL || 'http://localhost:8080/app', 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. // Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'Open Sign', appName: 'Opensign',
allowClientClassCreation: false, allowClientClassCreation: false,
allowExpiredAuthDataToken: false, allowExpiredAuthDataToken: false,
encodeParseObjectInCloudFunction: true, encodeParseObjectInCloudFunction: true,
@@ -112,9 +114,7 @@ export const config = {
module: 'parse-server-api-mail-adapter', module: 'parse-server-api-mail-adapter',
options: { options: {
// The email address from which emails are sent. // The email address from which emails are sent.
sender: process.env.SMTP_ENABLE sender: 'Opensign™' + ' <' + mailsender + '>',
? process.env.SMTP_USER_EMAIL
: process.env.MAILGUN_SENDER,
// The email templates. // The email templates.
templates: { templates: {
// The template used by Parse Server to send an email for password // The template used by Parse Server to send an email for password
@@ -147,6 +147,14 @@ export const config = {
google: { google: {
enabled: true, 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 // Client-keys like the javascript key or the .NET key are not necessary with parse-server