Merge pull request #1422 from OpenSignLabs/validation

feat: add functionality to enable/disable signature types
This commit is contained in:
prafull-opensignlabs
2024-11-07 14:15:27 +05:30
committed by GitHub
28 changed files with 1300 additions and 677 deletions
@@ -94,6 +94,7 @@
"My Signature": "My Signature",
"API Token": "API Token",
"Webhook": "Webhook",
"Preferences": "Preferences",
"Teams": "Teams",
"Team-update-successfully": "Team Updated successfully.",
"Users": "Users"
@@ -684,6 +685,7 @@
"add-user-template": "You need to add a role before you can add fields for it.",
"pdf-uncompatible": "This pdf is not compatible with opensign, please contact <support@opensignlabs.com>",
"text-field-tour":"Fields of type 'Text' must be filled in advance before the document is sent. If you need the signers to provide input, use the 'Text Input' field instead.",
"attach-signer-tour":"You need to attach a Signer to every role. You can do that by clicking this icon. Once you select a Signer it will be attached to all the fields associated with that role which appear in the same colour."
"attach-signer-tour":"You need to attach a Signer to every role. You can do that by clicking this icon. Once you select a Signer it will be attached to all the fields associated with that role which appear in the same colour.",
"allowed-signature-types": "Allowed signature types",
"at-least-one-signature-type": "At least one signature type should be enabled."
}
@@ -94,6 +94,7 @@
"My Signature": "Mi firma",
"API Token": "Token API",
"Webhook": "Webhook",
"Preferences": "Preferencias",
"Teams": "Equipos",
"Team-update-successfully": "Equipo actualizado correctamente.",
"Users": "Usuarios"
@@ -684,6 +685,7 @@
"form-title-1": "Configuración del flujo de documentos",
"form-title-2": "Configuración de seguridad",
"text-field-tour":"Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
"attach-signer-tour" :"Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color."
"attach-signer-tour" :"Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
"allowed-signature-types": "Tipos de firma permitidos",
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma."
}
@@ -94,6 +94,7 @@
"My Signature": "Ma signature",
"API Token": "Jeton API",
"Webhook": "Webhook",
"Preferences": "Préférences",
"Teams": "Équipe",
"Team-update-successfully": "Équipe mise à jour avec succès.",
"Users": "Utilisateurs"
@@ -683,7 +684,7 @@
"add-user-template":"Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
"pdf-uncompatible":"Ce pdf n'est pas compatible avec opensign, veuillez contacter <support@opensignlabs.com>",
"text-field-tour" :"Les champs de type 'Texte' doivent être remplis à l'avance avant l'envoi du document. Si vous avez besoin que les signataires fournissent des informations, utilisez plutôt le champ 'Saisie de texte'.",
"attach-signer-tour" :"Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur."
"attach-signer-tour" :"Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur.",
"allowed-signature-types": "Types de signature autorisés",
"at-least-one-signature-type": "Au moins un type de signature doit être activé."
}
+5
View File
@@ -36,6 +36,7 @@ const Webhook = lazy(() => import("./pages/Webhook"));
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
const DraftTemplate = lazy(() => import("./pages/DraftTemplate"));
const Preferences = lazy(() => import("./pages/Preferences"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
@@ -199,6 +200,10 @@ function App() {
</>
)}
<Route path="/users" element={<UserList />} />
<Route
path="/preferences"
element={<LazyPage Page={Preferences} />}
/>
</Route>
<Route path="/sso" element={<SSOVerify />} />
<Route
+5 -1
View File
@@ -258,7 +258,11 @@ const BulkSendUi = (props) => {
: [...existSigner]
});
} else {
Documents.push({ ...props.item, Placeholders: updatedPlaceholders });
Documents.push({
...props.item,
Placeholders: updatedPlaceholders,
SignatureType: props.signatureType
});
}
});
await batchQuery(Documents);
@@ -1,16 +1,16 @@
import React from "react";
import { useTranslation } from "react-i18next";
function DefaultSignature({ defaultSignImg, xyPostion, setDefaultSignAlert }) {
function DefaultSignature(props) {
const { t } = useTranslation();
const confirmToaddDefaultSign = () => {
if (xyPostion.length > 0) {
setDefaultSignAlert({
if (props?.xyPostion.length > 0) {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("default-sign-alert")
});
} else {
setDefaultSignAlert({
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
});
@@ -22,22 +22,32 @@ function DefaultSignature({ defaultSignImg, xyPostion, setDefaultSignAlert }) {
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
{t("signature")}
</div>
<div className="flex flex-col items-center mt-[10px] font-semibold">
<div
className={`${
props?.isDefault ? "" : "bg-black/70"
} flex flex-col items-center mt-[10px] font-semibold relative`}
>
<p className="text-base-content">{t("Your-Signature")}</p>
<div className="op-card shadow-md h-[111px] w-[90%] p-2">
<img
alt="signature"
className="w-full h-full object-contain"
src={defaultSignImg}
src={props?.defaultSignImg}
/>
</div>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mt-[10px]"
onClick={() => confirmToaddDefaultSign()}
disabled={!props?.isDefault}
>
{t("auto-sign-all")}
</button>
{!props.isDefault && (
<div className="absolute text-white w-full h-full flex items-center justify-center -rotate-45 text-[11px] cursor-default">
Document creator has disabled this option
</div>
)}
</div>
</div>
);
@@ -445,17 +445,13 @@ function Placeholder(props) {
handleOnClickSettingIcon();
}}
className="fa-light fa-gear icon"
style={{
color: "#188ae2",
right: "29px",
top: "-19px"
}}
style={{ color: "#188ae2", right: "29px", top: "-19px" }}
></i>
) : (
/* condition to add setting icon for placeholder & template flow for all widgets except signature and date */
((!props?.pos?.type && props.pos.isStamp) ||
(props?.pos?.type &&
!["date", "signature"].includes(props.pos.type) &&
!["date"].includes(props.pos.type) &&
!props.isSignYourself)) && (
<i
onClick={(e) => {
@@ -469,7 +465,7 @@ function Placeholder(props) {
className="fa-light fa-gear icon"
style={{
color: "#188ae2",
right: props?.pos?.type === textWidget ? "32px" : "47px",
right: props?.pos?.type === textWidget ? "32px" : "51px",
top: "-19px"
}}
></i>
+417 -359
View File
@@ -1,39 +1,20 @@
import React, { useRef, useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import SignatureCanvas from "react-signature-canvas";
function SignPad({
isSignPad,
isStamp,
setIsImageSelect,
onSaveSign,
setIsSignPad,
setImage,
isImageSelect,
imageRef,
onImageChange,
onSaveImage,
image,
defaultSign,
setSignature,
myInitial,
isInitial,
setIsInitial,
setIsStamp,
widgetType,
currWidgetsDetails,
setCurrWidgetsDetails
}) {
function SignPad(props) {
const { t } = useTranslation();
const [penColor, setPenColor] = useState("blue");
const allColor = ["blue", "red", "black"];
const canvasRef = useRef(null);
const [isDefaultSign, setIsDefaultSign] = useState(false);
const [isTab, setIsTab] = useState("draw");
const [isTab, setIsTab] = useState("");
const [isSignImg, setIsSignImg] = useState("");
const [signValue, setSignValue] = useState("");
const [textWidth, setTextWidth] = useState(0);
const [textHeight, setTextHeight] = useState(0);
const [signatureType, setSignatureType] = useState("draw");
const [signatureType, setSignatureType] = useState("");
const [isSignTypes, setIsSignTypes] = useState(true);
const fontOptions = [
{ value: "Fasthand" },
{ value: "Dancing Script" },
@@ -53,24 +34,56 @@ function SignPad({
const jsonSender = JSON.parse(senderUser);
const currentUserName = jsonSender && jsonSender?.name;
useEffect(() => {
handleTab();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function handleTab() {
const signtypes = props?.signatureTypes || [];
const index = signtypes?.findIndex((x) => x.enabled === true);
if (index !== -1) {
const tab = props?.signatureTypes[index].name;
if (tab === "draw") {
setIsTab("draw");
setSignatureType("draw");
} else if (tab === "upload") {
props?.setIsImageSelect(true);
setIsTab("uploadImage");
} else if (tab === "typed") {
setIsTab("type");
} else if (tab === "default") {
setIsTab("mysignature");
} else {
setIsTab(true);
}
} else {
setIsSignTypes(false);
}
}
function isTabEnabled(tabName) {
const isEnabled = props?.signatureTypes.find(
(x) => x.name === tabName
)?.enabled;
return isEnabled;
}
//function for clear signature image
const handleClear = () => {
setCurrWidgetsDetails({});
props?.setCurrWidgetsDetails({});
if (isTab === "draw") {
if (canvasRef.current) {
canvasRef.current.clear();
} else if (isStamp) {
setImage("");
} else if (props?.isStamp) {
props?.setImage("");
}
setIsSignImg("");
} else if (isTab === "uploadImage") {
setImage("");
props?.setImage("");
}
// setIsInitial(false);
};
//function for set signature url
const handleSignatureChange = (data) => {
setSignature(data);
props?.setSignature(data);
setIsSignImg(data);
};
//save button component
@@ -88,58 +101,57 @@ function SignPad({
)}
<button
onClick={() => {
setCurrWidgetsDetails({});
if (!image) {
props?.setCurrWidgetsDetails({});
if (!props?.image) {
if (isTab === "mysignature") {
setIsSignImg("");
if (isInitial) {
onSaveSign(signatureType, "initials");
if (props?.isInitial) {
props?.onSaveSign(signatureType, "initials");
} else {
onSaveSign(null, "default");
props?.onSaveSign(null, "default");
}
} else {
if (isTab === "type") {
setIsSignImg("");
onSaveSign(
props?.onSaveSign(
null,
false,
!isInitial && textWidth > 150 ? 150 : textWidth,
!isInitial && textHeight > 35 ? 35 : textHeight
!props?.isInitial && textWidth > 150 ? 150 : textWidth,
!props?.isInitial && textHeight > 35 ? 35 : textHeight
);
} else {
setIsSignImg("");
canvasRef.current.clear();
onSaveSign(signatureType);
props?.onSaveSign(signatureType);
}
}
setPenColor("blue");
} else {
setIsSignImg("");
onSaveImage(signatureType);
props?.onSaveImage(signatureType);
}
setIsSignPad(false);
setIsInitial(false);
setIsImageSelect(false);
props?.setIsSignPad(false);
props?.setIsInitial(false);
props?.setIsImageSelect(false);
setIsDefaultSign(false);
setImage();
setIsTab("draw");
props?.setImage();
handleTab();
setSignValue("");
setIsStamp(false);
props?.setIsStamp(false);
}}
type="button"
className={`${
isSignImg || image || isDefaultSign || textWidth
isSignImg || props?.image || isDefaultSign || textWidth
? ""
: "pointer-events-none"
} op-btn op-btn-primary shadow-lg`}
disabled={
(isTab === "draw" && isSignImg) ||
(isTab === "image" && image) ||
(isTab === "image" && props?.image) ||
(isTab === "mysignature" && isDefaultSign) ||
(isTab === "type" && signValue)
? false
: image
: props?.image
? false
: true
}
@@ -151,12 +163,12 @@ function SignPad({
};
//useEffect for set already draw or save signature url/text url of signature text type and draw type for initial type and signature type widgets
useEffect(() => {
if (currWidgetsDetails && canvasRef.current) {
const isWidgetType = currWidgetsDetails?.type;
const signatureType = currWidgetsDetails?.signatureType;
const url = currWidgetsDetails?.SignUrl;
if (props?.currWidgetsDetails && canvasRef.current) {
const isWidgetType = props?.currWidgetsDetails?.type;
const signatureType = props?.currWidgetsDetails?.signatureType;
const url = props?.currWidgetsDetails?.SignUrl;
//checking widget type and draw type signature url
if (isInitial) {
if (props?.isInitial) {
if (isWidgetType === "initials" && signatureType === "draw" && url) {
canvasRef.current.fromDataURL(url);
}
@@ -171,12 +183,12 @@ function SignPad({
const trimmedName = currentUserName && currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = isInitial ? firstCharacter : currentUserName;
const userName = props?.isInitial ? firstCharacter : currentUserName;
setSignValue(userName || "");
setFontSelect("Fasthand");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSignPad]);
}, [props.isSignPad]);
useEffect(() => {
const loadFont = async () => {
try {
@@ -204,7 +216,7 @@ function SignPad({
? signValue?.trim()
: currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = isInitial ? firstCharacter : signValue;
const userName = props?.isInitial ? firstCharacter : signValue;
setSignValue(userName);
convertToImg(fontSelect, userName);
}
@@ -233,7 +245,7 @@ function SignPad({
// Draw the text content on the canvas
const ctx = canvasElement.getContext("2d");
const pixelRatio = window.devicePixelRatio || 1;
const addExtraWidth = isInitial ? 10 : 50;
const addExtraWidth = props?.isInitial ? 10 : 50;
const width = span.offsetWidth + addExtraWidth;
const height = span.offsetHeight;
setTextWidth(width);
@@ -255,7 +267,7 @@ function SignPad({
document.body.removeChild(span);
// Convert the canvas to image data
const dataUrl = canvasElement.toDataURL("image/png");
setSignature(dataUrl);
props?.setSignature(dataUrl);
};
const PenColorComponent = (props) => {
@@ -297,319 +309,365 @@ function SignPad({
</div>
);
};
// `handleCancelBtn` function trigger when user click on cross button
const handleCancelBtn = () => {
setPenColor("blue");
props?.setIsSignPad(false);
props?.setIsInitial(false);
props?.setIsImageSelect(false);
setIsDefaultSign(false);
props?.setImage();
handleTab();
setSignValue("");
props?.setIsStamp(false);
};
return (
<div>
{isSignPad && (
{props?.isSignPad && (
<div className="op-modal op-modal-open">
<div className="op-modal-box px-[13px] pt-2 pb-0">
<div className="flex justify-between text-base-content items-center">
<div className="text-[1.2rem]">
<div className="flex flex-row justify-between mt-[3px]">
<div className="flex flex-row justify-between gap-[5px] md:gap-[8px] text-[11px] md:text-base">
{isStamp ? (
<span className="text-base-content font-bold text-lg">
{widgetType === "image" ||
currWidgetsDetails?.type === "image"
? t("upload-image")
: t("upload-stamp-image")}
</span>
) : (
<>
<div>
<span
onClick={() => {
setIsDefaultSign(false);
setIsImageSelect(false);
setIsTab("draw");
setImage();
if (isSignImg) {
setSignature(isSignImg);
}
}}
className={`${
isTab === "draw"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("draw")}
{isSignTypes ? (
<>
<div className="flex justify-between text-base-content items-center">
<div className="text-[1.2rem]">
<div className="flex flex-row justify-between mt-[3px]">
<div className="flex flex-row justify-between gap-[5px] md:gap-[8px] text-[11px] md:text-base">
{props?.isStamp ? (
<span className="text-base-content font-bold text-lg">
{props?.widgetType === "image" ||
props?.currWidgetsDetails?.type === "image"
? t("upload-image")
: t("upload-stamp-image")}
</span>
</div>
<div>
<span
onClick={() => {
setIsDefaultSign(false);
setIsImageSelect(true);
setIsTab("uploadImage");
setSignatureType("");
}}
className={`${
isTab === "uploadImage"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("upload-image")}
</span>
</div>
<div>
<span
onClick={() => {
setIsDefaultSign(false);
setIsImageSelect(false);
setIsTab("type");
setSignatureType("");
setImage();
}}
className={`${
isTab === "type"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("type")}
</span>
</div>
{!isInitial && defaultSign ? (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-signature")}
</span>
</div>
) : (
isInitial &&
myInitial && (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-initials")}
</span>
</div>
)
<>
{isTabEnabled("draw") && (
<div>
<span
onClick={() => {
setIsDefaultSign(false);
props?.setIsImageSelect(false);
setIsTab("draw");
props?.setImage();
if (isSignImg) {
props?.setSignature(isSignImg);
}
}}
className={`${
isTab === "draw"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("draw")}
</span>
</div>
)}
{isTabEnabled("upload") && (
<div>
<span
onClick={() => {
setIsDefaultSign(false);
props?.setIsImageSelect(true);
setIsTab("uploadImage");
setSignatureType("");
}}
className={`${
isTab === "uploadImage"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("upload-image")}
</span>
</div>
)}
{isTabEnabled("typed") && (
<div>
<span
onClick={() => {
setIsDefaultSign(false);
props?.setIsImageSelect(false);
setIsTab("type");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "type"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("type")}
</span>
</div>
)}
{!props?.isInitial &&
props?.defaultSign &&
isTabEnabled("default") ? (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
props?.setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-signature")}
</span>
</div>
) : (
props?.isInitial &&
props?.myInitial &&
isTabEnabled("default") && (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
props?.setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-initials")}
</span>
</div>
)
)}
</>
)}
</>
)}
</div>
</div>
</div>
<div
className="text-[1.5rem] cursor-pointer"
onClick={() => {
setPenColor("blue");
setIsSignPad(false);
setIsInitial(false);
setIsImageSelect(false);
setIsDefaultSign(false);
setImage();
setIsTab("draw");
setSignatureType("draw");
setSignValue("");
setIsStamp(false);
}}
>
&times;
</div>
</div>
<div className="p-[20px] h-full">
{isDefaultSign ? (
<>
<div className="flex justify-center">
<div
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
marginBottom: 6,
cursor: "pointer"
}}
className={
isInitial ? "intialSignatureCanvas" : "signatureCanvas"
}
>
<img
alt="stamp img"
className="w-full h-full object-contain bg-white"
draggable="false"
src={isInitial ? myInitial : defaultSign}
/>
</div>
</div>
<div className="flex justify-end">
<SaveBtn />
</div>
</>
) : isImageSelect || isStamp ? (
!image ? (
<div className="flex justify-center">
<div
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
marginBottom: 6,
cursor: "pointer"
}}
className={
isInitial ? "intialSignatureCanvas" : "signatureCanvas"
}
onClick={() => imageRef.current.click()}
>
<input
type="file"
onChange={onImageChange}
className="filetype"
accept="image/png,image/jpeg"
ref={imageRef}
hidden
/>
<i className="fa-light fa-cloud-upload-alt uploadImgLogo"></i>
<div className="text-[10px]">{t("upload")}</div>
</div>
</div>
) : (
<>
<div className="flex justify-center">
<div
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
marginBottom: 6,
overflow: "hidden"
}}
className={
isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
}
>
<img
alt="print img"
ref={imageRef}
src={image.src}
draggable="false"
className=" object-contain h-full w-full"
/>
</div>
</div>
<div className="flex justify-end">
<SaveBtn />
</div>
</>
)
) : isTab === "type" ? (
<div>
<div className="flex justify-between items-center">
<span className="mr-[5px] text-[12px]">
{isInitial ? t("initial-teb") : t("signature-tab")}:
</span>
<input
maxLength={isInitial ? 3 : 30}
style={{ fontFamily: fontSelect, color: penColor }}
type="text"
className="ml-1 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-[20px]"
placeholder="Your signature"
value={signValue}
onChange={(e) => {
setSignValue(e.target.value);
convertToImg(fontSelect, e.target.value);
}}
/>
</div>
<div className="border-[1px] border-[#d6d3d3] mt-[10px] ml-[5px]">
{fontOptions.map((font, ind) => {
return (
<div
key={ind}
style={{
cursor: "pointer",
fontFamily: font.value,
backgroundColor:
fontSelect === font.value && "rgb(206 225 247)"
}}
onClick={() => {
setFontSelect(font.value);
convertToImg(font.value, signValue);
}}
>
<div
style={{
padding: "5px 10px 5px 10px",
fontSize: "20px",
color: penColor
}}
>
{signValue ? signValue : "Your signature"}
</div>
</div>
);
})}
</div>
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent convertToImg={convertToImg} />
<SaveBtn />
<div
className="text-[1.5rem] cursor-pointer"
onClick={handleCancelBtn}
>
&times;
</div>
</div>
) : (
<>
<div className="flex justify-center">
<SignatureCanvas
ref={canvasRef}
penColor={penColor}
canvasProps={{
className: isInitial
? "intialSignatureCanvas"
: "signatureCanvas",
style: {
border: "1.6px solid #007bff",
borderRadius: "2px"
}
}}
// backgroundColor="rgb(255, 255, 255)"
onEnd={() =>
handleSignatureChange(canvasRef.current?.toDataURL())
}
dotSize={1}
/>
<div className="p-[20px] h-full">
{isDefaultSign ? (
<>
<div className="flex justify-center">
<div
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
marginBottom: 6,
cursor: "pointer"
}}
className={
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
}
>
<img
alt="stamp img"
className="w-full h-full object-contain bg-white"
draggable="false"
src={
props?.isInitial
? props?.myInitial
: props?.defaultSign
}
/>
</div>
</div>
<div className="flex justify-end">
<SaveBtn />
</div>
</>
) : props?.isImageSelect || props?.isStamp ? (
!props?.image ? (
<div className="flex justify-center">
<div
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
marginBottom: 6,
cursor: "pointer"
}}
className={
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
}
onClick={() => props?.imageRef.current.click()}
>
<input
type="file"
onChange={props?.onImageChange}
className="filetype"
accept="image/png,image/jpeg"
ref={props?.imageRef}
hidden
/>
<i className="fa-light fa-cloud-upload-alt uploadImgLogo"></i>
<div className="text-[10px]">{t("upload")}</div>
</div>
</div>
) : (
<>
<div className="flex justify-center">
<div
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
marginBottom: 6,
overflow: "hidden"
}}
className={
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
}
>
<img
alt="print img"
ref={props?.imageRef}
src={props?.image.src}
draggable="false"
className=" object-contain h-full w-full"
/>
</div>
</div>
<div className="flex justify-end">
<SaveBtn />
</div>
</>
)
) : isTab === "type" ? (
<div>
<div className="flex justify-between items-center">
<span className="mr-[5px] text-[12px]">
{props?.isInitial
? t("initial-teb")
: t("signature-tab")}
:
</span>
<input
maxLength={props?.isInitial ? 3 : 30}
style={{ fontFamily: fontSelect, color: penColor }}
type="text"
className="ml-1 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-[20px]"
placeholder="Your signature"
value={signValue}
onChange={(e) => {
setSignValue(e.target.value);
convertToImg(fontSelect, e.target.value);
}}
/>
</div>
<div className="border-[1px] border-[#d6d3d3] mt-[10px] ml-[5px]">
{fontOptions.map((font, ind) => {
return (
<div
key={ind}
style={{
cursor: "pointer",
fontFamily: font.value,
backgroundColor:
fontSelect === font.value &&
"rgb(206 225 247)"
}}
onClick={() => {
setFontSelect(font.value);
convertToImg(font.value, signValue);
}}
>
<div
style={{
padding: "5px 10px 5px 10px",
fontSize: "20px",
color: penColor
}}
>
{signValue ? signValue : "Your signature"}
</div>
</div>
);
})}
</div>
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent convertToImg={convertToImg} />
<SaveBtn />
</div>
</div>
) : (
<>
<div className="flex justify-center">
<SignatureCanvas
ref={canvasRef}
penColor={penColor}
canvasProps={{
className: props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas",
style: {
border: "1.6px solid #007bff",
borderRadius: "2px"
}
}}
// backgroundColor="rgb(255, 255, 255)"
onEnd={() =>
handleSignatureChange(
canvasRef.current?.toDataURL()
)
}
dotSize={1}
/>
</div>
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent />
<SaveBtn />
</div>
</>
)}
</div>
</>
) : (
<div className="">
<div className="relative flex flex-row items-center justify-between">
<div className="text-base-content font-bold text-lg">
Signature
</div>
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent />
<SaveBtn />
<div
className="text-[1.5rem] cursor-pointer"
onClick={handleCancelBtn}
>
&times;
</div>
</>
)}
</div>
</div>
<div className="mx-3 mb-6 mt-3">
<p>{t("at-least-one-signature-type")}</p>
</div>
</div>
)}
</div>
</div>
)}
@@ -2,7 +2,11 @@ import React, { useEffect, useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import "../../styles/AddUser.css";
import RegexParser from "regex-parser";
import { textInputWidget, textWidget } from "../../constant/Utils";
import {
signatureTypes,
textInputWidget,
textWidget
} from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
import Tooltip from "../../primitives/Tooltip";
@@ -11,6 +15,7 @@ import { useTranslation } from "react-i18next";
const WidgetNameModal = (props) => {
const { t } = useTranslation();
const signTypes = props?.signatureType || signatureTypes;
const [formdata, setFormdata] = useState({
name: "",
defaultValue: "",
@@ -21,6 +26,7 @@ const WidgetNameModal = (props) => {
const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"];
const inputOpt = ["text", "email", "number"];
const [signatureType, setSignatureType] = useState([]);
useEffect(() => {
if (props.defaultdata) {
@@ -40,12 +46,22 @@ const WidgetNameModal = (props) => {
name: props.defaultdata?.options?.name || ""
});
}
if (signTypes.length > 0) {
const defaultSignatureType = signTypes || [];
setSignatureType(defaultSignatureType);
}
// eslint-disable-next-line
}, [props.defaultdata]);
const handleSubmit = (e) => {
e.preventDefault();
if (props.handleData) {
props.handleData(formdata);
if (["signature", "initials"].includes(props.defaultdata?.type)) {
const data = { ...formdata, signatureType };
props.handleData(data, props.defaultdata?.type);
} else {
props.handleData(formdata);
}
setFormdata({
name: "",
defaultValue: "",
@@ -53,6 +69,7 @@ const WidgetNameModal = (props) => {
hint: "",
textvalidate: ""
});
setSignatureType(signTypes);
}
};
const handleChange = (e) => {
@@ -102,6 +119,15 @@ const WidgetNameModal = (props) => {
}
}
}
const handleCheckboxChange = (index) => {
// Create a copy of the signatureType array
const updatedSignatureType = [...signatureType];
// Toggle the enabled value for the clicked item
updatedSignatureType[index].enabled = !updatedSignatureType[index].enabled;
// Update the state with the modified array
setSignatureType(updatedSignatureType);
};
return (
<ModalUi
isOpen={props.isOpen}
@@ -111,24 +137,30 @@ const WidgetNameModal = (props) => {
<form
onSubmit={handleSubmit}
className={`${
props.defaultdata?.type === textInputWidget ? "pt-0" : ""
props.defaultdata?.type === textInputWidget
? "pt-0"
: ["signature", "initials"].includes(props.defaultdata?.type)
? "pt-2"
: ""
} p-[20px] text-base-content`}
>
<div className="mb-[0.75rem] text-[13px]">
<label htmlFor="name">
{t("name")}
<span className="text-[red]"> *</span>
</label>
<input
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
{!["signature", "initials"].includes(props.defaultdata?.type) && (
<div className="mb-[0.75rem] text-[13px]">
<label htmlFor="name">
{t("name")}
<span className="text-[red]"> *</span>
</label>
<input
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
)}
{props.defaultdata?.type === textInputWidget && (
<>
{isEnableSubscription && (
@@ -226,30 +258,63 @@ const WidgetNameModal = (props) => {
</div>
</>
)}
<div className="mb-[0.75rem]">
<div className="flex flex-row gap-[10px] mb-[0.5rem]">
{statusArr.map((data, ind) => {
return (
<div key={ind} className="flex flex-row gap-[5px] items-center">
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
name="status"
onChange={() =>
setFormdata({ ...formdata, status: data.toLowerCase() })
}
checked={
formdata.status.toLowerCase() === data.toLowerCase()
}
/>
<div className="text-[13px] font-medium">
{t(`widget-status.${data}`)}
{!["signature", "initials"].includes(props.defaultdata?.type) && (
<div className="mb-[0.75rem]">
<div className="flex flex-row gap-[10px] mb-[0.5rem]">
{statusArr.map((data, ind) => {
return (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
name="status"
onChange={() =>
setFormdata({ ...formdata, status: data.toLowerCase() })
}
checked={
formdata.status.toLowerCase() === data.toLowerCase()
}
/>
<div className="text-[13px] font-medium">
{t(`widget-status.${data}`)}
</div>
</div>
</div>
);
})}
);
})}
</div>
</div>
</div>
)}
{props.defaultdata?.type === "signature" && (
<div className="mb-[0.75rem]">
<label htmlFor="signaturetype" className="text-[14px] mb-[0.7rem]">
{t("allowed-signature-types")}
</label>
<div className=" ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
{signatureType.map((type, i) => {
return (
<div key={i} className="flex flex-row gap-[5px] items-center">
<input
className="mr-[2px] op-checkbox op-checkbox-xs"
type="checkbox"
name="signaturetype"
onChange={() => handleCheckboxChange(i)}
checked={type.enabled}
/>
<div
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default"
title={`Enabling this allow signers to ${type.name} signature`}
>
{type.name}
</div>
</div>
);
})}
</div>
</div>
)}
{props.defaultdata?.type === textInputWidget && (
<div className="mb-[0.75rem]">
<label htmlFor="hint" className="text-[13px]">
+48 -23
View File
@@ -637,10 +637,10 @@ export const signPdfFun = async (
return { status: "error", message: "something went wrong." };
}
}
//change image width and height to 100/40 in png base64
const getNewse64 = await changeImageWH(base64Sign);
//remove suffiix of base64
const suffixbase64 = getNewse64 && getNewse64.split(",").pop();
//change image width and height to 300/120 in png base64
const imagebase64 = await changeImageWH(base64Sign);
//remove suffiix of base64 (without type)
const suffixbase64 = imagebase64 && imagebase64.split(",").pop();
const params = {
mailProvider: activeMailAdapter,
@@ -685,7 +685,6 @@ export const createDocument = async (
) => {
if (template && template.length > 0) {
const Doc = template[0];
let placeholdersArr = [];
if (placeholders?.length > 0) {
placeholdersArr = placeholders;
@@ -703,6 +702,9 @@ export const createDocument = async (
}
});
}
const SignatureType = Doc?.SignatureType
? { SignatureType: Doc?.SignatureType }
: {};
const data = {
Name: Doc.Name,
URL: pdfUrl,
@@ -727,7 +729,8 @@ export const createDocument = async (
RemindOnceInEvery: parseInt(Doc?.RemindOnceInEvery || 5),
IsEnableOTP: Doc?.IsEnableOTP || false,
IsTourEnabled: Doc?.IsTourEnabled || false,
FileAdapterId: Doc?.FileAdapterId || ""
FileAdapterId: Doc?.FileAdapterId || "",
...SignatureType
};
try {
@@ -1166,10 +1169,7 @@ export function onSaveSign(
Height: posHeight,
SignUrl: signatureImg,
signatureType: type && type,
options: {
...position.options,
response: signatureImg
}
options: { ...position.options, response: signatureImg }
};
}
return position;
@@ -1357,11 +1357,16 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
const form = pdfDoc.getForm();
const page = pages[pageNo - 1];
const images = await Promise.all(
widgetsPositionArr.map(async (url) => {
let signUrl = url.SignUrl && url.SignUrl;
if (signUrl) {
const res = await fetch(signUrl);
return res.arrayBuffer();
widgetsPositionArr.map(async (widget) => {
// `SignUrl` this is wrong nomenclature and maintain for older code in this options we save base64 of signature image from sign pad
let signbase64 = widget.SignUrl && widget.SignUrl;
if (signbase64) {
let arr = signbase64.split(","),
mime = arr[0].match(/:(.*?);/)[1];
const res = await fetch(signbase64);
const arrayBuffer = await res.arrayBuffer();
const obj = { mimetype: mime, arrayBuffer: arrayBuffer };
return obj;
}
})
);
@@ -1372,17 +1377,17 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
if (
["signature", "stamp", "initials", "image"].includes(position.type)
) {
if (position.ImageType && position.ImageType === "image/jpeg") {
img = await pdfDoc.embedJpg(images[id]);
if (images[id].mimetype === "image/png") {
img = await pdfDoc.embedPng(images[id].arrayBuffer);
} else {
img = await pdfDoc.embedPng(images[id]);
img = await pdfDoc.embedJpg(images[id].arrayBuffer);
}
} else if (!position.type) {
// to handle old widget when only stamp and signature are exists
if (position.ImageType && position.ImageType === "image/jpeg") {
img = await pdfDoc.embedJpg(images[id]);
if (images[id].mimetype === "image/png") {
img = await pdfDoc.embedPng(images[id].arrayBuffer);
} else {
img = await pdfDoc.embedPng(images[id]);
img = await pdfDoc.embedJpg(images[id].arrayBuffer);
}
}
let widgetWidth, widgetHeight;
@@ -1969,7 +1974,7 @@ export const getAppLogo = async () => {
}
};
export const getTenantDetails = async (objectId, jwttoken) => {
export const getTenantDetails = async (objectId, jwttoken, contactId) => {
try {
const url = `${localStorage.getItem("baseUrl")}functions/gettenant`;
const parseAppId = localStorage.getItem("parseAppId");
@@ -1977,7 +1982,7 @@ export const getTenantDetails = async (objectId, jwttoken) => {
const token = jwttoken
? { jwttoken: jwttoken }
: { "X-Parse-Session-Token": accesstoken };
const data = jwttoken ? {} : { userId: objectId };
const data = jwttoken ? {} : { userId: objectId, contactId: contactId };
const res = await axios.post(url, data, {
headers: {
"Content-Type": "application/json",
@@ -2572,3 +2577,23 @@ export function generateTitleFromFilename(filename) {
return "Untitled Document";
}
}
export const signatureTypes = [
{ name: "draw", enabled: true },
{ name: "typed", enabled: true },
{ name: "upload", enabled: true },
{ name: "default", enabled: true }
];
// `handleSignatureType` is used to return update signature types as per tenant or user
export async function handleSignatureType(tenantSignTypes, signatureType) {
const docSignTypes = signatureType || signatureTypes;
let updatedSignatureType = signatureType || signatureTypes;
if (tenantSignTypes?.length > 0) {
updatedSignatureType = tenantSignTypes?.map((item) => {
const match = docSignTypes.find((data) => data.name === item.name);
return match ? { ...item, enabled: match.enabled } : item;
});
}
return updatedSignatureType;
}
+8
View File
@@ -154,6 +154,14 @@ const sidebarList = [
pageType: "",
description: "",
objectId: "webhook"
},
{
icon: "fa-light fa-sliders",
title: "Preferences",
target: "_self",
pageType: "",
description: "",
objectId: "preferences"
}
]
}
+82 -24
View File
@@ -30,7 +30,10 @@ import {
handleRemoveWidgets,
handleRotateWarning,
color,
copytoData
copytoData,
getTenantDetails,
signatureTypes,
handleSignatureType
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css";
@@ -125,6 +128,7 @@ const DraftTemplate = () => {
const [isPublic, setIsPublic] = useState("");
const [isPublicErr, setIsPublicError] = useState("");
const [isAllRoleLinked, setIsAllRolelinked] = useState(false);
const [signatureType, setSignatureType] = useState([]);
const [, drop] = useDrop({
accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor),
@@ -189,9 +193,36 @@ const DraftTemplate = () => {
handleNavigation(plan);
}
}
//function to fetch tenant Details
const fetchTenantDetails = async () => {
const user = JSON.parse(
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
)
);
if (user) {
try {
const tenantDetails = await getTenantDetails(user?.objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist"));
} else if (tenantDetails) {
const signatureType = tenantDetails?.SignatureType || [];
const filterSignTypes = signatureType?.filter(
(x) => x.enabled === true
);
return filterSignTypes;
}
} catch (e) {
alert(t("user-not-exist"));
}
} else {
alert(t("user-not-exist"));
}
};
// `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
const fetchTemplate = async () => {
try {
const tenantSignTypes = await fetchTenantDetails();
const params = { templateId: templateId };
const token = jwttoken
? { jwttoken: jwttoken }
@@ -222,6 +253,12 @@ const DraftTemplate = () => {
}
if (documentData && documentData.length > 0) {
const docSignTypes = documentData?.[0]?.SignatureType || signatureTypes;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
setSignatureType(updatedSignatureType);
if (isEnableSubscription) {
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
}
@@ -290,6 +327,36 @@ const DraftTemplate = () => {
setHandleError(t("no-data-avaliable"));
setIsLoading({ isLoad: false });
}
const res = await contractUsers(jwttoken);
if (res[0] && res.length) {
const userSignatureType = res[0]?.SignatureType || signatureTypes;
const docSignTypes =
documentData?.[0]?.SignatureType || userSignatureType;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
setSignatureType(updatedSignatureType);
setSignerUserId(res[0].objectId);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRecipients = tourstatus.filter(
(data) => data.templateTour
);
if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].templateTour);
}
}
setIsLoading({ isLoad: false });
} else if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({ isLoad: false });
} else if (res.length === 0) {
setHandleError(t("no-data-avaliable"));
setIsLoading({ isLoad: false });
}
} catch (err) {
console.log("err ", err);
if (err?.response?.data?.code === 101) {
@@ -298,27 +365,6 @@ const DraftTemplate = () => {
setHandleError(t("something-went-wrong-mssg"));
}
}
const res = await contractUsers(jwttoken);
if (res[0] && res.length) {
setSignerUserId(res[0].objectId);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRecipients = tourstatus.filter(
(data) => data.templateTour
);
if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].templateTour);
}
}
setIsLoading({ isLoad: false });
} else if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({ isLoad: false });
} else if (res.length === 0) {
setHandleError(t("no-data-avaliable"));
setIsLoading({ isLoad: false });
}
};
//function for setting position after drop signature button over pdf
const addPositionOfSignature = (item, monitor) => {
@@ -694,7 +740,8 @@ const DraftTemplate = () => {
RemindOnceInEvery: parseInt(pdfDetails[0]?.RemindOnceInEvery),
NextReminderDate: pdfDetails[0]?.NextReminderDate,
IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false,
IsTourEnabled: pdfDetails[0]?.IsTourEnabled === true ? true : false
IsTourEnabled: pdfDetails[0]?.IsTourEnabled === true ? true : false,
SignatureType: signatureType
};
const baseURL = localStorage.getItem("baseUrl");
const url = `${baseURL}functions/savetemplate`;
@@ -1121,7 +1168,15 @@ const DraftTemplate = () => {
setFontColor();
};
const handleWidgetdefaultdata = (defaultdata) => {
const handleWidgetdefaultdata = (defaultdata, isSignWidget) => {
if (isSignWidget) {
const updatedPdfDetails = [...pdfDetails];
const signtypes = defaultdata.signatureType || signatureType;
updatedPdfDetails[0].SignatureType = signtypes;
// Update the SignatureType with the modified array
setPdfDetails(updatedPdfDetails);
setSignatureType(signtypes);
}
const options = ["email", "number", "text"];
let inputype;
if (defaultdata.textvalidate) {
@@ -1321,6 +1376,7 @@ const DraftTemplate = () => {
template.URL = pdfUrl;
template.Signers = signers;
template.Placeholders = signerPos;
template.SignatureType = signatureType;
const Documents = [template];
const token = jwttoken
? { jwttoken: jwttoken }
@@ -1495,6 +1551,7 @@ const DraftTemplate = () => {
) : (
<>
<BulkSendUi
signatureType={signatureType}
Placeholders={signerPos}
item={pdfDetails?.[0]}
handleClose={handleQuickSendClose}
@@ -1822,6 +1879,7 @@ const DraftTemplate = () => {
/>
</ModalUi>
<WidgetNameModal
signatureType={signatureType}
widgetName={widgetName}
defaultdata={currWidgetsDetails}
isOpen={isNameModal}
+9 -5
View File
@@ -136,10 +136,10 @@ const ManageSign = () => {
const replaceSpace = sanitizeFileName(sanitizename);
let file;
if (signature) {
file = base64StringtoFile(signature, `${replaceSpace}_sign.png`);
file = base64StringtoFile(signature, `${replaceSpace}_sign`);
} else {
if (image && !isUrl) {
file = base64StringtoFile(image, `${replaceSpace}__sign.png`);
file = base64StringtoFile(image, `${replaceSpace}__sign`);
}
}
let imgUrl;
@@ -153,12 +153,12 @@ const ManageSign = () => {
let initialFile;
if (Initials) {
initialFile = base64StringtoFile(Initials, `${replaceSpace}_sign.png`);
initialFile = base64StringtoFile(Initials, `${replaceSpace}_sign`);
} else {
if (imgInitials && !isInitialsUrl) {
initialFile = base64StringtoFile(
imgInitials,
`${replaceSpace}__sign.png`
`${replaceSpace}__sign`
);
}
}
@@ -179,14 +179,18 @@ const ManageSign = () => {
};
function base64StringtoFile(base64String, filename) {
let arr = base64String.split(","),
// type of uploaded image
mime = arr[0].match(/:(.*?);/)[1],
// decode base64
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, { type: mime });
const ext = mime.split("/").pop();
const name = `${filename}.${ext}`;
return new File([u8arr], name, { type: mime });
}
const uploadFile = async (file) => {
+56 -26
View File
@@ -33,7 +33,10 @@ import {
getDefaultSignature,
onClickZoomIn,
onClickZoomOut,
fetchUrl
fetchUrl,
signatureTypes,
handleSignatureType,
getTenantDetails
} from "../constant/Utils";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
@@ -136,6 +139,7 @@ function PdfRequestFiles(props) {
const isHeader = useSelector((state) => state.showHeader);
const divRef = useRef(null);
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [signatureType, setSignatureType] = useState([]);
const isMobile = window.innerWidth < 767;
let isGuestSignFlow = false;
@@ -302,17 +306,13 @@ function PdfRequestFiles(props) {
: [];
if (documentData && documentData[0]?.error) {
props?.setTemplateStatus &&
props?.setTemplateStatus({
status: "Invalid"
});
props?.setTemplateStatus({ status: "Invalid" });
throw new Error("error: Invalid TemplateId");
} else if (documentData && documentData.length > 0) {
if (documentData[0]?.IsPublic) {
//handle condition when someone use plan js then setTemplateStatus is not supporting
props?.setTemplateStatus &&
props?.setTemplateStatus({
status: "Success"
});
props?.setTemplateStatus({ status: "Success" });
const url =
documentData[0] &&
(documentData[0]?.SignedUrl || documentData[0]?.URL);
@@ -350,24 +350,18 @@ function PdfRequestFiles(props) {
}
setUnSignedSigners(placeholdersOrSigners);
setPdfDetails(documentData);
setIsLoading({
isLoad: false
});
setIsLoading({ isLoad: false });
} else {
props?.setTemplateStatus &&
props?.setTemplateStatus({
status: "Private"
});
setIsLoading(false);
props?.setTemplateStatus({ status: "Private" });
setIsLoading({ isLoad: false });
setHandleError(t("something-went-wrong-mssg"));
console.error("error: TemplateId is not public");
return;
}
} else {
props?.setTemplateStatus &&
props?.setTemplateStatus({
status: "Invalid"
});
props?.setTemplateStatus({ status: "Invalid" });
setIsLoading(false);
setHandleError(t("something-went-wrong-mssg"));
console.error("error: Invalid TemplateId");
@@ -384,6 +378,33 @@ function PdfRequestFiles(props) {
return;
}
};
const fetchTenantDetails = async (contactId) => {
const user = JSON.parse(
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
)
);
try {
const tenantDetails = await getTenantDetails(
user?.objectId, // userId
"", // jwttoken
contactId // contactId
);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist"));
} else if (tenantDetails) {
const signatureType = tenantDetails?.SignatureType || [];
const filterSignTypes = signatureType?.filter(
(x) => x.enabled === true
);
return filterSignTypes;
}
} catch (e) {
alert(t("user-not-exist"));
}
};
//function for get document details for perticular signer with signer'object id
const getDocumentDetails = async (docId, isNextUser) => {
try {
@@ -391,11 +412,21 @@ function PdfRequestFiles(props) {
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
const contactId = jsonSender?.objectId
? ""
: contactBookId || signerObjectId || "";
const tenantSignTypes = await fetchTenantDetails(contactId);
// `currUserId` will be contactId or extUserId
let currUserId;
//getting document details
const documentData = await contractDocument(docId);
if (documentData && documentData.length > 0) {
const docSignTypes = documentData?.[0]?.SignatureType || signatureTypes;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
setSignatureType(updatedSignatureType);
const url =
documentData[0] &&
(documentData[0]?.SignedUrl || documentData[0]?.URL);
@@ -425,7 +456,7 @@ function PdfRequestFiles(props) {
currUserId = getCurrentSigner?.objectId
? getCurrentSigner.objectId
: contactBookId || signerObjectId || "";
: contactBookId || signerObjectId || ""; //signerObjectId is contactBookId refer from public template flow
if (isEnableSubscription) {
await checkIsSubscribed(
documentData[0]?.ExtUserPtr?.objectId,
@@ -966,11 +997,7 @@ function PdfRequestFiles(props) {
const newDate = new Date(expireDate);
const localExpireDate = newDate.toLocaleDateString(
"en-US",
{
day: "numeric",
month: "long",
year: "numeric"
}
{ day: "numeric", month: "long", year: "numeric" }
);
let senderEmail = pdfDetails?.[0].ExtUserPtr.Email;
let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone;
@@ -1379,9 +1406,7 @@ function PdfRequestFiles(props) {
try {
await axios.post(
`${localStorage.getItem("baseUrl")}functions/updatecontacttour`,
{
contactId: signerObjectId
},
{ contactId: signerObjectId },
{
headers: {
"Content-Type": "application/json",
@@ -2106,6 +2131,7 @@ function PdfRequestFiles(props) {
</ModalUi>
{/* this component is used for signature pad modal */}
<SignPad
signatureTypes={signatureType}
isSignPad={isSignPad}
isStamp={isStamp}
setIsImageSelect={setIsImageSelect}
@@ -2254,6 +2280,10 @@ function PdfRequestFiles(props) {
setIsLoading={setIsLoading}
xyPostion={signerPos}
setDefaultSignAlert={setDefaultSignAlert}
isDefault={
signatureType?.find((x) => x.name === "default")
?.enabled || false
}
/>
)}
</div>
+52 -40
View File
@@ -38,7 +38,9 @@ import {
onClickZoomOut,
rotatePdfPage,
handleRemoveWidgets,
handleRotateWarning
handleRotateWarning,
signatureTypes,
handleSignatureType
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import { useNavigate } from "react-router-dom";
@@ -152,28 +154,22 @@ function PlaceHolderSign() {
const [pdfRotateBase64, setPdfRotatese64] = useState("");
const [planCode, setPlanCode] = useState("");
const [unSignedWidgetId, setUnSignedWidgetId] = useState("");
const [signatureType, setSignatureType] = useState(signatureTypes);
const isMobile = window.innerWidth < 767;
const [, drop] = useDrop({
accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor),
collect: (monitor) => ({
isOver: !!monitor.isOver()
})
collect: (monitor) => ({ isOver: !!monitor.isOver() })
});
const [{ isDragSign }, dragSignature] = useDrag({
type: "BOX",
item: { type: "BOX", id: 1, text: "signature" },
collect: (monitor) => ({
isDragSign: !!monitor.isDragging()
})
collect: (monitor) => ({ isDragSign: !!monitor.isDragging() })
});
const [{ isDragStamp }, dragStamp] = useDrag({
type: "BOX",
item: { type: "BOX", id: 2, text: "stamp" },
collect: (monitor) => ({
isDragStamp: !!monitor.isDragging()
})
collect: (monitor) => ({ isDragStamp: !!monitor.isDragging() })
});
const documentId = docId;
useEffect(() => {
@@ -202,10 +198,15 @@ function PlaceHolderSign() {
if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist"));
} else if (tenantDetails) {
const signatureType = tenantDetails?.SignatureType || [];
const filterSignTypes = signatureType?.filter(
(x) => x.enabled === true
);
if (tenantDetails?.RequestBody) {
setRequestBody(tenantDetails?.RequestBody);
setRequestSubject(tenantDetails?.RequestSubject);
}
return filterSignTypes;
}
} catch (e) {
alert(t("user-not-exist"));
@@ -256,11 +257,17 @@ function PlaceHolderSign() {
}
//function for get document details
const getDocumentDetails = async () => {
fetchTenantDetails();
const tenantSignTypes = await fetchTenantDetails();
//getting document details
const documentData = await contractDocument(documentId);
if (documentData && documentData.length > 0) {
const url = documentData[0] && documentData[0]?.URL;
const docSignTypes = documentData?.[0]?.SignatureType || signatureTypes;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
setSignatureType(updatedSignatureType);
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
const arrayBuffer = await convertPdfArrayBuffer(url);
if (arrayBuffer === "Error") {
@@ -436,6 +443,14 @@ function PlaceHolderSign() {
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({ isLoad: false });
} else if (res.length && res[0]?.objectId) {
const userSignatureType = res[0]?.SignatureType || signatureTypes;
const docSignTypes =
documentData?.[0]?.SignatureType || userSignatureType;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
setSignatureType(updatedSignatureType);
setActiveMailAdapter(res[0]?.active_mail_adapter);
setSignerUserId(res[0].objectId);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
@@ -972,21 +987,15 @@ function PlaceHolderSign() {
//filter label widgets after add label widgets data on pdf
const filterPrefill = signerPos.filter((data) => data.Role !== "prefill");
try {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
SentToOthers: true,
SignatureType: pdfDetails?.[0]?.SignatureType
};
if (updateExpiryDate) {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
SentToOthers: true,
ExpiryDate: { iso: updateExpiryDate, __type: "Date" }
};
} else {
data = {
Placeholders: filterPrefill,
SignedUrl: pdfUrl,
Signers: signers,
SentToOthers: true
};
data["ExpiryDate"] = { iso: updateExpiryDate, __type: "Date" };
}
await axios
.put(
@@ -1428,7 +1437,15 @@ function PlaceHolderSign() {
setFontSize();
setFontColor();
};
const handleWidgetdefaultdata = (defaultdata) => {
const handleWidgetdefaultdata = (defaultdata, isSignWidget) => {
if (isSignWidget) {
const updatedPdfDetails = [...pdfDetails];
const signtypes = defaultdata.signatureType || signatureType;
updatedPdfDetails[0].SignatureType = signtypes;
// Update the SignatureType with the modified array
setPdfDetails(updatedPdfDetails);
setSignatureType(signtypes);
}
const options = ["email", "number", "text"];
let inputype;
if (defaultdata.textvalidate) {
@@ -1549,14 +1566,12 @@ function PlaceHolderSign() {
} else {
updatedTourStatus = [{ placeholder: true }];
}
await axios
.put(
try {
await axios.put(
`${localStorage.getItem(
"baseUrl"
)}classes/contracts_Users/${signerUserId}`,
{
TourStatus: updatedTourStatus
},
{ TourStatus: updatedTourStatus },
{
headers: {
"Content-Type": "application/json",
@@ -1564,14 +1579,10 @@ function PlaceHolderSign() {
sessionToken: localStorage.getItem("accesstoken")
}
}
)
.then(() => {
// const json = Listdata.data;
// const res = json.results;
})
.catch((err) => {
console.log("axois err ", err);
});
);
} catch (err) {
console.log("axois err ", err);
}
}
};
const handleRecipientSign = () => {
@@ -2230,6 +2241,7 @@ function PlaceHolderSign() {
signersData={signersdata}
/>
<WidgetNameModal
signatureType={signatureType}
widgetName={widgetName}
defaultdata={currWidgetsDetails}
isOpen={isNameModal}
+171
View File
@@ -0,0 +1,171 @@
import React, { useEffect, useState } from "react";
import Title from "../components/Title";
import Alert from "../primitives/Alert";
import { useTranslation } from "react-i18next";
import Loader from "../primitives/Loader";
import Tooltip from "../primitives/Tooltip";
import {
getTenantDetails,
handleSignatureType,
signatureTypes
} from "../constant/Utils";
import Parse from "parse";
const Preferences = () => {
const { t } = useTranslation();
const [isalert, setIsAlert] = useState({ type: "success", msg: "" });
const [isTopLoader, setIsTopLoader] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [signatureType, setSignatureType] = useState([]);
const [errMsg, setErrMsg] = useState("");
useEffect(() => {
fetchSignType();
}, []);
const fetchSignType = async () => {
setIsTopLoader(true);
try {
const user = JSON.parse(
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
)
);
const tenantDetails = await getTenantDetails(user?.objectId);
const signatureType = tenantDetails?.SignatureType || [];
const tenantSignTypes = signatureType?.filter((x) => x.enabled === true);
const getUser = await Parse.Cloud.run("getUserDetails");
if (getUser) {
const _getUser = JSON.parse(JSON.stringify(getUser));
if (tenantSignTypes?.length > 0) {
const signatureType = _getUser?.SignatureType || signatureTypes;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
signatureType
);
setSignatureType(updatedSignatureType);
} else {
const SignatureType = _getUser?.SignatureType || signatureTypes;
setSignatureType(SignatureType);
}
}
} catch (err) {
console.log("err while getting user details", err);
setErrMsg("Something went wrong");
} finally {
setIsTopLoader(false);
}
};
console.log("sign", signatureType);
const handleCheckboxChange = (index) => {
// Create a copy of the signatureType array
const updatedSignatureType = [...signatureType];
// Toggle the enabled value for the clicked item
updatedSignatureType[index].enabled = !updatedSignatureType[index].enabled;
// Update the state with the modified array
setSignatureType(updatedSignatureType);
};
const handleSave = async () => {
console.log("submit", signatureType);
setIsLoader(true);
const isEnabled = signatureType.some((x) => x.enabled === true);
if (isEnabled) {
try {
const params = { SignatureType: signatureType };
const updateRes = await Parse.Cloud.run("updatesignaturetype", params);
if (updateRes) {
setIsAlert({ type: "success", msg: "Saved successfully." });
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
}
} catch (err) {
console.log("err while updating signtype", err);
} finally {
setIsLoader(false);
}
} else {
setIsAlert({
type: "danger",
msg: "please enable at least one signature type"
});
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
setIsLoader(false);
}
};
return (
<React.Fragment>
<Title title={"API Token"} />
{isalert.msg && <Alert type={isalert.type}>{isalert.msg}</Alert>}
{isTopLoader ? (
<div className="flex justify-center items-center h-screen">
<Loader />
</div>
) : (
<>
{errMsg ? (
<div className="flex justify-center items-center h-screen">
{errMsg}
</div>
) : (
<div className="relative bg-base-100 text-base-content flex flex-col justify-center shadow-md rounded-box mb-3">
{isLoader && (
<div className="flex justify-center items-center absolute w-full h-full rounded-box bg-black/30">
<Loader />
</div>
)}
<h1 className="ml-4 mt-3 mb-2 font-semibold">
OpenSign {t("Preferences")}{" "}
<span>
<Tooltip
message={`OpenSign™ ${t("Preferences")}`}
isSubscribe={true}
/>
</span>
</h1>
<div className="ml-4 mt-1 mb-2 flex flex-col">
<div className="mb-[0.75rem]">
<label
htmlFor="signaturetype"
className="text-[14px] mb-[0.7rem] font-medium"
>
Allowed signature types
</label>
<div className=" ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
{signatureType.map((type, i) => (
<div
key={i}
className="flex flex-row gap-[5px] items-center"
>
<input
className="mr-[2px] op-checkbox op-checkbox-xs"
type="checkbox"
name="signaturetype"
onChange={() => handleCheckboxChange(i)}
checked={type.enabled}
/>
<div
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default"
title={`Enabling this allow signers to ${type.name} signature`}
>
{/* {t(`widget-status.${data}`)} */}
{type.name}
</div>
</div>
))}
</div>
<button
className="op-btn op-btn-primary"
onClick={handleSave}
>
Save
</button>
</div>
</div>
</div>
)}
</>
)}
</React.Fragment>
);
};
export default Preferences;
+7 -5
View File
@@ -36,7 +36,8 @@ import {
getDefaultSignature,
onClickZoomIn,
onClickZoomOut,
rotatePdfPage
rotatePdfPage,
signatureTypes
} from "../constant/Utils";
import { useParams } from "react-router-dom";
import Tour from "reactour";
@@ -748,10 +749,10 @@ function SignYourSelf() {
alert(t("something-went-wrong-mssg"));
}
}
//change image width and height to 100/40 in png base64
const getNewse64 = await changeImageWH(base64Sign);
//remove suffiix of base64
const suffixbase64 = getNewse64 && getNewse64.split(",").pop();
//change image width and height to 300/120 in png base64
const imagebase64 = await changeImageWH(base64Sign);
//remove suffiix of base64 (without type)
const suffixbase64 = imagebase64 && imagebase64.split(",").pop();
const params = {
pdfFile: base64Url,
docId: documentId,
@@ -1293,6 +1294,7 @@ function SignYourSelf() {
/>
{/* this is modal of signature pad */}
<SignPad
signatureTypes={signatureTypes}
isSignPad={isSignPad}
isStamp={isStamp}
setIsImageSelect={setIsImageSelect}
+98 -97
View File
@@ -30,7 +30,10 @@ import {
onClickZoomIn,
handleRemoveWidgets,
handleRotateWarning,
color
color,
signatureTypes,
getTenantDetails,
handleSignatureType
} from "../constant/Utils";
import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css";
@@ -69,10 +72,7 @@ const TemplatePlaceholder = () => {
const [isSendAlert, setIsSendAlert] = useState(false);
const [isCreateDocModal, setIsCreateDocModal] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(false);
const [isRotate, setIsRotate] = useState({
status: false,
degree: 0
});
const [isRotate, setIsRotate] = useState({ status: false, degree: 0 });
const [isLoading, setIsLoading] = useState({
isLoad: true,
message: t("loading-mssg")
@@ -107,32 +107,17 @@ const TemplatePlaceholder = () => {
const [, drop] = useDrop({
accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor),
collect: (monitor) => ({
isOver: !!monitor.isOver()
})
collect: (monitor) => ({ isOver: !!monitor.isOver() })
});
const [{ isDragSign }, dragSignature] = useDrag({
type: "BOX",
item: {
type: "BOX",
id: 1,
text: "signature"
},
collect: (monitor) => ({
isDragSign: !!monitor.isDragging()
})
item: { type: "BOX", id: 1, text: "signature" },
collect: (monitor) => ({ isDragSign: !!monitor.isDragging() })
});
const [{ isDragStamp }, dragStamp] = useDrag({
type: "BOX",
item: {
type: "BOX",
id: 2,
text: "stamp"
},
collect: (monitor) => ({
isDragStamp: !!monitor.isDragging()
})
item: { type: "BOX", id: 2, text: "stamp" },
collect: (monitor) => ({ isDragStamp: !!monitor.isDragging() })
});
const [uniqueId, setUniqueId] = useState("");
const [isModalRole, setIsModalRole] = useState(false);
@@ -153,6 +138,7 @@ const TemplatePlaceholder = () => {
const [fontColor, setFontColor] = useState();
const [zoomPercent, setZoomPercent] = useState(0);
const [scale, setScale] = useState(1);
const [signatureType, setSignatureType] = useState([]);
useEffect(() => {
fetchTemplate();
@@ -200,9 +186,38 @@ const TemplatePlaceholder = () => {
handleNavigation(plan);
}
}
//function to fetch tenant Details
const fetchTenantDetails = async () => {
const user = JSON.parse(
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
)
);
if (user) {
try {
const tenantDetails = await getTenantDetails(user?.objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist"));
} else if (tenantDetails) {
const signatureType = tenantDetails?.SignatureType || [];
const filterSignTypes = signatureType?.filter(
(x) => x.enabled === true
);
return filterSignTypes;
}
} catch (e) {
alert(t("user-not-exist"));
}
} else {
alert(t("user-not-exist"));
}
};
// `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
const fetchTemplate = async () => {
try {
const tenantSignTypes = await fetchTenantDetails();
const params = { templateId: templateId };
const templateDeatils = await axios.post(
`${localStorage.getItem("baseUrl")}functions/getTemplate`,
@@ -221,6 +236,12 @@ const TemplatePlaceholder = () => {
: [];
if (documentData && documentData.length > 0) {
const docSignTypes = documentData?.[0]?.SignatureType || signatureTypes;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
setSignatureType(updatedSignatureType);
if (isEnableSubscription) {
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
}
@@ -247,11 +268,7 @@ const TemplatePlaceholder = () => {
blockColor: x.blockColor
};
} else {
return {
Role: x.Role,
Id: x.Id,
blockColor: x.blockColor
};
return { Role: x.Role, Id: x.Id, blockColor: x.blockColor };
}
});
setSignersData(updatedSigners);
@@ -274,11 +291,7 @@ const TemplatePlaceholder = () => {
documentData[0].Placeholders.length > 0
) {
let updatedSigners = documentData[0].Placeholders.map((x) => {
return {
Role: x.Role,
Id: x.Id,
blockColor: x.blockColor
};
return { Role: x.Role, Id: x.Id, blockColor: x.blockColor };
});
setSignerPos(documentData[0].Placeholders);
setUniqueId(updatedSigners[0].Id);
@@ -291,17 +304,41 @@ const TemplatePlaceholder = () => {
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
) {
const loadObj = {
isLoad: false
};
setHandleError(t("something-went-wrong-mssg"));
setIsLoading(loadObj);
setIsLoading({ isLoad: false });
} else {
setHandleError(t("no-data-avaliable"));
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
setIsLoading({ isLoad: false });
}
const res = await contractUsers();
if (res[0] && res.length) {
const userSignatureType = res[0]?.SignatureType || signatureTypes;
const docSignTypes =
documentData?.[0]?.SignatureType || userSignatureType;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
setSignatureType(updatedSignatureType);
setSignerUserId(res[0].objectId);
setCurrentEmail(res[0].Email);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRecipients = tourstatus.filter(
(data) => data.templateTour
);
if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].templateTour);
}
}
setIsLoading({ isLoad: false });
} else if (res === "Error: Something went wrong!") {
setHandleError(t("something-went-wrong-mssg"));
setIsLoading({ isLoad: false });
} else if (res.length === 0) {
setHandleError(t("no-data-avaliable"));
setIsLoading({ isLoad: false });
}
} catch (err) {
console.log("err ", err);
@@ -311,37 +348,6 @@ const TemplatePlaceholder = () => {
setHandleError(t("something-went-wrong-mssg"));
}
}
const res = await contractUsers();
if (res[0] && res.length) {
setSignerUserId(res[0].objectId);
setCurrentEmail(res[0].Email);
const tourstatus = res[0].TourStatus && res[0].TourStatus;
if (tourstatus && tourstatus.length > 0) {
setTourStatus(tourstatus);
const checkTourRecipients = tourstatus.filter(
(data) => data.templateTour
);
if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].templateTour);
}
}
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
} else if (res === "Error: Something went wrong!") {
const loadObj = {
isLoad: false
};
setHandleError(t("something-went-wrong-mssg"));
setIsLoading(loadObj);
} else if (res.length === 0) {
setHandleError(t("no-data-avaliable"));
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
}
};
//function for setting position after drop signature button over pdf
@@ -389,10 +395,7 @@ const TemplatePlaceholder = () => {
Height: widgetHeight / (containerScale * scale)
};
dropData.push(dropObj);
placeHolder = {
pageNumber: pageNumber,
pos: dropData
};
placeHolder = { pageNumber: pageNumber, pos: dropData };
} else {
const offset = monitor.getClientOffset();
//adding and updating drop position in array when user drop signature button in div
@@ -423,10 +426,7 @@ const TemplatePlaceholder = () => {
};
dropData.push(dropObj);
placeHolder = {
pageNumber: pageNumber,
pos: dropData
};
placeHolder = { pageNumber: pageNumber, pos: dropData };
}
let filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
const getPlaceHolder = filterSignerPos?.placeHolder;
@@ -443,10 +443,7 @@ const TemplatePlaceholder = () => {
);
const getPos = currentPagePosition?.pos;
const newSignPos = getPos.concat(dropData);
let xyPos = {
pageNumber: pageNumber,
pos: newSignPos
};
let xyPos = { pageNumber: pageNumber, pos: newSignPos };
updatePlace.push(xyPos);
const updatesignerPos = signerPos.map((x) =>
x.Id === uniqueId ? { ...x, placeHolder: updatePlace } : x
@@ -482,7 +479,6 @@ const TemplatePlaceholder = () => {
setWidgetType(dragTypeValue);
setSignKey(key);
setSelectWidgetId(key);
setIsMailSend(false);
} else {
setIsReceipent(false);
@@ -581,9 +577,7 @@ const TemplatePlaceholder = () => {
}
}
setIsMailSend(false);
setTimeout(() => {
setIsDragging(false);
}, 200);
setTimeout(() => setIsDragging(false), 200);
};
//function for delete signature block
const handleDeleteSign = (key, Id) => {
@@ -729,8 +723,7 @@ const TemplatePlaceholder = () => {
const handleSaveTemplate = async () => {
if (signersdata?.length) {
const loadObj = { isLoad: true, message: t("loading-mssg") };
setIsLoading(loadObj);
setIsLoading({ isLoad: true, message: t("loading-mssg") });
setIsSendAlert(false);
let signers = [];
if (signersdata?.length > 0) {
@@ -769,7 +762,8 @@ const TemplatePlaceholder = () => {
NextReminderDate: pdfDetails[0]?.NextReminderDate,
IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false,
IsTourEnabled: pdfDetails[0]?.IsTourEnabled === true ? true : false,
URL: pdfUrl
URL: pdfUrl,
SignatureType: signatureType
};
const updateTemplate = new Parse.Object("contracts_Template");
updateTemplate.id = templateId;
@@ -878,9 +872,7 @@ const TemplatePlaceholder = () => {
`${localStorage.getItem(
"baseUrl"
)}classes/contracts_Users/${signerUserId}`,
{
TourStatus: updatedTourStatus
},
{ TourStatus: updatedTourStatus },
{
headers: {
"Content-Type": "application/json",
@@ -1214,7 +1206,15 @@ const TemplatePlaceholder = () => {
setFontColor();
};
const handleWidgetdefaultdata = (defaultdata) => {
const handleWidgetdefaultdata = (defaultdata, isSignWidget) => {
if (isSignWidget) {
const updatedPdfDetails = [...pdfDetails];
const signtypes = defaultdata.signatureType || signatureType;
updatedPdfDetails[0].SignatureType = signtypes;
// Update the SignatureType with the modified array
setPdfDetails(updatedPdfDetails);
setSignatureType(signtypes);
}
const options = ["email", "number", "text"];
let inputype;
if (defaultdata.textvalidate) {
@@ -1710,6 +1710,7 @@ const TemplatePlaceholder = () => {
/>
</ModalUi>
<WidgetNameModal
signatureType={signatureType}
widgetName={widgetName}
defaultdata={currWidgetsDetails}
isOpen={isNameModal}
@@ -15,7 +15,10 @@ import {
copytoData,
fetchUrl,
getSignedUrl,
replaceMailVaribles
getTenantDetails,
handleSignatureType,
replaceMailVaribles,
signatureTypes
} from "../constant/Utils";
import Confetti from "react-confetti";
import EditorToolbar, {
@@ -77,6 +80,7 @@ const ReportTable = (props) => {
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [isEmbed, setIsEmbed] = useState(false);
const [isPublicTour, setIsPublicTour] = useState();
const [signatureType, setSignatureType] = useState([]);
const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class);
const startIndex = (currentPage - 1) * props.docPerPage;
@@ -195,6 +199,33 @@ const ReportTable = (props) => {
}
}, [isMoreDocs, pageNumbers, currentPage, setIsNextRecord]);
//function to fetch tenant Details
const fetchTenantDetails = async () => {
const user = JSON.parse(
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
)
);
if (user) {
try {
const tenantDetails = await getTenantDetails(user?.objectId);
if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist"));
} else if (tenantDetails) {
const signatureType = tenantDetails?.SignatureType || [];
const filterSignTypes = signatureType?.filter(
(x) => x.enabled === true
);
return filterSignTypes;
}
} catch (e) {
alert(t("user-not-exist"));
}
} else {
alert(t("user-not-exist"));
}
};
// `handleURL` is used to open microapp
const handleURL = async (item, act) => {
if (props.ReportName === "Templates") {
@@ -203,9 +234,7 @@ const ReportTable = (props) => {
} else {
setActLoader({ [`${item.objectId}_${act.btnId}`]: true });
try {
const params = {
templateId: item.objectId
};
const params = { templateId: item.objectId };
const templateDeatils = await axios.post(
`${localStorage.getItem("baseUrl")}functions/getTemplate`,
params,
@@ -249,6 +278,16 @@ const ReportTable = (props) => {
}
}
}
const tenantSignTypes = await fetchTenantDetails();
const docSignTypes = Doc?.SignatureType || signatureTypes;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
const SignatureType =
updatedSignatureType.length > 0
? { SignatureType: updatedSignatureType }
: {};
let placeholdersArr = [];
if (Doc.Placeholders?.length > 0) {
placeholdersArr = Doc.Placeholders;
@@ -275,7 +314,8 @@ const ReportTable = (props) => {
AutomaticReminders: Doc?.AutomaticReminders || false,
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5,
IsEnableOTP: Doc?.IsEnableOTP || false,
FileAdapterId: Doc?.FileAdapterId || ""
FileAdapterId: Doc?.FileAdapterId || "",
...SignatureType
};
try {
const res = await axios.post(
@@ -910,6 +950,13 @@ const ReportTable = (props) => {
}
);
const templateRes = axiosRes.data && axiosRes.data.result;
const tenantSignTypes = await fetchTenantDetails();
const docSignTypes = templateRes?.SignatureType || signatureTypes;
const updatedSignatureType = await handleSignatureType(
tenantSignTypes,
docSignTypes
);
setSignatureType(updatedSignatureType);
setPlaceholders(templateRes?.Placeholders);
setTemplateDetails(templateRes);
setIsLoader({});
@@ -1933,6 +1980,7 @@ const ReportTable = (props) => {
Placeholders={placeholders}
item={templateDeatils}
handleClose={handleQuickSendClose}
signatureType={signatureType}
/>
)}
</ModalUi>
@@ -94,6 +94,7 @@
"My Signature": "My Signature",
"API Token": "API Token",
"Webhook": "Webhook",
"Preferences": "Preferences",
"Teams": "Teams",
"Team-update-successfully": "Team Updated successfully.",
"Users": "Users"
@@ -683,7 +684,8 @@
"public-tour-message": "The template needs to be public before you can generate a shareable link.",
"add-user-template": "You need to add a role before you can add fields for it.",
"pdf-uncompatible": "This pdf is not compatible with opensign, please contact <support@opensignlabs.com>",
"text-field-tour":"Fields of type 'Text' must be filled in advance before the document is sent. If you need the signers to provide input, use the 'Text Input' field instead.",
"attach-signer-tour":"You need to attach a Signer to every role. You can do that by clicking this icon. Once you select a Signer it will be attached to all the fields associated with that role which appear in the same colour."
"text-field-tour": "Fields of type 'Text' must be filled in advance before the document is sent. If you need the signers to provide input, use the 'Text Input' field instead.",
"attach-signer-tour": "You need to attach a Signer to every role. You can do that by clicking this icon. Once you select a Signer it will be attached to all the fields associated with that role which appear in the same colour.",
"allowed-signature-types": "Allowed signature types",
"at-least-one-signature-type": "At least one signature type should be enabled."
}
@@ -94,6 +94,7 @@
"My Signature": "Mi firma",
"API Token": "Token API",
"Webhook": "Webhook",
"Preferences": "Preferencias",
"Teams": "Equipos",
"Team-update-successfully": "Equipo actualizado correctamente.",
"Users": "Usuarios"
@@ -676,13 +677,15 @@
"add-user-template": "Es necesario agregar un rol antes de poder agregarle campos.",
"pdf-uncompatible": "Este pdf no es compatible con opensign, comuníquese con <support@opensignlabs.com>",
"enable-tour": "Habilitar recorrido",
"istourenabled-help":{
"istourenabled-help": {
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
},
"form-title-1": "Configuración del flujo de documentos",
"form-title-2": "Configuración de seguridad",
"text-field-tour":"Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
"attach-signer-tour" :"Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color."
"text-field-tour": "Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
"attach-signer-tour": "Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
"allowed-signature-types": "Tipos de firma permitidos",
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma."
}
@@ -94,6 +94,7 @@
"My Signature": "Ma signature",
"API Token": "Jeton API",
"Webhook": "Webhook",
"Preferences": "Préférences",
"Teams": "Équipe",
"Team-update-successfully": "Équipe mise à jour avec succès.",
"Users": "Utilisateurs"
@@ -672,16 +673,18 @@
"server-down": "Impossible de se connecter au serveur OpenSign. Si vous hébergez vous-même OpenSign, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez OpenSign localement, vous y accédez peut-être via un numéro de port incorrect.",
"admin-exists": "L'administrateur existe déjà. Veuillez vous connecter à l'application en utilisant les informations d'identification de l'administrateur afin de gérer les utilisateurs.",
"enable-tour": "Activer la visite",
"istourenabled-otp-help":{
"istourenabled-otp-help": {
"p1": "Cela permettra une visite guidée pour les signataires, fournissant des instructions pendant le processus de signature.",
"p2": "Cela désactivera la visite guidée, garantissant ainsi une expérience de signature plus rapide et ininterrompue.",
"p3": "Conseil : si vos signataires connaissent OpenSign et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
"p3": "Conseil : si vos signataires connaissent OpenSign et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
},
"form-title-1": "Paramètre du flux de documents",
"form-title-2": "Paramètre de sécurité",
"public-tour-message": "Le modèle doit être public avant que vous puissiez générer un lien partageable.",
"add-user-template": "Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
"pdf-uncompatible": "Ce pdf n'est pas compatible avec opensign, veuillez contacter <support@opensignlabs.com>",
"text-field-tour" :"Les champs de type 'Texte' doivent être remplis à l'avance avant l'envoi du document. Si vous avez besoin que les signataires fournissent des informations, utilisez plutôt le champ 'Saisie de texte'.",
"attach-signer-tour" :"Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur."
"text-field-tour": "Les champs de type 'Texte' doivent être remplis à l'avance avant l'envoi du document. Si vous avez besoin que les signataires fournissent des informations, utilisez plutôt le champ 'Saisie de texte'.",
"attach-signer-tour": "Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur.",
"allowed-signature-types": "Types de signature autorisés",
"at-least-one-signature-type": "Au moins un type de signature doit être activé."
}
+2
View File
@@ -83,6 +83,7 @@ import isUserInContactBook from './parsefunction/isUserInContactBook.js';
import updateTourStatus from './parsefunction/updateTourStatus.js';
import saveTemplate from './parsefunction/saveTemplate.js';
import updateToPublicTemplate from './parsefunction/updateToPublicTemplate.js';
import updateSignatureType from './parsefunction/updatesignaturetype.js';
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
@@ -178,3 +179,4 @@ Parse.Cloud.define('savecontact', savecontact);
Parse.Cloud.define('isuserincontactbook', isUserInContactBook);
Parse.Cloud.define('updatetourstatus', updateTourStatus);
Parse.Cloud.define('updatetopublictemplate', updateToPublicTemplate);
Parse.Cloud.define('updatesignaturetype', updateSignatureType);
@@ -82,6 +82,7 @@ export default async function GetTemplate(request) {
template.include('Signers');
template.include('CreatedBy');
template.include('ExtUserPtr.TenantId');
template.include('Placeholders.signerPtr');
}
}
const res = await template.first({ useMasterKey: true });
@@ -251,6 +251,7 @@ async function batchQuery(userId, Documents, Ip, parseConfig) {
IsEnableOTP: x?.IsEnableOTP || false,
IsTourEnabled: x?.IsTourEnabled || false,
FileAdapterId: x?.FileAdapterId || '',
...(x?.SignatureType ? { SignatureType: x?.SignatureType } : {}),
},
};
});
@@ -1,29 +1,48 @@
import { parseJwt } from '../../Utils.js';
import jwt from 'jsonwebtoken';
async function getTenantByUserId(userId) {
async function getTenantByUserId(userId, contactId) {
try {
const query = new Parse.Query('contracts_Users');
query.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: userId,
});
const extuser = await query.first({ useMasterKey: true });
if (extuser) {
const user = extuser?.get('CreatedBy')?.id || userId;
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
tenantCreditsQuery.equalTo('UserId', {
if (contactId) {
const contactquery = new Parse.Query('contracts_Contactbook');
contactquery.equalTo('objectId', contactId);
const contactuser = await contactquery.first({ useMasterKey: true });
if (contactuser) {
const user = contactuser?.get('CreatedBy')?.id || userId;
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
tenantCreditsQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: user,
});
tenantCreditsQuery.exclude('FileAdapters,PfxFile,ContactNumber');
const res = await tenantCreditsQuery.first({ useMasterKey: true });
return res;
} else {
return {};
}
} else {
const query = new Parse.Query('contracts_Users');
query.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: user,
objectId: userId,
});
tenantCreditsQuery.exclude('FileAdapters');
tenantCreditsQuery.exclude('PfxFile');
const res = await tenantCreditsQuery.first({ useMasterKey: true });
return res;
} else {
return {};
const extuser = await query.first({ useMasterKey: true });
if (extuser) {
const user = extuser?.get('CreatedBy')?.id || userId;
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
tenantCreditsQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: user,
});
tenantCreditsQuery.exclude('FileAdapters,PfxFile');
const res = await tenantCreditsQuery.first({ useMasterKey: true });
return res;
} else {
return {};
}
}
} catch (err) {
console.log('err in getTenant ', err);
@@ -33,6 +52,7 @@ async function getTenantByUserId(userId) {
export default async function getTenant(request) {
const jwttoken = request.headers.jwttoken || '';
const userId = request.params.userId || '';
const contactId = request.params.contactId || '';
if (jwttoken) {
const jwtDecode = parseJwt(jwttoken);
if (jwtDecode?.user_email) {
@@ -50,12 +70,14 @@ export default async function getTenant(request) {
const appRes = await tokenQuery.first({ useMasterKey: true });
const decoded = jwt.verify(verifyToken, appRes?.get('token'));
if (decoded?.user_email) {
return await getTenantByUserId(apiUserId);
return await getTenantByUserId(apiUserId, contactId);
} else {
return { status: 'error', result: 'Invalid token!' };
}
}
} else if (userId) {
return await getTenantByUserId(userId);
} else if (userId || contactId) {
return await getTenantByUserId(userId, contactId);
} else {
return {};
}
}
@@ -0,0 +1,44 @@
export default async function updateSignatureType(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
const SignatureType = request.params.SignatureType || [];
if (SignatureType.length > 0) {
const isEnabled = SignatureType.some(x => x.enabled === true);
if (isEnabled) {
try {
const orgQuery = new Parse.Query('contracts_Users');
orgQuery.equalTo('UserId', {
__type: 'Pointer',
className: '_User',
objectId: request.user.id,
});
const resUser = await orgQuery.first({ useMasterKey: true });
if (resUser) {
const newOrg = new Parse.Object('contracts_Users');
newOrg.id = resUser.id;
newOrg.set('SignatureType', SignatureType);
const updateUserRes = await newOrg.save(null, { useMasterKey: true });
if (updateUserRes) {
const _updateUserRes = JSON.parse(JSON.stringify(updateUserRes));
return _updateUserRes;
}
} else {
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Premission denied.');
}
} catch (err) {
console.log('err in addorganization', err);
const code = err?.code || 400;
const msg = err?.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
} else {
throw new Parse.Error(
Parse.Error.INVALID_QUERY,
'At least one signature type should be enabled.'
);
}
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide signature types.');
}
}
@@ -0,0 +1,43 @@
/**
*
* @param {Parse} Parse
*/
exports.up = async Parse => {
const docSchema = new Parse.Schema('contracts_Document');
docSchema.addArray('SignatureType');
await docSchema.update();
const templateSchema = new Parse.Schema('contracts_Template');
templateSchema.addArray('SignatureType');
await templateSchema.update();
const extUserSchema = new Parse.Schema('contracts_Users');
extUserSchema.addArray('SignatureType');
await extUserSchema.update();
const tenantSchema = new Parse.Schema('partners_Tenant');
tenantSchema.addArray('SignatureType');
return tenantSchema.update();
};
/**
*
* @param {Parse} Parse
*/
exports.down = async Parse => {
const docSchema = new Parse.Schema('contracts_Document');
docSchema.deleteField('SignatureType');
await docSchema.update();
const templateSchema = new Parse.Schema('contracts_Template');
docSchema.deleteField('SignatureType');
await templateSchema.update();
const extUserSchema = new Parse.Schema('contracts_Users');
docSchema.deleteField('SignatureType');
await extUserSchema.update();
const tenantSchema = new Parse.Schema('partners_Tenant');
docSchema.deleteField('SignatureType');
return tenantSchema.update();
};