Compare commits

...
40 Commits
Author SHA1 Message Date
AndrewandGitHub 04c228cd36 Merge pull request #1249 from OpenSignLabs/validation
fix: pagination move on the top records in reports
2024-09-24 18:30:28 +05:30
prafull-opensignlabs 915f8e7700 fix: pagination move on the top records in reports 2024-09-24 18:26:43 +05:30
AndrewandGitHub 218cea00b5 Merge pull request #1247 from OpenSignLabs/validation 2024-09-24 17:29:03 +05:30
prafull-opensignlabs f38f5ea670 fix: file upload get stuck while uploading docx 2024-09-24 17:28:24 +05:30
AndrewandGitHub 791d8beb06 Merge pull request #1246 from OpenSignLabs/validation 2024-09-24 13:58:51 +05:30
prafull-opensignlabs 57a9f3aca0 fix: document title not get generated when upload image 2024-09-24 13:56:25 +05:30
prafull-opensignlabs ae8bf187c8 fix: not able draw boxes in debug ui on single page pdf 2024-09-24 12:37:50 +05:30
prafull-opensignlabs 88f2ab1f8e fix: close sidebar in mobile view when user click on profile menu from header 2024-09-24 12:19:32 +05:30
AndrewandGitHub 76f5136073 Merge pull request #1245 from OpenSignLabs/validation 2024-09-23 18:10:14 +05:30
prafull-opensignlabs 0f84cd8949 feat: generate document title automatically on the basis file name 2024-09-23 18:09:23 +05:30
AndrewandGitHub adfdf7606f Merge pull request #1244 from OpenSignLabs/validation
feat: add validatesmtp endpoint
2024-09-23 17:01:25 +05:30
prafull-opensignlabs ce671d4294 feat: add validatesmtp endpoint 2024-09-23 17:00:45 +05:30
AndrewandGitHub 2e7cf7a9df Merge pull request #1243 from OpenSignLabs/validation 2024-09-23 11:54:22 +05:30
prafull-opensignlabs f699d93e44 fix: resend mail is not went throught customsmtp 2024-09-23 11:53:15 +05:30
AndrewandGitHub b7a22d94db Merge pull request #1236 from OpenSignLabs/validation
refactor: add parenthesis
2024-09-20 20:09:33 +05:30
prafull-opensignlabs 3d854756d1 refactor: add parenthesis 2024-09-20 20:07:17 +05:30
AndrewandGitHub 251e7058f1 Merge pull request #1235 from OpenSignLabs/validation
feat: custom smtp support
2024-09-20 19:59:04 +05:30
prafull-opensignlabs 368eb24217 feat: custom smtp support 2024-09-20 19:57:30 +05:30
AndrewandGitHub f89ef54b82 Merge pull request #1233 from OpenSignLabs/raktima-patch-3
feat: enable dismiss button to hide notification in header
2024-09-20 19:30:15 +05:30
RaktimaNXG b9d9f8772b feat: enable dismiss button to hide notification in header 2024-09-20 18:17:31 +05:30
AndrewandGitHub f2ee03e850 Merge pull request #1232 from OpenSignLabs/raktima-patch-3 2024-09-20 18:11:21 +05:30
RaktimaNXG 92e59a0072 fix: zip file 2024-09-20 17:58:57 +05:30
RaktimaNXG 5394c0b520 refactor code 2024-09-20 16:41:41 +05:30
RaktimaNXG e4a9c6f8c2 fix: remove console 2024-09-20 15:49:50 +05:30
RaktimaNXG e559d037c3 feat: add zip file to dowload pdf and certificate in all download option of completed pdf 2024-09-20 15:33:13 +05:30
RaktimaNXG c9909a0869 Merge branch 'staging' of https://github.com/OpenSignLabs/OpenSign into raktima-patch-3 2024-09-20 12:10:44 +05:30
RaktimaNXG 988fc1bbfd fix: error 2024-09-20 12:08:01 +05:30
RaktimaNXG 1831bbcd70 feat: add zip file to download pdf and certificate 2024-09-20 12:04:10 +05:30
prafull-opensignlabsandGitHub ced9bbdbd5 Merge pull request #1230 from OpenSignLabs/fix-Confetti
fix: tour design
2024-09-20 10:15:36 +05:30
RaktimaNXG 15d57d937b fix: tour design 2024-09-19 19:38:25 +05:30
AndrewandGitHub 555979869d Merge pull request #1228 from OpenSignLabs/validation
refactor: provide appName
2024-09-19 19:09:56 +05:30
prafull-opensignlabs 4e6477297e refactor: provide appName 2024-09-19 19:07:54 +05:30
AndrewandGitHub e9e06fc53b Merge pull request #1227 from OpenSignLabs/fix-Confetti 2024-09-19 19:06:46 +05:30
RaktimaNXG ecb2818589 fix: update confetti effect 2024-09-19 19:04:59 +05:30
AndrewandGitHub 1a1bc41283 Merge pull request #1226 from OpenSignLabs/validation 2024-09-19 17:44:04 +05:30
prafull-opensignlabs 4188caed33 feat: add document logs in in-progress report 2024-09-19 17:42:44 +05:30
prafull-opensignlabsandGitHub ffc742b460 Merge pull request #1224 from OpenSignLabs/raktima-patch-3
fix: ui design in mobile view
2024-09-19 15:57:25 +05:30
RaktimaNXG 0c04cc6ce6 fix: update translation file 2024-09-19 15:27:49 +05:30
RaktimaNXG 4e6dfed838 refactor code 2024-09-19 14:42:13 +05:30
RaktimaNXG fdc0524ad2 fix: ui design in mobile view 2024-09-19 14:35:10 +05:30
38 changed files with 1311 additions and 647 deletions
+70
View File
@@ -19,6 +19,7 @@
"i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"nth-check": "^2.1.1",
@@ -12748,6 +12749,12 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
@@ -16181,6 +16188,54 @@
"node": ">=4.0"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/jszip/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/jszip/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/jszip/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/jszip/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
@@ -16286,6 +16341,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lilconfig": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
@@ -22606,6 +22670,12 @@
"node": ">= 0.4"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+1
View File
@@ -14,6 +14,7 @@
"i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
"nth-check": "^2.1.1",
@@ -151,7 +151,8 @@
"Name": "Name",
"Status": "Status",
"created-date": "Created Date",
"Type": "Type"
"Type": "Type",
"Logs": "Logs"
},
"report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -572,9 +573,10 @@
"pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .",
"placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.",
"placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.",
"placeholder-sign-3": "Drag or click on a field to add it to the document.",
"placeholder-sign-4": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-5": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
"placeholder-sign-3": "Click this button to add more recipients/signers to the document.",
"placeholder-sign-4": "Drag or click on a field to add it to the document.",
"placeholder-sign-5": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-6": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like HR Manager and New Employee. Each time you use the template, you can assign the New Employee role to different incoming staff members, while the HR Manager role remains constant, facilitating a seamless onboarding process for each recruit. ",
"redirect": "Click the 'Use' button to create a new document from an existing template.",
"bulksend": "To quickly create and send multiple documents using an existing template, click the 'Quick Send' button.",
@@ -647,5 +649,10 @@
"p4": "Please choose the option that best suits your document signing requirements."
},
"advanced-options":"Advanced options",
"hide-advanced-options":"Hide Advanced options"
"hide-advanced-options":"Hide Advanced options",
"download-files":"Download files",
"download-pdf":"Download Pdf",
"pdf-certificate":"Download Pdf + Certificate",
"document-logs":"Document logs"
}
@@ -136,7 +136,8 @@
"Name": "Nom",
"Status": "Statut",
"created-date": "Date de création",
"Type": "Saisir"
"Type": "Saisir",
"Logs": "Journaux"
},
"btnLabel": {
"sign": "Signer",
@@ -571,9 +572,10 @@
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.",
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
"placeholder-sign-3": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"placeholder-sign-4": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
"placeholder-sign-5": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
"placeholder-sign-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
"placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"placeholder-sign-5": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
"placeholder-sign-6": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
"bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.",
@@ -646,6 +648,10 @@
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
},
"advanced-options":"Options avancées",
"hide-advanced-options": "Masquer les options avancées"
"hide-advanced-options": "Masquer les options avancées",
"download-files" :"Télécharger des fichiers",
"download-pdf" :"Télécharger le PDF",
"pdf-certificate" :"Télécharger Pdf + Certificat",
"document-logs":"Journaux de documents"
}
File diff suppressed because one or more lines are too long
+42 -9
View File
@@ -20,6 +20,9 @@ const Header = ({ showSidebar }) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { width } = useWindowSize();
const current_notification_version = "1"; // Update this with each new feature release
// Get the dismissed version from local storage
const dismissedVersion = localStorage.getItem("notificationDismissedVersion");
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
@@ -30,9 +33,15 @@ const Header = ({ showSidebar }) => {
);
const [emailUsed, setEmailUsed] = useState(0);
const [isModal, setIsModal] = useState(false);
const [showNotification, setShowNotification] = useState(
(!dismissedVersion || dismissedVersion !== current_notification_version) &&
true
);
const toggleDropdown = () => {
setIsOpen(!isOpen);
if (width <= 768) {
showSidebar();
}
};
useEffect(() => {
checkSubscription();
@@ -58,6 +67,13 @@ const Header = ({ showSidebar }) => {
} catch (err) {
console.log("err in while fetching monthlyfreeEmails", err);
}
} else {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
}
@@ -115,17 +131,34 @@ const Header = ({ showSidebar }) => {
const handleMailUsed = () => {
setIsModal(!isModal);
};
// Handle dismissing the notification
const dismissNotification = () => {
// Store the current version as dismissed in local storage
localStorage.setItem(
"notificationDismissedVersion",
current_notification_version
);
setShowNotification(false);
};
return (
<div>
{isEnableSubscription && (
<div className="shadow py-1 text-center bg-[#CAE4FA] text-[14px] p-1">
{t("header-news")}
<span
className="cursor-pointer font-medium underline text-blue-800"
onClick={() => navigate("/profile")}
{isEnableSubscription && showNotification && (
<div className="flex justify-between items-center shadow py-1 bg-[#CAE4FA] p-1">
<div className="text-center text-[14px] ml-auto">
{t("header-news")}
<span
className="cursor-pointer font-medium underline text-blue-800"
onClick={() => navigate("/profile")}
>
{" " + t("header-news-btn") + "."}
</span>
</div>
<div
className="ml-auto mr-1 cursor-pointer"
onClick={() => dismissNotification()}
>
{" " + t("header-news-btn") + "."}
</span>
<i className="fa-light fa-xmark"></i>
</div>
</div>
)}
<div className="op-navbar bg-base-100 shadow">
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import axios from "axios";
import { handleDownloadPdf, handleToPrint } from "../../constant/Utils";
import { handleToPrint } from "../../constant/Utils";
import { themeColor, emailRegex } from "../../constant/const";
import Loader from "../../primitives/Loader";
import ModalUi from "../../primitives/ModalUi";
@@ -15,7 +15,8 @@ function EmailComponent({
sender,
setIsAlert,
extUserId,
activeMailAdapter
activeMailAdapter,
setIsDownloadModal
}) {
const { t } = useTranslation();
const [emailList, setEmailList] = useState([]);
@@ -129,6 +130,11 @@ function EmailComponent({
}
}
};
const handleClose = () => {
setIsEmail(false);
setEmailValue("");
setEmailList([]);
};
return (
<div>
{/* isEmail */}
@@ -165,9 +171,10 @@ function EmailComponent({
)}
<button
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
onClick={() => {
handleClose();
setIsDownloadModal(true);
}}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
{t("download")}
@@ -263,11 +270,7 @@ function EmailComponent({
<button
type="button"
className="op-btn op-btn-ghost ml-2"
onClick={() => {
setIsEmail(false);
setEmailValue("");
setEmailList([]);
}}
onClick={() => handleClose()}
>
{t("close")}
</button>
+57 -42
View File
@@ -39,7 +39,8 @@ function Header({
clickOnZoomOut,
handleRotationFun,
isDisableRotate,
templateId
templateId,
setIsDownloadModal
}) {
const { t } = useTranslation();
const filterPrefill =
@@ -94,9 +95,13 @@ function Header({
>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
onClick={() => {
if (isCompleted) {
setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
>
<div className="flex flex-row">
<i
@@ -209,57 +214,67 @@ function Header({
sideOffset={5}
>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfDetails[0]?.URL,
setIsDownloading
)
handleDownloadPdf(pdfDetails, setIsDownloading)
}
>
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
<span className="font-[500]">{t("download")}</span>
<div className="flex flex-row">
<i
className="fa-light fa-arrow-down mr-[3px]"
aria-hidden="true"
></i>
<span className="font-[500]">
{t("download")}
</span>
</div>
</DropdownMenu.Item>
{!isDisableRotate && (
<>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() => handleRotationFun(90)}
>
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-right")}
</span>
<div className="flex flex-row">
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-right")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() => handleRotationFun(-90)}
>
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-left")}
</span>
<div className="flex flex-row">
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("rotate-left")}
</span>
</div>
</DropdownMenu.Item>
</>
)}
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() => clickOnZoomIn()}
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
<span className="font-[500]">{t("zoom-in")}</span>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">{t("zoom-in")}</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="flex flex-row justify-center items-center text-[13px] focus:outline-none cursor-pointer"
className="DropdownMenuItem"
onClick={() => clickOnZoomOut()}
>
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
<span className="font-[500]">{t("zoom-out")}</span>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("zoom-out")}
</span>
</div>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Portal>
@@ -372,9 +387,13 @@ function Header({
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
onClick={() => {
if (isCompleted) {
setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
}
}}
>
<i
className="fa-light fa-download py-[3px]"
@@ -429,11 +448,7 @@ function Header({
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
handleDownloadPdf(pdfDetails, setIsDownloading)
}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
@@ -473,9 +488,9 @@ function Header({
<button
type="button"
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
onClick={() =>
handleDownloadPdf(pdfDetails, pdfUrl, setIsDownloading)
}
onClick={() => {
setIsDownloadModal(true);
}}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("download")}</span>
+76 -22
View File
@@ -2038,29 +2038,30 @@ export const fetchUrl = async (url, pdfName) => {
console.error("Error downloading the file:", error);
}
};
export const getSignedUrl = async (pdfUrl, docId) => {
//use only axios here due to public template sign
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl, docId: docId || "" },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
const url = axiosRes.data.result;
return url;
};
//handle download signed pdf
export const handleDownloadPdf = async (
pdfDetails,
pdfUrl,
setIsDownloading
) => {
const pdfName = pdfDetails[0] && pdfDetails[0].Name;
export const handleDownloadPdf = async (pdfDetails, setIsDownloading) => {
const pdfName = pdfDetails[0] && pdfDetails[0]?.Name;
const pdfUrl = pdfDetails?.[0]?.SignedUrl || pdfDetails?.[0]?.URL;
setIsDownloading("pdf");
const docId = !pdfDetails?.[0]?.IsEnableOTP ? pdfDetails?.[0]?.objectId : "";
try {
// const url = await Parse.Cloud.run("getsignedurl", { url: pdfUrl });
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl, docId: docId },
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
const url = axiosRes.data.result;
const url = await getSignedUrl(pdfUrl, docId);
await fetchUrl(url, pdfName);
setIsDownloading("");
} catch (err) {
@@ -2130,13 +2131,18 @@ export const handleToPrint = async (
//handle download signed pdf
export const handleDownloadCertificate = async (
pdfDetails,
setIsDownloading
setIsDownloading,
isZip
) => {
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try {
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
const certificateUrl = pdfDetails[0] && pdfDetails[0]?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
if (isZip) {
return certificateUrl;
} else {
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
}
} catch (err) {
console.log("err in download in certificate", err);
}
@@ -2162,7 +2168,13 @@ export const handleDownloadCertificate = async (
if (doc?.CertificateUrl) {
await fetch(doc?.CertificateUrl);
const certificateUrl = doc?.CertificateUrl;
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
if (isZip) {
setIsDownloading("");
return certificateUrl;
} else {
saveAs(certificateUrl, `Certificate_signed_by_OpenSign™.pdf`);
}
setIsDownloading("");
} else {
setIsDownloading("certificate");
@@ -2462,3 +2474,45 @@ export const handleRotateWarning = (signerPos, pageNumber) => {
return false;
}
};
// `generateTitleFromFilename` to generate Title of document from file name
export function generateTitleFromFilename(filename) {
try {
// Step 1: Trim whitespace
let title = filename.trim();
// Step 2: Remove the file extension (everything after the last '.')
const lastDotIndex = title.lastIndexOf(".");
if (lastDotIndex > 0) {
title = title.substring(0, lastDotIndex);
}
// Step 3: Replace special characters (except Unicode letters, digits, spaces, and hyphens)
title = title.replace(/[^\p{L}\p{N}\s-]/gu, " ");
// Step 4: Replace multiple spaces with a single space
title = title.replace(/\s+/g, " ");
// Step 5: Capitalize first letter of each word (Title Case), handling Unicode characters
title = title.replace(
/\p{L}\S*/gu,
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
);
// Step 6: Restrict length of title (optional, let's say 100 characters)
if (title.length > 100) {
title = title.substring(0, 100).trim();
}
// Step 7: Handle empty or invalid title by falling back to "Untitled Document"
if (!title || title.length === 0) {
return "Untitled Document";
}
return title;
} catch (error) {
// Handle unexpected errors gracefully by returning a default title
console.error("Error generating title from filename:", error);
return "Untitled Document";
}
}
+10 -1
View File
@@ -10,6 +10,15 @@ export default function reportJson(id) {
"Owner",
"Signers"
];
const iphead = [
"Sr.No",
"Title",
"Note",
"Folder",
"File",
"Status",
"Signers"
];
const contactbook = ["Sr.No", "Name", "Email", "Phone"];
const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"];
@@ -63,7 +72,7 @@ export default function reportJson(id) {
case "1MwEuxLEkF":
return {
reportName: "In-progress documents",
heading: head,
heading: iphead,
actions: [
{
btnId: "8901",
+1 -1
View File
@@ -74,7 +74,7 @@ const DebugPdf = () => {
const inferPdfType = async (pdf) => {
try {
const firstPage = await pdf.getPage(2);
const firstPage = await pdf.getPage(pdf?.numPages > 1 ? 2 : 1);
const scale = 1;
const { width, height } = firstPage.getViewport({ scale });
setPdfDimension({ width: width, height: height });
+24 -4
View File
@@ -9,7 +9,12 @@ import SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
import { SaveFileSize } from "../constant/saveFileSize";
import { checkIsSubscribed, getFileName, toDataUrl } from "../constant/Utils";
import {
checkIsSubscribed,
generateTitleFromFilename,
getFileName,
toDataUrl
} from "../constant/Utils";
import { PDFDocument } from "pdf-lib";
import axios from "axios";
import { isEnableSubscription } from "../constant/const";
@@ -143,7 +148,7 @@ const Forms = (props) => {
setIsDecrypting(true);
const size = files?.[0].size;
const name = generatePdfName(16);
const url = "https://ai.nxglabs.in/decryptpdf"; //"https://ai.nxglabs.in/decryptpdf"; //
const url = "https://ai.nxglabs.in/decryptpdf"; //
let formData = new FormData();
formData.append("file", files[0]);
formData.append("password", "");
@@ -186,6 +191,9 @@ const Forms = (props) => {
if (parseFile.url()) {
setFileUpload(parseFile.url());
setfileload(false);
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId);
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, parseFile.url(), tenantId);
return parseFile.url();
@@ -259,6 +267,8 @@ const Forms = (props) => {
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
@@ -286,12 +296,17 @@ const Forms = (props) => {
if (res.data) {
setFileUpload(res.data.url);
setfileload(false);
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
}
} catch (err) {
e.target.value = "";
setfileload(false);
setpercentage(0);
console.log("err in libreconverter ", err);
alert(
"We are currently experiencing some issues with processing DOCX files. Please upload the PDF version or contact us on support@opensignlabs.com"
);
}
}
}
@@ -330,6 +345,8 @@ const Forms = (props) => {
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(file.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
@@ -367,9 +384,10 @@ const Forms = (props) => {
});
setFileUpload(response.url());
setfileload(false);
if (response.url()) {
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(file.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, response.url(), tenantId);
return response.url();
}
@@ -548,6 +566,8 @@ const Forms = (props) => {
setFormData((prev) => ({ ...prev, password: "" }));
setFileUpload(parseFile.url());
setfileload(false);
const title = generateTitleFromFilename(formData?.file?.name);
setFormData((obj) => ({ ...obj, Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, parseFile.url(), tenantId);
return parseFile.url();
@@ -827,7 +847,7 @@ const Forms = (props) => {
</div>
)}
{isAdvanceOpt && (
<div className={` overflow-y-auto z-[500] transition-all`}>
<div className="overflow-y-auto z-[500] transition-all">
{props.title === "Request Signatures" && (
<div className="text-xs mt-2">
<label className="block">
+26 -15
View File
@@ -28,7 +28,6 @@ import {
contractUsers,
handleSendOTP,
contactBook,
handleDownloadPdf,
handleToPrint,
handleDownloadCertificate,
getDefaultSignature,
@@ -36,21 +35,22 @@ import {
onClickZoomOut,
fetchUrl
} from "../constant/Utils";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
import PdfDeclineModal from "../primitives/PdfDeclineModal";
import Title from "../components/Title";
import DefaultSignature from "../components/pdf/DefaultSignature";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Loader from "../primitives/Loader";
import { useSelector } from "react-redux";
import SignerListComponent from "../components/pdf/SignerListComponent";
import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom";
import { useTranslation } from "react-i18next";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
import Loader from "../primitives/Loader";
import PdfDeclineModal from "../primitives/PdfDeclineModal";
function PdfRequestFiles(props) {
const { t } = useTranslation();
@@ -136,6 +136,7 @@ function PdfRequestFiles(props) {
const [plancode, setPlanCode] = useState("");
const isHeader = useSelector((state) => state.showHeader);
const divRef = useRef(null);
const [isDownloadModal, setIsDownloadModal] = useState(false);
const isMobile = window.innerWidth < 767;
@@ -1658,6 +1659,8 @@ function PdfRequestFiles(props) {
<Confetti
width={window.innerWidth}
height={window.innerHeight}
recycle={false} // Prevents confetti from repeating
gravity={0.1} // Adjust the gravity to control the speed
/>
</div>
)}
@@ -1670,7 +1673,8 @@ function PdfRequestFiles(props) {
: "auto"
}}
className={`${
props.templateId && "m-1 border-[0.5px] border-gray-300"
(props.templateId || isGuestSignFlow) &&
"mx-2 border-[0.5px] border-gray-300"
} relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300`}
>
{!requestSignTour &&
@@ -1993,13 +1997,13 @@ function PdfRequestFiles(props) {
<button
type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-primary"
onClick={() =>
handleDownloadPdf(
pdfDetails,
pdfUrl,
setIsDownloading
)
}
onClick={() => {
setIsCompleted((prev) => ({
...prev,
isModal: false
}));
setIsDownloadModal(true);
}}
>
<i
className="fa-light fa-download"
@@ -2079,6 +2083,7 @@ function PdfRequestFiles(props) {
clickOnZoomOut={clickOnZoomOut}
isDisableRotate={true}
templateId={props.templateId}
setIsDownloadModal={setIsDownloadModal}
/>
<div ref={divRef} data-tut="pdfArea" className="h-[95%]">
@@ -2205,6 +2210,12 @@ function PdfRequestFiles(props) {
</button>
</div>
</ModalUi>
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal}
pdfDetails={pdfDetails}
isDocId={true}
/>
</>
)}
</DndProvider>
+12 -13
View File
@@ -11,8 +11,6 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import WidgetComponent from "../components/pdf/WidgetComponent";
import Tour from "reactour";
import { useLocation, useParams } from "react-router-dom";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import SignerListPlace from "../components/pdf/SignerListPlace";
import Header from "../components/pdf/PdfHeader";
import { RWebShare } from "react-web-share";
@@ -45,23 +43,25 @@ import {
import RenderPdf from "../components/pdf/RenderPdf";
import { useNavigate } from "react-router-dom";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import LinkUserModal from "../primitives/LinkUserModal";
import Title from "../components/Title";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import WidgetNameModal from "../components/pdf/WidgetNameModal";
import { SaveFileSize } from "../constant/saveFileSize";
import { EmailBody } from "../components/pdf/EmailBody";
import Upgrade from "../primitives/Upgrade";
import Alert from "../primitives/Alert";
import Loader from "../primitives/Loader";
import { useSelector } from "react-redux";
import PdfZoom from "../components/pdf/PdfZoom";
import LottieWithLoader from "../primitives/DotLottieReact";
import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
import Loader from "../primitives/Loader";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import LinkUserModal from "../primitives/LinkUserModal";
import QuotaCard from "../primitives/QuotaCard";
import LottieWithLoader from "../primitives/DotLottieReact";
import Alert from "../primitives/Alert";
import Upgrade from "../primitives/Upgrade";
function PlaceHolderSign() {
const { t } = useTranslation();
@@ -1250,7 +1250,7 @@ function PlaceHolderSign() {
selector: '[data-tut="addWidgets"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.placeholder-sign-3")}
message={t("tour-mssg.placeholder-sign-4")}
isChecked={handleDontShow}
/>
),
@@ -1261,19 +1261,18 @@ function PlaceHolderSign() {
selector: '[data-tut="pdfArea"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.placeholder-sign-4")}
message={t("tour-mssg.placeholder-sign-5")}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
},
{
selector: '[data-tut="headerArea"]',
content: () => (
<TourContentWithBtn
message={t("tour-mssg.placeholder-sign-5")}
message={t("tour-mssg.placeholder-sign-6")}
isChecked={handleDontShow}
/>
),
+21 -6
View File
@@ -4,7 +4,6 @@ import "../styles/signature.css";
import Parse from "parse";
import Confetti from "react-confetti";
import axios from "axios";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
@@ -42,21 +41,23 @@ import {
import { useParams } from "react-router-dom";
import Tour from "reactour";
import Signedby from "../components/pdf/Signedby";
import HandleError from "../primitives/HandleError";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Title from "../components/Title";
import ModalUi from "../primitives/ModalUi";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import { useSelector } from "react-redux";
import TextFontSetting from "../components/pdf/TextFontSetting";
import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
import Loader from "../primitives/Loader";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
//For signYourself inProgress section signer can add sign and complete doc sign.
function SignYourSelf() {
const { t } = useTranslation();
@@ -130,6 +131,7 @@ function SignYourSelf() {
const [pdfRotateBase64, setPdfRotatese64] = useState("");
const [isRotate, setIsRotate] = useState({ status: false, degree: 0 });
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: true });
const [isDownloadModal, setIsDownloadModal] = useState(false);
const divRef = useRef(null);
const nodeRef = useRef(null);
const [, drop] = useDrop({
@@ -1190,7 +1192,12 @@ function SignYourSelf() {
)}
{isCelebration && (
<div className="relative z-[1000]">
<Confetti width={window.innerWidth} height={window.innerHeight} />
<Confetti
width={window.innerWidth}
height={window.innerHeight}
recycle={false} // Prevents confetti from repeating
gravity={0.1} // Adjust the gravity to control the speed
/>
</div>
)}
<div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300">
@@ -1327,6 +1334,7 @@ function SignYourSelf() {
setIsAlert={setIsAlert}
extUserId={extUserId}
activeMailAdapter={activeMailAdapter}
setIsDownloadModal={setIsDownloadModal}
/>
{/* pdf header which contain funish back button */}
<Header
@@ -1346,6 +1354,7 @@ function SignYourSelf() {
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
widgetsDetails={xyPostion}
setIsDownloadModal={setIsDownloadModal}
/>
<div ref={divRef} data-tut="reactourSecond" className="h-full">
{containerWH?.width && containerWH?.height && (
@@ -1450,6 +1459,12 @@ function SignYourSelf() {
setIsRotate={setIsRotate}
handleRemoveWidgets={handleRemoveWidgets}
/>
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal}
pdfDetails={pdfDetails}
isDocId={true}
/>
<TextFontSetting
isTextSetting={isTextSetting}
setIsTextSetting={setIsTextSetting}
@@ -9,8 +9,6 @@ import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrag, useDrop } from "react-dnd";
import WidgetComponent from "../components/pdf/WidgetComponent";
import Tour from "reactour";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import HandleError from "../primitives/HandleError";
import SignerListPlace from "../components/pdf/SignerListPlace";
import Header from "../components/pdf/PdfHeader";
import WidgetNameModal from "../components/pdf/WidgetNameModal";
@@ -36,18 +34,21 @@ import {
import RenderPdf from "../components/pdf/RenderPdf";
import "../styles/AddUser.css";
import Title from "../components/Title";
import LinkUserModal from "../primitives/LinkUserModal";
import EditTemplate from "../components/pdf/EditTemplate";
import ModalUi from "../primitives/ModalUi";
import AddRoleModal from "../components/pdf/AddRoleModal";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import Parse from "parse";
import { useSelector } from "react-redux";
import PdfZoom from "../components/pdf/PdfZoom";
import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import HandleError from "../primitives/HandleError";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import LinkUserModal from "../primitives/LinkUserModal";
const TemplatePlaceholder = () => {
const { t } = useTranslation();
const navigate = useNavigate();
@@ -0,0 +1,136 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import ModalUi from "./ModalUi";
import {
getSignedUrl,
handleDownloadCertificate,
handleDownloadPdf,
sanitizeFileName
} from "../constant/Utils";
import Loader from "./Loader";
import JSZip from "jszip";
import { saveAs } from "file-saver";
function DownloadPdfZip(props) {
const { t } = useTranslation();
const [selectType, setSelectType] = useState(1);
const [isDownloading, setIsDownloading] = useState(false);
const downloadType = [
{ id: 1, label: t("download-pdf") },
{ id: 2, label: t("pdf-certificate") }
];
const handleDownload = async () => {
if (selectType === 1) {
await handleDownloadPdf(props.pdfDetails, setIsDownloading);
setSelectType(1);
props.setIsDownloadModal(false);
} else if (selectType === 2) {
setIsDownloading("pdf");
const zip = new JSZip();
const pdfDetails = props.pdfDetails;
const pdfName = pdfDetails?.[0]?.Name || "Document";
const pdfUrl = pdfDetails?.[0]?.SignedUrl || "";
try {
// Fetch the first PDF (Signed Document)
const docId =
props.isDocId && !pdfDetails?.[0]?.IsEnableOTP
? pdfDetails?.[0]?.objectId
: "";
const signedUrl = await getSignedUrl(pdfUrl, docId);
const pdf1Response = await fetch(signedUrl);
if (!pdf1Response.ok) {
throw new Error(`Failed to fetch PDF: ${signedUrl}`);
}
const pdf1Blob = await pdf1Response.blob();
const isZip = true;
// Fetch the Certificate (or generate its URL dynamically)
const certificateUrl = await handleDownloadCertificate(
pdfDetails,
setIsDownloading,
isZip
);
const pdf2Response = await fetch(certificateUrl);
if (!pdf2Response.ok) {
throw new Error(`Failed to fetch certificate PDF: ${certificateUrl}`);
}
const pdf2Blob = await pdf2Response.blob();
// Add files to ZIP
zip.file(
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.pdf`,
pdf1Blob
);
zip.file("Certificate_signed_by_OpenSign™.pdf", pdf2Blob);
// Generate the ZIP and trigger download
const zipBlob = await zip.generateAsync({ type: "blob" });
saveAs(
zipBlob,
`${sanitizeFileName(pdfName)}_signed_by_OpenSign™.zip`
);
setSelectType(1);
props.setIsDownloadModal(false);
setIsDownloading("");
} catch (error) {
console.error("Error creating ZIP file:", error);
}
}
};
return (
<ModalUi
isOpen={props.isDownloadModal}
title={t("download-files")}
handleClose={() => props.setIsDownloadModal(false)}
>
<div className="p-[20px] h-full">
{downloadType.map((data, ind) => {
return (
<label
key={ind}
className="flex items-center gap-1 mb-2 cursor-pointer"
>
<input
className="mr-[8px] op-radio op-radio-xs"
type="radio"
value={data.id}
onChange={() => setSelectType(data.id)}
checked={selectType === data.id}
/>
{data.label}
</label>
);
})}
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<button
onClick={() => handleDownload()}
type="submit"
className="op-btn op-btn-primary"
>
{t("download")}
</button>
</div>
{isDownloading === "pdf" && (
<div className="fixed z-[200] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<ModalUi
isOpen={isDownloading === "certificate"}
title={
isDownloading === "certificate"
? t("generating-certificate")
: t("pdf-download")
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
{isDownloading === "certificate"}{" "}
<p>{t("generate-certificate-alert")}</p>
</div>
</ModalUi>
</ModalUi>
);
}
export default DownloadPdfZip;
+122 -56
View File
@@ -14,6 +14,7 @@ import {
checkIsSubscribed,
copytoData,
fetchUrl,
getSignedUrl,
replaceMailVaribles
} from "../constant/Utils";
import Confetti from "react-confetti";
@@ -30,6 +31,7 @@ import SubscribeCard from "./SubscribeCard";
import { validplan } from "../json/plansArr";
import { serverUrl_fn } from "../constant/appinfo";
import { useTranslation } from "react-i18next";
import DownloadPdfZip from "./DownloadPdfZip";
const ReportTable = (props) => {
const { t } = useTranslation();
@@ -69,7 +71,9 @@ const ReportTable = (props) => {
const [publicUserName, setIsPublicUserName] = useState("");
const [isViewShare, setIsViewShare] = useState({});
const [isSubscribe, setIsSubscribe] = useState(true);
const [isModal, setIsModal] = useState({});
const [reason, setReason] = useState("");
const [isDownloadModal, setIsDownloadModal] = useState(false);
const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class);
const startIndex = (currentPage - 1) * props.docPerPage;
@@ -648,10 +652,15 @@ const ReportTable = (props) => {
setActLoader({ [`${item.objectId}`]: true });
const url = item?.SignedUrl || item?.URL || "";
const pdfName = item?.Name || "exported_file";
const isCompleted = item?.IsCompleted || false;
if (url) {
try {
const signedUrl = await Parse.Cloud.run("getsignedurl", { url: url });
await fetchUrl(signedUrl, pdfName);
if (isCompleted) {
setIsDownloadModal({ [item.objectId]: true });
} else {
const signedUrl = await getSignedUrl(url);
await fetchUrl(signedUrl, pdfName);
}
setActLoader({});
} catch (err) {
console.log("err in getsignedurl", err);
@@ -777,7 +786,7 @@ const ReportTable = (props) => {
sessionToken: localStorage.getItem("accesstoken")
};
let params = {
mailProvider: doc?.ExtUserPtr?.active_mail_adapter,
mailProvider: doc?.ExtUserPtr?.active_mail_adapter || "",
extUserId: doc?.ExtUserPtr?.objectId,
recipient: userDetails?.Email,
subject: mail.subject,
@@ -1125,10 +1134,15 @@ const ReportTable = (props) => {
<Loader />
</div>
)}
<div className="p-2 w-full overflow-auto bg-base-100 text-base-content op-card shadow-lg">
<div className="p-2 w-full overflow-hidden bg-base-100 text-base-content op-card shadow-lg">
{isCelebration && (
<div className="relative z-[1000]">
<Confetti width={window.innerWidth} height={window.innerHeight} />
<Confetti
width={window.innerWidth}
height={window.innerHeight}
recycle={false} // Prevents confetti from repeating
gravity={0.1} // Adjust the gravity to control the speed
/>
</div>
)}
{isAlert && <Alert type={alertMsg.type}>{alertMsg.message}</Alert>}
@@ -1168,8 +1182,8 @@ const ReportTable = (props) => {
</div>
<div
className={`${
isDashboard && props.List?.length > 0 ? "h-[317px]" : "h-full"
} w-full`}
isDashboard && props.List?.length > 0 ? "min-h-[317px]" : "h-full"
} overflow-auto w-full`}
>
<table className="op-table border-collapse w-full ">
<thead className="text-[14px]">
@@ -1205,48 +1219,50 @@ const ReportTable = (props) => {
</td>
<td className="px-4 py-2 ">{item?.Email || "-"}</td>
<td className="px-4 py-2">{item?.Phone || "-"}</td>
<td className="px-3 py-2 text-white grid grid-cols-2">
{props.actions?.length > 0 &&
props.actions.map((act, index) => (
<button
key={index}
onClick={() => handleActionBtn(act, item)}
title={t(`btnLabel.${act.hoverLabel}`)}
className={`${
act?.btnColor ? act.btnColor : ""
} op-btn op-btn-sm`}
<td className="px-3 py-2">
<div className="text-base-content min-w-max flex flex-row gap-x-2 gap-y-1 justify-start items-center">
{props.actions?.length > 0 &&
props.actions.map((act, index) => (
<button
key={index}
onClick={() => handleActionBtn(act, item)}
title={t(`btnLabel.${act.hoverLabel}`)}
className={`${
act?.btnColor ? act.btnColor : ""
} op-btn op-btn-sm`}
>
<i className={act.btnIcon}></i>
</button>
))}
{isDeleteModal[item.objectId] && (
<ModalUi
isOpen
title={"Delete Contact"}
handleClose={handleClose}
>
<i className={act.btnIcon}></i>
</button>
))}
{isDeleteModal[item.objectId] && (
<ModalUi
isOpen
title={"Delete Contact"}
handleClose={handleClose}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("contact-delete-alert")}
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("contact-delete-alert")}
</div>
<hr className="bg-[#ccc] mt-4 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() => handleDelete(item)}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
<hr className="bg-[#ccc] mt-4 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() => handleDelete(item)}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
)}
</ModalUi>
)}
</div>
</td>
</tr>
) : (
@@ -1288,9 +1304,55 @@ const ReportTable = (props) => {
{item?.URL ? t("download") : "-"}
</button>
</td>
<td className="px-4 py-2">
{formatRow(item?.ExtUserPtr)}
</td>
{props.ReportName === "In-progress documents" ? (
<td className="px-4 py-2">
<button
onClick={() =>
item?.AuditTrail?.length > 0 &&
setIsModal({ [item?.objectId]: true })
}
className={`${
item?.AuditTrail?.length
? "border-green-400"
: "cursor-default op-border-primary"
} focus:outline-none w-[60px] border-[2px] text-[12px] rounded-full md:self-center`}
>
{item?.AuditTrail?.length ? "VIEWED" : "SENT"}
</button>
{isModal[item.objectId] && (
<ModalUi
isOpen
title={t("document-logs")}
handleClose={() => setIsModal({})}
>
{item?.AuditTrail?.map((x, i) => (
<div
key={i}
className="pl-3 first:mt-2 text-sm font-medium flex flex-col md:flex-row items-start md:gap-4 border-t-[1px] border-gray-600"
>
<div className="py-2 break-all font-bold md:text-[12px] md:col-span-2 w-full md:w-[210px]">
{x?.UserPtr?.Email || "-"}
</div>
<button className="px-2 cursor-default border-[2px] text-[12px] border-green-400 rounded-full md:self-center">
{x?.Activity?.toUpperCase() || "-"}
</button>
<div className=" text-[12px] py-2">
{x?.Activity === "Signed"
? new Date(x?.SignedOn)?.toUTCString()
: new Date(
x?.ViewedOn
)?.toUTCString() || "-"}
</div>
</div>
))}
</ModalUi>
)}
</td>
) : (
<td className="px-4 py-2">
{formatRow(item?.ExtUserPtr)}
</td>
)}
<td className="px-4 py-2">
{!item?.IsSignyourself && item?.Placeholders ? (
<button
@@ -1308,11 +1370,7 @@ const ReportTable = (props) => {
<td className=" pl-[20px] py-2">
{props.ReportName === "Templates" && (
<div className="flex flex-row">
<label
className={
"cursor-pointer relative inline-flex items-center mb-0"
}
>
<label className="cursor-pointer relative inline-flex items-center mb-0">
<input
checked={props.isPublic?.[item.objectId]}
onChange={(e) =>
@@ -1485,7 +1543,7 @@ const ReportTable = (props) => {
</td>
)}
<td className="px-2 py-2">
<div className="text-base-content flex flex-row gap-x-2 gap-y-1 justify-start items-center">
<div className="text-base-content min-w-max flex flex-row gap-x-2 gap-y-1 justify-start items-center">
{props.actions?.length > 0 &&
props.actions.map((act, index) =>
props.ReportName === "Templates" ? (
@@ -2059,6 +2117,14 @@ const ReportTable = (props) => {
</div>
</ModalUi>
)}
{isDownloadModal[item.objectId] && (
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal[item.objectId]}
pdfDetails={[item]}
isDocId={false}
/>
)}
</td>
</tr>
)
@@ -24,7 +24,7 @@ export default function TourContentWithBtn({ message, isChecked, video }) {
></iframe>
</div>
)}
<label className="flex items-center justify-center mb-0">
<label className="flex items-center justify-center mt-3 mb-0">
<input
type="checkbox"
className="op-checkbox op-checkbox-xs mr-1"
@@ -151,7 +151,8 @@
"Name": "Name",
"Status": "Status",
"created-date": "Created Date",
"Type": "Type"
"Type": "Type",
"Logs": "Logs"
},
"report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -572,9 +573,10 @@
"pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .",
"placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.",
"placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.",
"placeholder-sign-3": "Drag or click on a field to add it to the document.",
"placeholder-sign-4": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-5": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
"placeholder-sign-3": "Click this button to add more recipients/signers to the document.",
"placeholder-sign-4": "Drag or click on a field to add it to the document.",
"placeholder-sign-5": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-6": "Clicking 'Send' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like HR Manager and New Employee. Each time you use the template, you can assign the New Employee role to different incoming staff members, while the HR Manager role remains constant, facilitating a seamless onboarding process for each recruit. ",
"redirect": "Click the 'Use' button to create a new document from an existing template.",
"bulksend": "To quickly create and send multiple documents using an existing template, click the 'Quick Send' button.",
@@ -627,6 +629,9 @@
"regenerate-test-token": "Regenerate Test Token",
"help-test-token": "This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once youve completed your testing, you can upgrade to one of our paid plans to generate a production token.",
"help-api-token": "This token can be used to access the production APIs at the {{origin}}/api/v1 endpoint. It can only be generated on one of our paid plans.",
"reason": "Reason",
"decline-by": "Declined/revoked by",
"document-declined": "Document declined",
"quota-mail-info-head": "Monthly request signatures email limit",
"quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.",
"quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.",
@@ -644,5 +649,9 @@
"p4": "Please choose the option that best suits your document signing requirements."
},
"advanced-options": "Advanced options",
"hide-advanced-options": "Hide Advanced options"
"hide-advanced-options": "Hide Advanced options",
"download-files": "Download files",
"download-pdf": "Download Pdf",
"pdf-certificate": "Download Pdf + Certificate",
"document-logs": "Document logs"
}
@@ -136,7 +136,8 @@
"Name": "Nom",
"Status": "Statut",
"created-date": "Date de création",
"Type": "Saisir"
"Type": "Saisir",
"Logs": "Journaux"
},
"btnLabel": {
"sign": "Signer",
@@ -571,9 +572,10 @@
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.",
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
"placeholder-sign-3": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"placeholder-sign-4": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
"placeholder-sign-5": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
"placeholder-sign-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
"placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"placeholder-sign-5": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
"placeholder-sign-6": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
"bulksend": "Pour créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.",
@@ -626,10 +628,13 @@
"regenerate-test-token": "Régénérer le jeton de test",
"help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à lun de nos forfaits payants pour générer un jeton de production.",
"help-api-token": "Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
"reason": "Raison",
"decline-by": "Refusé/révoqué par",
"document-declined": "document refusé",
"quota-mail-info-head": "Limite mensuelle d'e-mails de signatures de demandes",
"quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.",
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
"quota-mail-tip": "Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
"quota-mail-tip-tip": "Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
"quota-mail-head": "Quota atteint",
"unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
"sent-this-month": "envoyé ce mois-ci",
@@ -643,5 +648,9 @@
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
},
"advanced-options": "Options avancées",
"hide-advanced-options": "Masquer les options avancées"
"hide-advanced-options": "Masquer les options avancées",
"download-files": "Télécharger des fichiers",
"download-pdf": "Télécharger le PDF",
"pdf-certificate": "Télécharger Pdf + Certificat",
"document-logs": "Journaux de documents"
}
+1
View File
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
dotenv.config();
export const cloudServerUrl = 'http://localhost:8080/app';
export const appName = process.env.APP_NAME || 'OpenSign™';
export function customAPIurl() {
const url = new URL(cloudServerUrl);
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
@@ -8,6 +8,7 @@ import saveInvoice from './saveInvoice.js';
import savePayments from './savePayments.js';
import gooogleauth from './googleauth.js';
import autoReminder from './autoReminder.js';
import validateSmtp from './validateSmtp.js';
export const app = express();
dotenv.config();
@@ -22,3 +23,4 @@ app.post('/saveinvoice', saveInvoice);
app.post('/savepayment', savePayments);
app.post('/googleauth', gooogleauth);
app.post('/sendreminder', autoReminder);
app.post('/validatesmtp', validateSmtp);
@@ -315,6 +315,7 @@ export default async function createDocumentWithTemplate(request, response) {
from: sender,
html: html,
extUserId: template.ExtUserPtr.objectId,
mailProvider: template?.ExtUserPtr?.active_mail_adapter || '',
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
@@ -371,6 +371,7 @@ export default async function createDocumentwithCoordinate(request, response) {
from: sender,
html: html,
extUserId: extUser.id,
mailProvider: parseExtUser?.active_mail_adapter || '',
};
await axios.post(url, params, { headers: headers });
@@ -41,6 +41,7 @@ export default async function resendMail(request, response) {
if (resDoc) {
const _resDoc = resDoc.toJSON();
const contact = _resDoc.Signers.find(x => x.Email === userMail);
const activeMailAdapter = _resDoc?.ExtUserPtr?.active_mail_adapter || '';
if (contact) {
try {
const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
@@ -131,6 +132,7 @@ export default async function resendMail(request, response) {
from: sender,
html: html,
extUserId: _resDoc.ExtUserPtr.objectId,
mailProvider: activeMailAdapter,
};
const res = await axios.post(url, params, { headers: headers });
@@ -0,0 +1,72 @@
import { createTransport } from 'nodemailer';
import { appName } from '../../Utils.js';
// Online Javascript Editor for free
// Write, Edit and Run your Javascript code using JS Online Compiler
function generateOtpWithSum10() {
let otp = [];
let sum = 0;
// First, generate 3 random digits (0-9) and add them to the sum
for (let i = 0; i < 3; i++) {
let digit = Math.floor(Math.random() * 10);
otp.push(digit);
sum += digit;
}
// Calculate the last digit to ensure the sum equals 10
let lastDigit = 10 - sum;
// If the last digit is not valid (e.g., greater than 9), regenerate
if (lastDigit >= 0 && lastDigit <= 9) {
otp.push(lastDigit);
} else {
return generateOtpWithSum10(); // Recursively generate again
}
return otp.join('');
}
export default async function validateSmtp(request, response) {
const host = request.body.host;
const port = request.body?.port?.toString();
const username = request.body.username;
const password = request.body.password;
const secure = request.body?.secure;
const email = request.body?.email;
const otp = generateOtpWithSum10();
// console.log('Generated OTP:', otp);
try {
const smtpsecure = secure || port !== '465' ? false : true;
const transporterSMTP = createTransport({
host: host,
port: port,
secure: smtpsecure,
auth: { user: username, pass: password },
});
const from = appName;
const mailsender = username;
const messageParams = {
from: from + ' <' + mailsender + '>',
to: email,
subject: `Your ${appName} SMTP credentials verification code`,
text: 'mail',
html:
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>SMTP Verification Code</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your verification code is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
otp +
'</p></div> </div> </div></body></html>',
};
const res = await transporterSMTP.sendMail(messageParams);
console.log('custom smtp transporter res: ', res?.response);
if (!res.err) {
response.status(200).json({ message: 'success' });
}
} catch (err) {
console.log('err ', err);
const code = err?.responseCode || 400;
const message = err?.response || 'failed!';
response.status(code).json({ error: message });
}
}
+4
View File
@@ -68,6 +68,8 @@ import BuyCredits from './parsefunction/BuyCredits.js';
import getContact from './parsefunction/getContact.js';
import updateContactTour from './parsefunction/updateContactTour.js';
import declinedocument from './parsefunction/declinedocument.js';
import addcustomsmtp from './parsefunction/addcustomsmtp.js';
import deactivateMailAdapter from './parsefunction/deactivateMailAdapter.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);
@@ -148,3 +150,5 @@ Parse.Cloud.define('buycredits', BuyCredits);
Parse.Cloud.define('getcontact', getContact);
Parse.Cloud.define('updatecontacttour', updateContactTour);
Parse.Cloud.define('declinedoc', declinedocument);
Parse.Cloud.define('addsmtp', addcustomsmtp);
Parse.Cloud.define('deactivatemailadapter', deactivateMailAdapter);
@@ -1,4 +1,4 @@
import { smtpenable, updateMailCount } from '../../Utils.js';
import { appName, smtpenable, updateMailCount } from '../../Utils.js';
async function getDocument(docId) {
try {
const query = new Parse.Query('contracts_Document');
@@ -28,12 +28,12 @@ async function sendMailOTPv1(request) {
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
try {
await Parse.Cloud.sendEmail({
from: 'Opensign™' + ' <' + mailsender + '>',
from: appName + ' <' + mailsender + '>',
recipient: recipient,
subject: 'Your OpenSign™ OTP',
subject: `Your ${appName} OTP`,
text: 'This email is a test.',
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for OpenSign™ verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>" +
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for ${appName} verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
code +
'</p></div> </div> </div></body></html>',
});
@@ -0,0 +1,33 @@
export default async function addcustomsmtp(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
const host = request.params.host;
const port = request.params.port;
const username = request.params.username;
const password = request.params.password;
if (host && port && username && password) {
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const extUserCls = new Parse.Object('contracts_Users');
extUserCls.id = extUser.id;
extUserCls.set('SmtpConfig', { host, port, username, password });
extUserCls.set('active_mail_adapter', 'smtp');
const updateExtUser = await extUserCls.save(null, { useMasterKey: true });
// console.log('updateExtUser ', updateExtUser);
return updateExtUser.updatedAt;
}
return extUser;
} catch (err) {
console.log('Err in add custom smtp', 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, 'Please provide all parameters.');
}
}
@@ -74,6 +74,7 @@ async function sendMail(document, sessionToken) {
const orgName = document.ExtUserPtr.Company ? document.ExtUserPtr.Company : '';
const themeBGcolor = '#47a3ad';
let params = {
mailProvider: document?.ExtUserPtr?.active_mail_adapter || '',
extUserId: document.ExtUserPtr.objectId,
recipient: objectId ? existSigner?.Email : signerMail[i].email,
subject: `${document.ExtUserPtr.Name} has requested you to sign "${document.Name}"`,
@@ -0,0 +1,24 @@
export default async function deactivateMailAdapter(request) {
if (!request?.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
try {
const extUserCls = new Parse.Query('contracts_Users');
extUserCls.equalTo('UserId', request.user);
const extUser = await extUserCls.first({ useMasterKey: true });
if (extUser) {
const extUserCls = new Parse.Object('contracts_Users');
extUserCls.id = extUser.id;
extUserCls.unset('active_mail_adapter');
const updateExtUser = await extUserCls.save(null, { useMasterKey: true });
// console.log('updateExtUser ', updateExtUser);
return updateExtUser.updatedAt;
}
return extUser;
} catch (err) {
console.log('Err in add custom smtp', err);
const code = err.code || 400;
const msg = err.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
}
}
@@ -65,6 +65,8 @@ export default function reportJson(id, userId) {
'Folder.Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.Email',
'Signers.Phone',
@@ -100,6 +102,8 @@ export default function reportJson(id, userId) {
'Folder.Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.Email',
'Signers.Phone',
@@ -139,6 +143,7 @@ export default function reportJson(id, userId) {
'TimeToCompleteDays',
'Placeholders',
'IsSignyourself',
'IsCompleted',
],
};
// declined documents report
@@ -227,6 +232,8 @@ export default function reportJson(id, userId) {
'Folder.Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.Email',
'Signers.Phone',
@@ -268,6 +275,8 @@ export default function reportJson(id, userId) {
'Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.UserId',
'AuditTrail',
@@ -335,6 +344,7 @@ export default function reportJson(id, userId) {
'Folder.Name',
'URL',
'ExtUserPtr.Name',
'ExtUserPtr.active_mail_adapter',
'Signers.Name',
'Signers.Email',
'Signers.Phone',
@@ -75,15 +75,21 @@ const makeEmail = async (to, from, subject, html, url, pdfName) => {
}
}
const attachmentParts = attachments.map(attachment => {
const content = fs.readFileSync(attachment.path);
const encodedContent = content.toString('base64');
return [
`Content-Type: ${attachment.type}\n`,
'MIME-Version: 1.0\n',
`Content-Disposition: attachment; filename="${attachment.filename}"\n`,
`Content-Transfer-Encoding: base64\n\n`,
`${encodedContent}\n`,
].join('');
if (fs.existsSync(attachment.path)) {
try {
const content = fs.readFileSync(attachment.path);
const encodedContent = content.toString('base64');
return [
`Content-Type: ${attachment.type}\n`,
'MIME-Version: 1.0\n',
`Content-Disposition: attachment; filename="${attachment.filename}"\n`,
`Content-Transfer-Encoding: base64\n\n`,
`${encodedContent}\n`,
].join('');
} catch (err) {
console.log('Err in read attachments sendmailv3', attachment.path);
}
}
});
const attachmentBody = attachmentParts.join(`\n--${boundary}\n`);
@@ -187,6 +187,111 @@ async function sendMailProvider(req, plan, monthchange) {
}
}
}
async function sendcustomsmtp(extRes, req) {
const smtpsecure = extRes.SmtpConfig.port !== '465' ? false : true;
const transporterSMTP = createTransport({
host: extRes.SmtpConfig.host,
port: extRes.SmtpConfig.port,
secure: smtpsecure,
auth: { user: extRes.SmtpConfig.username, pass: extRes.SmtpConfig.password },
});
if (req.params.url) {
let Pdf = fs.createWriteStream('test.pdf');
const writeToLocalDisk = () => {
return new Promise((resolve, reject) => {
if (useLocal !== 'true') {
https.get(req.params.url, async function (response) {
response.pipe(Pdf);
response.on('end', () => resolve('success'));
});
} else {
const path = new URL(req.params.url)?.pathname;
const localurl = 'http://localhost:8080' + path;
http.get(localurl, async function (response) {
response.pipe(Pdf);
response.on('end', () => resolve('success'));
});
}
});
};
// `writeToLocalDisk` is used to create pdf file from doc url
const ress = await writeToLocalDisk();
if (ress) {
function readTolocal() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let PdfBuffer = fs.readFileSync(Pdf.path);
resolve(PdfBuffer);
}, 100);
});
}
// `PdfBuffer` used to create buffer from pdf file
let PdfBuffer = await readTolocal();
const pdfName = req.params.pdfName ? `${req.params.pdfName}.pdf` : 'exported.pdf';
const file = { filename: pdfName, content: PdfBuffer };
let attachment;
const certificatePath = './exports/certificate.pdf';
if (fs.existsSync(certificatePath)) {
try {
// `certificateBuffer` used to create buffer from pdf file
const certificateBuffer = fs.readFileSync(certificatePath);
const certificate = { filename: 'certificate.pdf', content: certificateBuffer };
attachment = [file, certificate];
} catch (err) {
attachment = [file];
console.log('Err in read certificate sendmailv3', err);
}
} else {
attachment = [file];
}
const from = req.params.from || '';
const mailsender = extRes.SmtpConfig.username;
const messageParams = {
from: from + ' <' + mailsender + '>',
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
attachments: attachment,
};
const res = await transporterSMTP.sendMail(messageParams);
console.log('custom smtp transporter res: ', res?.response);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId); //, plan, monthchange
}
if (fs.existsSync(certificatePath)) {
try {
fs.unlinkSync(certificatePath);
} catch (err) {
console.log('Err in unlink certificate sendmailv3');
}
}
return { status: 'success', code: 200 };
}
}
} else {
const from = req.params.from || '';
const mailsender = extRes.SmtpConfig.username;
const messageParams = {
from: from + ' <' + mailsender + '>',
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
};
const res = await transporterSMTP.sendMail(messageParams);
console.log('custom smtp transporter res: ', res?.response);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId); //, plan, monthchange
}
return { status: 'success', code: 200 };
}
}
}
async function sendmailv3(req) {
const mailProvider = req.params.mailProvider || 'default';
if (mailProvider) {
@@ -206,7 +311,11 @@ async function sendmailv3(req) {
const extRes = await extUserQuery.get(extUserId, { useMasterKey: true });
if (extRes) {
const _extRes = JSON.parse(JSON.stringify(extRes));
if (_extRes.google_refresh_token && mailProvider === 'google') {
if (
_extRes.active_mail_adapter === 'google' &&
_extRes.google_refresh_token &&
mailProvider === 'google'
) {
const res = await sendMailGmailProvider(_extRes, template);
if (res.code === 200) {
await updateMailCount(req.params.extUserId);
@@ -214,6 +323,14 @@ async function sendmailv3(req) {
} else {
return { status: 'error' };
}
} else if (_extRes.active_mail_adapter === 'smtp' && mailProvider === 'smtp') {
const res = await sendcustomsmtp(_extRes, req);
if (res.code === 200) {
await updateMailCount(req.params.extUserId);
return { status: 'success' };
} else {
return { status: 'error' };
}
} else {
if (Plan && Plan === 'freeplan') {
let MonthlyFreeEmails = _extRes?.MonthlyFreeEmails || 0;
@@ -10,7 +10,7 @@
<br />
<p><a href="{{{link}}}" style="background-color: lightskyblue; cursor: pointer; border-radius: 5px; padding: 10px; border-style: solid; border-width: 2px; text-decoration: none; font-weight: bolder; color:blue">Verify email</a></p>
<br />
<p><a href="https://www.opensignlabs.com">OpenSign™</a></p>
<p><a href="https://www.opensignlabs.com">{{appName}}</a></p>
</body>
</html>
@@ -1 +1 @@
OpenSign™ Email Address Verification
{{appName}} Email Address Verification
+4 -9
View File
@@ -17,7 +17,7 @@ import { exec } from 'child_process';
import { createTransport } from 'nodemailer';
import { app as v1 } from './cloud/customRoute/v1/apiV1.js';
import { PostHog } from 'posthog-node';
import { cloudServerUrl, smtpenable, smtpsecure, useLocal } from './Utils.js';
import { appName, cloudServerUrl, smtpenable, smtpsecure, useLocal } from './Utils.js';
import { SSOAuth } from './auth/authadapter.js';
let fsAdapter;
if (useLocal !== 'true') {
@@ -102,7 +102,7 @@ export const config = {
verifyUserEmails: false,
publicServerURL: process.env.SERVER_URL || cloudServerUrl,
// Your apps name. This will appear in the subject and body of the emails that are sent.
appName: 'Opensign',
appName: appName,
allowClientClassCreation: false,
allowExpiredAuthDataToken: false,
encodeParseObjectInCloudFunction: true,
@@ -112,7 +112,7 @@ export const config = {
module: 'parse-server-api-mail-adapter',
options: {
// The email address from which emails are sent.
sender: 'Opensign™' + ' <' + mailsender + '>',
sender: appName + ' <' + mailsender + '>',
// The email templates.
templates: {
// The template used by Parse Server to send an email for password
@@ -141,12 +141,7 @@ export const config = {
}
: {}),
filesAdapter: fsAdapter,
auth: {
google: {
enabled: true,
},
sso: SSOAuth,
},
auth: { google: { enabled: true }, sso: SSOAuth },
};
// Client-keys like the javascript key or the .NET key are not necessary with parse-server
// If you wish you require them, you can set them as options in the initialization above: