Compare commits

...
Author SHA1 Message Date
prafull-opensignlabs 04f71be6a2 Merge pull request #1044 from nxglabs/raktima-patch-main-2
fix: remove agreement checkbox and remove agreement checkbox tour
2025-07-25 15:38:38 +00:00
prafull-opensignlabsandGitHub 07603f4bc2 Merge pull request #1856 from OpenSignLabs/imgbot
[ImgBot] Optimize images
2025-07-24 14:40:19 +05:30
ImgBotApp ea16180551 [ImgBot] Optimize images
*Total -- 487.98kb -> 238.96kb (51.03%)

/apps/OpenSign/public/logo512.png -- 10.72kb -> 2.72kb (74.62%)
/apps/OpenSignServer/logo.png -- 86.48kb -> 24.83kb (71.28%)
/apps/OpenSign/src/assets/images/logo.png -- 86.48kb -> 24.83kb (71.28%)
/apps/OpenSign/public/static/js/assets/images/logo.2a7bff0c1189183fafe71d7d5b94e0cd.png -- 86.48kb -> 24.83kb (71.28%)
/apps/OpenSign/public/logo192.png -- 2.13kb -> 0.65kb (69.69%)
/apps/OpenSign/src/assets/images/dp.png -- 12.83kb -> 4.25kb (66.9%)
/apps/OpenSign/src/assets/images/folder.png -- 4.39kb -> 1.93kb (56.05%)
/apps/OpenSign/public/static/js/assets/images/logo-dark.png -- 95.21kb -> 57.21kb (39.91%)
/apps/OpenSignServer/public/assets/images/parse-logo.png -- 5.16kb -> 3.81kb (26.21%)
/apps/OpenSign/src/assets/images/pdf3.png -- 11.66kb -> 9.77kb (16.21%)
/apps/OpenSign/src/assets/images/login_img.svg -- 40.75kb -> 39.10kb (4.04%)
/apps/OpenSign/src/assets/images/recreatedoc.png -- 40.69kb -> 40.08kb (1.48%)
/apps/OpenSign/src/assets/images/pad.svg -- 5.00kb -> 4.94kb (1.07%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2025-07-24 08:28:02 +00:00
prafull-opensignlabsandGitHub 75ad566d54 Merge pull request #1841 from OpenSignLabs/updates-16144605887
v2.26.1
2025-07-08 19:01:19 +05:30
nxglabs fda9fb534b Merge pull request #963 from nxglabs/staging
v2.26.1
2025-07-08 13:27:37 +00:00
prafull-opensignlabsandGitHub cb3e31ebbf Merge pull request #1838 from OpenSignLabs/updates-16124490027
dark-theme logo
2025-07-07 23:43:31 +05:30
18 changed files with 234 additions and 214 deletions
@@ -379,6 +379,7 @@
"certificate": "Zertifikat",
"decline": "Ablehnen",
"finish": "Fertigstellen",
"done": "Fertig",
"mail": "E-Mail",
"sign-now": "Jetzt unterzeichnen",
"successfully-signed": "Erfolgreich unterzeichnet!",
@@ -1041,5 +1042,6 @@
"vertical": "Vertikal",
"horizontal": "Horizontal",
"billing": "Abrechnung",
"console": "Konsole"
"console": "Konsole",
"action-prohibited": "Diese Aktion ist für Ihre E-Mail-Domain nicht erlaubt. Bitte wenden Sie sich an Ihren Administrator, um Hilfe zu erhalten."
}
@@ -379,6 +379,7 @@
"certificate": "Certificate",
"decline": "Decline",
"finish": "Finish",
"done": "Done",
"mail": "Mail",
"sign-now": "Sign now",
"successfully-signed": "Successfully signed!",
@@ -1041,5 +1042,6 @@
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Billing",
"console": "Console"
"console": "Console",
"action-prohibited": "This action isn't allowed for your email domain. Please contact your administrator for assistance."
}
@@ -380,6 +380,7 @@
"certificate": "Certificado",
"decline": "Rechazar",
"finish": "Finalizar",
"done": "Hecho",
"mail": "Correo",
"sign-now": "Firmar ahora",
"successfully-signed": "¡Firmado exitosamente!",
@@ -1041,5 +1042,6 @@
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Facturación",
"console": "Consola"
"console": "Consola",
"action-prohibited": "Esta acción no está permitida para su dominio de correo electrónico. Por favor, contacte con su administrador para obtener ayuda."
}
@@ -379,6 +379,7 @@
"certificate": "Certificat",
"decline": "refusé",
"finish": "terminé",
"done": "Terminé",
"mail": "Mail",
"sign-now": "Signez maintenant",
"successfully-signed": "Signé avec succès !",
@@ -1041,5 +1042,6 @@
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Facturation",
"console": "Console"
"console": "Console",
"action-prohibited": "Cette action n'est pas autorisée pour votre domaine de messagerie. Veuillez contacter votre administrateur pour obtenir de l'aide."
}
@@ -379,6 +379,7 @@
"certificate": "प्रमाण पत्र",
"decline": "अस्वीकार करें",
"finish": "समाप्त करें",
"done": "हो गया",
"mail": "मेल",
"sign-now": "अभी हस्ताक्षर करें",
"successfully-signed": "सफलतापूर्वक हस्ताक्षर किए गए!",
@@ -1041,5 +1042,6 @@
"vertical": "वर्टिकल",
"horizontal": "हॉरिज़ॉन्टल",
"billing": "बिलिंग",
"console": "कंसोल"
"console": "कंसोल",
"action-prohibited": "यह क्रिया आपके ईमेल डोमेन के लिए अनुमत नहीं है। कृपया सहायता के लिए अपने व्यवस्थापक से संपर्क करें।"
}
@@ -379,6 +379,7 @@
"certificate": "Certificato",
"decline": "Rifiuta",
"finish": "Completa",
"done": "Fatto",
"mail": "Email",
"sign-now": "Firma ora",
"successfully-signed": "Firmato con successo!",
@@ -1041,5 +1042,6 @@
"vertical": "Verticale",
"horizontal": "Orizzontale",
"billing": "Fatturazione",
"console": "Console"
"console": "Console",
"action-prohibited": "Questa azione non è consentita per il tuo dominio email. Contatta il tuo amministratore per ricevere assistenza."
}
+6
View File
@@ -19,6 +19,10 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
const { width } = useWindowSize();
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const isAdmin =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0]?.UserRole ===
"contracts_Admin";
const [isOpen, setIsOpen] = useState(false);
const [applogo, setAppLogo] = useState("");
const [isDarkTheme, setIsDarkTheme] = useState();
@@ -123,11 +127,13 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
{applogo && (
<img
className="object-contain h-full w-auto"
src={
isDarkTheme
? "/static/js/assets/images/logo-dark.png"
: applogo
}
alt="logo"
/>
)}
</div>
@@ -9,7 +9,6 @@ function AgreementContent(props) {
const h2Style = "text-base-content font-medium text-lg";
const ulStyle = "list-disc px-4 py-3";
const handleOnclick = () => {
props.setIsAgreeTour(false);
props.setIsAgree(true);
props.setIsShowAgreeTerms(false);
props.showFirstWidget();
@@ -4,7 +4,6 @@ import AgreementContent from "./AgreementContent";
function AgreementSign(props) {
const { t } = useTranslation();
const [isChecked, setIsChecked] = useState(false);
const [isShowAgreeTerms, setIsShowAgreeTerms] = useState(false);
return (
@@ -12,39 +11,12 @@ function AgreementSign(props) {
<div className="op-modal op-modal-open absolute z-[448]">
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
<div className="flex flex-row items-center">
<label className="inline-flex justify-center items-center cursor-pointer mb-0">
{/* 1) This div becomes the “fake” checkbox */}
<div
data-tut="IsAgree"
className={`w-6 h-6 border-2 mr-3 rounded-full flex text-center items-center justify-center ${isChecked ? "op-border-primary" : "border-red-500"}`}
>
{isChecked ? (
<span className="op-text-primary text-sm font-bold"></span>
) : (
<span className="text-red-500 text-sm font-bold">X</span>
)}
</div>
{/* 2) Visually hide the native checkbox but keep it in the DOM */}
<input
className="sr-only"
type="checkbox"
checked={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
props.showFirstWidget();
}}
/>
</label>
<div className="text-[11px] md:text-base text-base-content">
<span>{t("agree-p1")}</span>
<span
className="font-bold text-blue-600 cursor-pointer"
onClick={() => {
setIsShowAgreeTerms(true);
props.setIsAgreeTour(false);
}}
>
{t("agree-p2")}
@@ -52,15 +24,11 @@ function AgreementSign(props) {
<span> {t("agree-p3")}</span>
</div>
</div>
<div className="flex ml-[35px] mt-3">
<div className="flex mt-3">
<button
onClick={() => {
if (isChecked) {
props.setIsAgreeTour(false);
props.setIsAgree(true);
} else {
props.setIsAgreeTour(true);
}
props.setIsAgree(true);
props.showFirstWidget()
}}
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
>
@@ -74,7 +42,6 @@ function AgreementSign(props) {
</div>
{isShowAgreeTerms && (
<AgreementContent
setIsAgreeTour={props.setIsAgreeTour}
setIsAgree={props.setIsAgree}
setIsShowAgreeTerms={setIsShowAgreeTerms}
showFirstWidget={props.showFirstWidget}
@@ -3,43 +3,39 @@ import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";
function DefaultSignature(props) {
const { t } = useTranslation();
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
const myInitial = useSelector((state) => state.widget.myInitial)
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
const myInitial = useSelector((state) => state.widget.myInitial);
const tabName = ["my-signature", "my-initials"];
const [activeTab, setActiveTab] = useState(0);
const confirmToaddDefaultSign = (type) => {
if (!props.isAgree) {
props.setIsAgreeTour(true);
} else {
if (props?.xyPosition.length > 0) {
//check signature or initial widgets exist or not for auto signing
const getCurrentSignerXY = props?.xyPosition.filter(
(data) => data.Id === props.uniqueId
);
const checkIsSignInitialExist = getCurrentSignerXY?.every(
(placeholderObj) =>
placeholderObj?.placeHolder?.some((placeholder) =>
placeholder?.pos?.some((posItem) => posItem?.type === type)
)
);
if (checkIsSignInitialExist) {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("default-sign-alert", { widgetsType: type }),
type: type
});
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("defaultSign-alert", { widgetsType: type })
});
}
if (props?.xyPosition.length > 0) {
//check signature or initial widgets exist or not for auto signing
const getCurrentSignerXY = props?.xyPosition.filter(
(data) => data.Id === props.uniqueId
);
const checkIsSignInitialExist = getCurrentSignerXY?.every(
(placeholderObj) =>
placeholderObj?.placeHolder?.some((placeholder) =>
placeholder?.pos?.some((posItem) => posItem?.type === type)
)
);
if (checkIsSignInitialExist) {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("default-sign-alert", { widgetsType: type }),
type: type
});
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
alertMessage: t("defaultSign-alert", { widgetsType: type })
});
}
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
});
}
};
@@ -180,13 +180,8 @@ function Placeholder(props) {
const widgetClickHandler = () => {
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
//condition to check in request signing flow user click on agree or not
if (props?.data?.signerObjId === props?.signerObjId && !props.isDragging) {
if (!props.isAgree && !props.isSelfSign) {
props.setIsAgreeTour && props.setIsAgreeTour(true);
} else {
handleWidgetIdandPopup();
}
handleWidgetIdandPopup();
} //condition for open contact details popup when sign templet from public signing flow
else if (props?.uniqueId && props.data?.Id === props?.uniqueId) {
handleWidgetIdandPopup();
@@ -108,7 +108,7 @@ function PlaceholderType(props) {
alt="signature"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls "
className={`${props.pos.signatureType !== "type" ? "object-contain" : ""} w-full h-full select-none-cls`}
/>
) : (
<div className={widgetCls}>
@@ -132,7 +132,7 @@ function PlaceholderType(props) {
alt="stamp"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls"
className="w-full h-full select-none-cls object-contain"
/>
) : (
<div className={widgetCls}>
@@ -253,7 +253,7 @@ function PlaceholderType(props) {
alt="initials"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls"
className={`${props.pos.signatureType !== "type" ? "object-contain" : ""} w-full h-full select-none-cls`}
/>
) : (
<div className={widgetCls}>
@@ -402,7 +402,7 @@ function PlaceholderType(props) {
alt="image"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full select-none-cls"
className="w-full h-full select-none-cls object-contain"
/>
) : (
<div className={widgetCls}>
@@ -160,8 +160,6 @@ function RenderPdf(props) {
ispublicTemplate={props.ispublicTemplate}
handleUserDetails={props.handleUserDetails}
isResize={props.isResize}
setIsAgreeTour={props.setIsAgreeTour}
isAgree={props.isAgree}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
setUniqueId={props.setUniqueId}
@@ -18,7 +18,8 @@ import {
cellsWidget,
textWidget,
years,
convertTextToImg
convertTextToImg,
convertJpegToPng
} from "../../constant/Utils";
import CellsWidget from "./CellsWidget";
import DatePicker from "react-datepicker";
@@ -110,7 +111,6 @@ function WidgetsValueModal(props) {
const [signature, setSignature] = useState();
const [isImageSelect, setIsImageSelect] = useState(false);
const [isFinish, setIsFinish] = useState(false);
const [imgWH, setImgWH] = useState({});
const [fontSelect, setFontSelect] = useState(fontOptions[0].value);
const [isSavedSign, setIsSavedSign] = useState(false);
const [isAutoSign, setIsAutoSign] = useState(false);
@@ -257,11 +257,11 @@ function WidgetsValueModal(props) {
setOriginalImage(null); // Reset original image when a new file is selected
setRemoveBgEnabled(false); // Optionally reset toggle
// Ensure compressedFileSize calls setImage, which then triggers the useEffect for BG removal.
compressedFileSize(file, setImgWH, setImage);
compressedFileSize(file, setImage);
}
};
//function is used to save image,stamp widgets data
const handleSaveImage = () => {
const handleSaveImage = (signatureType) => {
const widgetsType = currWidgetsDetails?.type;
//`isApplyAll` is used when user edit stamp then updated signature apply all existing drawn signatures
const isApplyAll = true;
@@ -278,10 +278,10 @@ function WidgetsValueModal(props) {
// Get updated placeholder list
const updatedPlaceholders = onSaveImage(
signatureType,
signer.placeHolder,
index,
currWidgetsDetails?.key,
imgWH,
image,
isAutoSign,
widgetsType,
@@ -299,11 +299,13 @@ function WidgetsValueModal(props) {
return object.pageNumber === pageNumber;
});
const getImage = onSaveImage(
signatureType,
props?.xyPosition,
index,
currWidgetsDetails?.key,
imgWH,
image
image,
false,
widgetsType
);
setXyPosition(getImage);
}
@@ -311,36 +313,28 @@ function WidgetsValueModal(props) {
};
//function is used to save draw type or initial type signature
const handleSaveSignature = (
type,
const handleSaveSignature = async (
signType,
isDefaultSign,
width,
height,
typedSignature
) => {
//get current click widget type
// get current click widget type
const widgetsType = currWidgetsDetails?.type;
//if there are any width and height then it will typed signature
// if there are any width and height then it will typed signature
const isTypeText = width && height ? true : false;
//final getting signature url via default select sign/default initial sign/self draw sign
const signatureImg = isDefaultSign
// final getting signature url via default select sign/default initial sign/self draw sign
const signUrl = isDefaultSign
? isDefaultSign === "initials"
? myInitial
: defaultSignImg
: signature;
? await convertJpegToPng(myInitial, "myinitials") // default initials set through my signature tab
: await convertJpegToPng(defaultSignImg, "mysign") // default signature set through my signature tab
: signature; // signature done by user thorugh draw, upload, typed
const signatureImg = signUrl;
let imgWH = { width: width ? width : "", height: height ? height : "" };
setIsImageSelect(false);
setImage();
//set default signature image width and height
if (isDefaultSign) {
const img = new Image();
img.src = defaultSignImg;
if (img.complete) {
imgWH = { width: img.width, height: img.height };
}
}
//`isApplyAll` is used when user edit signature/initial then updated signature apply all existing drawn signatures
// `isApplyAll` is used when user edit signature/initial then updated signature apply all existing drawn signatures
const isApplyAll = true;
if (uniqueId) {
setXyPosition((prevState) =>
@@ -351,13 +345,12 @@ function WidgetsValueModal(props) {
(x) => x.pageNumber === pageNumber
);
const updatedPlaceholders = onSaveSign(
type,
signType,
signer.placeHolder,
placeholderIndex,
currWidgetsDetails?.key,
signatureImg,
imgWH,
isDefaultSign,
isTypeText,
typedSignature,
isAutoSign,
@@ -377,13 +370,12 @@ function WidgetsValueModal(props) {
return object.pageNumber === pageNumber;
});
const getUpdatePosition = onSaveSign(
type,
signType,
props?.xyPosition,
index,
currWidgetsDetails?.key,
signatureImg,
imgWH,
isDefaultSign,
isTypeText,
typedSignature,
false,
@@ -535,7 +527,7 @@ function WidgetsValueModal(props) {
}
};
// `handlesavesign` is used to save signaute, initials, stamp as a default
// `handlesavesign` is used to save signature, initials, stamp as a default
const handleSaveSign = async () => {
if (signature || image?.src) {
setIsLoader(true);
@@ -556,7 +548,7 @@ function WidgetsValueModal(props) {
objectId: User?.id
};
if (imageUrl) {
// below code is used to save or update default signaute, initials, stamp
// below code is used to save or update default signature, initials, stamp
try {
const signCls = new Parse.Object("contracts_Signature");
if (props?.saveSignCheckbox?.signId) {
@@ -642,13 +634,14 @@ function WidgetsValueModal(props) {
} else {
setSignature("");
canvasRef?.current?.clear();
handleSaveSignature(isTab);
const tab = isTab === "uploadImage" ? "image" : isTab;
handleSaveSignature(tab);
}
}
setPenColor("blue");
} else {
setSignature("");
handleSaveImage();
handleSaveImage("image");
}
setIsImageSelect(false);
setIsDefaultSign(false);
@@ -1178,7 +1171,9 @@ function WidgetsValueModal(props) {
hidden
/>
<i className="fa-light fa-cloud-upload-alt uploadImgLogo text-base-content"></i>
<div className="text-[10px] text-base-content">{t("upload")}</div>
<div className="text-[10px] text-base-content">
{t("upload")}
</div>
</div>
</div>
) : (
@@ -1324,7 +1319,6 @@ function WidgetsValueModal(props) {
dotSize={1}
/>
</div>
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent />
</div>
@@ -1988,7 +1982,7 @@ function WidgetsValueModal(props) {
handleClickOnNext(isFinishDoc);
}}
>
{t("finish")}
{t("done")}
</button>
) : (
<button
+138 -61
View File
@@ -361,7 +361,7 @@ export const selectFormat = (data) => {
case "DD MMMM, YYYY":
return "dd MMMM, yyyy";
case "DD.MM.YYYY":
return "dd.MM.yyyy";
return "dd.MM.yyyy";
default:
return "MM/dd/yyyy";
}
@@ -390,7 +390,7 @@ export const changeDateToMomentFormat = (format) => {
case "dd MMMM, yyyy":
return "DD MMMM, YYYY";
case "dd.MM.yyyy":
return"DD.MM.YYYY";
return "DD.MM.YYYY";
default:
return "L";
}
@@ -1318,15 +1318,14 @@ export function convertTextToImg(fontStyle, text, color, widgetDims) {
return dataUrl;
}
//function for save button to save signature or image url
// `onSaveSign` trigger on save button to save for signature, initials widget for type: draw, typed, default
export function onSaveSign(
type,
xyPosition,
index,
signKey,
signatureImg,
defaultImgWH,
isDefaultSign,
updatedImgWH,
isTypeText,
typedSignature,
isAutoSign,
@@ -1341,27 +1340,11 @@ export function onSaveSign(
widgetsType && ["signature", "initials"].includes(widgetsType);
const updateXYData = getXYdata.map((position) => {
if (position.key === signKey) {
if (isDefaultSign === "initials") {
let imgWH = { width: position.Width, height: position.Height };
getIMGWH = calculateImgAspectRatio(imgWH, position);
} else if (isDefaultSign === "default") {
getIMGWH = calculateImgAspectRatio(defaultImgWH, position);
} else if (isTypeText) {
if (isTypeText) {
getIMGWH = {
newWidth: defaultImgWH.width,
newHeight: defaultImgWH.height
newWidth: updatedImgWH.width,
newHeight: updatedImgWH.height
};
} else {
const defaultWidth = defaultWidthHeight(position?.type).width;
const defaultHeight = defaultWidthHeight(position?.type).height;
getIMGWH = calculateImgAspectRatio(
{
width: defaultWidth ? defaultWidth : 150,
height: defaultHeight ? defaultHeight : 60
},
position
);
}
posWidth = getIMGWH ? getIMGWH.newWidth : 150;
posHeight = getIMGWH ? getIMGWH.newHeight : 60;
@@ -1371,8 +1354,8 @@ export function onSaveSign(
: signatureImg;
return {
...position,
Width: posWidth,
Height: posHeight,
...(type === "type" ? { Width: posWidth } : {}),
...(type === "type" ? { Height: posHeight } : {}),
SignUrl: signImg,
...(isSignOrInitials && { signatureType: type || "" }),
options: { ...position.options, response: signImg },
@@ -1392,7 +1375,7 @@ export function onSaveSign(
}
return obj;
});
//condition when draw/upload signature/initials then apply it all related to widgets (signature,image,typed signature or default signature)
//condition when draw/upload signature/initials then apply it all related to widgets (draw, typed signature or default signature)
if (isApplyAll || isAutoSign) {
const updatedArray = updateXYposition.map((page) => ({
...page,
@@ -1404,9 +1387,6 @@ export function onSaveSign(
: signatureImg;
return {
...item,
// Only copy signature content, keep existing dimensions
// Width: posWidth,
// Height: posHeight,
SignUrl: signImg,
...(isSignOrInitials && { signatureType: type || "" }),
options: { ...item.options, response: signImg },
@@ -1426,6 +1406,54 @@ export function onSaveSign(
}
}
/**
* Scales and centers a base64encoded image into a fixedsize widget
* and returns a new base64 PNG.
*
* @param {string} base64Image A data-URL (e.g. "data:image/png;base64,…")
* @param {{ Width: number, Height: number }} widgetDims
* @returns {Promise<string>} A Promise that resolves to a data-URL of the new image
*/
export async function convertBase64ToImg(base64Image, widgetDims) {
const { Width: maxWidth, Height: maxHeight } = widgetDims;
// Detect input format from data URL
const match = base64Image.match(/^data:(image\/(png|jpeg|jpg|webp));base64,/);
const inputMime = match ? match[1] : "image/png"; // fallback to PNG if unknown
// 1. Load the image off-DOM
const img = new Image();
img.src = base64Image;
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
});
// 2. Compute scale to fit within widget (preserving aspect ratio)
const { naturalWidth: imgW, naturalHeight: imgH } = img;
const scale = Math.min(maxWidth / imgW, maxHeight / imgH, 1);
const drawW = imgW * scale;
const drawH = imgH * scale;
// 3. Prepare a high-DPI canvas
const pxRatio = (window.devicePixelRatio || 1) * 2;
const canvas = document.createElement("canvas");
canvas.width = Math.ceil(maxWidth * pxRatio);
canvas.height = Math.ceil(maxHeight * pxRatio);
const ctx = canvas.getContext("2d");
ctx.scale(pxRatio, pxRatio);
ctx.clearRect(0, 0, maxWidth, maxHeight);
// 4. Center the image in the widget rectangle
const x = (maxWidth - drawW) / 2;
const y = (maxHeight - drawH) / 2;
ctx.drawImage(img, x, y, drawW, drawH);
// 5. Return new base64 in same format as input
const quality =
inputMime.includes("jpeg") || inputMime.includes("jpg") ? 0.9 : undefined;
return canvas.toDataURL(inputMime, quality);
}
//function to use After setting the signature URL for the first signature widget, clicking on subsequent
//signature widgets should automatically apply and display the signature. apply for initial,signature and stamp widget
export const handleCopySignUrl = (
@@ -1488,35 +1516,30 @@ export const calculateImgAspectRatio = (imgWH, pos) => {
return { newHeight, newWidth };
};
//function for upload stamp or image
// `onSaveImage` trigger for upload image through stamp, image, signature, initials widgets
export function onSaveImage(
signatureType,
xyPosition,
index,
signKey,
imgWH,
image,
isAutoSign,
widgetsType,
isApplyAll
) {
let getIMGWH;
// let getIMGWH;
const isSignOrInitials =
widgetsType && ["signature", "initials"].includes(widgetsType);
//get current page position
const getXYData = xyPosition[index].pos;
const updateXYData = getXYData.map((position) => {
if (position.key === signKey) {
getIMGWH = calculateImgAspectRatio(imgWH, position);
return {
...position,
Width: getIMGWH.newWidth,
Height: getIMGWH.newHeight,
SignUrl: image.src,
ImageType: image.imgType,
options: {
...position.options,
response: image.src
}
...(isSignOrInitials && { signatureType: signatureType || "" }),
options: { ...position.options, response: image.src }
};
}
return position;
@@ -1528,17 +1551,16 @@ export function onSaveImage(
}
return obj;
});
//condition when user upload(stamp) then apply it all related to widgets
// condition when user upload(stamp) then apply it all related to widgets
if (isApplyAll || isAutoSign) {
const updatedArray = updateXYposition.map((page) => ({
...page,
pos: page.pos.map(
(item) =>
// below condition to exclude apply all for image widget
item.type === widgetsType && item.type !== "image"
? {
...item,
// Width: getIMGWH.newWidth,
// Height: getIMGWH.newHeight,
SignUrl: image.src,
ImageType: image.imgType,
options: {
@@ -1556,7 +1578,7 @@ export function onSaveImage(
}
//function for select image and upload image
export const onImageSelect = (setImgWH, setImage, file) => {
export const onImageSelect = (setImage, file) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onloadend = function (e) {
@@ -1575,13 +1597,12 @@ export const onImageSelect = (setImgWH, setImage, file) => {
width = 184 * imgR;
height = 184;
}
setImgWH({ width: width, height: height });
};
image.src = reader.result;
setImage({ src: image.src, imgType: file.type });
};
};
export const compressedFileSize = (file, setImgWH, setImage) => {
export const compressedFileSize = (file, setImage) => {
// Create a new FileReader instance to read the uploaded file
const reader = new FileReader();
@@ -1606,7 +1627,7 @@ export const compressedFileSize = (file, setImgWH, setImage) => {
// Draw the image onto the canvas with the specified dimensions
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
// Get the original file type (default to "image/png" if not recognized)
const fileType = file.type || "image/png";
const fileType = "image/png"; //file.type || "image/png"
// Compress the image and convert it into a Blob
canvas.toBlob(
(blob) => {
@@ -1616,7 +1637,7 @@ export const compressedFileSize = (file, setImgWH, setImage) => {
lastModified: Date.now() // Update the last modified timestamp
});
// Pass the compressed file to a custom function for further processing
onImageSelect(setImgWH, setImage, compressedFile);
onImageSelect(setImage, compressedFile);
},
fileType, // Output format for the compressed image
0.3 // Compression quality (30%)
@@ -1741,10 +1762,12 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
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;
const widgetDims = { Width: widget.Width, Height: widget.Height };
if (signbase64) {
let arr = signbase64.split(","),
mime = arr[0].match(/:(.*?);/)[1];
const res = await fetch(signbase64);
const signatureImg = await convertBase64ToImg(signbase64, widgetDims);
const res = await fetch(signatureImg);
const arrayBuffer = await res.arrayBuffer();
const obj = { mimetype: mime, arrayBuffer: arrayBuffer };
return obj;
@@ -2272,28 +2295,25 @@ export const addDefaultSignatureImg = (xyPosition, defaultSignImg, type) => {
const img = new Image();
img.src = defaultSignImg;
if (img.complete) {
imgWH = {
width: img.width,
height: img.height
};
imgWH = { width: img.width, height: img.height };
}
let xyDefaultPos = [];
for (let i = 0; i < xyPosition.length; i++) {
let getIMGWH;
// let getIMGWH;
const getXYdata = xyPosition[i].pos;
const getPageNo = xyPosition[i].pageNumber;
const getPosData = getXYdata;
const addSign = getPosData.map((position) => {
getIMGWH = calculateImgAspectRatio(imgWH, position);
// getIMGWH = calculateImgAspectRatio(imgWH, position);
if (position.type) {
if (position?.type === type) {
return {
...position,
SignUrl: defaultSignImg,
Width: getIMGWH.newWidth,
Height: getIMGWH.newHeight,
// Width: getIMGWH.newWidth,
// Height: getIMGWH.newHeight,
ImageType: "default",
options: {
...position.options,
@@ -2305,8 +2325,8 @@ export const addDefaultSignatureImg = (xyPosition, defaultSignImg, type) => {
return {
...position,
SignUrl: defaultSignImg,
Width: getIMGWH.newWidth,
Height: getIMGWH.newHeight,
// Width: getIMGWH.newWidth,
// Height: getIMGWH.newHeight,
ImageType: "default",
options: {
...position.options,
@@ -3653,3 +3673,60 @@ export function getFileAsArrayBuffer(file) {
reader.readAsArrayBuffer(file);
});
}
/**
* Converts a JPEG/JPG File/Blob into a PNG File.
*
* @param {string} base64Image - A data-URL (e.g. "data:image/jpeg;base64,…")
* @param {string} filename - Desired filename (e.g. "input").
* @returns {Promise<File>} - A promise that resolves with a new PNG File.
*/
export function convertJpegToPng(base64Image, filename) {
const arr = base64Image.split(",");
const mimeMatch = arr[0].match(/:(.*?);/);
if (!mimeMatch) throw new Error("Invalid dataURL");
const mime = mimeMatch[1];
const type = mime.split("/")[1];
if (type === "png") {
return base64Image;
} else {
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) u8arr[n] = bstr.charCodeAt(n);
const inputFile = new File([u8arr], `${filename}.${type}`, { type: mime });
return new Promise((resolve, reject) => {
// ensure its JPEG/JPG (you can remove this check if not needed)
if (!/image\/jpe?g/.test(inputFile.type)) {
return reject(new Error("Input must be a JPEG or JPG image"));
}
// Read the file as a data URL
const reader = new FileReader();
reader.onerror = () => reject(new Error("Failed to read the file"));
reader.onload = () => {
const img = new Image();
img.onerror = () => reject(new Error("Failed to load image"));
img.onload = () => {
// draw image onto a canvas
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// directly get PNG as base64 data URL
try {
const pngDataUrl = canvas.toDataURL("image/png");
resolve(pngDataUrl);
} catch (err) {
reject(new Error("Failed to convert canvas to PNG"));
}
};
img.src = reader.result;
};
reader.readAsDataURL(inputFile);
});
}
}
+5 -1
View File
@@ -125,7 +125,11 @@ function Login() {
}
} catch (error) {
console.error("Error while logging in user", error);
showToast("danger", "Invalid username/password or region");
if (error?.code === 1001) {
showToast("danger", t("action-prohibited"));
} else {
showToast("danger", "Invalid username/password or region");
}
}
};
const handleLoginBtn = async (event) => {
+2 -30
View File
@@ -144,7 +144,6 @@ function PdfRequestFiles(
const [signatureType, setSignatureType] = useState([]);
const [pdfBase64Url, setPdfBase64Url] = useState("");
const [isAgree, setIsAgree] = useState(false);
const [isAgreeTour, setIsAgreeTour] = useState(false);
const [redirectTimeLeft, setRedirectTimeLeft] = useState(5);
const [isredirectCanceled, setIsredirectCanceled] = useState(true);
const [isDragging, setIsDragging] = useState(false);
@@ -1378,18 +1377,7 @@ function PdfRequestFiles(
setIsUiLoading(false);
alert(t("expiry-date-error"));
}
};
const AgreementTour = [
{
selector: '[data-tut="IsAgree"]',
content: () => <p className="p-0">{t("agrrement-alert")}</p>,
position: "top",
style: { fontSize: "13px" }
}
];
const handleCloseAgreeTour = () => {
setIsAgreeTour(false);
};
};
// `handleRedirectCancel` is used to cancel redirecting to redirectUrl
const handleRedirectCancel = () => {
setIsredirectCanceled(true);
@@ -1599,7 +1587,7 @@ function PdfRequestFiles(
}
}
};
//function to get first widget and page number to assign currect signer and tour message
//function to get first widget id and page number to assign correct signer and show tour message
const showFirstWidget = () => {
if (!requestSignTour) {
const getCurrentUserPlaceholder = signerPos?.find(
@@ -1644,21 +1632,9 @@ function PdfRequestFiles(
!isDecline?.isDeclined && (
<AgreementSign
setIsAgree={setIsAgree}
setIsAgreeTour={setIsAgreeTour}
showFirstWidget={showFirstWidget}
/>
)}
<Tour
showNumber={false}
showNavigation={false}
showNavigationNumber={false}
onRequestClose={handleCloseAgreeTour}
steps={AgreementTour}
isOpen={isAgreeTour}
rounded={5}
closeWithMask={false}
/>
{isUiLoading && (
<div className="absolute h-[100vh] w-full flex flex-col justify-center items-center z-[999] bg-[#e6f2f2] bg-opacity-80">
<Loader />
@@ -2011,8 +1987,6 @@ function PdfRequestFiles(
scale={scale}
uniqueId={uniqueId}
pdfBase64Url={pdfBase64Url}
setIsAgreeTour={setIsAgreeTour}
isAgree={isAgree}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
isDragging={isDragging}
@@ -2095,8 +2069,6 @@ function PdfRequestFiles(
signatureType?.find((x) => x.name === "default")
?.enabled || false
}
isAgree={isAgree}
setIsAgreeTour={setIsAgreeTour}
/>
)}
</div>
@@ -43,9 +43,9 @@ async function saveUser(userDetails) {
}
export default async function usersignup(request) {
const userDetails = request.params.userDetails;
const user = await saveUser(userDetails);
try {
const user = await saveUser(userDetails);
const extClass = userDetails.role.split('_')[0];
const extQuery = new Parse.Query(extClass + '_Users');