mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-03 16:28:56 +02:00
Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2afe37942 | ||
|
|
41c3b4746d | ||
|
|
f52d013b06 | ||
|
|
0045603cb0 | ||
|
|
4235bc077d | ||
|
|
58b3cc6ba2 | ||
|
|
97f5d09306 | ||
|
|
fa98972b27 | ||
|
|
76848b7e09 | ||
|
|
c3faaa65b9 | ||
|
|
e819513650 | ||
|
|
dd0e2f0e58 | ||
|
|
fa08efeac6 | ||
|
|
f3ba02496c | ||
|
|
5cbbb00f66 | ||
|
|
7050df8b4c | ||
|
|
4fb27f4896 | ||
|
|
145aa5da97 | ||
|
|
1a455415f1 | ||
|
|
44ef1fe825 | ||
|
|
cb07e376d5 | ||
|
|
9b472a6b8b | ||
|
|
9840fe9412 | ||
|
|
d1a99d5211 | ||
|
|
5f7736882b | ||
|
|
d8095e077a | ||
|
|
c8fae00ce5 | ||
|
|
7c6a03a2d2 | ||
|
|
40c21321dd | ||
|
|
476dbb555d | ||
|
|
19b4bfb9ef | ||
|
|
e9a85162e7 | ||
|
|
960c297995 | ||
|
|
50e525f448 | ||
|
|
c86f048498 | ||
|
|
39fd2f697f | ||
|
|
3f2cf3ddf4 | ||
|
|
5a53a70dcf | ||
|
|
66323f9347 | ||
|
|
c65f17a216 | ||
|
|
9bf93fb231 | ||
|
|
f418e02270 | ||
|
|
403254a5c6 | ||
|
|
c893d96ad3 | ||
|
|
3971118af8 | ||
|
|
4db0d93f41 | ||
|
|
124fbfd798 | ||
|
|
893e433805 |
+2
-2
@@ -77,6 +77,6 @@ Engage with the community on our [Gitter Channel](https://gitter.im/OpenSignLabs
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under its [MIT License](LICENSE).
|
||||
By contributing, you agree that your contributions will be licensed under its [AGPL-V3 License](LICENSE).
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* All content that resides under the "apps/OpenSignServer/cloud/customRoute" directory of this repository, if that directory exists, is licensed under the license defined in "apps/OpenSignServer/cloud/customRoute".
|
||||
* All third party components incorporated into the OpenSign Software are licensed under the original license provided by the owner of the applicable component.
|
||||
* Content outside of the above mentioned directories or restrictions above is available under the "GNU AFFERO GENERAL PUBLIC LICENSE" as defined below -
|
||||
|
||||
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
|
||||
Generated
+1067
-254
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@lottiefiles/dotlottie-react": "^0.9.3",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.1.4",
|
||||
"@radix-ui/themes": "^3.1.6",
|
||||
"@react-pdf/renderer": "^4.0.0",
|
||||
"@reduxjs/toolkit": "^2.3.0",
|
||||
"axios": "^1.7.7",
|
||||
@@ -34,13 +34,13 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.0.3",
|
||||
"react-i18next": "^15.1.1",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.1.1",
|
||||
"react-quill-new": "^3.3.3",
|
||||
"react-redux": "^9.1.2",
|
||||
"react-rnd": "^10.4.13",
|
||||
"react-router-dom": "^6.27.0",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.8.3",
|
||||
@@ -54,7 +54,7 @@
|
||||
"regex-parser": "^2.3.0",
|
||||
"serve": "^14.2.4",
|
||||
"styled-components": "^5.3.0",
|
||||
"web-vitals": "^4.2.3",
|
||||
"web-vitals": "^4.2.4",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -103,17 +103,17 @@
|
||||
"babel-loader": "^9.2.1",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.0.1",
|
||||
"concurrently": "^9.1.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.14",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"eslint": "^9.13.0",
|
||||
"eslint": "^9.14.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-react": "^7.37.1",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"lint-staged": "^15.2.10",
|
||||
"mini-css-extract-plugin": "^2.9.1",
|
||||
"postcss": "^8.4.48",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-quick": "^4.0.0",
|
||||
"tailwindcss": "^3.4.14",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"header-news": "New feature: Public profiles are now live! Easily create and share public templates",
|
||||
"header-news": "New feature: Teams plan users can now integrate their own AWS S3 buckets for file storage",
|
||||
"header-news-btn": "Setup Now",
|
||||
"create-account": "Create Account",
|
||||
"login": "Login",
|
||||
@@ -134,7 +134,8 @@
|
||||
"option": "Option",
|
||||
"Embed": "Embed",
|
||||
"Copy TemplateId":"Copy TemplateId",
|
||||
"Copy Public URL":"Copy Public URL"
|
||||
"Copy Public URL":"Copy Public URL",
|
||||
"extend-expiry-date": "Extend expiry date"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Sr.No",
|
||||
@@ -155,7 +156,8 @@
|
||||
"Status": "Status",
|
||||
"created-date": "Created Date",
|
||||
"Type": "Type",
|
||||
"Logs": "Logs"
|
||||
"Logs": "Logs",
|
||||
"Expiry-date": "Expiry Date"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "These are documents you have started but have not finalized for sending.",
|
||||
@@ -266,7 +268,8 @@
|
||||
"make-template-public-alert": "Are you sure you want to make this template public?",
|
||||
"make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.",
|
||||
"public-role": "Public role",
|
||||
"public-url": "Public profile needed",
|
||||
"public-url": "Public profile",
|
||||
"embed-template": "Embed Template",
|
||||
"public-url-copy": "Here’s your public URL: ",
|
||||
"public-url-copy-mssg":"Copy it or share it with the signer, and you will be able to see all your publicly set templates.",
|
||||
"add-public-url-alert": "Please complete your OpenSign public profile setup. Once done, you’ll be able to make templates public. This process only needs to be completed once.",
|
||||
@@ -512,7 +515,7 @@
|
||||
"error": "Error",
|
||||
"invalid-document": "Error: invalid document!",
|
||||
"document-not-found": "Document not Found!",
|
||||
"document-signed-alert-1": "You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document will be sent to the owner over email once it is signed by all signers.",
|
||||
"document-signed-alert-1": "You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document & completion certificate will be sent to the owner & all signers over email once it is signed by all signers.",
|
||||
"encrypted-pdf-not-support": "Currently encrypted pdf files are not supported.",
|
||||
"document-signed-alert-4": " Congratulations! 🎉 This document has been successfully signed by all participants!",
|
||||
"document-signed-alert-5": "This document has been signed by all Signers.",
|
||||
@@ -652,6 +655,7 @@
|
||||
"angular-npm-mssg-1": "To integrate OpenSign into your Angular project, simply run the following command:",
|
||||
"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-reset": "Your signature request email credits will be reset on",
|
||||
"quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.",
|
||||
"quota-mail-tip":"Tip: You can still sign <1>unlimited documents</1> by manually sharing the signing request links.",
|
||||
"quota-mail-head":"Quota Reached",
|
||||
@@ -674,14 +678,14 @@
|
||||
"document-logs":"Document logs",
|
||||
"server-down": "Unable to connect to the OpenSign server. If you are self-hosting OpenSign, please ensure that all the steps in the documentation have been followed correctly. If you're running OpenSign locally, you might be accessing it through an incorrect port number.",
|
||||
"admin-exists": "Admin already exists. Please login to the application using admin credentials in order to manage users.",
|
||||
"enable-tour":"Enable Tour",
|
||||
"istourenabled-help":{
|
||||
"enable-tour":"Enable Tour",
|
||||
"istourenabled-help":{
|
||||
"p1":"This will enable a guided tour for signers, providing instructions during the signing process.",
|
||||
"p2":"This will disable the guided tour, ensuring a faster, uninterrupted signing experience.",
|
||||
"p3":"Tip: If your signers are familiar with OpenSign and frequently use it, you may want to disable the guided tour for a more seamless experience. Choose the option that best fits your document signing needs."
|
||||
},
|
||||
"form-title-1":"Document Flow Setting",
|
||||
"form-title-2":"Security Settings",
|
||||
"form-title-1":"Document Flow Setting",
|
||||
"form-title-2":"Security Settings",
|
||||
"public-tour-message":"The template needs to be public before you can generate a shareable link.",
|
||||
"add-user-template": "You need to add a role before you can add fields for it.",
|
||||
"pdf-uncompatible": "This pdf is not compatible with opensign, please contact <support@opensignlabs.com>",
|
||||
@@ -697,5 +701,17 @@
|
||||
"l2": "Enter your name and choose from various font styles to create a signature.",
|
||||
"l3": "Upload an existing digital version of your signature.",
|
||||
"l4": "Use a pre-set signature style saved under 'My Signature' in your settings."
|
||||
}
|
||||
},
|
||||
"notify-on-signatures": "Notify on signatures",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "The document creator will receive an email notification whenever a signer signs the document.",
|
||||
"note": "Note: Notification email will not be sent for the last signer, as the document creator will receive a completion email instead."
|
||||
},
|
||||
"expiry-date": "Expiry Date",
|
||||
"expiry-date-updated": "The expiry date has been successfully extended to {{newexpirydate}}",
|
||||
"expiry-date-error": "Please provide a new expiry date that is later than the current one",
|
||||
"delete-page":"Delete page",
|
||||
"delete-alert":"Can not delete single page",
|
||||
"delete-alert-2":"Are you sure want to delete this page ?",
|
||||
"delete-note":"Note - once delete this page you can not undo"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"header-news": "Nueva funcionalidad: ¡Los perfiles públicos ahora están disponibles! Crea y comparte plantillas públicas fácilmente",
|
||||
"header-news": "Nueva característica: los usuarios del plan Teams ahora pueden integrar sus propios depósitos de AWS S3 para el almacenamiento de archivos",
|
||||
"header-news-btn": "Configurar ahora",
|
||||
"create-account": "Crear cuenta",
|
||||
"login": "Iniciar sesión",
|
||||
@@ -134,7 +134,8 @@
|
||||
"option": "Opción",
|
||||
"Embed": "Incrustar",
|
||||
"Copy TemplateId": "Copiar ID de plantilla",
|
||||
"Copy Public URL": "Copiar URL pública"
|
||||
"Copy Public URL": "Copiar URL pública",
|
||||
"extend-expiry-date": "Ampliar fecha de caducidad"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nº",
|
||||
@@ -155,7 +156,8 @@
|
||||
"Status": "Estado",
|
||||
"created-date": "Fecha de creación",
|
||||
"Type": "Tipo",
|
||||
"Logs": "Registros"
|
||||
"Logs": "Registros",
|
||||
"Expiry-date": "Date d'expiration"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Estos son documentos que has iniciado pero no has finalizado para su envío.",
|
||||
@@ -266,7 +268,8 @@
|
||||
"make-template-public-alert": "¿En definitiva quieres convertir esta plantilla en pública?",
|
||||
"make-template-private-alert": "¿En definitiva quieres convertir esta plantilla en privada? Esto lo removerá de tu perfil público.",
|
||||
"public-role": "Rol público",
|
||||
"public-url": "Se requiere perfil público",
|
||||
"public-url": "Perfil publico",
|
||||
"embed-template": "Insertar plantilla",
|
||||
"public-url-copy": "Aquí está tu URL pública: ",
|
||||
"public-url-copy-mssg": "Cópiala o compártela con el firmante y podrás ver todas tus plantillas públicas.",
|
||||
"add-public-url-alert": "Por favor, completa la configuración de tu perfil público OpenSign. Una vez terminado, podrás crear plantillas públicas. Este proceso solo necesita ser completado una vez.",
|
||||
@@ -512,7 +515,7 @@
|
||||
"error": "Error",
|
||||
"invalid-document": "Error: ¡Documento inválido!",
|
||||
"document-not-found": "¡Documento no encontrado!",
|
||||
"document-signed-alert-1": "Has firmado el documento exitosamente. Puedes descargar o imprimir una copia del documento parcialmente firmado. Una copia del documento firmado digitalmente será enviada al propietario mediante correo electrónico una vez haya sido firmado por todos los firmantes.",
|
||||
"document-signed-alert-1": "Ha firmado exitosamente el documento. Puede descargar o imprimir una copia del documento parcialmente firmado. Se enviará una copia del documento firmado digitalmente y del certificado de finalización al propietario y a todos los firmantes por correo electrónico una vez que todos los firmantes lo hayan firmado.",
|
||||
"encrypted-pdf-not-support": "Los archivos PDF encriptados no están soportados actualmente.",
|
||||
"document-signed-alert-4": " ¡Felicitaciones! 🎉 ¡Este documento ha sido firmado exitosamente por todos los participantes!",
|
||||
"document-signed-alert-5": "Este documento ha sido firmado por todos los firmantes.",
|
||||
@@ -652,6 +655,7 @@
|
||||
"angular-npm-mssg-1": "Para integrar OpenSign a tu proyecto Angular, simplemente ejecuta los siguientes comandos:",
|
||||
"quota-mail-info-head": "Límite mensual de solicitudes de firma por correo",
|
||||
"quota-mail-info": "Puedes enviar hasta 15 solicitudes de firma por correo cada mes. Mejora ahora para enviar solicitudes de firma sin límite.",
|
||||
"quota-mail-reset": "Los créditos de tu correo electrónico de solicitud de firma se restablecerán el",
|
||||
"quota-mail": "Has alcanzado tu límite de 15 solicitudes de firma por correo para este mes. Mejora ahora para continuar enviando correos.",
|
||||
"quota-mail-tip": "Consejo: Todavía puedes solciitar la firma de <1>documentos ilimitados</1> compartiendo manualmente los enlaces de solicitud de firma.",
|
||||
"quota-mail-head": "Cuota alcanzada",
|
||||
@@ -678,24 +682,36 @@
|
||||
"add-user-template": "Es necesario agregar un rol antes de poder agregarle campos.",
|
||||
"pdf-uncompatible": "Este pdf no es compatible con opensign, comuníquese con <support@opensignlabs.com>",
|
||||
"enable-tour": "Habilitar recorrido",
|
||||
"istourenabled-help":{
|
||||
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
|
||||
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
|
||||
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
|
||||
"istourenabled-help":{
|
||||
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
|
||||
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
|
||||
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
|
||||
},
|
||||
"form-title-1": "Configuración del flujo de documentos",
|
||||
"form-title-2": "Configuración de seguridad",
|
||||
"text-field-tour":"Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
|
||||
"attach-signer-tour" :"Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
|
||||
"allowed-signature-types": "Tipos de firma permitidos",
|
||||
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma.",
|
||||
"expect-default-one-signature-type": "Habilite un tipo de firma adicional aparte del predeterminado.",
|
||||
"add-signer-note": "Nota - Este contacto se agregará a su lista de contactos",
|
||||
"allowed-signature-types-help": {
|
||||
"form-title-1": "Configuración del flujo de documentos",
|
||||
"form-title-2": "Configuración de seguridad",
|
||||
"text-field-tour":"Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
|
||||
"attach-signer-tour" :"Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
|
||||
"allowed-signature-types": "Tipos de firma permitidos",
|
||||
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma.",
|
||||
"expect-default-one-signature-type": "Habilite un tipo de firma adicional aparte del predeterminado.",
|
||||
"add-signer-note": "Nota - Este contacto se agregará a su lista de contactos",
|
||||
"allowed-signature-types-help": {
|
||||
"p1": "Esta preferencia de firma controla qué opciones de firma están disponibles para sus firmantes. Solo los tipos de firma que seleccione aparecerán en el widget de firma cuando se firme un documento. Por ejemplo, si elige sólo la opción 'Draw' en las preferencias, su firmante verá sólo la opción 'Draw' en el widget de firma, mientras que las otras tres opciones no estarán disponibles.",
|
||||
"l1": "Cree una firma manuscrita directamente en la pantalla usando su dedo o un lápiz.",
|
||||
"l2": "Ingrese su nombre y elija entre varios estilos de fuente para crear una firma.",
|
||||
"l3": "Cargue una versión digital existente de su firma.",
|
||||
"l4": "Utilice un estilo de firma preestablecido guardado en 'Mi firma' en su configuración."
|
||||
}
|
||||
},
|
||||
"notify-on-signatures": "Notificar sobre firmas",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "El creador del documento recibirá una notificación por correo electrónico cada vez que un firmante firme el documento.",
|
||||
"note": "Nota: No se enviará un correo electrónico de notificación al último firmante, ya que el creador del documento recibirá un correo electrónico de finalización."
|
||||
},
|
||||
"expiry-date": "Date d'expiration",
|
||||
"expiry-date-updated": "La fecha de vencimiento se ha extendido con éxito hasta el {{newexpirydate}}",
|
||||
"expiry-date-error": "Proporcione una nueva fecha de vencimiento que sea posterior a la actual",
|
||||
"delete-page": "eliminar página",
|
||||
"delete-alert": "No se puede eliminar una sola página",
|
||||
"delete-alert-2": "¿Está seguro de que desea eliminar esta página?",
|
||||
"delete-note": "Nota: una vez que elimines esta página, no podrás deshacerla"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"header-news": "Nouvelle fonctionnalité : les profils publics sont désormais en ligne ! Créez et partagez facilement des modèles publics",
|
||||
"header-news": "Nouvelle fonctionnalité : les utilisateurs du forfait Teams peuvent désormais intégrer leurs propres compartiments AWS S3 pour le stockage de fichiers",
|
||||
"header-news-btn": "Configurer maintenant",
|
||||
"create-account": "Créer un compte",
|
||||
"login": "Se Connecter",
|
||||
@@ -138,7 +138,8 @@
|
||||
"Status": "Statut",
|
||||
"created-date": "Date de création",
|
||||
"Type": "Saisir",
|
||||
"Logs": "Journaux"
|
||||
"Logs": "Journaux",
|
||||
"Expiry-date": "Fecha de caducidad"
|
||||
},
|
||||
"btnLabel": {
|
||||
"sign": "Signer",
|
||||
@@ -154,7 +155,8 @@
|
||||
"option": "Option",
|
||||
"Embed": "Intégrer",
|
||||
"Copy TemplateId": "Copier l'ID du modèle",
|
||||
"Copy Public URL":"Copier l'URL publique"
|
||||
"Copy Public URL":"Copier l'URL publique",
|
||||
"extend-expiry-date": "Prolonger la date d'expiration"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
@@ -265,7 +267,8 @@
|
||||
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
|
||||
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
|
||||
"public-role": "Rôle public",
|
||||
"public-url": "Profil public requis",
|
||||
"public-url": "Profil public",
|
||||
"embed-template": "Intégrer le modèle",
|
||||
"public-url-copy": "Voici votre URL publique : ",
|
||||
"public-url-copy-mssg": "copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
|
||||
"add-public-url-alert": "Veuillez terminer la configuration de votre profil public OpenSign. Une fois cela fait, vous pourrez rendre les modèles publics. Ce processus ne doit être effectué qu’une seule fois.",
|
||||
@@ -511,7 +514,7 @@
|
||||
"error": "Erreur",
|
||||
"invalid-document": "Erreur : document invalide !",
|
||||
"document-not-found": "Document introuvable !",
|
||||
"document-signed-alert-1": "Vous avez signé le document avec succès. Vous pouvez télécharger ou imprimer une copie du document partiellement signé. Une copie du document signé numériquement sera envoyée au propriétaire par e-mail une fois qu'il sera signé par tous les signataires.",
|
||||
"document-signed-alert-1": "Vous avez signé le document avec succès. Vous pouvez télécharger ou imprimer une copie du document partiellement signé. Une copie du document signé numériquement et du certificat d'achèvement sera envoyée au propriétaire et à tous les signataires par e-mail une fois qu'il aura été signé par tous les signataires.",
|
||||
"encrypted-pdf-not-support": "Les fichiers PDF actuellement cryptés ne sont pas pris en charge. ",
|
||||
"document-signed-alert-4": "Toutes nos félicitations! 🎉 Ce document a été signé avec succès par tous les participants !",
|
||||
"document-signed-alert-5": "Ce document a été signé par tous les signataires.",
|
||||
@@ -651,6 +654,7 @@
|
||||
"angular-npm-mssg-1" :"Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
|
||||
"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-reset": "Les crédits de votre email de demande de signature seront réinitialisés le",
|
||||
"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-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",
|
||||
@@ -696,5 +700,17 @@
|
||||
"l2": "Entrez votre nom et choisissez parmi différents styles de police pour créer une signature.",
|
||||
"l3": "Téléchargez une version numérique existante de votre signature.",
|
||||
"l4": "Utilisez un style de signature prédéfini enregistré sous 'Ma signature' dans vos paramètres."
|
||||
}
|
||||
},
|
||||
"notify-on-signatures": "Notifier les signatures",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "Le créateur du document recevra une notification par e-mail chaque fois qu'un signataire signera le document.",
|
||||
"note": "Remarque : L'e-mail de notification ne sera pas envoyé pour le dernier signataire, car le créateur du document recevra à la place un e-mail de fin."
|
||||
},
|
||||
"expiry-date": "Fecha de caducidad",
|
||||
"expiry-date-updated": "La fecha de vencimiento se ha extendido con éxito hasta el {{newexpirydate}}",
|
||||
"expiry-date-error": "Veuillez fournir une nouvelle date d'expiration postérieure à la date actuelle",
|
||||
"delete-page": "supprimer la page",
|
||||
"delete-alert": "Impossible de supprimer une seule page",
|
||||
"delete-alert-2": "Etes-vous sûr de vouloir supprimer cette page ?",
|
||||
"delete-note": "Remarque : une fois cette page supprimée, vous ne pouvez plus l'annuler"
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ const GenerateToken = lazy(() => import("./pages/GenerateToken"));
|
||||
const Webhook = lazy(() => import("./pages/Webhook"));
|
||||
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
|
||||
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
|
||||
const DraftTemplate = lazy(() => import("./pages/DraftTemplate"));
|
||||
const Preferences = lazy(() => import("./pages/Preferences"));
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
||||
@@ -206,10 +205,6 @@ function App() {
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/sso" element={<SSOVerify />} />
|
||||
<Route
|
||||
path="/drafttemplate/:jwttoken"
|
||||
element={<DraftTemplate />}
|
||||
/>
|
||||
<Route path="*" element={<PageNotFound />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
|
||||
@@ -274,6 +274,7 @@ const AddSigner = (props) => {
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
disabled={addYourself}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
placeholder={t("phone-optional")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex gap-x-2 justify-start">
|
||||
|
||||
@@ -431,13 +431,13 @@ const AddUser = (props) => {
|
||||
{t("phone")}
|
||||
{/* <span className="text-[red] text-[13px]"> *</span> */}
|
||||
</label>
|
||||
{/* added hint for phone number being optional */}
|
||||
<input
|
||||
type="text"
|
||||
name="phone"
|
||||
placeholder="optional"
|
||||
placeholder={t("phone-optional")}
|
||||
value={formdata.phone}
|
||||
onChange={(e) => handleChange(e)}
|
||||
// required
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import Parse from "parse";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
import {
|
||||
checkIsSubscribed,
|
||||
formatDate,
|
||||
getAppLogo,
|
||||
openInNewTab,
|
||||
saveLanguageInLocal
|
||||
@@ -33,6 +34,7 @@ const Header = ({ showSidebar, setIsMenu }) => {
|
||||
);
|
||||
const [emailUsed, setEmailUsed] = useState(0);
|
||||
const [isModal, setIsModal] = useState(false);
|
||||
const [emailResetDate, setEmailResetDate] = useState("");
|
||||
const [showNotification, setShowNotification] = useState(
|
||||
(!dismissedVersion || dismissedVersion !== current_notification_version) &&
|
||||
true
|
||||
@@ -64,6 +66,14 @@ const Header = ({ showSidebar, setIsMenu }) => {
|
||||
const extUser = await Parse.Cloud.run("getUserDetails");
|
||||
const MonthlyFreeEmails = extUser?.get("MonthlyFreeEmails") || 0;
|
||||
setEmailUsed(MonthlyFreeEmails);
|
||||
if (extUser) {
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
if (_extUser?.LastEmailCountReset?.iso) {
|
||||
const resetDate = formatDate(_extUser?.LastEmailCountReset?.iso);
|
||||
setEmailResetDate(resetDate);
|
||||
}
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in while fetching monthlyfreeEmails", err);
|
||||
}
|
||||
@@ -140,15 +150,22 @@ const Header = ({ showSidebar, setIsMenu }) => {
|
||||
);
|
||||
setShowNotification(false);
|
||||
};
|
||||
const handleSetupNowNavigation = () => {
|
||||
const url = isStaging
|
||||
? "https://staging-console.opensignlabs.com/fileadapter"
|
||||
: "https://console.opensignlabs.com/fileadapter";
|
||||
|
||||
openInNewTab(url);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{isEnableSubscription && showNotification && (
|
||||
<div className="flex justify-between items-center shadow py-1 bg-[#CAE4FA] p-1">
|
||||
<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")}
|
||||
onClick={() => handleSetupNowNavigation()}
|
||||
>
|
||||
{" " + t("header-news-btn") + "."}
|
||||
</span>
|
||||
@@ -291,7 +308,11 @@ const Header = ({ showSidebar, setIsMenu }) => {
|
||||
</div>
|
||||
</div>
|
||||
<ModalUi isOpen={isModal}>
|
||||
<QuotaCard isPaidInfo={true} handlClose={handleMailUsed} />
|
||||
<QuotaCard
|
||||
isPaidInfo={true}
|
||||
handlClose={handleMailUsed}
|
||||
emailResetDate={emailResetDate}
|
||||
/>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -6,9 +6,9 @@ function RotateAlert(props) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen={props.isRotate}
|
||||
isOpen={props.showRotateAlert}
|
||||
title={t("Rotation-alert")}
|
||||
handleClose={() => props.setIsRotate({ status: false, degree: 0 })}
|
||||
handleClose={() => props.setShowRotateAlert({ status: false, degree: 0 })}
|
||||
>
|
||||
{" "}
|
||||
<div className="p-[20px] h-full">
|
||||
@@ -16,7 +16,9 @@ function RotateAlert(props) {
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => props.setIsRotate({ status: false, degree: 0 })}
|
||||
onClick={() =>
|
||||
props.setShowRotateAlert({ status: false, degree: 0 })
|
||||
}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost shadow-md"
|
||||
>
|
||||
|
||||
@@ -17,7 +17,11 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
IsEnableOTP: template?.IsEnableOTP ? `${template?.IsEnableOTP}` : "false",
|
||||
IsTourEnabled: template?.IsTourEnabled
|
||||
? `${template?.IsTourEnabled}`
|
||||
: "false"
|
||||
: "false",
|
||||
NotifyOnSignatures:
|
||||
template?.NotifyOnSignatures !== undefined
|
||||
? template?.NotifyOnSignatures
|
||||
: false
|
||||
});
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
useEffect(() => {
|
||||
@@ -64,6 +68,11 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
AutomaticReminders: !formData.AutomaticReminders
|
||||
}));
|
||||
};
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
const handleNotifySignChange = (value) => {
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||
};
|
||||
return (
|
||||
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
|
||||
<div className="text-base-content">
|
||||
@@ -120,27 +129,29 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
</div>
|
||||
<div className="mb-[0.35rem]">
|
||||
<label className="text-[13px]">{t("send-in-order")}</label>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.SendinOrder === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("no")}</div>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="SendinOrder"
|
||||
checked={formData.SendinOrder === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-[8px] ml-[8px] mb-[5px]">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.SendinOrder === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-[12px]">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isEnableSubscription && (
|
||||
@@ -156,7 +167,7 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe
|
||||
? "cursor-pointer "
|
||||
? "cursor-pointer"
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0`}
|
||||
>
|
||||
@@ -221,39 +232,40 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1 `}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsEnableOTP"
|
||||
checked={formData.IsEnableOTP === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1 `}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsEnableOTP"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsEnableOTP === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsEnableOTP"
|
||||
checked={formData.IsEnableOTP === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1 `}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsEnableOTP"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsEnableOTP === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-xs mt-2">
|
||||
<label className="block">
|
||||
<span>
|
||||
@@ -283,27 +295,83 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsTourEnabled"
|
||||
checked={formData.IsTourEnabled === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsTourEnabled"
|
||||
checked={formData.IsTourEnabled === "true"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsTourEnabled"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsTourEnabled === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
name="IsTourEnabled"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={formData.IsTourEnabled === "false"}
|
||||
onChange={handleStrInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription ? "" : "text-gray-300"
|
||||
} block`}
|
||||
>
|
||||
{t("notify-on-signatures")}
|
||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
<Tooltip id="nos-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||
<p className="font-bold">{t("notify-on-signatures")}</p>
|
||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||
<p>{t("notify-on-signatures-help.note")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription
|
||||
? ""
|
||||
: "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={formData.NotifyOnSignatures === true}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe || !isEnableSubscription
|
||||
? ""
|
||||
: "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={formData.NotifyOnSignatures === false}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-[1rem] flex justify-start">
|
||||
|
||||
@@ -18,17 +18,16 @@ function EmbedTab(props) {
|
||||
{
|
||||
id: 0,
|
||||
title: "Installation",
|
||||
codeString: `
|
||||
npm install @opensign/react`
|
||||
codeString: `npm install @opensign/react`
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "Usage",
|
||||
codeString: `
|
||||
import React from "react";
|
||||
import React from "react";
|
||||
import Opensign from "@opensign/react";
|
||||
|
||||
export function App() {
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="app">
|
||||
<Opensign
|
||||
@@ -40,7 +39,6 @@ export function App() {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
`
|
||||
}
|
||||
];
|
||||
@@ -49,15 +47,14 @@ export function App() {
|
||||
{
|
||||
id: 0,
|
||||
title: "Installation",
|
||||
codeString: `
|
||||
npm install @opensign/angular`
|
||||
codeString: `npm install @opensign/angular`
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "Usage",
|
||||
codeString: `
|
||||
import { Component } from '@angular/core';
|
||||
import {OpensignComponent} from "@opensign/angular"
|
||||
import { OpensignComponent } from "@opensign/angular"
|
||||
|
||||
@Component({
|
||||
selector:'app-root',
|
||||
@@ -68,17 +65,15 @@ import {OpensignComponent} from "@opensign/angular"
|
||||
(onLoadError)="handleError($event)"
|
||||
></opensign>\`,
|
||||
})
|
||||
export class AppComponent{
|
||||
export class AppComponent {
|
||||
handleLoad() {
|
||||
console.log("success");
|
||||
}
|
||||
handleError(error: string) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
`
|
||||
}
|
||||
];
|
||||
@@ -98,6 +93,9 @@ export class AppComponent{
|
||||
|
||||
return (
|
||||
<div className="mt-4 border-t-[1px]">
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] pb-[5px]">
|
||||
{t("embed-template")}
|
||||
</h3>
|
||||
<div className="flex justify-center items-center mt-2">
|
||||
<div role="tablist" className="op-tabs op-tabs-bordered">
|
||||
{tabName.map((tabData, ind) => (
|
||||
@@ -107,7 +105,7 @@ export class AppComponent{
|
||||
role="tab"
|
||||
className={`${
|
||||
activeTab === ind ? "op-tab-active" : ""
|
||||
} op-tab flex items-center`}
|
||||
} op-tab flex items-center pb-10 md:pb-0`}
|
||||
>
|
||||
<i
|
||||
className={`${tabData.icon}`}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import PrevNext from "./PrevNext";
|
||||
import {
|
||||
deletePdfPage,
|
||||
handleDownloadCertificate,
|
||||
handleDownloadPdf,
|
||||
handleRemoveWidgets,
|
||||
handleToPrint
|
||||
} from "../../constant/Utils";
|
||||
import "../../styles/signature.css";
|
||||
@@ -41,7 +43,13 @@ function Header({
|
||||
isDisableRotate,
|
||||
templateId,
|
||||
setIsDownloadModal,
|
||||
disabledBackBtn
|
||||
disabledBackBtn,
|
||||
setPdfArrayBuffer,
|
||||
setPdfBase64Url,
|
||||
pdfArrayBuffer,
|
||||
setIsUploadPdf,
|
||||
setSignerPos,
|
||||
userId
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const filterPrefill =
|
||||
@@ -49,6 +57,7 @@ function Header({
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||
const enabledBackBtn = disabledBackBtn === true ? false : true;
|
||||
|
||||
//function for show decline alert
|
||||
@@ -56,6 +65,18 @@ function Header({
|
||||
const currentDecline = { currnt: "Sure", isDeclined: true };
|
||||
setIsDecline(currentDecline);
|
||||
};
|
||||
const handleDetelePage = async () => {
|
||||
setIsUploadPdf(true);
|
||||
const pdfupdatedData = await deletePdfPage(pdfArrayBuffer, pageNumber);
|
||||
if (pdfupdatedData?.totalPages === 1) {
|
||||
alert(t("delete-alert"));
|
||||
} else {
|
||||
setPdfBase64Url(pdfupdatedData.base64);
|
||||
setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
setIsDeletePage(false);
|
||||
handleRemoveWidgets(setSignerPos, signerPos, pageNumber, userId);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex py-[5px]">
|
||||
{isMobile && isShowHeader ? (
|
||||
@@ -255,6 +276,17 @@ function Header({
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => setIsDeletePage(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i className="fa-light fa-trash text-gray-500 2xl:text-[30px] mr-[3px]"></i>
|
||||
<span className="font-[500]">
|
||||
{t("delete-page")}
|
||||
</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -553,6 +585,32 @@ function Header({
|
||||
<p>{t("generate-certificate-alert")}</p>
|
||||
</div>
|
||||
</ModalUi>
|
||||
<ModalUi
|
||||
isOpen={isDeletePage}
|
||||
title={t("validation-alert")}
|
||||
handleClose={() => setIsDeletePage(false)}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<p>{t("delete-alert-2")}</p>
|
||||
<p className="pt-3 font-medium">{t("delete-note")}</p>
|
||||
{/* <p>{t("deletepage-alert")}</p> */}
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => handleDetelePage()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDeletePage(false)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,115 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { deletePdfPage, handleRemoveWidgets } from "../../constant/Utils";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
|
||||
function PdfZoom(props) {
|
||||
const { t } = useTranslation();
|
||||
const [isDeletePage, setIsDeletePage] = useState(false);
|
||||
const handleDetelePage = async () => {
|
||||
props.setIsUploadPdf(true);
|
||||
try {
|
||||
const pdfupdatedData = await deletePdfPage(
|
||||
props.pdfArrayBuffer,
|
||||
props.pageNumber
|
||||
);
|
||||
if (pdfupdatedData?.totalPages === 1) {
|
||||
alert(t("delete-alert"));
|
||||
} else {
|
||||
props.setPdfBase64Url(pdfupdatedData.base64);
|
||||
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
|
||||
setIsDeletePage(false);
|
||||
handleRemoveWidgets(
|
||||
props.setSignerPos,
|
||||
props.signerPos,
|
||||
props.pageNumber,
|
||||
props.userId
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in delete pdf page", e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[43px] 2xl:mt-[63px]">
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => props.clickOnZoomIn()}
|
||||
title={t("zoom-in")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
{!props?.isDisableRotate && (
|
||||
<>
|
||||
<>
|
||||
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[43px] 2xl:mt-[63px]">
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => props.clickOnZoomIn()}
|
||||
title={t("zoom-in")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
{!props.isDisableDelete && (
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => props.handleRotationFun(90)}
|
||||
title={t("rotate-right")}
|
||||
onClick={() => setIsDeletePage(true)}
|
||||
title={t("delete-page")}
|
||||
>
|
||||
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px]"></i>
|
||||
<i className="fa-light fa-trash text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
title={t("rotate-left")}
|
||||
onClick={() => props.handleRotationFun(-90)}
|
||||
>
|
||||
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
|
||||
onClick={() => props.clickOnZoomOut()}
|
||||
style={{
|
||||
cursor: props.zoomPercent > 0 ? "pointer" : "default"
|
||||
}}
|
||||
title={t("zoom-out")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
|
||||
)}
|
||||
|
||||
{!props?.isDisableRotate && (
|
||||
<>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
onClick={() => props.handleRotationFun(90)}
|
||||
title={t("rotate-right")}
|
||||
>
|
||||
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
|
||||
title={t("rotate-left")}
|
||||
onClick={() => props.handleRotationFun(-90)}
|
||||
>
|
||||
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<span
|
||||
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
|
||||
onClick={() => props.clickOnZoomOut()}
|
||||
style={{
|
||||
cursor: props.zoomPercent > 0 ? "pointer" : "default"
|
||||
}}
|
||||
title={t("zoom-out")}
|
||||
>
|
||||
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<ModalUi
|
||||
isOpen={isDeletePage}
|
||||
title={t("validation-alert")}
|
||||
handleClose={() => setIsDeletePage(false)}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<p>Are you sure want to delete this page ?</p>
|
||||
<p className="pt-3 font-medium">
|
||||
Note - once delete this page you can not undo
|
||||
</p>
|
||||
{/* <p>{t("deletepage-alert")}</p> */}
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => handleDetelePage()}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsDeletePage(false)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ function RenderAllPdfPage({
|
||||
signerPos,
|
||||
signerObjectId,
|
||||
containerWH,
|
||||
pdfRotateBase64
|
||||
pdfBase64Url
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [signPageNumber, setSignPageNumber] = useState([]);
|
||||
@@ -67,7 +67,7 @@ function RenderAllPdfPage({
|
||||
)
|
||||
);
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${pdfBase64Url}`;
|
||||
return (
|
||||
<div ref={pageContainer} className="hidden w-[20%] bg-base-100 md:block">
|
||||
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
|
||||
@@ -80,7 +80,7 @@ function RenderAllPdfPage({
|
||||
<Document
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={onDocumentLoad}
|
||||
file={(pdfRotateBase64 && pdfDataBase64) || signPdfUrl}
|
||||
file={(pdfBase64Url && pdfDataBase64) || signPdfUrl}
|
||||
>
|
||||
{Array.from(new Array(allPages), (el, index) => (
|
||||
<div
|
||||
|
||||
@@ -61,7 +61,7 @@ function RenderPdf({
|
||||
setIsSelectId,
|
||||
ispublicTemplate,
|
||||
handleUserDetails,
|
||||
pdfRotateBase64,
|
||||
pdfBase64Url,
|
||||
fontSize,
|
||||
setFontSize,
|
||||
fontColor,
|
||||
@@ -256,8 +256,7 @@ function RenderPdf({
|
||||
);
|
||||
}
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
|
||||
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${pdfBase64Url}`;
|
||||
return (
|
||||
<>
|
||||
{successEmail && (
|
||||
@@ -377,10 +376,11 @@ function RenderPdf({
|
||||
return (
|
||||
<React.Fragment key={ind}>
|
||||
{data.pageNumber === pageNumber &&
|
||||
data.pos.map((pos) => {
|
||||
data.pos.map((pos, id) => {
|
||||
return (
|
||||
pos && (
|
||||
<Placeholder
|
||||
key={id}
|
||||
pos={pos}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
@@ -443,7 +443,7 @@ function RenderPdf({
|
||||
}
|
||||
}}
|
||||
file={
|
||||
(pdfRotateBase64 && pdfDataBase64) ||
|
||||
(pdfBase64Url && pdfDataBase64) ||
|
||||
pdfDetails[0]?.SignedUrl ||
|
||||
pdfDetails[0].URL
|
||||
}
|
||||
@@ -640,9 +640,8 @@ function RenderPdf({
|
||||
setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
// ref={pdfRef}
|
||||
file={
|
||||
(pdfRotateBase64 && pdfDataBase64) ||
|
||||
(pdfBase64Url && pdfDataBase64) ||
|
||||
pdfDetails[0]?.SignedUrl ||
|
||||
pdfDetails[0].URL
|
||||
}
|
||||
|
||||
@@ -7,10 +7,7 @@ const DROPBOX_SCRIPT_ID = "dropboxjs";
|
||||
|
||||
export default function DropboxChooser({ children, onSuccess, onCancel }) {
|
||||
useDropScript(DROPBOX_SDK_URL, {
|
||||
attrs: {
|
||||
id: DROPBOX_SCRIPT_ID,
|
||||
"data-app-key": DROPBOX_APP_KEY
|
||||
}
|
||||
attrs: { id: DROPBOX_SCRIPT_ID, "data-app-key": DROPBOX_APP_KEY }
|
||||
});
|
||||
|
||||
const options = useMemo(
|
||||
|
||||
@@ -705,6 +705,9 @@ export const createDocument = async (
|
||||
const SignatureType = Doc?.SignatureType
|
||||
? { SignatureType: Doc?.SignatureType }
|
||||
: {};
|
||||
const NotifyOnSignatures = Doc?.NotifyOnSignatures
|
||||
? { NotifyOnSignatures: Doc?.NotifyOnSignatures }
|
||||
: {};
|
||||
const data = {
|
||||
Name: Doc.Name,
|
||||
URL: pdfUrl,
|
||||
@@ -730,7 +733,8 @@ export const createDocument = async (
|
||||
IsEnableOTP: Doc?.IsEnableOTP || false,
|
||||
IsTourEnabled: Doc?.IsTourEnabled || false,
|
||||
FileAdapterId: Doc?.FileAdapterId || "",
|
||||
...SignatureType
|
||||
...SignatureType,
|
||||
...NotifyOnSignatures
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1453,7 +1457,6 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
if (position?.options?.values.length > 0) {
|
||||
position?.options?.values.forEach((item, ind) => {
|
||||
const checkboxRandomId = "checkbox" + randomId();
|
||||
|
||||
if (
|
||||
position?.options?.response &&
|
||||
position?.options?.response?.length > 0
|
||||
@@ -1505,8 +1508,6 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
});
|
||||
}
|
||||
} else if (widgetTypeExist) {
|
||||
const fontSize = position?.options?.fontSize || 12;
|
||||
parseInt(fontSize);
|
||||
let textContent;
|
||||
if (position?.options?.response) {
|
||||
textContent = position.options?.response;
|
||||
@@ -1541,7 +1542,6 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
// Function to break text into lines based on when user go next line on press enter button
|
||||
const breakTextIntoLines = (textContent, width) => {
|
||||
const lines = [];
|
||||
|
||||
for (const word of textContent.split("\n")) {
|
||||
const lineWidth = font.widthOfTextAtSize(`${word}`, fontSize);
|
||||
//checking string length to container width
|
||||
@@ -1583,7 +1583,6 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
y += 18; // Adjust the line height as needed
|
||||
}
|
||||
} else if (position.type === "dropdown") {
|
||||
const fontsize = parseInt(position?.options?.fontSize) || 12;
|
||||
const dropdownRandomId = "dropdown" + randomId();
|
||||
const dropdown = form.createDropdown(dropdownRandomId);
|
||||
dropdown.addOptions(position?.options?.values);
|
||||
@@ -1598,10 +1597,10 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
// - `FontSize` is the size you want to set (e.g., 12)
|
||||
// - `Tf` specifies the font and size
|
||||
// - `0 g` sets the text color to black
|
||||
const defaultAppearance = `/Helv ${fontsize} Tf 0 g`;
|
||||
const defaultAppearance = `/Helv ${fontSize} Tf 0 g`;
|
||||
// Set the default appearance for the dropdown field
|
||||
dropdown.acroField.setDefaultAppearance(defaultAppearance);
|
||||
dropdown.setFontSize(fontsize);
|
||||
dropdown.setFontSize(fontSize);
|
||||
const dropdownObj = {
|
||||
x: xPos(position),
|
||||
y: yPos(position),
|
||||
@@ -2426,23 +2425,9 @@ export const getDefaultSignature = async (objectId) => {
|
||||
};
|
||||
|
||||
//function to rotate pdf page
|
||||
export async function rotatePdfPage(
|
||||
url,
|
||||
rotateDegree,
|
||||
pageNumber,
|
||||
pdfRotateBase64
|
||||
) {
|
||||
let file;
|
||||
|
||||
//condition to handle pdf base64 in arrayBuffer format
|
||||
if (pdfRotateBase64) {
|
||||
file = base64ToArrayBuffer(pdfRotateBase64);
|
||||
} else {
|
||||
//condition to handle pdf url in arrayBuffer format
|
||||
file = await convertPdfArrayBuffer(url);
|
||||
}
|
||||
export async function rotatePdfPage(rotateDegree, pageNumber, pdfArrayBuffer) {
|
||||
// Load the existing PDF
|
||||
const pdfDoc = await PDFDocument.load(file);
|
||||
const pdfDoc = await PDFDocument.load(pdfArrayBuffer);
|
||||
// Get the page according to page number
|
||||
const page = pdfDoc.getPage(pageNumber);
|
||||
//get current page rotation angle
|
||||
@@ -2508,21 +2493,35 @@ export const handleRemoveWidgets = (
|
||||
setSignerPos,
|
||||
signerPos,
|
||||
pageNumber,
|
||||
userId,
|
||||
setIsRotate
|
||||
) => {
|
||||
const updatedSignerPos = signerPos.map((placeholderObj) => {
|
||||
return {
|
||||
...placeholderObj,
|
||||
placeHolder: placeholderObj?.placeHolder?.filter(
|
||||
(data) => data?.pageNumber !== pageNumber
|
||||
)
|
||||
};
|
||||
});
|
||||
setSignerPos(updatedSignerPos);
|
||||
setIsRotate({
|
||||
status: false,
|
||||
degree: 0
|
||||
});
|
||||
const isSigners = signerPos.some((data) => data.signerPtr);
|
||||
if (isSigners) {
|
||||
const updatedSignerPos = signerPos.map((placeholderObj) => {
|
||||
if (placeholderObj.Id === userId) {
|
||||
return {
|
||||
...placeholderObj,
|
||||
placeHolder: placeholderObj?.placeHolder?.filter(
|
||||
(data) => data?.pageNumber !== pageNumber
|
||||
)
|
||||
};
|
||||
} else {
|
||||
return placeholderObj;
|
||||
}
|
||||
});
|
||||
setSignerPos(updatedSignerPos);
|
||||
setIsRotate &&
|
||||
setIsRotate({
|
||||
status: false,
|
||||
degree: 0
|
||||
});
|
||||
} else {
|
||||
const updatedSignerPos = signerPos.filter(
|
||||
(data) => data?.pageNumber !== pageNumber
|
||||
);
|
||||
setSignerPos(updatedSignerPos);
|
||||
}
|
||||
};
|
||||
//function to show warning when user rotate page and there are some already widgets on that page
|
||||
export const handleRotateWarning = (signerPos, pageNumber) => {
|
||||
@@ -2597,3 +2596,37 @@ export async function handleSignatureType(tenantSignTypes, signatureType) {
|
||||
}
|
||||
return updatedSignatureType;
|
||||
}
|
||||
|
||||
// `formatDate` is used to format date to dd-mmm-yyy
|
||||
export const formatDate = (date) => {
|
||||
// Create a Date object
|
||||
const newDate = new Date(date);
|
||||
// Format the date
|
||||
const formattedDate = newDate.toLocaleDateString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric"
|
||||
});
|
||||
const format = formattedDate.replaceAll(/ /g, "-");
|
||||
return format;
|
||||
};
|
||||
|
||||
export const deletePdfPage = async (pdfArrayBuffer, pageNumber) => {
|
||||
// Load the existing PDF
|
||||
const pdfDoc = await PDFDocument.load(pdfArrayBuffer);
|
||||
// Get the total number of pages
|
||||
const totalPages = pdfDoc.getPageCount();
|
||||
// Ensure the page index is valid
|
||||
if (totalPages > 1) {
|
||||
//Remove the specified page
|
||||
pdfDoc.removePage(pageNumber - 1);
|
||||
// Save the modified PDF
|
||||
const modifiedPdfBytes = await pdfDoc.saveAsBase64({
|
||||
useObjectStreams: false
|
||||
});
|
||||
const arrayBuffer = base64ToArrayBuffer(modifiedPdfBytes);
|
||||
return { arrayBuffer: arrayBuffer, base64: modifiedPdfBytes };
|
||||
} else {
|
||||
return { totalPages: 1 };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,28 +2,12 @@ import { isEnableSubscription } from "../constant/const";
|
||||
|
||||
export default function reportJson(id) {
|
||||
// console.log("json ", json);
|
||||
const head = ["Sr.No", "Title", "Note", "Folder", "File", "Owner", "Signers"];
|
||||
const declineHead = [
|
||||
"Sr.No",
|
||||
"Title",
|
||||
"Reason",
|
||||
"Folder",
|
||||
"File",
|
||||
"Owner",
|
||||
"Signers"
|
||||
];
|
||||
const iphead = [
|
||||
"Sr.No",
|
||||
"Title",
|
||||
"Note",
|
||||
"Folder",
|
||||
"File",
|
||||
"Status",
|
||||
"Signers"
|
||||
];
|
||||
const contactbook = ["Sr.No", "Name", "Email", "Phone"];
|
||||
const head = ["Title", "Note", "Folder", "File", "Owner", "Signers"];
|
||||
const declineHead = ["Title", "Reason", "Folder", "File", "Owner", "Signers"];
|
||||
const iphead = ["Title", "Note", "Folder", "File", "Signers"];
|
||||
const contactbook = ["Name", "Email", "Phone"];
|
||||
const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
|
||||
const templateReport = ["Sr.No", "Title", "File", "Owner", "Signers"];
|
||||
const templateReport = ["Title", "File", "Owner", "Signers"];
|
||||
const templateSubAction = isEnableSubscription
|
||||
? [
|
||||
{
|
||||
@@ -228,6 +212,25 @@ export default function reportJson(id) {
|
||||
btnIcon: "fa-light fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
},
|
||||
{
|
||||
btnId: "3234",
|
||||
hoverLabel: "option",
|
||||
btnColor: "",
|
||||
textColor: "black",
|
||||
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
|
||||
action: "option",
|
||||
subaction: [
|
||||
{
|
||||
btnId: "2199",
|
||||
btnLabel: "extend-expiry-date",
|
||||
hoverLabel: "Extend expiry date",
|
||||
btnColor: "op-btn-primary",
|
||||
btnIcon: "fa-light fa-hourglass-end",
|
||||
redirectUrl: "",
|
||||
action: "extendexpiry"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
helpMsg:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -68,7 +68,8 @@ const Forms = (props) => {
|
||||
remindOnceInEvery: 5,
|
||||
autoreminder: false,
|
||||
IsEnableOTP: "false",
|
||||
IsTourEnabled: "false"
|
||||
IsTourEnabled: "false",
|
||||
NotifyOnSignatures: ""
|
||||
});
|
||||
const [fileupload, setFileUpload] = useState("");
|
||||
const [fileload, setfileload] = useState(false);
|
||||
@@ -102,21 +103,25 @@ const Forms = (props) => {
|
||||
if (isEnableSubscription) {
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
if (subscribe.isValid && extUserData.NotifyOnSignatures !== undefined) {
|
||||
setFormData((obj) => ({
|
||||
...obj,
|
||||
NotifyOnSignatures: extUserData.NotifyOnSignatures
|
||||
}));
|
||||
} else if (subscribe.isValid) {
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: true }));
|
||||
}
|
||||
} else {
|
||||
setIsSubscribe(true);
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: true }));
|
||||
}
|
||||
};
|
||||
|
||||
function getFileAsArrayBuffer(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (e) => {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
|
||||
reader.onerror = (e) => {
|
||||
reject(e.target.error);
|
||||
};
|
||||
|
||||
reader.onload = (e) => resolve(e.target.result);
|
||||
reader.onerror = (e) => reject(e.target.error);
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
@@ -525,7 +530,6 @@ const Forms = (props) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
const dropboxCancel = async () => {};
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -555,8 +559,14 @@ const Forms = (props) => {
|
||||
const IsEnableOTP =
|
||||
formData?.IsEnableOTP === "false" ? false : true;
|
||||
object.set("IsEnableOTP", IsEnableOTP);
|
||||
if (isSubscribe && formData.NotifyOnSignatures !== undefined) {
|
||||
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
|
||||
}
|
||||
} else {
|
||||
object.set("IsEnableOTP", false);
|
||||
if (formData.NotifyOnSignatures !== undefined) {
|
||||
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
|
||||
}
|
||||
}
|
||||
}
|
||||
object.set("URL", fileupload);
|
||||
@@ -639,7 +649,7 @@ const Forms = (props) => {
|
||||
setIsReset(true);
|
||||
setSigners([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
setFormData({
|
||||
let obj = {
|
||||
Name: "",
|
||||
Description: "",
|
||||
Note:
|
||||
@@ -654,7 +664,8 @@ const Forms = (props) => {
|
||||
autoreminder: false,
|
||||
IsEnableOTP: "false",
|
||||
IsTourEnabled: "true"
|
||||
});
|
||||
};
|
||||
setFormData(obj);
|
||||
setFileUpload("");
|
||||
setpercentage(0);
|
||||
setTimeout(() => setIsReset(false), 50);
|
||||
@@ -772,6 +783,12 @@ const Forms = (props) => {
|
||||
const handleAutoReminder = () => {
|
||||
setFormData((prev) => ({ ...prev, autoreminder: !formData.autoreminder }));
|
||||
};
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
const handleNotifySignChange = (value) => {
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="shadow-md rounded-box my-[2px] p-3 bg-base-100 text-base-content">
|
||||
<Title title={props?.title} />
|
||||
@@ -865,10 +882,7 @@ const Forms = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
{process.env.REACT_APP_DROPBOX_API_KEY && (
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
<DropboxChooser onSuccess={dropboxSuccess} />
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
@@ -890,10 +904,7 @@ const Forms = (props) => {
|
||||
required
|
||||
/>
|
||||
{process.env.REACT_APP_DROPBOX_API_KEY && (
|
||||
<DropboxChooser
|
||||
onSuccess={dropboxSuccess}
|
||||
onCancel={dropboxCancel}
|
||||
/>
|
||||
<DropboxChooser onSuccess={dropboxSuccess} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -1215,7 +1226,7 @@ const Forms = (props) => {
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe
|
||||
? "cursor-pointer "
|
||||
? "cursor-pointer"
|
||||
: "pointer-events-none opacity-50"
|
||||
} relative block items-center mb-0 mt-1.5`}
|
||||
>
|
||||
@@ -1251,7 +1262,7 @@ const Forms = (props) => {
|
||||
)}
|
||||
<span className="font-[400] mt-2">{t("form-title-2")}</span>
|
||||
{isEnableSubscription && (
|
||||
<div className="text-xs mt-4">
|
||||
<div className="text-xs mt-3">
|
||||
<label className="block">
|
||||
<span className={isSubscribe ? "" : " text-gray-300"}>
|
||||
{t("isenable-otp")}{" "}
|
||||
@@ -1262,7 +1273,7 @@ const Forms = (props) => {
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>{" "}
|
||||
</a>
|
||||
{!isSubscribe && isEnableSubscription && (
|
||||
<Upgrade />
|
||||
)}
|
||||
@@ -1340,7 +1351,7 @@ const Forms = (props) => {
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>{" "}
|
||||
</a>
|
||||
</span>
|
||||
<Tooltip id="istourenabled-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
@@ -1364,7 +1375,7 @@ const Forms = (props) => {
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"true"}
|
||||
@@ -1375,7 +1386,7 @@ const Forms = (props) => {
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div className={` flex items-center gap-2 ml-2 mb-1 `}>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={"false"}
|
||||
@@ -1388,6 +1399,58 @@ const Forms = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe ? "" : "text-gray-300"
|
||||
} block`}
|
||||
>
|
||||
{t("notify-on-signatures")}
|
||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
<Tooltip id="nos-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||
<p className="font-bold">
|
||||
{t("notify-on-signatures")}
|
||||
</p>
|
||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||
<p>{t("notify-on-signatures-help.note")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={formData.NotifyOnSignatures === true}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={formData.NotifyOnSignatures === false}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import axios from "axios";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { emailRegex, isEnableSubscription } from "../constant/const";
|
||||
import {
|
||||
contractUsers,
|
||||
getAppLogo,
|
||||
@@ -189,23 +189,30 @@ function GuestLogin() {
|
||||
};
|
||||
const handleUserData = async (e) => {
|
||||
e.preventDefault();
|
||||
const params = { ...contact, docId: documentId };
|
||||
try {
|
||||
setLoading(true);
|
||||
const linkContactRes = await Parse.Cloud.run("linkcontacttodoc", params);
|
||||
setContactId(linkContactRes.contactId);
|
||||
const IsEnableOTP = await navigateToDoc(
|
||||
documentId,
|
||||
linkContactRes.contactId
|
||||
);
|
||||
if (!IsEnableOTP) {
|
||||
setEnterOtp(true);
|
||||
await SendOtp();
|
||||
if (!emailRegex.test(contact.email)) {
|
||||
alert("Please enter a valid email address.");
|
||||
} else {
|
||||
const params = { ...contact, docId: documentId };
|
||||
try {
|
||||
setLoading(true);
|
||||
const linkContactRes = await Parse.Cloud.run(
|
||||
"linkcontacttodoc",
|
||||
params
|
||||
);
|
||||
setContactId(linkContactRes.contactId);
|
||||
const IsEnableOTP = await navigateToDoc(
|
||||
documentId,
|
||||
linkContactRes.contactId
|
||||
);
|
||||
if (!IsEnableOTP) {
|
||||
setEnterOtp(true);
|
||||
await SendOtp();
|
||||
}
|
||||
} catch (err) {
|
||||
setLoading(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
console.log("Err in link ext contact", err);
|
||||
}
|
||||
} catch (err) {
|
||||
setLoading(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
console.log("Err in link ext contact", err);
|
||||
}
|
||||
};
|
||||
const handleInputChange = (e) => {
|
||||
@@ -348,6 +355,7 @@ function GuestLogin() {
|
||||
onChange={handleInputChange}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
disabled={loading}
|
||||
placeholder={t("phone-optional")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex justify-start">
|
||||
|
||||
@@ -41,7 +41,8 @@ import {
|
||||
fetchUrl,
|
||||
signatureTypes,
|
||||
handleSignatureType,
|
||||
getTenantDetails
|
||||
getTenantDetails,
|
||||
getBase64FromUrl
|
||||
} from "../constant/Utils";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
@@ -59,6 +60,7 @@ import LoaderWithMsg from "../primitives/LoaderWithMsg";
|
||||
import DownloadPdfZip from "../primitives/DownloadPdfZip";
|
||||
import Loader from "../primitives/Loader";
|
||||
import PdfDeclineModal from "../primitives/PdfDeclineModal";
|
||||
import { serverUrl_fn } from "../constant/appinfo";
|
||||
|
||||
function PdfRequestFiles(props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -145,6 +147,7 @@ function PdfRequestFiles(props) {
|
||||
const divRef = useRef(null);
|
||||
const [isDownloadModal, setIsDownloadModal] = useState(false);
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
const [pdfBase64Url, setPdfBase64Url] = useState("");
|
||||
const isMobile = window.innerWidth < 767;
|
||||
|
||||
let isGuestSignFlow = false;
|
||||
@@ -322,10 +325,9 @@ function PdfRequestFiles(props) {
|
||||
documentData[0] &&
|
||||
(documentData[0]?.SignedUrl || documentData[0]?.URL);
|
||||
if (url) {
|
||||
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
|
||||
const arrayBuffer = await convertPdfArrayBuffer(url);
|
||||
if (arrayBuffer === "Error") {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
const base64Pdf = await getBase64FromUrl(url);
|
||||
if (base64Pdf) {
|
||||
setPdfBase64Url(base64Pdf);
|
||||
}
|
||||
} else {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
@@ -442,10 +444,9 @@ function PdfRequestFiles(props) {
|
||||
documentData[0] &&
|
||||
(documentData[0]?.SignedUrl || documentData[0]?.URL);
|
||||
if (url) {
|
||||
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
|
||||
const arrayBuffer = await convertPdfArrayBuffer(url);
|
||||
if (arrayBuffer === "Error") {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
const base64Pdf = await getBase64FromUrl(url);
|
||||
if (base64Pdf) {
|
||||
setPdfBase64Url(base64Pdf);
|
||||
}
|
||||
} else {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
@@ -1752,6 +1753,42 @@ function PdfRequestFiles(props) {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const handleExpiry = async (expiryDate) => {
|
||||
setIsUiLoading(true);
|
||||
const doc = pdfDetails?.[0];
|
||||
const oldExpiryDate = new Date(doc?.ExpiryDate?.iso);
|
||||
const newExpiryDate = new Date(expiryDate);
|
||||
if (newExpiryDate > oldExpiryDate) {
|
||||
const updateExpiryDate = new Date(expiryDate).toISOString();
|
||||
const expiryIsoFormat = { iso: updateExpiryDate, __type: "Date" };
|
||||
try {
|
||||
const serverUrl = serverUrl_fn();
|
||||
const url = serverUrl + `/classes/contracts_Document/`;
|
||||
const body = { ExpiryDate: expiryIsoFormat };
|
||||
const res = await axios.put(url + doc.objectId, body, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
});
|
||||
if (res.data && res.data.updatedAt) {
|
||||
setIsExpired(false);
|
||||
let doc = pdfDetails?.[0];
|
||||
doc.ExpiryDate = expiryIsoFormat;
|
||||
setPdfDetails([doc]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
} finally {
|
||||
setIsUiLoading(false);
|
||||
}
|
||||
} else {
|
||||
setIsUiLoading(false);
|
||||
alert(t("expiry-date-error"));
|
||||
}
|
||||
};
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Title title={props.templateId ? "Public Sign" : "Request Sign"} />
|
||||
@@ -1838,10 +1875,12 @@ function PdfRequestFiles(props) {
|
||||
{/* this modal is used for show expired alert */}
|
||||
<PdfDeclineModal
|
||||
show={isExpired}
|
||||
doc={pdfDetails?.[0]}
|
||||
headMsg={t("expired-doc-title")}
|
||||
bodyMssg={t("expired-on-mssg", { expiredDate })}
|
||||
isDownloadBtn={true}
|
||||
handleDownloadBtn={handleDownloadBtn}
|
||||
handleExpiry={handleExpiry}
|
||||
/>
|
||||
{!isEmailVerified && (
|
||||
<VerifyEmail
|
||||
@@ -2024,15 +2063,12 @@ function PdfRequestFiles(props) {
|
||||
<RenderAllPdfPage
|
||||
signerPos={signerPos}
|
||||
signerObjectId={signerObjectId}
|
||||
signPdfUrl={
|
||||
pdfDetails[0] &&
|
||||
(pdfDetails[0]?.SignedUrl || pdfDetails[0]?.URL)
|
||||
}
|
||||
allPages={allPages}
|
||||
setAllPages={setAllPages}
|
||||
setPageNumber={setPageNumber}
|
||||
pageNumber={pageNumber}
|
||||
containerWH={containerWH}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
/>
|
||||
{/* pdf render view */}
|
||||
<div className=" w-full md:w-[57%] flex mr-4">
|
||||
@@ -2040,6 +2076,7 @@ function PdfRequestFiles(props) {
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
isDisableRotate={true}
|
||||
isDisableDelete={true}
|
||||
/>
|
||||
<div className=" w-full md:w-[95%] ">
|
||||
{/* this modal is used show this document is already sign */}
|
||||
@@ -2233,6 +2270,7 @@ function PdfRequestFiles(props) {
|
||||
uniqueId={uniqueId}
|
||||
ispublicTemplate={isPublicTemplate}
|
||||
handleUserDetails={handleUserDetails}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,9 @@ import {
|
||||
handleRemoveWidgets,
|
||||
handleRotateWarning,
|
||||
signatureTypes,
|
||||
handleSignatureType
|
||||
handleSignatureType,
|
||||
formatDate,
|
||||
getBase64FromUrl
|
||||
} from "../constant/Utils";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -139,7 +141,7 @@ function PlaceHolderSign() {
|
||||
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
|
||||
const isHeader = useSelector((state) => state.showHeader);
|
||||
const [activeMailAdapter, setActiveMailAdapter] = useState("");
|
||||
const [isRotate, setIsRotate] = useState({
|
||||
const [showRotateAlert, setShowRotateAlert] = useState({
|
||||
status: false,
|
||||
degree: 0
|
||||
});
|
||||
@@ -151,10 +153,12 @@ function PlaceHolderSign() {
|
||||
const [isCustomize, setIsCustomize] = useState(false);
|
||||
const [zoomPercent, setZoomPercent] = useState(0);
|
||||
const [scale, setScale] = useState(1);
|
||||
const [pdfRotateBase64, setPdfRotatese64] = useState("");
|
||||
const [pdfBase64Url, setPdfBase64Url] = useState("");
|
||||
const [planCode, setPlanCode] = useState("");
|
||||
const [unSignedWidgetId, setUnSignedWidgetId] = useState("");
|
||||
const [signatureType, setSignatureType] = useState(signatureTypes);
|
||||
const [emailResetDate, setEmailResetDate] = useState("");
|
||||
const [isUploadPdf, setIsUploadPdf] = useState(false);
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
@@ -264,11 +268,19 @@ function PlaceHolderSign() {
|
||||
const url = documentData[0] && documentData[0]?.URL;
|
||||
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
|
||||
const arrayBuffer = await convertPdfArrayBuffer(url);
|
||||
const base64Pdf = await getBase64FromUrl(url);
|
||||
if (arrayBuffer === "Error") {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
} else {
|
||||
setPdfBase64Url(base64Pdf);
|
||||
setPdfArrayBuffer(arrayBuffer);
|
||||
}
|
||||
if (documentData[0]?.ExtUserPtr?.LastEmailCountReset?.iso) {
|
||||
const resetDate = formatDate(
|
||||
documentData[0]?.ExtUserPtr?.LastEmailCountReset?.iso
|
||||
);
|
||||
setEmailResetDate(resetDate);
|
||||
}
|
||||
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
|
||||
if (isEnableSubscription) {
|
||||
checkIsSubscribed(documentData[0]?.ExtUserPtr?.Email);
|
||||
@@ -580,22 +592,33 @@ function PlaceHolderSign() {
|
||||
}
|
||||
setSignerPos(updatesignerPos);
|
||||
} else {
|
||||
let updatesignerPos;
|
||||
//if condition when widget type is prefill label text widget
|
||||
if (dragTypeValue === textWidget) {
|
||||
const prefileTextWidget = {
|
||||
signerPtr: {},
|
||||
signerObjId: "",
|
||||
blockColor: "#f58f8c",
|
||||
placeHolder: [placeHolder],
|
||||
Role: "prefill",
|
||||
Id: key
|
||||
};
|
||||
signerPos.push(prefileTextWidget);
|
||||
setSignerPos(signerPos);
|
||||
//check text widgets data (prefill) already exist then and want to add text widget on new page
|
||||
//create new page entry with old data and update placeholder
|
||||
if (filterSignerPos) {
|
||||
const addPrefillData =
|
||||
filterSignerPos && filterSignerPos?.placeHolder;
|
||||
addPrefillData.push(placeHolder);
|
||||
const updatePrefillPos = signerPos.map((x) =>
|
||||
x.Role === "prefill" ? { ...x, placeHolder: addPrefillData } : x
|
||||
);
|
||||
setSignerPos(updatePrefillPos);
|
||||
} //else condition if user do not have any text widget data
|
||||
else {
|
||||
const prefillTextWidget = {
|
||||
signerPtr: {},
|
||||
signerObjId: "",
|
||||
blockColor: "#f58f8c",
|
||||
placeHolder: [placeHolder],
|
||||
Role: "prefill",
|
||||
Id: key
|
||||
};
|
||||
setSignerPos((prev) => [...prev, prefillTextWidget]);
|
||||
}
|
||||
} else {
|
||||
//else condition to add placeholder widgets on multiple page first time
|
||||
updatesignerPos = signerPos.map((x) =>
|
||||
const updatesignerPos = signerPos.map((x) =>
|
||||
x.Id === uniqueId && x?.placeHolder
|
||||
? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
|
||||
: x.Id === uniqueId
|
||||
@@ -770,15 +793,17 @@ function PlaceHolderSign() {
|
||||
signerupdate.push(newUpdatePos[0]);
|
||||
setSignerPos(signerupdate);
|
||||
} else {
|
||||
const updatedData = signerPos.map((item) => {
|
||||
if (item.Id === Id) {
|
||||
// Create a copy of the item object and delete the placeHolder field
|
||||
const updatedItem = { ...item };
|
||||
delete updatedItem.placeHolder;
|
||||
return updatedItem;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
const updatedData = signerPos
|
||||
.filter((item) => !(item.Id === Id && item.Role === "prefill")) // Remove prefill object
|
||||
.map((item) => {
|
||||
if (item.Id === Id && item.Role !== "prefill") {
|
||||
// Create a copy of the item object and delete the placeHolder field
|
||||
const updatedItem = { ...item };
|
||||
delete updatedItem.placeHolder;
|
||||
return updatedItem;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setSignerPos(updatedData);
|
||||
}
|
||||
}
|
||||
@@ -838,15 +863,12 @@ function PlaceHolderSign() {
|
||||
} catch (e) {
|
||||
console.log("error to convertBase64ToFile in placeholder flow", e);
|
||||
}
|
||||
} else if (pdfRotateBase64) {
|
||||
} else if (pdfBase64Url) {
|
||||
try {
|
||||
const pdfUrl = await convertBase64ToFile(
|
||||
pdfDetails[0].Name,
|
||||
pdfRotateBase64
|
||||
pdfBase64Url
|
||||
);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const buffer = atob(pdfRotateBase64);
|
||||
SaveFileSize(buffer.length, pdfUrl, tenantId);
|
||||
return pdfUrl;
|
||||
} catch (e) {
|
||||
console.log("error to convertBase64ToFile in placeholder flow", e);
|
||||
@@ -919,7 +941,6 @@ function PlaceHolderSign() {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (signerPos?.length > 0 && !pdfDetails?.[0]?.IsCompleted) {
|
||||
@@ -928,7 +949,7 @@ function PlaceHolderSign() {
|
||||
}, 2000);
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [signerPos, signersdata, signatureType]);
|
||||
}, [signerPos, signersdata, signatureType, pdfBase64Url]);
|
||||
|
||||
// `autosavedetails` is used to save doc details after every 2 sec when changes are happern in placeholder like drag-drop widgets, remove signers
|
||||
const autosavedetails = async () => {
|
||||
@@ -942,12 +963,20 @@ function PlaceHolderSign() {
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
let pdfUrl;
|
||||
if (isUploadPdf) {
|
||||
pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfBase64Url);
|
||||
}
|
||||
try {
|
||||
const docCls = new Parse.Object("contracts_Document");
|
||||
docCls.id = documentId;
|
||||
docCls.set("Placeholders", signerPos);
|
||||
docCls.set("Signers", signers);
|
||||
docCls.set("SignatureType", signatureType);
|
||||
if (pdfUrl) {
|
||||
docCls.set("URL", pdfUrl);
|
||||
}
|
||||
|
||||
await docCls.save();
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
@@ -1720,30 +1749,36 @@ function PlaceHolderSign() {
|
||||
};
|
||||
//`handleRotationFun` function is used to roatate pdf particular page
|
||||
const handleRotationFun = async (rotateDegree) => {
|
||||
const isRotate = handleRotateWarning(signerPos, pageNumber);
|
||||
if (isRotate) {
|
||||
setIsRotate({ status: true, degree: rotateDegree });
|
||||
const rotatePlaceholderExist = handleRotateWarning(signerPos, pageNumber);
|
||||
//show rotation alert if widgets already exist
|
||||
if (rotatePlaceholderExist) {
|
||||
setShowRotateAlert({ status: true, degree: rotateDegree });
|
||||
} else {
|
||||
setIsUploadPdf(true);
|
||||
const urlDetails = await rotatePdfPage(
|
||||
pdfDetails[0].URL,
|
||||
rotateDegree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
pdfBase64Url
|
||||
);
|
||||
setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
}
|
||||
};
|
||||
const handleRemovePlaceholder = async () => {
|
||||
handleRemoveWidgets(setSignerPos, signerPos, pageNumber, setIsRotate);
|
||||
handleRemoveWidgets(
|
||||
setSignerPos,
|
||||
signerPos,
|
||||
pageNumber,
|
||||
uniqueId,
|
||||
setShowRotateAlert
|
||||
);
|
||||
const urlDetails = await rotatePdfPage(
|
||||
pdfDetails[0].URL,
|
||||
isRotate.degree,
|
||||
showRotateAlert.degree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
pdfArrayBuffer
|
||||
);
|
||||
setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
@@ -1800,7 +1835,7 @@ function PlaceHolderSign() {
|
||||
setPageNumber={setPageNumber}
|
||||
setSignBtnPosition={setSignBtnPosition}
|
||||
pageNumber={pageNumber}
|
||||
pdfRotateBase64={pdfRotateBase64}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
/>
|
||||
{/* pdf render view */}
|
||||
<div className=" w-full md:w-[57%] flex mr-4">
|
||||
@@ -1808,6 +1843,14 @@ function PlaceHolderSign() {
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
handleRotationFun={handleRotationFun}
|
||||
pdfArrayBuffer={pdfArrayBuffer}
|
||||
pageNumber={pageNumber}
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||
setIsUploadPdf={setIsUploadPdf}
|
||||
setSignerPos={setSignerPos}
|
||||
signerPos={signerPos}
|
||||
userId={uniqueId}
|
||||
/>
|
||||
<div className=" w-full md:w-[95%] ">
|
||||
{/* this modal is used show alert set placeholder for all signers before send mail */}
|
||||
@@ -1926,6 +1969,7 @@ function PlaceHolderSign() {
|
||||
) : mailStatus === "quotareached" ? (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
<QuotaCard
|
||||
emailResetDate={emailResetDate}
|
||||
handleClose={() => {
|
||||
setIsSend(false);
|
||||
setSignerPos([]);
|
||||
@@ -2071,6 +2115,12 @@ function PlaceHolderSign() {
|
||||
handleRotationFun={handleRotationFun}
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
setIsUploadPdf={setIsUploadPdf}
|
||||
pdfArrayBuffer={pdfArrayBuffer}
|
||||
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
setSignerPos={setSignerPos}
|
||||
userId={uniqueId}
|
||||
/>
|
||||
|
||||
<div
|
||||
@@ -2119,7 +2169,7 @@ function PlaceHolderSign() {
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
setIsSelectId={setIsSelectId}
|
||||
pdfRotateBase64={pdfRotateBase64}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
@@ -2264,8 +2314,8 @@ function PlaceHolderSign() {
|
||||
setFontColor={setFontColor}
|
||||
/>
|
||||
<RotateAlert
|
||||
isRotate={isRotate.status}
|
||||
setIsRotate={setIsRotate}
|
||||
showRotateAlert={showRotateAlert.status}
|
||||
setShowRotateAlert={setShowRotateAlert}
|
||||
handleRemoveWidgets={handleRemovePlaceholder}
|
||||
/>
|
||||
</DndProvider>
|
||||
|
||||
@@ -5,12 +5,15 @@ import { useTranslation } from "react-i18next";
|
||||
import Loader from "../primitives/Loader";
|
||||
import Tooltip from "../primitives/Tooltip";
|
||||
import {
|
||||
checkIsSubscribed,
|
||||
getTenantDetails,
|
||||
handleSignatureType,
|
||||
signatureTypes
|
||||
} from "../constant/Utils";
|
||||
import Parse from "parse";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import Upgrade from "../primitives/Upgrade";
|
||||
const Preferences = () => {
|
||||
const { t } = useTranslation();
|
||||
const [isalert, setIsAlert] = useState({ type: "success", msg: "" });
|
||||
@@ -18,11 +21,19 @@ const Preferences = () => {
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
const [errMsg, setErrMsg] = useState("");
|
||||
const [isNotifyOnSignatures, setIsNotifyOnSignatures] = useState();
|
||||
const [isSubscribe, setIsSubscribe] = useState(false);
|
||||
useEffect(() => {
|
||||
fetchSignType();
|
||||
}, []);
|
||||
|
||||
const fetchSignType = async () => {
|
||||
if (isEnableSubscription) {
|
||||
const subscribe = await checkIsSubscribed();
|
||||
setIsSubscribe(subscribe.isValid);
|
||||
} else {
|
||||
setIsSubscribe(true);
|
||||
}
|
||||
setIsTopLoader(true);
|
||||
try {
|
||||
const user = JSON.parse(
|
||||
@@ -36,6 +47,11 @@ const Preferences = () => {
|
||||
const getUser = await Parse.Cloud.run("getUserDetails");
|
||||
if (getUser) {
|
||||
const _getUser = JSON.parse(JSON.stringify(getUser));
|
||||
const notifyOnSignatures =
|
||||
_getUser?.NotifyOnSignatures !== undefined
|
||||
? _getUser?.NotifyOnSignatures
|
||||
: "";
|
||||
setIsNotifyOnSignatures(notifyOnSignatures);
|
||||
if (tenantSignTypes?.length > 0) {
|
||||
const signatureType = _getUser?.SignatureType || signatureTypes;
|
||||
const updatedSignatureType = await handleSignatureType(
|
||||
@@ -69,39 +85,61 @@ const Preferences = () => {
|
||||
// `handleSave` is used save updated value signature type
|
||||
const handleSave = async () => {
|
||||
setIsLoader(true);
|
||||
const enabledSignTypes = signatureType?.filter((x) => x.enabled);
|
||||
const isDefaultSignTypeOnly =
|
||||
enabledSignTypes?.length === 1 && enabledSignTypes[0]?.name === "default";
|
||||
if (enabledSignTypes.length === 0) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("at-least-one-signature-type")
|
||||
});
|
||||
} else if (isDefaultSignTypeOnly) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("expect-default-one-more-signature-type")
|
||||
});
|
||||
} else {
|
||||
if (signatureType.length > 0 || isNotifyOnSignatures) {
|
||||
let params = {};
|
||||
if (signatureType.length > 0) {
|
||||
const enabledSignTypes = signatureType?.filter((x) => x.enabled);
|
||||
const isDefaultSignTypeOnly =
|
||||
enabledSignTypes?.length === 1 &&
|
||||
enabledSignTypes[0]?.name === "default";
|
||||
if (enabledSignTypes.length === 0) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("at-least-one-signature-type")
|
||||
});
|
||||
} else if (isDefaultSignTypeOnly) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("expect-default-one-more-signature-type")
|
||||
});
|
||||
} else {
|
||||
params = { ...params, SignatureType: signatureType };
|
||||
}
|
||||
}
|
||||
if (isNotifyOnSignatures) {
|
||||
params = { ...params, NotifyOnSignatures: isNotifyOnSignatures };
|
||||
}
|
||||
try {
|
||||
const updateRes = await Parse.Cloud.run("updatesignaturetype", {
|
||||
SignatureType: signatureType
|
||||
});
|
||||
const updateRes = await Parse.Cloud.run("updatepreferences", params);
|
||||
if (updateRes) {
|
||||
setIsAlert({ type: "success", msg: "Saved successfully." });
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.NotifyOnSignatures = isNotifyOnSignatures;
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Error updating signature type", err);
|
||||
setIsAlert({ type: "danger", msg: err.message });
|
||||
}
|
||||
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
}
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
};
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
const handleNotifySignChange = (value) => {
|
||||
setIsNotifyOnSignatures(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={"API Token"} />
|
||||
<Title title={t("Preferences")} />
|
||||
{isalert.msg && <Alert type={isalert.type}>{isalert.msg}</Alert>}
|
||||
{isTopLoader ? (
|
||||
<div className="flex justify-center items-center h-screen">
|
||||
@@ -200,6 +238,64 @@ const Preferences = () => {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<label
|
||||
className={`${
|
||||
isSubscribe ? "" : "text-gray-300"
|
||||
} text-[14px] mb-[0.7rem] font-medium`}
|
||||
>
|
||||
{t("notify-on-signatures")}
|
||||
<a data-tooltip-id="nos-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
<ReactTooltip id="nos-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
|
||||
<p className="font-bold">{t("notify-on-signatures")}</p>
|
||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||
<p>{t("notify-on-signatures-help.note")}</p>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
{!isSubscribe && isEnableSubscription && <Upgrade />}
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={isNotifyOnSignatures === true}
|
||||
/>
|
||||
<div className="text-[13px] font-medium cursor-default capitalize">
|
||||
{t("yes")}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={`${
|
||||
isSubscribe ? "" : "pointer-events-none opacity-50"
|
||||
} flex items-center gap-2 ml-2 mb-1`}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={isNotifyOnSignatures === false}
|
||||
/>
|
||||
<div className="text-[13px] font-medium cursor-default capitalize">
|
||||
{t("no")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<button
|
||||
className="op-btn op-btn-primary"
|
||||
onClick={handleSave}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import Parse from "parse";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import { isEnableSubscription } from "../constant/const";
|
||||
import { emailRegex, isEnableSubscription } from "../constant/const";
|
||||
import { fetchSubscription } from "../constant/Utils";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { showTenant } from "../redux/reducers/ShowTenant";
|
||||
@@ -88,54 +88,58 @@ const SSOVerify = () => {
|
||||
// `handleSubmitbtn` is used to create a user in the extended class
|
||||
const handleSubmitbtn = async (e) => {
|
||||
e.preventDefault();
|
||||
setIsLoader(true);
|
||||
let phone;
|
||||
if (userDetails?.phone) {
|
||||
phone = validateInput(userDetails?.phone);
|
||||
if (!emailRegex.test(userDetails.email)) {
|
||||
alert("Please enter a valid email address.");
|
||||
} else {
|
||||
phone = true;
|
||||
}
|
||||
if (userDetails.Destination && userDetails.Company && phone) {
|
||||
const payload = { sessionToken: localStorage.getItem("accesstoken") };
|
||||
if (payload && payload.sessionToken) {
|
||||
const params = {
|
||||
userDetails: {
|
||||
name: userDetails.name,
|
||||
email: userDetails.email,
|
||||
phone: userDetails?.phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company,
|
||||
jobTitle: userDetails.Destination
|
||||
}
|
||||
};
|
||||
try {
|
||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
||||
if (userSignUp && userSignUp.sessionToken) {
|
||||
const LocalUserDetails = params.userDetails;
|
||||
localStorage.setItem(
|
||||
"userDetails",
|
||||
JSON.stringify(LocalUserDetails)
|
||||
);
|
||||
await thirdpartyLoginfn(userSignUp.sessionToken);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
alert(userSignUp.message);
|
||||
setIsLoader(true);
|
||||
let phone;
|
||||
if (userDetails?.phone) {
|
||||
phone = validateInput(userDetails?.phone);
|
||||
} else {
|
||||
phone = true;
|
||||
}
|
||||
if (userDetails.Destination && userDetails.Company && phone) {
|
||||
const payload = { sessionToken: localStorage.getItem("accesstoken") };
|
||||
if (payload && payload.sessionToken) {
|
||||
const params = {
|
||||
userDetails: {
|
||||
name: userDetails.name,
|
||||
email: userDetails.email,
|
||||
phone: userDetails?.phone || "",
|
||||
role: "contracts_User",
|
||||
company: userDetails.Company,
|
||||
jobTitle: userDetails.Destination
|
||||
}
|
||||
};
|
||||
try {
|
||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
||||
if (userSignUp && userSignUp.sessionToken) {
|
||||
const LocalUserDetails = params.userDetails;
|
||||
localStorage.setItem(
|
||||
"userDetails",
|
||||
JSON.stringify(LocalUserDetails)
|
||||
);
|
||||
await thirdpartyLoginfn(userSignUp.sessionToken);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
alert(userSignUp.message);
|
||||
setIsLoader(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("error in usersignup", err);
|
||||
localStorage.removeItem("accesstoken");
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
setIsLoader(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("error in usersignup", err);
|
||||
} else {
|
||||
localStorage.removeItem("accesstoken");
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
alert(t("server-error"));
|
||||
setIsLoader(false);
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem("accesstoken");
|
||||
alert(t("server-error"));
|
||||
alert(t("filed-required-correctly"));
|
||||
setIsLoader(false);
|
||||
}
|
||||
} else {
|
||||
alert(t("filed-required-correctly"));
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
// `thirdpartyLoginfn` is used to save necessary parameters locally for the logged-in user
|
||||
|
||||
@@ -37,7 +37,9 @@ import {
|
||||
onClickZoomIn,
|
||||
onClickZoomOut,
|
||||
rotatePdfPage,
|
||||
signatureTypes
|
||||
signatureTypes,
|
||||
getBase64FromUrl,
|
||||
convertBase64ToFile
|
||||
} from "../constant/Utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
import Tour from "reactour";
|
||||
@@ -129,11 +131,12 @@ function SignYourSelf() {
|
||||
const [zoomPercent, setZoomPercent] = useState(0);
|
||||
const isHeader = useSelector((state) => state.showHeader);
|
||||
const [scale, setScale] = useState(1);
|
||||
const [pdfRotateBase64, setPdfRotatese64] = useState("");
|
||||
const [pdfBase64Url, setPdfBase64Url] = useState("");
|
||||
const [isRotate, setIsRotate] = useState({ status: false, degree: 0 });
|
||||
const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: true });
|
||||
const [isDownloadModal, setIsDownloadModal] = useState(false);
|
||||
const [isResize, setIsResize] = useState(false);
|
||||
const [isUploadPdf, setIsUploadPdf] = useState(false);
|
||||
const divRef = useRef(null);
|
||||
const nodeRef = useRef(null);
|
||||
const [, drop] = useDrop({
|
||||
@@ -221,13 +224,17 @@ function SignYourSelf() {
|
||||
? documentData[0]?.Placeholders
|
||||
: [];
|
||||
setXyPostion(placeholders);
|
||||
const url = documentData[0] && documentData[0]?.URL;
|
||||
const url =
|
||||
documentData[0] &&
|
||||
(documentData[0]?.SignedUrl || documentData[0]?.URL);
|
||||
if (url) {
|
||||
//convert document url in array buffer format to use embed widgets in pdf using pdf-lib
|
||||
const arrayBuffer = await convertPdfArrayBuffer(url);
|
||||
const base64Pdf = await getBase64FromUrl(url);
|
||||
if (arrayBuffer === "Error") {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
} else {
|
||||
setPdfBase64Url(base64Pdf);
|
||||
setPdfArrayBuffer(arrayBuffer);
|
||||
}
|
||||
} else {
|
||||
@@ -562,15 +569,23 @@ function SignYourSelf() {
|
||||
}, 2000);
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [xyPostion]);
|
||||
}, [xyPostion, pdfBase64Url]);
|
||||
|
||||
// `autosavedetails` is used to save doc details after every 2 sec when changes are happern in placeholder like drag-drop widgets, remove signers
|
||||
const autosavedetails = async () => {
|
||||
try {
|
||||
const docCls = new Parse.Object("contracts_Document");
|
||||
docCls.id = documentId;
|
||||
let pdfUrl;
|
||||
if (isUploadPdf) {
|
||||
pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfBase64Url);
|
||||
}
|
||||
console.log("isUploadPdf", isUploadPdf);
|
||||
docCls.set("Placeholders", xyPostion);
|
||||
docCls.set("IsSignyourself", true);
|
||||
if (pdfUrl) {
|
||||
docCls.set("URL", pdfUrl);
|
||||
}
|
||||
await docCls.save();
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
@@ -765,7 +780,7 @@ function SignYourSelf() {
|
||||
const signedpdf = JSON.parse(JSON.stringify(resSignPdf));
|
||||
setPdfUrl(signedpdf);
|
||||
getDocumentDetails(false);
|
||||
setPdfRotatese64("");
|
||||
setPdfBase64Url("");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1127,14 +1142,14 @@ function SignYourSelf() {
|
||||
if (isPlaceholder) {
|
||||
setIsRotate({ status: true, degree: rotateDegree });
|
||||
} else {
|
||||
setIsUploadPdf(true);
|
||||
const urlDetails = await rotatePdfPage(
|
||||
pdfUrl || pdfDetails[0].URL,
|
||||
rotateDegree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
pdfBase64Url
|
||||
);
|
||||
setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1158,10 +1173,10 @@ function SignYourSelf() {
|
||||
pdfUrl || pdfDetails[0].URL,
|
||||
isRotate.degree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
pdfArrayBuffer
|
||||
);
|
||||
setPdfArrayBuffer && setPdfArrayBuffer(urlDetails.arrayBuffer);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
setXyPostion(updatePlaceholder);
|
||||
setIsRotate({ status: false, degree: 0 });
|
||||
};
|
||||
@@ -1228,13 +1243,20 @@ function SignYourSelf() {
|
||||
setSignBtnPosition={setSignBtnPosition}
|
||||
pageNumber={pageNumber}
|
||||
containerWH={containerWH}
|
||||
pdfRotateBase64={pdfRotateBase64}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
/>
|
||||
<div className=" w-full md:w-[57%] flex mr-4">
|
||||
<PdfZoom
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
handleRotationFun={handleRotationFun}
|
||||
pdfArrayBuffer={pdfArrayBuffer}
|
||||
pageNumber={pageNumber}
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||
setIsUploadPdf={setIsUploadPdf}
|
||||
setSignerPos={setXyPostion}
|
||||
signerPos={xyPostion}
|
||||
/>
|
||||
<div className="w-full md:w-[95%]">
|
||||
<ModalUi
|
||||
@@ -1348,6 +1370,12 @@ function SignYourSelf() {
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
widgetsDetails={xyPostion}
|
||||
setIsDownloadModal={setIsDownloadModal}
|
||||
setIsUploadPdf={setIsUploadPdf}
|
||||
pdfArrayBuffer={pdfArrayBuffer}
|
||||
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
setSignerPos={setXyPostion}
|
||||
signerPos={xyPostion}
|
||||
/>
|
||||
<div ref={divRef} data-tut="reactourSecond" className="h-full">
|
||||
{containerWH?.width && containerWH?.height && (
|
||||
@@ -1388,7 +1416,7 @@ function SignYourSelf() {
|
||||
handleTextSettingModal={handleTextSettingModal}
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
pdfRotateBase64={pdfRotateBase64}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
|
||||
@@ -33,7 +33,9 @@ import {
|
||||
color,
|
||||
signatureTypes,
|
||||
getTenantDetails,
|
||||
handleSignatureType
|
||||
handleSignatureType,
|
||||
getBase64FromUrl,
|
||||
convertPdfArrayBuffer
|
||||
} from "../constant/Utils";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import "../styles/AddUser.css";
|
||||
@@ -102,7 +104,8 @@ const TemplatePlaceholder = () => {
|
||||
const [isNameModal, setIsNameModal] = useState(false);
|
||||
const [isTextSetting, setIsTextSetting] = useState(false);
|
||||
const [pdfLoad, setPdfLoad] = useState(false);
|
||||
const [pdfRotateBase64, setPdfRotatese64] = useState("");
|
||||
const [pdfBase64Url, setPdfBase64Url] = useState("");
|
||||
const [isUploadPdf, setIsUploadPdf] = useState(false);
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
@@ -139,6 +142,7 @@ const TemplatePlaceholder = () => {
|
||||
const [zoomPercent, setZoomPercent] = useState(0);
|
||||
const [scale, setScale] = useState(1);
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
fetchTemplate();
|
||||
@@ -247,6 +251,17 @@ const TemplatePlaceholder = () => {
|
||||
tenantSignTypes,
|
||||
docSignTypes
|
||||
);
|
||||
const url = documentData[0] && documentData[0]?.URL;
|
||||
if (url) {
|
||||
const arrayBuffer = await convertPdfArrayBuffer(url);
|
||||
const base64Pdf = await getBase64FromUrl(url);
|
||||
if (arrayBuffer === "Error") {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
} else {
|
||||
setPdfBase64Url(base64Pdf);
|
||||
setPdfArrayBuffer(arrayBuffer);
|
||||
}
|
||||
}
|
||||
setSignatureType(updatedSignatureType);
|
||||
const updatedPdfDetails = [...documentData];
|
||||
updatedPdfDetails[0].SignatureType = updatedSignatureType;
|
||||
@@ -709,12 +724,19 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
let pdfUrl;
|
||||
if (isUploadPdf) {
|
||||
pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfBase64Url);
|
||||
}
|
||||
try {
|
||||
const templateCls = new Parse.Object("contracts_Template");
|
||||
templateCls.id = templateId;
|
||||
templateCls.set("Placeholders", signerPos);
|
||||
templateCls.set("Signers", signers);
|
||||
templateCls.set("SignatureType", signatureType);
|
||||
if (pdfUrl) {
|
||||
templateCls.set("URL", pdfUrl);
|
||||
}
|
||||
await templateCls.save();
|
||||
} catch (err) {
|
||||
console.log("error in autosave template", err);
|
||||
@@ -739,12 +761,9 @@ const TemplatePlaceholder = () => {
|
||||
});
|
||||
}
|
||||
let pdfUrl = pdfDetails[0]?.URL;
|
||||
if (pdfRotateBase64) {
|
||||
if (pdfBase64Url) {
|
||||
try {
|
||||
pdfUrl = await convertBase64ToFile(
|
||||
pdfDetails[0].Name,
|
||||
pdfRotateBase64
|
||||
);
|
||||
pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfBase64Url);
|
||||
} catch (e) {
|
||||
console.log("error to convertBase64ToFile in placeholder flow", e);
|
||||
}
|
||||
@@ -763,7 +782,11 @@ const TemplatePlaceholder = () => {
|
||||
IsEnableOTP: pdfDetails[0]?.IsEnableOTP === true ? true : false,
|
||||
IsTourEnabled: pdfDetails[0]?.IsTourEnabled === true ? true : false,
|
||||
URL: pdfUrl,
|
||||
SignatureType: signatureType
|
||||
SignatureType: signatureType,
|
||||
NotifyOnSignatures:
|
||||
pdfDetails[0]?.NotifyOnSignatures !== undefined
|
||||
? pdfDetails[0]?.NotifyOnSignatures
|
||||
: false
|
||||
};
|
||||
const updateTemplate = new Parse.Object("contracts_Template");
|
||||
updateTemplate.id = templateId;
|
||||
@@ -896,9 +919,9 @@ const TemplatePlaceholder = () => {
|
||||
setIsCreateDocModal(false);
|
||||
setIsCreateDoc(true);
|
||||
let pdfUrl = pdfDetails[0]?.URL;
|
||||
if (pdfRotateBase64) {
|
||||
if (pdfBase64Url) {
|
||||
try {
|
||||
pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfRotateBase64);
|
||||
pdfUrl = await convertBase64ToFile(pdfDetails[0].Name, pdfBase64Url);
|
||||
} catch (e) {
|
||||
console.log("error to convertBase64ToFile in placeholder flow", e);
|
||||
}
|
||||
@@ -1323,24 +1346,31 @@ const TemplatePlaceholder = () => {
|
||||
if (isRotate) {
|
||||
setIsRotate({ status: true, degree: rotateDegree });
|
||||
} else {
|
||||
setIsUploadPdf(false);
|
||||
const urlDetails = await rotatePdfPage(
|
||||
pdfDetails[0].URL,
|
||||
rotateDegree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
pdfArrayBuffer
|
||||
);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
}
|
||||
};
|
||||
const handleRemovePlaceholder = async () => {
|
||||
handleRemoveWidgets(setSignerPos, signerPos, pageNumber, setIsRotate);
|
||||
handleRemoveWidgets(
|
||||
setSignerPos,
|
||||
signerPos,
|
||||
pageNumber,
|
||||
uniqueId,
|
||||
setIsRotate
|
||||
);
|
||||
const urlDetails = await rotatePdfPage(
|
||||
pdfDetails[0].URL,
|
||||
isRotate.degree,
|
||||
pageNumber - 1,
|
||||
pdfRotateBase64
|
||||
pdfBase64Url
|
||||
);
|
||||
setPdfRotatese64(urlDetails.base64);
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -1385,6 +1415,7 @@ const TemplatePlaceholder = () => {
|
||||
setPageNumber={setPageNumber}
|
||||
setSignBtnPosition={setSignBtnPosition}
|
||||
pageNumber={pageNumber}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
/>
|
||||
|
||||
{/* pdf render view */}
|
||||
@@ -1393,6 +1424,13 @@ const TemplatePlaceholder = () => {
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
handleRotationFun={handleRotationFun}
|
||||
pdfArrayBuffer={pdfArrayBuffer}
|
||||
pageNumber={pageNumber}
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||
setIsUploadPdf={setIsUploadPdf}
|
||||
setSignerPos={setSignerPos}
|
||||
signerPos={signerPos}
|
||||
/>
|
||||
<div className="w-full md:w-[95%]">
|
||||
{/* this modal is used show alert set placeholder for all signers before send mail */}
|
||||
@@ -1538,6 +1576,7 @@ const TemplatePlaceholder = () => {
|
||||
handleRotationFun={handleRotationFun}
|
||||
clickOnZoomIn={clickOnZoomIn}
|
||||
clickOnZoomOut={clickOnZoomOut}
|
||||
setSignerPos={setSignerPos}
|
||||
/>
|
||||
<div
|
||||
ref={divRef}
|
||||
@@ -1582,7 +1621,7 @@ const TemplatePlaceholder = () => {
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
setIsSelectId={setIsSelectId}
|
||||
pdfRotateBase64={pdfRotateBase64}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
fontSize={fontSize}
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
|
||||
@@ -197,6 +197,7 @@ const AddContact = (props) => {
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
disabled={addYourself}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
placeholder={t("phone-optional")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
checkIsSubscribed,
|
||||
copytoData,
|
||||
fetchUrl,
|
||||
formatDate,
|
||||
getSignedUrl,
|
||||
getTenantDetails,
|
||||
handleSignatureType,
|
||||
@@ -81,6 +82,7 @@ const ReportTable = (props) => {
|
||||
const [isEmbed, setIsEmbed] = useState(false);
|
||||
const [isPublicTour, setIsPublicTour] = useState();
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
const [expiryDate, setExpiryDate] = useState("");
|
||||
const Extand_Class = localStorage.getItem("Extand_Class");
|
||||
const extClass = Extand_Class && JSON.parse(Extand_Class);
|
||||
const startIndex = (currentPage - 1) * props.docPerPage;
|
||||
@@ -288,6 +290,10 @@ const ReportTable = (props) => {
|
||||
updatedSignatureType.length > 0
|
||||
? { SignatureType: updatedSignatureType }
|
||||
: {};
|
||||
const NotifyOnSignatures =
|
||||
Doc.NotifyOnSignatures !== undefined
|
||||
? { NotifyOnSignatures: Doc.NotifyOnSignatures }
|
||||
: {};
|
||||
let placeholdersArr = [];
|
||||
if (Doc.Placeholders?.length > 0) {
|
||||
placeholdersArr = Doc.Placeholders;
|
||||
@@ -315,7 +321,8 @@ const ReportTable = (props) => {
|
||||
RemindOnceInEvery: Doc?.RemindOnceInEvery || 5,
|
||||
IsEnableOTP: Doc?.IsEnableOTP || false,
|
||||
FileAdapterId: Doc?.FileAdapterId || "",
|
||||
...SignatureType
|
||||
...SignatureType,
|
||||
...NotifyOnSignatures
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(
|
||||
@@ -428,6 +435,8 @@ const ReportTable = (props) => {
|
||||
} else {
|
||||
setIsPublicTour({ [item.objectId]: true });
|
||||
}
|
||||
} else if (act.action) {
|
||||
setIsModal({ [`extendexpiry_${item.objectId}`]: true });
|
||||
}
|
||||
};
|
||||
// Get current list
|
||||
@@ -658,15 +667,12 @@ const ReportTable = (props) => {
|
||||
}
|
||||
|
||||
const closeTour = async () => {
|
||||
// console.log("closeTour");
|
||||
setIsTour(false);
|
||||
if (props.isDontShow) {
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const appId = localStorage.getItem("parseAppId");
|
||||
const json = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
const extUserId = json && json.length > 0 && json[0].objectId;
|
||||
// console.log("extUserId ", extUserId)
|
||||
|
||||
let updatedTourStatus = [];
|
||||
if (tourStatusArr.length > 0) {
|
||||
updatedTourStatus = [...tourStatusArr];
|
||||
@@ -684,14 +690,8 @@ const ReportTable = (props) => {
|
||||
|
||||
await axios.put(
|
||||
serverUrl + "classes/contracts_Users/" + extUserId,
|
||||
{
|
||||
TourStatus: updatedTourStatus
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"X-Parse-Application-Id": appId
|
||||
}
|
||||
}
|
||||
{ TourStatus: updatedTourStatus },
|
||||
{ headers: { "X-Parse-Application-Id": appId } }
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1205,6 +1205,129 @@ const ReportTable = (props) => {
|
||||
const closePublicTour = () => {
|
||||
setIsPublicTour();
|
||||
};
|
||||
const handleUpdateExpiry = async (e, item) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (expiryDate) {
|
||||
const oldExpiryDate = new Date(item?.ExpiryDate?.iso);
|
||||
const newExpiryDate = new Date(expiryDate);
|
||||
if (newExpiryDate > oldExpiryDate) {
|
||||
setActLoader({ [`${item.objectId}`]: true });
|
||||
const updateExpiryDate = new Date(expiryDate).toISOString();
|
||||
const expiryIsoFormat = { iso: updateExpiryDate, __type: "Date" };
|
||||
try {
|
||||
const serverUrl = serverUrl_fn();
|
||||
const cls = "contracts_Document";
|
||||
const url = serverUrl + `/classes/${cls}/`;
|
||||
const body = { ExpiryDate: expiryIsoFormat };
|
||||
const res = await axios.put(url + item.objectId, body, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
});
|
||||
if (res.data && res.data.updatedAt) {
|
||||
setIsAlert(true);
|
||||
setAlertMsg({
|
||||
type: "success",
|
||||
message: t("expiry-date-updated", {
|
||||
newexpirydate: new Date(expiryDate)?.toLocaleDateString()
|
||||
})
|
||||
});
|
||||
if (props.ReportName === "Expired Documents") {
|
||||
const upldatedList = props.List.filter(
|
||||
(x) => x.objectId !== item.objectId
|
||||
);
|
||||
props.setList(upldatedList);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
setIsAlert(true);
|
||||
setAlertMsg({
|
||||
type: "danger",
|
||||
message: t("something-went-wrong-mssg")
|
||||
});
|
||||
} finally {
|
||||
setActLoader({});
|
||||
setExpiryDate();
|
||||
setTimeout(() => setIsAlert(false), 2000);
|
||||
setIsModal({});
|
||||
}
|
||||
} else {
|
||||
setIsAlert(true);
|
||||
setAlertMsg({ type: "danger", message: t("expiry-date-error") });
|
||||
setTimeout(() => setIsAlert(false), 2000);
|
||||
}
|
||||
} else {
|
||||
setIsAlert(true);
|
||||
setAlertMsg({ type: "danger", message: t("expiry-date-error") });
|
||||
setTimeout(() => setIsAlert(false), 2000);
|
||||
}
|
||||
};
|
||||
// `formatStatusRow` is used to format status row
|
||||
const formatStatusRow = (item) => {
|
||||
const signers = item?.Placeholders?.map((x, i) => {
|
||||
const matchSigner = item?.AuditTrail?.find(
|
||||
(audit) => audit?.UserPtr?.objectId === x.signerObjId
|
||||
);
|
||||
if (matchSigner) {
|
||||
return {
|
||||
id: i,
|
||||
Email: matchSigner.UserPtr.Email,
|
||||
Activity: matchSigner?.Activity?.toUpperCase() || "-",
|
||||
SignedOn: matchSigner?.SignedOn
|
||||
? new Date(matchSigner?.SignedOn)?.toUTCString()
|
||||
: "-",
|
||||
ViewedOn: matchSigner?.ViewedOn
|
||||
? new Date(matchSigner?.ViewedOn)?.toUTCString()
|
||||
: "-"
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
id: i,
|
||||
Email: x?.signerPtr?.Email || x?.email,
|
||||
Activity: "SENT",
|
||||
SignedOn: "-",
|
||||
ViewedOn: "-"
|
||||
};
|
||||
}
|
||||
});
|
||||
return signers?.map((x, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="text-sm font-medium flex flex-row gap-2 items-center"
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsModal({ [`${item.objectId}_${i}`]: true })}
|
||||
className={`${
|
||||
x.Activity === "SIGNED"
|
||||
? "op-border-primary op-text-primary"
|
||||
: x.Activity === "VIEWED"
|
||||
? "border-green-400 text-green-400"
|
||||
: "border-black text-black"
|
||||
} focus:outline-none border-2 w-[60px] h-[30px] text-[11px] rounded-full`}
|
||||
>
|
||||
{x?.Activity?.toUpperCase() || "-"}
|
||||
</button>
|
||||
<div className="py-2 font-bold text-[12px]">{x?.Email || "-"}</div>
|
||||
{isModal[`${item.objectId}_${i}`] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={t("document-logs")}
|
||||
handleClose={() => setIsModal({})}
|
||||
>
|
||||
<div className="pl-3 first:mt-2 border-t-[1px] border-gray-600 text-[12px] py-2">
|
||||
<p className="font-bold"> {x?.Email}</p>
|
||||
<p>Viewed on: {x?.ViewedOn}</p>
|
||||
<p>Signed on: {x?.SignedOn}</p>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{Object.keys(actLoader)?.length > 0 && (
|
||||
@@ -1284,18 +1407,18 @@ const ReportTable = (props) => {
|
||||
}`}
|
||||
>
|
||||
<table className="op-table border-collapse w-full mb-4">
|
||||
<thead className="text-[14px]">
|
||||
<thead className="text-[14px] text-center">
|
||||
<tr className="border-y-[1px]">
|
||||
{props.heading?.map((item, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<th className="px-4 py-2">{t(`report-heading.${item}`)}</th>
|
||||
<th className="p-2">{t(`report-heading.${item}`)}</th>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{props.ReportName === "Templates" && isEnableSubscription && (
|
||||
<th className="px-4 py-2">{t("public")}</th>
|
||||
<th className="p-2">{t("public")}</th>
|
||||
)}
|
||||
{props.actions?.length > 0 && (
|
||||
<th className="px-4 py-2 text-transparent pointer-events-none">
|
||||
<th className="p-2 text-transparent pointer-events-none">
|
||||
{t("action")}
|
||||
</th>
|
||||
)}
|
||||
@@ -1308,15 +1431,17 @@ const ReportTable = (props) => {
|
||||
props.ReportName === "Contactbook" ? (
|
||||
<tr className="border-y-[1px]" key={index}>
|
||||
{props.heading.includes("Sr.No") && (
|
||||
<th className="px-4 py-2">
|
||||
{startIndex + index + 1}
|
||||
</th>
|
||||
<th className="p-2">{startIndex + index + 1}</th>
|
||||
)}
|
||||
<td className="px-4 py-2 font-semibold">
|
||||
{item?.Name}{" "}
|
||||
</td>
|
||||
<td className="px-4 py-2 ">{item?.Email || "-"}</td>
|
||||
<td className="px-4 py-2">{item?.Phone || "-"}</td>
|
||||
<td className="p-2 text-center">
|
||||
{item?.Email || "-"}
|
||||
</td>
|
||||
<td className="p-2 text-center">
|
||||
{item?.Phone || "-"}
|
||||
</td>
|
||||
<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 &&
|
||||
@@ -1373,34 +1498,39 @@ const ReportTable = (props) => {
|
||||
key={index}
|
||||
>
|
||||
{props.heading.includes("Sr.No") && (
|
||||
<th className="px-4 py-2">
|
||||
<th className="px-2 py-2">
|
||||
{startIndex + index + 1}
|
||||
</th>
|
||||
)}
|
||||
<td className="px-4 py-2 font-semibold min-w-56 max-w-56">
|
||||
{item?.Name}{" "}
|
||||
<td className="p-2 min-w-56 max-w-56 flex flex-col">
|
||||
<div className="font-semibold">{item?.Name}</div>
|
||||
{item?.ExpiryDate?.iso && (
|
||||
<div className="text-gray-500">
|
||||
Expire: {formatDate(item?.ExpiryDate?.iso)}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
{props?.heading?.includes("Reason") && (
|
||||
<td className="px-4 py-2">
|
||||
<td className="p-2 text-center">
|
||||
{item?.DeclineReason?.length > 25
|
||||
? item?.DeclineReason?.slice(0, 25) + "..."
|
||||
: item?.DeclineReason || "-"}
|
||||
</td>
|
||||
)}
|
||||
{props.heading.includes("Note") && (
|
||||
<td className="px-4 py-2">
|
||||
{item?.Note?.length > 25
|
||||
? item?.Note?.slice(0, 25) + "..."
|
||||
: item?.Note || "-"}
|
||||
<td className="p-2 text-center">
|
||||
<p className="truncate w-[100px]">
|
||||
{item?.Note || "-"}
|
||||
</p>
|
||||
</td>
|
||||
)}
|
||||
{props.heading.includes("Folder") && (
|
||||
<td className="px-4 py-2">
|
||||
<td className="p-2 text-center">
|
||||
{item?.Folder?.Name ||
|
||||
t("sidebar.OpenSign™ Drive")}
|
||||
</td>
|
||||
)}
|
||||
<td className="px-4 py-2">
|
||||
<td className="p-2 text-center">
|
||||
<button
|
||||
onClick={() => handleDownload(item)}
|
||||
className="op-link op-link-primary"
|
||||
@@ -1409,73 +1539,40 @@ const ReportTable = (props) => {
|
||||
{item?.URL ? t("download") : "-"}
|
||||
</button>
|
||||
</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">
|
||||
{props.heading.includes("Owner") && (
|
||||
<td className="p-2 text-center">
|
||||
{formatRow(item?.ExtUserPtr)}
|
||||
</td>
|
||||
)}
|
||||
<td className="px-4 py-2">
|
||||
{!item?.IsSignyourself && item?.Placeholders ? (
|
||||
<button
|
||||
onClick={() => handleViewSigners(item)}
|
||||
className="op-link op-link-primary"
|
||||
>
|
||||
{t("view")}
|
||||
</button>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</td>
|
||||
{props.heading.includes("Signers") &&
|
||||
["In-progress documents", "Need your sign"].includes(
|
||||
props.ReportName
|
||||
) ? (
|
||||
<td className="px-1 py-2">
|
||||
{!item?.IsSignyourself && item?.Placeholders && (
|
||||
<>{formatStatusRow(item)}</>
|
||||
)}
|
||||
</td>
|
||||
) : (
|
||||
<td className="p-2 text-center">
|
||||
{!item?.IsSignyourself && item?.Placeholders ? (
|
||||
<button
|
||||
onClick={() => handleViewSigners(item)}
|
||||
className="op-link op-link-primary"
|
||||
>
|
||||
{t("view")}
|
||||
</button>
|
||||
) : (
|
||||
"-"
|
||||
)}
|
||||
</td>
|
||||
)}
|
||||
{props.ReportName === "Templates" &&
|
||||
isEnableSubscription && (
|
||||
<td className=" pl-[20px] py-2">
|
||||
{props.ReportName === "Templates" && (
|
||||
<div
|
||||
className="flex flex-row "
|
||||
className="flex flex-row"
|
||||
data-tut="IsPublic"
|
||||
>
|
||||
<label className="cursor-pointer relative inline-flex items-center mb-0">
|
||||
@@ -1758,6 +1855,41 @@ const ReportTable = (props) => {
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
{isModal["extendexpiry_" + item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={t("btnLabel.extend-expiry-date")}
|
||||
reduceWidth={"md:max-w-[450px]"}
|
||||
handleClose={() => setIsModal({})}
|
||||
>
|
||||
<form
|
||||
className="px-4 py-2 flex flex-col"
|
||||
onSubmit={(e) => handleUpdateExpiry(e, item)}
|
||||
>
|
||||
<label className="mr-2">
|
||||
{t("expiry-date")} {"(dd-mm-yyyy)"}
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="rounded-full mb-2 bg-base-300 w-full px-4 py-2 text-black border-2 hover:border-spacing-2"
|
||||
defaultValue={
|
||||
item?.ExpiryDate?.iso?.split("T")?.[0]
|
||||
}
|
||||
onChange={(e) => {
|
||||
setExpiryDate(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<div className="flex justify-start mb-1">
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("update")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalUi>
|
||||
)}
|
||||
{isShareWith[item.objectId] && (
|
||||
<div className="op-modal op-modal-open">
|
||||
<div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative">
|
||||
|
||||
@@ -5,6 +5,29 @@ import { useTranslation } from "react-i18next";
|
||||
function CustomModal(props) {
|
||||
const { t } = useTranslation();
|
||||
const [reason, setReason] = useState("");
|
||||
const [isExtendExpiry, setIsExtendExpiry] = useState(false);
|
||||
const [expiryDate, setExpiryDate] = useState("");
|
||||
const localuser = localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
|
||||
const currentUser = JSON.parse(localuser);
|
||||
const isCreator = props?.doc
|
||||
? props?.doc?.CreatedBy?.objectId === currentUser?.objectId &&
|
||||
localStorage.getItem("_user_role") !== "Guest"
|
||||
: false;
|
||||
const handleExtendBtn = () => setIsExtendExpiry(!isExtendExpiry);
|
||||
|
||||
const handleUpdateExpiry = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (expiryDate) {
|
||||
props.handleExpiry && props.handleExpiry(expiryDate);
|
||||
} else {
|
||||
alert(t("expiry-date-error"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
props.show && (
|
||||
<dialog className="op-modal op-modal-open absolute z-[448]">
|
||||
@@ -12,17 +35,29 @@ function CustomModal(props) {
|
||||
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
|
||||
{props?.headMsg && props?.headMsg}
|
||||
</h3>
|
||||
<div className="p-[10px] px-[20px]">
|
||||
<p className="text-[15px]">{props.bodyMssg && props.bodyMssg}</p>
|
||||
</div>
|
||||
{props.isDownloadBtn && (
|
||||
<div className="flex justify-start w-full ml-[20px] mb-3 mt-1">
|
||||
<button
|
||||
className="op-btn op-btn-primary "
|
||||
onClick={() => props.handleDownloadBtn()}
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
{!isExtendExpiry && (
|
||||
<div className="p-[10px] px-[20px]">
|
||||
<p className="text-[15px]">{props.bodyMssg && props.bodyMssg}</p>
|
||||
</div>
|
||||
)}
|
||||
{!isExtendExpiry && (
|
||||
<div className="flex flex-row items-center">
|
||||
{isCreator && (
|
||||
<button
|
||||
className="op-btn op-btn-primary px-6 ml-[20px] mb-3 mt-1"
|
||||
onClick={() => handleExtendBtn()}
|
||||
>
|
||||
Extend
|
||||
</button>
|
||||
)}
|
||||
{props.isDownloadBtn && (
|
||||
<button
|
||||
className="op-btn op-btn-primary ml-[10px] mb-3 mt-1"
|
||||
onClick={() => props.handleDownloadBtn()}
|
||||
>
|
||||
Download
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{props.footerMessage && (
|
||||
@@ -60,6 +95,34 @@ function CustomModal(props) {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isExtendExpiry && (
|
||||
<form className="mx-3 mb-3" onSubmit={handleUpdateExpiry}>
|
||||
<label className="ml-2 mt-2">
|
||||
{t("expiry-date")} {"(dd-mm-yyyy)"}
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
className="rounded-full bg-base-300 w-full px-4 py-2 text-black border-2 hover:border-spacing-2"
|
||||
defaultValue={props?.doc?.ExpiryDate?.iso?.split("T")?.[0]}
|
||||
onChange={(e) => setExpiryDate(e.target.value)}
|
||||
/>
|
||||
<div className="flex flex-row items-center mt-2">
|
||||
<button type="submit" className="op-btn op-btn-primary mr-2">
|
||||
{t("update")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => {
|
||||
setExpiryDate("");
|
||||
setIsExtendExpiry(false);
|
||||
}}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
</dialog>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
const QuotaCard = ({ isPaidInfo, handlClose }) => {
|
||||
const QuotaCard = ({ isPaidInfo, handlClose, emailResetDate }) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
return isPaidInfo ? (
|
||||
@@ -17,6 +17,11 @@ const QuotaCard = ({ isPaidInfo, handlClose }) => {
|
||||
<div className="op-card-body">
|
||||
<h2 className="op-card-title">{t("quota-mail-info-head")}</h2>
|
||||
<p>{t("quota-mail-info")}</p>
|
||||
{emailResetDate && (
|
||||
<p>
|
||||
{t("quota-mail-reset")} {emailResetDate}
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<Trans i18nKey={"quota-mail-tip"}>
|
||||
Tip: You can still sign <strong>unlimited documents</strong> by
|
||||
@@ -39,7 +44,17 @@ const QuotaCard = ({ isPaidInfo, handlClose }) => {
|
||||
<div className="op-card op-bg-primary text-primary-content w-full shadow-lg">
|
||||
<div className="op-card-body">
|
||||
<p>{t("quota-mail")}</p>
|
||||
<p>{t("quota-mail-tip")}</p>
|
||||
{emailResetDate && (
|
||||
<p>
|
||||
{t("quota-mail-reset")} {emailResetDate}
|
||||
</p>
|
||||
)}
|
||||
<div>
|
||||
<Trans i18nKey={"quota-mail-tip"}>
|
||||
Tip: You can still sign <strong>unlimited documents</strong> by
|
||||
manually sharing the signing request links.
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="op-card-actions justify-end">
|
||||
<button
|
||||
onClick={() => navigate("/subscription")}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"header-news": "New feature: Public profiles are now live! Easily create and share public templates",
|
||||
"header-news": "New feature: Teams plan users can now integrate their own AWS S3 buckets for file storage",
|
||||
"header-news-btn": "Setup Now",
|
||||
"create-account": "Create Account",
|
||||
"login": "Login",
|
||||
@@ -134,7 +134,8 @@
|
||||
"option": "Option",
|
||||
"Embed": "Embed",
|
||||
"Copy TemplateId": "Copy TemplateId",
|
||||
"Copy Public URL": "Copy Public URL"
|
||||
"Copy Public URL": "Copy Public URL",
|
||||
"extend-expiry-date": "Extend expiry date"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Sr.No",
|
||||
@@ -155,7 +156,8 @@
|
||||
"Status": "Status",
|
||||
"created-date": "Created Date",
|
||||
"Type": "Type",
|
||||
"Logs": "Logs"
|
||||
"Logs": "Logs",
|
||||
"Expiry-date": "Expiry Date"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "These are documents you have started but have not finalized for sending.",
|
||||
@@ -266,7 +268,8 @@
|
||||
"make-template-public-alert": "Are you sure you want to make this template public?",
|
||||
"make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.",
|
||||
"public-role": "Public role",
|
||||
"public-url": "Public profile needed",
|
||||
"public-url": "Public profile",
|
||||
"embed-template": "Embed Template",
|
||||
"public-url-copy": "Here’s your public URL: ",
|
||||
"public-url-copy-mssg": "Copy it or share it with the signer, and you will be able to see all your publicly set templates.",
|
||||
"add-public-url-alert": "Please complete your OpenSign public profile setup. Once done, you’ll be able to make templates public. This process only needs to be completed once.",
|
||||
@@ -512,7 +515,7 @@
|
||||
"error": "Error",
|
||||
"invalid-document": "Error: invalid document!",
|
||||
"document-not-found": "Document not Found!",
|
||||
"document-signed-alert-1": "You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document will be sent to the owner over email once it is signed by all signers.",
|
||||
"document-signed-alert-1": "You have successfully signed the document. You can download or print a copy of the partially signed document. A copy of the digitally signed document & completion certificate will be sent to the owner & all signers over email once it is signed by all signers.",
|
||||
"encrypted-pdf-not-support": "Currently encrypted pdf files are not supported.",
|
||||
"document-signed-alert-4": " Congratulations! 🎉 This document has been successfully signed by all participants!",
|
||||
"document-signed-alert-5": "This document has been signed by all Signers.",
|
||||
@@ -652,6 +655,7 @@
|
||||
"angular-npm-mssg-1": "To integrate OpenSign into your Angular project, simply run the following command:",
|
||||
"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-reset": "Your signature request email credits will be reset on",
|
||||
"quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.",
|
||||
"quota-mail-tip": "Tip: You can still sign <1>unlimited documents</1> by manually sharing the signing request links.",
|
||||
"quota-mail-head": "Quota Reached",
|
||||
@@ -697,5 +701,17 @@
|
||||
"l2": "Enter your name and choose from various font styles to create a signature.",
|
||||
"l3": "Upload an existing digital version of your signature.",
|
||||
"l4": "Use a pre-set signature style saved under 'My Signature' in your settings."
|
||||
}
|
||||
},
|
||||
"notify-on-signatures": "Notify on signatures",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "The document creator will receive an email notification whenever a signer signs the document.",
|
||||
"note": "Note: Notification email will not be sent for the last signer, as the document creator will receive a completion email instead."
|
||||
},
|
||||
"expiry-date": "Expiry date",
|
||||
"expiry-date-updated": "The expiry date has been successfully extended to {{newexpirydate}}",
|
||||
"expiry-date-error": "Please provide a new expiry date that is later than the current one",
|
||||
"delete-page": "Delete page",
|
||||
"delete-alert": "Can not delete single page",
|
||||
"delete-alert-2": "Are you sure want to delete this page ?",
|
||||
"delete-note": "Note - once delete this page you can not undo"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"header-news": "Nueva funcionalidad: ¡Los perfiles públicos ahora están disponibles! Crea y comparte plantillas públicas fácilmente",
|
||||
"header-news": "Nueva característica: los usuarios del plan Teams ahora pueden integrar sus propios depósitos de AWS S3 para el almacenamiento de archivos",
|
||||
"header-news-btn": "Configurar ahora",
|
||||
"create-account": "Crear cuenta",
|
||||
"login": "Iniciar sesión",
|
||||
@@ -134,7 +134,8 @@
|
||||
"option": "Opción",
|
||||
"Embed": "Incrustar",
|
||||
"Copy TemplateId": "Copiar ID de plantilla",
|
||||
"Copy Public URL": "Copiar URL pública"
|
||||
"Copy Public URL": "Copiar URL pública",
|
||||
"Expiry-date": "Date d'expiration"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nº",
|
||||
@@ -155,7 +156,8 @@
|
||||
"Status": "Estado",
|
||||
"created-date": "Fecha de creación",
|
||||
"Type": "Tipo",
|
||||
"Logs": "Registros"
|
||||
"Logs": "Registros",
|
||||
"Expiry-date": "Date d'expiration"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Estos son documentos que has iniciado pero no has finalizado para su envío.",
|
||||
@@ -266,7 +268,8 @@
|
||||
"make-template-public-alert": "¿En definitiva quieres convertir esta plantilla en pública?",
|
||||
"make-template-private-alert": "¿En definitiva quieres convertir esta plantilla en privada? Esto lo removerá de tu perfil público.",
|
||||
"public-role": "Rol público",
|
||||
"public-url": "Se requiere perfil público",
|
||||
"public-url": "Perfil publico",
|
||||
"embed-template": "Insertar plantilla",
|
||||
"public-url-copy": "Aquí está tu URL pública: ",
|
||||
"public-url-copy-mssg": "Cópiala o compártela con el firmante y podrás ver todas tus plantillas públicas.",
|
||||
"add-public-url-alert": "Por favor, completa la configuración de tu perfil público OpenSign. Una vez terminado, podrás crear plantillas públicas. Este proceso solo necesita ser completado una vez.",
|
||||
@@ -512,7 +515,7 @@
|
||||
"error": "Error",
|
||||
"invalid-document": "Error: ¡Documento inválido!",
|
||||
"document-not-found": "¡Documento no encontrado!",
|
||||
"document-signed-alert-1": "Has firmado el documento exitosamente. Puedes descargar o imprimir una copia del documento parcialmente firmado. Una copia del documento firmado digitalmente será enviada al propietario mediante correo electrónico una vez haya sido firmado por todos los firmantes.",
|
||||
"document-signed-alert-1": "Ha firmado exitosamente el documento. Puede descargar o imprimir una copia del documento parcialmente firmado. Se enviará una copia del documento firmado digitalmente y del certificado de finalización al propietario y a todos los firmantes por correo electrónico una vez que todos los firmantes lo hayan firmado.",
|
||||
"encrypted-pdf-not-support": "Los archivos PDF encriptados no están soportados actualmente.",
|
||||
"document-signed-alert-4": " ¡Felicitaciones! 🎉 ¡Este documento ha sido firmado exitosamente por todos los participantes!",
|
||||
"document-signed-alert-5": "Este documento ha sido firmado por todos los firmantes.",
|
||||
@@ -652,6 +655,7 @@
|
||||
"angular-npm-mssg-1": "Para integrar OpenSign a tu proyecto Angular, simplemente ejecuta los siguientes comandos:",
|
||||
"quota-mail-info-head": "Límite mensual de solicitudes de firma por correo",
|
||||
"quota-mail-info": "Puedes enviar hasta 15 solicitudes de firma por correo cada mes. Mejora ahora para enviar solicitudes de firma sin límite.",
|
||||
"quota-mail-reset": "Los créditos de tu correo electrónico de solicitud de firma se restablecerán el",
|
||||
"quota-mail": "Has alcanzado tu límite de 15 solicitudes de firma por correo para este mes. Mejora ahora para continuar enviando correos.",
|
||||
"quota-mail-tip": "Consejo: Todavía puedes solciitar la firma de <1>documentos ilimitados</1> compartiendo manualmente los enlaces de solicitud de firma.",
|
||||
"quota-mail-head": "Cuota alcanzada",
|
||||
@@ -697,5 +701,17 @@
|
||||
"l2": "Ingrese su nombre y elija entre varios estilos de fuente para crear una firma.",
|
||||
"l3": "Cargue una versión digital existente de su firma.",
|
||||
"l4": "Utilice un estilo de firma preestablecido guardado en 'Mi firma' en su configuración."
|
||||
}
|
||||
},
|
||||
"notify-on-signatures": "Notificar sobre firmas",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "El creador del documento recibirá una notificación por correo electrónico cada vez que un firmante firme el documento.",
|
||||
"note": "Nota: No se enviará un correo electrónico de notificación al último firmante, ya que el creador del documento recibirá un correo electrónico de finalización."
|
||||
},
|
||||
"expiry-date": "Date d'expiration",
|
||||
"expiry-date-updated": "La fecha de vencimiento se ha extendido con éxito hasta el {{newexpirydate}}",
|
||||
"expiry-date-error": "Proporcione una nueva fecha de vencimiento que sea posterior a la actual",
|
||||
"delete-page": "eliminar página",
|
||||
"delete-alert": "No se puede eliminar una sola página",
|
||||
"delete-alert-2": "¿Está seguro de que desea eliminar esta página?",
|
||||
"delete-note": "Nota: una vez que elimines esta página, no podrás deshacerla"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"header-news": "Nouvelle fonctionnalité : les profils publics sont désormais en ligne ! Créez et partagez facilement des modèles publics",
|
||||
"header-news": "Nouvelle fonctionnalité : les utilisateurs du forfait Teams peuvent désormais intégrer leurs propres compartiments AWS S3 pour le stockage de fichiers",
|
||||
"header-news-btn": "Configurer maintenant",
|
||||
"create-account": "Créer un compte",
|
||||
"login": "Se Connecter",
|
||||
@@ -138,7 +138,8 @@
|
||||
"Status": "Statut",
|
||||
"created-date": "Date de création",
|
||||
"Type": "Saisir",
|
||||
"Logs": "Journaux"
|
||||
"Logs": "Journaux",
|
||||
"Expiry-date": "Fecha de caducidad"
|
||||
},
|
||||
"btnLabel": {
|
||||
"sign": "Signer",
|
||||
@@ -154,7 +155,8 @@
|
||||
"option": "Option",
|
||||
"Embed": "Intégrer",
|
||||
"Copy TemplateId": "Copier l'ID du modèle",
|
||||
"Copy Public URL": "Copier l'URL publique"
|
||||
"Copy Public URL": "Copier l'URL publique",
|
||||
"extend-expiry-date": "Prolonger la date d'expiration"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
@@ -265,7 +267,8 @@
|
||||
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
|
||||
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
|
||||
"public-role": "Rôle public",
|
||||
"public-url": "Profil public requis",
|
||||
"public-url": "Profil public",
|
||||
"embed-template": "Intégrer le modèle",
|
||||
"public-url-copy": "Voici votre URL publique : ",
|
||||
"public-url-copy-mssg": "copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
|
||||
"add-public-url-alert": "Veuillez terminer la configuration de votre profil public OpenSign. Une fois cela fait, vous pourrez rendre les modèles publics. Ce processus ne doit être effectué qu’une seule fois.",
|
||||
@@ -511,7 +514,7 @@
|
||||
"error": "Erreur",
|
||||
"invalid-document": "Erreur : document invalide !",
|
||||
"document-not-found": "Document introuvable !",
|
||||
"document-signed-alert-1": "Vous avez signé le document avec succès. Vous pouvez télécharger ou imprimer une copie du document partiellement signé. Une copie du document signé numériquement sera envoyée au propriétaire par e-mail une fois qu'il sera signé par tous les signataires.",
|
||||
"document-signed-alert-1": "Vous avez signé le document avec succès. Vous pouvez télécharger ou imprimer une copie du document partiellement signé. Une copie du document signé numériquement et du certificat d'achèvement sera envoyée au propriétaire et à tous les signataires par e-mail une fois qu'il aura été signé par tous les signataires.",
|
||||
"encrypted-pdf-not-support": "Les fichiers PDF actuellement cryptés ne sont pas pris en charge. ",
|
||||
"document-signed-alert-4": "Toutes nos félicitations! 🎉 Ce document a été signé avec succès par tous les participants !",
|
||||
"document-signed-alert-5": "Ce document a été signé par tous les signataires.",
|
||||
@@ -651,6 +654,7 @@
|
||||
"angular-npm-mssg-1": "Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
|
||||
"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-reset": "Les crédits de votre email de demande de signature seront réinitialisés le",
|
||||
"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-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",
|
||||
@@ -696,5 +700,17 @@
|
||||
"l2": "Entrez votre nom et choisissez parmi différents styles de police pour créer une signature.",
|
||||
"l3": "Téléchargez une version numérique existante de votre signature.",
|
||||
"l4": "Utilisez un style de signature prédéfini enregistré sous 'Ma signature' dans vos paramètres."
|
||||
}
|
||||
},
|
||||
"notify-on-signatures": "Notifier les signatures",
|
||||
"notify-on-signatures-help": {
|
||||
"p1": "Le créateur du document recevra une notification par e-mail chaque fois qu'un signataire signera le document.",
|
||||
"note": "Remarque : L'e-mail de notification ne sera pas envoyé pour le dernier signataire, car le créateur du document recevra à la place un e-mail de fin."
|
||||
},
|
||||
"expiry-date": "Fecha de caducidad",
|
||||
"expiry-date-updated": "La date d'expiration a été prolongée avec succès jusqu'au {{newexpirydate}}",
|
||||
"expiry-date-error": "Veuillez fournir une nouvelle date d'expiration postérieure à la date actuelle",
|
||||
"delete-page": "supprimer la page",
|
||||
"delete-alert": "Impossible de supprimer une seule page",
|
||||
"delete-alert-2": "Etes-vous sûr de vouloir supprimer cette page ?",
|
||||
"delete-note": "Remarque : une fois cette page supprimée, vous ne pouvez plus l'annuler"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
The OpenSign Enterprise license (the “Enterprise License”)
|
||||
Copyright (c) 2020-2024 Qik Innovations Private Limited.
|
||||
|
||||
With regard to the OpenSign Software:
|
||||
|
||||
This software and associated documentation files (the "Software") may only be
|
||||
used in production, if you (and any entity that you represent) have agreed to,
|
||||
and are in compliance with, the OpenSign Subscription Terms of Service(the “Enterprise Terms”), or other
|
||||
agreement governing the use of the Software, as agreed by you and OpenSign,
|
||||
and otherwise have a valid OpenSign Enterprise license for the
|
||||
correct number of user seats. Subject to the foregoing sentence, you are free to
|
||||
modify this Software and publish patches to the Software. You agree that OpenSign
|
||||
and/or its licensors (as applicable) retain all right, title and interest in and
|
||||
to all such modifications and/or patches, and all such modifications and/or
|
||||
patches may only be used, copied, modified, displayed, distributed, or otherwise
|
||||
exploited with a valid OpenSign Enterprise license for the correct
|
||||
number of user seats. Notwithstanding the foregoing, you may copy and modify
|
||||
the Software for development and testing purposes, without requiring a
|
||||
subscription. You agree that OpenSign and/or its licensors (as applicable) retain
|
||||
all right, title and interest in and to all such modifications. You are not
|
||||
granted any other rights beyond what is expressly stated herein. Subject to the
|
||||
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
|
||||
and/or sell the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
For all third party components incorporated into the OpenSign Software, those
|
||||
components are licensed under the original license provided by the owner of the
|
||||
applicable component.
|
||||
@@ -29,7 +29,6 @@ import createFolder from './routes/createFolder.js';
|
||||
import updateFolder from './routes/updateFolder.js';
|
||||
import getFolderList from './routes/getFolderList.js';
|
||||
import deleteFolder from './routes/deleteFolder.js';
|
||||
import draftTemplate from './routes/draftTemplate.js';
|
||||
dotenv.config();
|
||||
const storage = multer.memoryStorage();
|
||||
const upload = multer({ storage: storage });
|
||||
@@ -74,9 +73,6 @@ app.delete('/document/:document_id', deleteDocument);
|
||||
// get all types of documents on the basis of doctype
|
||||
app.get('/documentlist/:doctype', getDocumentList);
|
||||
|
||||
// create draft template with file base64
|
||||
app.post('/drafttemplate', draftTemplate);
|
||||
|
||||
// create Template with co-ordinate
|
||||
app.post('/createtemplate', createTemplatewithCoordinate);
|
||||
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
color,
|
||||
customAPIurl,
|
||||
saveFileUsage,
|
||||
formatWidgetOptions,
|
||||
sanitizeFileName,
|
||||
} from '../../../../Utils.js';
|
||||
import uploadFileToS3 from '../../../parsefunction/uploadFiletoS3.js';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const randomId = () => Math.floor(1000 + Math.random() * 9000);
|
||||
export default async function draftTemplate(request, response) {
|
||||
const name = request.body.title;
|
||||
const note = request.body.note;
|
||||
const description = request.body.description;
|
||||
const signers = request.body.signers;
|
||||
const folderId = request.body.folderId;
|
||||
const base64File = request.body.file;
|
||||
const fileData = request.files?.[0] ? request.files[0].buffer : null;
|
||||
const SendinOrder = request.body.sendInOrder || true;
|
||||
const isEnableOTP = request.body?.enableOTP === true ? true : false;
|
||||
const isTourEnabled = request.body?.enableTour || false;
|
||||
const isPublic = request.body?.isPublic;
|
||||
const protocol = customAPIurl();
|
||||
const baseUrl = new URL(process.env.PUBLIC_URL).origin;
|
||||
|
||||
try {
|
||||
const reqToken = request.headers['x-api-token'];
|
||||
if (!reqToken) {
|
||||
return response.status(400).json({ error: 'Please Provide API Token' });
|
||||
}
|
||||
const tokenQuery = new Parse.Query('appToken');
|
||||
tokenQuery.equalTo('token', reqToken);
|
||||
tokenQuery.include('userId');
|
||||
const token = await tokenQuery.first({ useMasterKey: true });
|
||||
if (token !== undefined) {
|
||||
// Valid Token then proceed request
|
||||
const parseUser = JSON.parse(JSON.stringify(token));
|
||||
const userPtr = {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: parseUser.userId.objectId,
|
||||
};
|
||||
const contractsUser = new Parse.Query('contracts_Users');
|
||||
contractsUser.equalTo('UserId', userPtr);
|
||||
contractsUser.include('TenantId');
|
||||
const extUser = await contractsUser.first({ useMasterKey: true });
|
||||
const parseExtUser = JSON.parse(JSON.stringify(extUser));
|
||||
let fileUrl;
|
||||
if (request.files?.[0]) {
|
||||
const base64 = fileData?.toString('base64');
|
||||
const file = new Parse.File(request.files?.[0]?.originalname, {
|
||||
base64: base64,
|
||||
});
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
const buffer = Buffer.from(base64, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
} else {
|
||||
const filename = sanitizeFileName(`${name}.pdf`);
|
||||
let adapter = {};
|
||||
const ActiveFileAdapter = parseExtUser?.TenantId?.ActiveFileAdapter || '';
|
||||
if (ActiveFileAdapter) {
|
||||
adapter =
|
||||
parseExtUser?.TenantId?.FileAdapters?.find(x => (x.id = ActiveFileAdapter)) || {};
|
||||
}
|
||||
if (adapter?.id) {
|
||||
const filedata = Buffer.from(base64File, 'base64');
|
||||
// `uploadFileToS3` is used to save document in user's file storage
|
||||
fileUrl = await uploadFileToS3(filedata, filename, 'application/pdf', adapter);
|
||||
} else {
|
||||
const file = new Parse.File(filename, { base64: base64File }, 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
fileUrl = file.url();
|
||||
}
|
||||
const buffer = Buffer.from(base64File, 'base64');
|
||||
saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId);
|
||||
}
|
||||
const extUserPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
};
|
||||
const object = new Parse.Object('contracts_Template');
|
||||
object.set('Name', name);
|
||||
if (note) {
|
||||
object.set('Note', note);
|
||||
}
|
||||
if (description) {
|
||||
object.set('Description', description);
|
||||
}
|
||||
if (SendinOrder) {
|
||||
object.set('SendinOrder', SendinOrder);
|
||||
}
|
||||
object.set('URL', fileUrl);
|
||||
object.set('CreatedBy', userPtr);
|
||||
object.set('ExtUserPtr', extUserPtr);
|
||||
object.set('IsEnableOTP', isEnableOTP);
|
||||
object.set('IsTourEnabled', isTourEnabled);
|
||||
const Public = isPublic !== undefined ? isPublic : false;
|
||||
if (isPublic !== undefined) {
|
||||
object.set('IsPublic', Public);
|
||||
}
|
||||
let contact = [];
|
||||
if (signers && signers.length > 0) {
|
||||
let parseSigners;
|
||||
if (base64File) {
|
||||
parseSigners = signers;
|
||||
} else {
|
||||
parseSigners = JSON.parse(signers);
|
||||
}
|
||||
let createContactUrl = protocol + '/v1/createcontact';
|
||||
|
||||
for (const [index, element] of parseSigners.entries()) {
|
||||
if (element?.name && element?.email) {
|
||||
const body = {
|
||||
name: element?.name,
|
||||
email: element?.email,
|
||||
phone: element?.phone || '',
|
||||
};
|
||||
try {
|
||||
const res = await axios.post(createContactUrl, body, {
|
||||
headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken },
|
||||
});
|
||||
// console.log('res ', res.data);
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: res.data?.objectId,
|
||||
};
|
||||
const newObj = { ...element, contactPtr: contactPtr, index: index };
|
||||
contact.push(newObj);
|
||||
} catch (err) {
|
||||
// console.log('err ', err.response);
|
||||
if (err?.response?.data?.objectId) {
|
||||
const contactPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: err.response.data?.objectId,
|
||||
};
|
||||
const newObj = { ...element, contactPtr: contactPtr, index: index };
|
||||
contact.push(newObj);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const newObj = { ...element, contactPtr: {}, index: index };
|
||||
contact.push(newObj);
|
||||
}
|
||||
}
|
||||
const updatedSigners = contact?.filter(x => x.contactPtr && x.contactPtr.objectId);
|
||||
if (updatedSigners && updatedSigners.length > 0) {
|
||||
object.set(
|
||||
'Signers',
|
||||
updatedSigners?.map(x => x.contactPtr)
|
||||
);
|
||||
}
|
||||
let updatePlaceholders = [];
|
||||
for (const signer of contact) {
|
||||
const placeHolder = [];
|
||||
if (signer.widgets?.length > 0) {
|
||||
for (const widget of signer.widgets) {
|
||||
const pageNumber = widget.page;
|
||||
const page = placeHolder.find(page => page.pageNumber === pageNumber);
|
||||
const options = formatWidgetOptions(widget.type, widget.options);
|
||||
const widgetData = {
|
||||
isStamp: widget.type === 'stamp' || widget.type === 'image',
|
||||
key: randomId(),
|
||||
isDrag: false,
|
||||
scale: 1,
|
||||
isMobile: false,
|
||||
zIndex: 1,
|
||||
type: widget.type === 'textbox' ? 'text input' : widget.type,
|
||||
options: options,
|
||||
Width: widget.w,
|
||||
Height: widget.h,
|
||||
xPosition: widget.x,
|
||||
yPosition: widget.y,
|
||||
};
|
||||
|
||||
if (page) {
|
||||
page.pos.push(widgetData);
|
||||
} else {
|
||||
placeHolder.push({ pageNumber, pos: [widgetData] });
|
||||
}
|
||||
}
|
||||
}
|
||||
updatePlaceholders.push({
|
||||
signerObjId: signer?.contactPtr?.objectId || '',
|
||||
signerPtr: signer?.contactPtr,
|
||||
Role: signer.role,
|
||||
Id: randomId(),
|
||||
blockColor: color[signer?.index],
|
||||
placeHolder,
|
||||
});
|
||||
}
|
||||
if (updatePlaceholders?.length > 0) {
|
||||
object.set('Placeholders', updatePlaceholders);
|
||||
}
|
||||
}
|
||||
if (folderId) {
|
||||
object.set('Folder', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Template',
|
||||
objectId: folderId,
|
||||
});
|
||||
}
|
||||
if (parseExtUser?.TenantId?.ActiveFileAdapter) {
|
||||
object.set('FileAdapterId', parseExtUser?.TenantId?.ActiveFileAdapter);
|
||||
}
|
||||
const newACL = new Parse.ACL();
|
||||
newACL.setPublicReadAccess(false);
|
||||
newACL.setPublicWriteAccess(false);
|
||||
newACL.setReadAccess(userPtr.objectId, true);
|
||||
newACL.setWriteAccess(userPtr.objectId, true);
|
||||
object.setACL(newACL);
|
||||
const res = await object.save(null, { useMasterKey: true });
|
||||
if (request.posthog) {
|
||||
request.posthog?.capture({
|
||||
distinctId: parseUser.userId.email,
|
||||
event: 'api_draft_template',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
const JwtToken = jwt.sign(
|
||||
{ user_email: parseUser.userId.email, template_id: res.id },
|
||||
reqToken
|
||||
);
|
||||
|
||||
return response.json({ objectId: res.id, url: `${baseUrl}/drafttemplate/${JwtToken}` });
|
||||
} else {
|
||||
return response.status(405).json({ error: 'Invalid API Token!' });
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
return response.status(400).json({ error: 'Something went wrong, please try again later!' });
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,7 @@ import updateTourStatus from './parsefunction/updateTourStatus.js';
|
||||
import saveTemplate from './parsefunction/saveTemplate.js';
|
||||
import updateToPublicTemplate from './parsefunction/updateToPublicTemplate.js';
|
||||
import updateSignatureType from './parsefunction/updatesignaturetype.js';
|
||||
import updatePreferences from './parsefunction/updatePreferences.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);
|
||||
@@ -180,3 +181,4 @@ Parse.Cloud.define('isuserincontactbook', isUserInContactBook);
|
||||
Parse.Cloud.define('updatetourstatus', updateTourStatus);
|
||||
Parse.Cloud.define('updatetopublictemplate', updateToPublicTemplate);
|
||||
Parse.Cloud.define('updatesignaturetype', updateSignatureType);
|
||||
Parse.Cloud.define('updatepreferences', updatePreferences);
|
||||
|
||||
@@ -74,6 +74,9 @@ const createDocumentFromTemplate = async (template, existContact, index) => {
|
||||
if (template?.SignatureType?.length > 0) {
|
||||
object.set('SignatureType', template?.SignatureType);
|
||||
}
|
||||
if (template?.NotifyOnSignatures) {
|
||||
object.set('NotifyOnSignatures', template?.NotifyOnSignatures);
|
||||
}
|
||||
let signers = template?.Signers || [];
|
||||
const signerobj = {
|
||||
__type: 'Pointer',
|
||||
|
||||
@@ -252,6 +252,7 @@ async function batchQuery(userId, Documents, Ip, parseConfig) {
|
||||
IsTourEnabled: x?.IsTourEnabled || false,
|
||||
FileAdapterId: x?.FileAdapterId || '',
|
||||
...(x?.SignatureType ? { SignatureType: x?.SignatureType } : {}),
|
||||
...(x?.NotifyOnSignatures ? { NotifyOnSignatures: x?.NotifyOnSignatures } : {}),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function getPresignedUrl(url, adapter) {
|
||||
AWS.config.update({ credentials: credentials, region: adapter?.region || process.env.DO_REGION });
|
||||
const spacesEndpoint = adapter?.endpoint || new AWS.Endpoint(process.env.DO_ENDPOINT);
|
||||
|
||||
const s3 = new AWS.S3({ endpoint: spacesEndpoint });
|
||||
const s3 = new AWS.S3({ endpoint: spacesEndpoint, signatureVersion: "v4" });
|
||||
|
||||
// Create a new URL object
|
||||
const parsedUrl = new URL(url);
|
||||
|
||||
@@ -458,17 +458,17 @@ export default async function GenerateCertificate(docDetails) {
|
||||
});
|
||||
|
||||
currentPage.drawText('Viewed on :', {
|
||||
x: half,
|
||||
x: half + 55,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(`${new Date(x.ViewedOn).toUTCString()}`, {
|
||||
x: half + 75,
|
||||
x: half + 112,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
@@ -490,17 +490,17 @@ export default async function GenerateCertificate(docDetails) {
|
||||
});
|
||||
|
||||
currentPage.drawText('Signed on :', {
|
||||
x: half,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
x: half + 55,
|
||||
y: yPosition3 + 5,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(`${new Date(x.SignedOn).toUTCString()}`, {
|
||||
x: half + 70,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
x: half + 108,
|
||||
y: yPosition3 + 5,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
@@ -520,22 +520,23 @@ export default async function GenerateCertificate(docDetails) {
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
currentPage.drawText('Security level :', {
|
||||
x: half,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(`Email, OTP Auth`, {
|
||||
x: half + 90,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
if (IsEnableOTP) {
|
||||
currentPage.drawText('Security level :', {
|
||||
x: half + 55,
|
||||
y: yPosition4 + 10,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
currentPage.drawText(`Email, OTP Auth`, {
|
||||
x: half + 125,
|
||||
y: yPosition4 + 10,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
}
|
||||
currentPage.drawText('Signature :', {
|
||||
x: 30,
|
||||
y: yPosition5,
|
||||
|
||||
@@ -95,6 +95,53 @@ async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
|
||||
}
|
||||
}
|
||||
|
||||
// `sendNotifyMail` is used to send notification mail of signer signed the document
|
||||
async function sendNotifyMail(doc, signUser, mailProvider) {
|
||||
try {
|
||||
const auditTrailCount = doc?.AuditTrail?.filter(x => x.Activity === 'Signed')?.length || 0;
|
||||
const signersCount = doc?.Placeholders?.length;
|
||||
const remaingsign = signersCount - auditTrailCount;
|
||||
if (remaingsign > 1 && doc?.NotifyOnSignatures) {
|
||||
const sender = doc.ExtUserPtr;
|
||||
const pdfName = doc.Name;
|
||||
const creatorName = doc.ExtUserPtr.Name;
|
||||
const creatorEmail = doc.ExtUserPtr.Email;
|
||||
const signerName = signUser.Name;
|
||||
const signerEmail = signUser.Email;
|
||||
const mailLogo = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png';
|
||||
const viewDocUrl = `${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
|
||||
const subject = `Document "${pdfName}" has been signed by ${signerName}`;
|
||||
const body =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='box-shadow:rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'>" +
|
||||
`<div><img src=${mailLogo} height='50' style='padding:20px' /></div><div style='padding:2px;font-family:system-ui;background-color:#47a3ad;'>` +
|
||||
`<p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed by ${signerName}</p>` +
|
||||
`</div><div style='padding:20px;font-family:system-ui;font-size:14px'><p>Dear ${creatorName},</p>` +
|
||||
`<p>${pdfName} has been signed by ${signerName} "${signerEmail}" successfully</p>` +
|
||||
`<p><a href=${viewDocUrl} target=_blank>View Document</a></p>` +
|
||||
`</div></div><div><p>This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender ${creatorEmail} directly. If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>`;
|
||||
|
||||
const params = {
|
||||
extUserId: sender.objectId,
|
||||
from: 'OpenSign™',
|
||||
recipient: creatorEmail,
|
||||
subject: subject,
|
||||
pdfName: pdfName,
|
||||
html: body,
|
||||
mailProvider: mailProvider,
|
||||
};
|
||||
await axios.post(serverUrl + '/functions/sendmailv3', params, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in sendnotifymail', err);
|
||||
}
|
||||
}
|
||||
|
||||
// `sendCompletedMail` is used to send copy of completed document mail
|
||||
async function sendCompletedMail(obj) {
|
||||
const url = obj.doc?.SignedUrl;
|
||||
@@ -384,6 +431,7 @@ async function PDF(req) {
|
||||
isCompleted = true;
|
||||
}
|
||||
const randomNumber = Math.floor(Math.random() * 5000);
|
||||
// below regex is used to replace all word with "_" except A to Z, a to z, numbers
|
||||
const docName = _resDoc?.Name?.replace(/[^a-zA-Z0-9._-]/g, '_')?.toLowerCase();
|
||||
const name = `signed_${docName}_${randomNumber}.pdf`;
|
||||
const filePath = `./exports/${name}`;
|
||||
@@ -446,6 +494,7 @@ async function PDF(req) {
|
||||
sign // sign base64
|
||||
);
|
||||
sendDoctoWebhook(_resDoc, data.imageUrl, 'signed', signUser);
|
||||
sendNotifyMail(_resDoc, signUser, mailProvider);
|
||||
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
|
||||
if (updatedDoc && updatedDoc.isCompleted) {
|
||||
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
|
||||
|
||||
@@ -76,6 +76,7 @@ export default function reportJson(id, userId) {
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'FileAdapterId',
|
||||
'ExpiryDate',
|
||||
],
|
||||
};
|
||||
// In progess report
|
||||
@@ -211,6 +212,7 @@ export default function reportJson(id, userId) {
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'FileAdapterId',
|
||||
'ExpiryDate',
|
||||
],
|
||||
};
|
||||
// Recently sent for signatures report show on dashboard
|
||||
@@ -292,6 +294,7 @@ export default function reportJson(id, userId) {
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'FileAdapterId',
|
||||
'ExpiryDate',
|
||||
],
|
||||
};
|
||||
// Drafts report show on dashboard
|
||||
@@ -362,6 +365,8 @@ export default function reportJson(id, userId) {
|
||||
'SharedWith.Name',
|
||||
'SendinOrder',
|
||||
'FileAdapterId',
|
||||
'SignatureType',
|
||||
'NotifyOnSignatures',
|
||||
],
|
||||
};
|
||||
default:
|
||||
|
||||
@@ -37,7 +37,8 @@ const makeEmail = async (to, from, subject, html, url, pdfName) => {
|
||||
let Pdf = fs.createWriteStream('test.pdf');
|
||||
const writeToLocalDisk = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const isSecure = new URL(url)?.protocol === 'https:';
|
||||
const isSecure =
|
||||
new URL(url)?.protocol === 'https:' && new URL(url)?.hostname !== 'localhost';
|
||||
if (useLocal !== 'true' || isSecure) {
|
||||
https.get(url, async function (response) {
|
||||
response.pipe(Pdf);
|
||||
|
||||
@@ -33,7 +33,9 @@ async function sendMailProvider(req, plan, monthchange) {
|
||||
let Pdf = fs.createWriteStream('test.pdf');
|
||||
const writeToLocalDisk = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const isSecure = new URL(req.params.url)?.protocol === 'https:';
|
||||
const isSecure =
|
||||
new URL(req.params.url)?.protocol === 'https:' &&
|
||||
new URL(req.params.url)?.hostname !== 'localhost';
|
||||
if (useLocal !== 'true' || isSecure) {
|
||||
https.get(req.params.url, async function (response) {
|
||||
response.pipe(Pdf);
|
||||
@@ -200,7 +202,9 @@ async function sendcustomsmtp(extRes, req) {
|
||||
let Pdf = fs.createWriteStream('test.pdf');
|
||||
const writeToLocalDisk = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const isSecure = new URL(req.params.url)?.protocol === 'https:';
|
||||
const isSecure =
|
||||
new URL(req.params.url)?.protocol === 'https:' &&
|
||||
new URL(req.params.url)?.hostname !== 'localhost';
|
||||
if (useLocal !== 'true' || isSecure) {
|
||||
https.get(req.params.url, async function (response) {
|
||||
response.pipe(Pdf);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
export default async function updatePreferences(request) {
|
||||
if (!request?.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
}
|
||||
const SignatureType = request.params.SignatureType || [];
|
||||
const NotifyOnSignatures = request.params.NotifyOnSignatures;
|
||||
if (SignatureType?.length > 0 || NotifyOnSignatures !== undefined) {
|
||||
try {
|
||||
const orgQuery = new Parse.Query('contracts_Users');
|
||||
orgQuery.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: request.user.id,
|
||||
});
|
||||
const resUser = await orgQuery.first({ useMasterKey: true });
|
||||
if (resUser) {
|
||||
const newOrg = new Parse.Object('contracts_Users');
|
||||
newOrg.id = resUser.id;
|
||||
if (NotifyOnSignatures !== undefined) {
|
||||
newOrg.set('NotifyOnSignatures', NotifyOnSignatures);
|
||||
}
|
||||
if (SignatureType.length > 0) {
|
||||
const enabledSignTypes = SignatureType?.filter(x => x.enabled);
|
||||
const isDefaultSignTypeOnly =
|
||||
enabledSignTypes?.length === 1 && enabledSignTypes[0]?.name === 'default';
|
||||
|
||||
if (enabledSignTypes.length === 0) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'At least one signature type should be enabled.'
|
||||
);
|
||||
} else if (isDefaultSignTypeOnly) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.INVALID_QUERY,
|
||||
'At least one signature type other than the default should be enabled.'
|
||||
);
|
||||
} else {
|
||||
newOrg.set('SignatureType', SignatureType);
|
||||
}
|
||||
}
|
||||
const updateUserRes = await newOrg.save(null, { useMasterKey: true });
|
||||
if (updateUserRes) {
|
||||
const _updateUserRes = JSON.parse(JSON.stringify(updateUserRes));
|
||||
return _updateUserRes;
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, 'Premission denied.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in addorganization', err);
|
||||
const code = err?.code || 400;
|
||||
const msg = err?.message || 'Something went wrong.';
|
||||
throw new Parse.Error(code, msg);
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide parameters.');
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ async function uploadFileToS3(buffer, fileName, mimeType, adapter) {
|
||||
client = new S3Client({
|
||||
region: adapter?.region,
|
||||
credentials: { accessKeyId: adapter?.accessKeyId, secretAccessKey: adapter?.secretAccessKey },
|
||||
signatureVersion: 'v4'
|
||||
});
|
||||
}
|
||||
const prefixId = crypto.randomBytes(16).toString('hex');
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
*
|
||||
* @param {Parse} Parse
|
||||
*/
|
||||
exports.up = async Parse => {
|
||||
const docSchema = new Parse.Schema('contracts_Document');
|
||||
docSchema.addBoolean('NotifyOnSignatures');
|
||||
await docSchema.update();
|
||||
|
||||
const templateSchema = new Parse.Schema('contracts_Template');
|
||||
templateSchema.addBoolean('NotifyOnSignatures');
|
||||
await templateSchema.update();
|
||||
|
||||
const extUserSchema = new Parse.Schema('contracts_Users');
|
||||
extUserSchema.addBoolean('NotifyOnSignatures');
|
||||
return extUserSchema.update();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Parse} Parse
|
||||
*/
|
||||
exports.down = async Parse => {
|
||||
const docSchema = new Parse.Schema('contracts_Document');
|
||||
docSchema.deleteField('NotifyOnSignatures');
|
||||
await docSchema.update();
|
||||
|
||||
const templateSchema = new Parse.Schema('contracts_Template');
|
||||
templateSchema.deleteField('NotifyOnSignatures');
|
||||
await templateSchema.update();
|
||||
|
||||
const extUserSchema = new Parse.Schema('contracts_Users');
|
||||
docSchema.deleteField('NotifyOnSignatures');
|
||||
return extUserSchema.update();
|
||||
};
|
||||
Generated
+480
-575
File diff suppressed because it is too large
Load Diff
@@ -18,15 +18,15 @@
|
||||
"watch": "nodemon index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.685.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.688.0",
|
||||
"@aws-sdk/client-s3": "^3.691.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.691.0",
|
||||
"@parse/fs-files-adapter": "^3.0.0",
|
||||
"@parse/s3-files-adapter": "^3.0.0",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@signpdf/placeholder-pdf-lib": "^3.2.4",
|
||||
"@signpdf/signer-p12": "^3.2.4",
|
||||
"@signpdf/signpdf": "^3.2.4",
|
||||
"aws-sdk": "^2.1691.0",
|
||||
"aws-sdk": "^2.1692.0",
|
||||
"axios": "^1.7.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
@@ -35,7 +35,7 @@
|
||||
"generate-api-key": "^1.0.2",
|
||||
"googleapis": "^144.0.0",
|
||||
"mailgun.js": "^10.2.3",
|
||||
"mongoose": "^8.8.0",
|
||||
"mongoose": "^8.8.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"multer-s3": "^3.0.1",
|
||||
"node-forge": "^1.3.1",
|
||||
|
||||
Reference in New Issue
Block a user