Merge pull request #1644 from OpenSignLabs/updates-13949958694

Merge pull request #395 from OpenSignLabs
This commit is contained in:
prafull-opensignlabs
2025-03-19 20:41:10 +05:30
committed by GitHub
40 changed files with 1601 additions and 1609 deletions
+2 -3
View File
@@ -1,5 +1,5 @@
# Use an official Node runtime as the base image # Use an official Node runtime as the base image
FROM node:18 FROM node:22.14.0
# Set the working directory inside the container # Set the working directory inside the container
WORKDIR /usr/src/app WORKDIR /usr/src/app
@@ -16,8 +16,7 @@ COPY apps/OpenSign/.husky .
# Define environment variables if needed # Define environment variables if needed
ENV NODE_ENV=production ENV NODE_ENV=production
ENV REACT_APP_DEPLOYMENT=free_selfhost ENV GENERATE_SOURCEMAP=false
# build # build
RUN npm run build RUN npm run build
@@ -79,6 +79,7 @@
"Storage": "Speicher", "Storage": "Speicher",
"Signing certificate": "Signierzertifikat", "Signing certificate": "Signierzertifikat",
"Teams": "Teams", "Teams": "Teams",
"General": "Allgemein",
"Teams-Children": { "Teams-Children": {
"Organizations": "Organisationen", "Organizations": "Organisationen",
"OrgAdmins": "OrgAdmins" "OrgAdmins": "OrgAdmins"
@@ -149,7 +150,8 @@
"Copy Public URL": "Öffentliche URL kopieren", "Copy Public URL": "Öffentliche URL kopieren",
"extend-expiry-date": "Ablaufdatum verlängern", "extend-expiry-date": "Ablaufdatum verlängern",
"Duplicate Template": "Vorlage duplizieren", "Duplicate Template": "Vorlage duplizieren",
"Duplicate": "Duplikat" "Duplicate": "Duplikat",
"daily-mail-quota": "Tägliches E-Mail-Kontingent"
}, },
"report-heading": { "report-heading": {
"Sr.No": "Nr.", "Sr.No": "Nr.",
@@ -169,7 +171,7 @@
"Name": "Name", "Name": "Name",
"Status": "Status", "Status": "Status",
"created-date": "Erstellungsdatum", "created-date": "Erstellungsdatum",
"Type": "Typ", "Type": "Type",
"Logs": "Protokolle", "Logs": "Protokolle",
"Expiry-date": "Ablaufdatum" "Expiry-date": "Ablaufdatum"
}, },
@@ -604,6 +606,7 @@
"placeholder-sign-4": "Ziehen Sie ein Feld in das Dokument oder klicken Sie darauf, um es hinzuzufügen.", "placeholder-sign-4": "Ziehen Sie ein Feld in das Dokument oder klicken Sie darauf, um es hinzuzufügen.",
"placeholder-sign-5": "Der PDF-Inhaltsbereich zeigt bereits die vorhandenen Platzhalter der Vorlage an. Diese Platzhalter entsprechen der Farbe des Empfängernamens, um sie leicht erkennbar zu machen.", "placeholder-sign-5": "Der PDF-Inhaltsbereich zeigt bereits die vorhandenen Platzhalter der Vorlage an. Diese Platzhalter entsprechen der Farbe des Empfängernamens, um sie leicht erkennbar zu machen.",
"placeholder-sign-6": "Mit einem Klick auf 'Weiter' wird das Dokument gespeichert. Im nächsten Schritt können Sie die E-Mails, die an die Empfänger versendet werden sollen, anpassen oder die Signaturlinks kopieren und diese selbst mit den Empfängern teilen.", "placeholder-sign-6": "Mit einem Klick auf 'Weiter' wird das Dokument gespeichert. Im nächsten Schritt können Sie die E-Mails, die an die Empfänger versendet werden sollen, anpassen oder die Signaturlinks kopieren und diese selbst mit den Empfängern teilen.",
"report-1":"Klicken Sie auf die Schaltfläche „Hinzufügen“, um eine neue Vorlage zu erstellen. Vorlagen sind wiederverwendbare Dokumente, mit denen schnell neue Dokumente mit derselben Struktur und unterschiedlichen Unterzeichnern erstellt werden können. Eine HR-Vorlage für die Einarbeitung könnte beispielsweise vordefinierte Rollen wie „Personalleiter“ und „Neuer Mitarbeiter“ enthalten. Bei jeder Verwendung der Vorlage können Sie die Rolle „Neuer Mitarbeiter“ verschiedenen neuen Mitarbeitern zuweisen, während die Rolle „Personalleiter“ unverändert bleibt. So wird ein nahtloser Einarbeitungsprozess für jeden neuen Mitarbeiter ermöglicht.",
"redirect": "Klicken Sie auf die Schaltfläche 'Verwenden', um ein neues Dokument aus einer bestehenden Vorlage zu erstellen.", "redirect": "Klicken Sie auf die Schaltfläche 'Verwenden', um ein neues Dokument aus einer bestehenden Vorlage zu erstellen.",
"bulksend": "Um schnell mehrere Dokumente mithilfe einer vorhandenen Vorlage zu versenden, indem Sie einfach die E-Mail-Adressen der Empfänger erstellen, klicken Sie auf die Schaltfläche Massenversand", "bulksend": "Um schnell mehrere Dokumente mithilfe einer vorhandenen Vorlage zu versenden, indem Sie einfach die E-Mail-Adressen der Empfänger erstellen, klicken Sie auf die Schaltfläche Massenversand",
"option": "Dieses Menü zeigt weitere Optionen wie Bearbeiten und Löschen. Verwenden Sie die Schaltfläche 'Bearbeiten', um Unterzeichnerrollen hinzuzufügen, Felder zu ändern und Ihre Vorlage zu aktualisieren. Änderungen gelten für alle zukünftigen Dokumente, die aus dieser Vorlage erstellt werden, wirken sich jedoch nicht auf vorhandene Dokumente aus. Verwenden Sie die Schaltfläche 'Löschen', um die Vorlage zu entfernen.", "option": "Dieses Menü zeigt weitere Optionen wie Bearbeiten und Löschen. Verwenden Sie die Schaltfläche 'Bearbeiten', um Unterzeichnerrollen hinzuzufügen, Felder zu ändern und Ihre Vorlage zu aktualisieren. Änderungen gelten für alle zukünftigen Dokumente, die aus dieser Vorlage erstellt werden, wirken sich jedoch nicht auf vorhandene Dokumente aus. Verwenden Sie die Schaltfläche 'Löschen', um die Vorlage zu entfernen.",
@@ -848,5 +851,10 @@
"draft-template-info-p1": "Um Ihre Vorlage öffentlich zu machen, muss sie entweder eine einzelne Rolle enthalten oder, wenn sie mehrere Rollen umfasst, müssen alle zusätzlichen Rollen bereits den Unterzeichnern zugewiesen sein. Die nicht zugewiesene öffentliche Rolle muss leer bleiben und an erster Stelle stehen.", "draft-template-info-p1": "Um Ihre Vorlage öffentlich zu machen, muss sie entweder eine einzelne Rolle enthalten oder, wenn sie mehrere Rollen umfasst, müssen alle zusätzlichen Rollen bereits den Unterzeichnern zugewiesen sein. Die nicht zugewiesene öffentliche Rolle muss leer bleiben und an erster Stelle stehen.",
"visit-below-link": "Besuchen Sie den untenstehenden Link, um mehr zu erfahren -", "visit-below-link": "Besuchen Sie den untenstehenden Link, um mehr zu erfahren -",
"storage-help": "Durch die Aktivierung von BYOC können Sie Ihren eigenen S3-Speicher verbinden, sodass Ihre Dateien vollständig unter Ihrer Kontrolle bleiben und keine externen Kopien gespeichert werden. Wenn Ihnen Datenautonomie wichtig ist, erwägen Sie ein Upgrade auf Teams, um diese Funktion freizuschalten.", "storage-help": "Durch die Aktivierung von BYOC können Sie Ihren eigenen S3-Speicher verbinden, sodass Ihre Dateien vollständig unter Ihrer Kontrolle bleiben und keine externen Kopien gespeichert werden. Wenn Ihnen Datenautonomie wichtig ist, erwägen Sie ein Upgrade auf Teams, um diese Funktion freizuschalten.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com." "daily-quota-reached": "Sie haben Ihr tägliches Kontingent erreicht. Für Unterstützung kontaktieren Sie bitte quotas@opensignlabs.com.",
"enabled-signature-type": "Aktivierte Signaturtypen",
"enabled-signature-type-help": "Die Einstellung 'Aktivierte Signaturtypen' bestimmt, welche Signaturoptionen in Ihrer Organisation verfügbar sind. Wenn Sie beispielsweise die Option 'Zeichnen' deaktivieren, wird sie den Mitgliedern Ihrer Organisation im Signatur-Widget nicht angezeigt, während die anderen drei Optionen weiterhin zugänglich bleiben.",
"indexing-public-profile": "Erlaube die Indexierung des öffentlichen Profils durch Suchmaschinen",
"user-created-successfully": "Benutzer erfolgreich erstellt.",
"only-15-reminder-allowed": "Sie können bis zu 15 automatische Erinnerungen festlegen. Wenn zum Beispiel 'TimeToComplete' auf 15 Tage und 'RemindOnceInEvery' auf 1 Tag eingestellt ist, erreichen Sie das maximale Limit von 15 Erinnerungen. Passen Sie Ihre Einstellungen entsprechend an."
} }
@@ -79,6 +79,7 @@
"Storage": "Storage", "Storage": "Storage",
"Signing certificate": "Signing certificate", "Signing certificate": "Signing certificate",
"Teams": "Teams", "Teams": "Teams",
"General": "General",
"Teams-Children": { "Teams-Children": {
"Organizations": "Organizations", "Organizations": "Organizations",
"OrgAdmins": "OrgAdmins" "OrgAdmins": "OrgAdmins"
@@ -149,7 +150,8 @@
"Copy Public URL": "Copy public URL", "Copy Public URL": "Copy public URL",
"extend-expiry-date": "Extend expiry date", "extend-expiry-date": "Extend expiry date",
"Duplicate Template": "Duplicate template", "Duplicate Template": "Duplicate template",
"Duplicate": "Duplicate" "Duplicate": "Duplicate",
"daily-mail-quota": "Daily Email Quota"
}, },
"report-heading": { "report-heading": {
"Sr.No": "Sr.No", "Sr.No": "Sr.No",
@@ -849,5 +851,10 @@
"draft-template-info-p1": "To make your template public, it must either contain a single role, or, if it includes multiple roles, all additional roles must already be assigned to signers. The unassigned public role should remain empty and must be placed in the first position.", "draft-template-info-p1": "To make your template public, it must either contain a single role, or, if it includes multiple roles, all additional roles must already be assigned to signers. The unassigned public role should remain empty and must be placed in the first position.",
"visit-below-link": "Visit below link to know more -", "visit-below-link": "Visit below link to know more -",
"storage-help": "Enabling BYOC lets you connect your own S3 storage so your files remain entirely under your control—no external copies retained. If data autonomy matters to you, consider upgrading to Teams to unlock this feature.", "storage-help": "Enabling BYOC lets you connect your own S3 storage so your files remain entirely under your control—no external copies retained. If data autonomy matters to you, consider upgrading to Teams to unlock this feature.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com." "daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com.",
"enabled-signature-type": "Enabled Signature Types",
"enabled-signature-type-help": "The 'Enabled Signature Types' setting determines which signature options are available across your organization. For example, if you disable the 'Draw' option, members of your organization will not see it in the signature widget, while the other three options will remain accessible.",
"indexing-public-profile": "Allow indexing of public profile by search engines",
"user-created-successfully": "user created successfully.",
"only-15-reminder-allowed": "You can set up to 15 automatic reminders. For example, if 'TimeToComplete' is 15 days and 'RemindOnceInEvery' is 1 day, you'll reach the maximum limit of 15 reminders. Adjust your settings accordingly."
} }
@@ -79,6 +79,7 @@
"Storage": "Almacenamiento", "Storage": "Almacenamiento",
"Signing certificate": "Certificado de firma", "Signing certificate": "Certificado de firma",
"Teams": "Equipos", "Teams": "Equipos",
"General": "General",
"Teams-Children": { "Teams-Children": {
"Organizations": "Organizaciones", "Organizations": "Organizaciones",
"OrgAdmins": "OrgAdmins" "OrgAdmins": "OrgAdmins"
@@ -149,7 +150,8 @@
"Copy Public URL": "Copiar URL pública", "Copy Public URL": "Copiar URL pública",
"extend-expiry-date": "Date d'expiration", "extend-expiry-date": "Date d'expiration",
"Duplicate Template": "Plantilla duplicada", "Duplicate Template": "Plantilla duplicada",
"Duplicate": "Duplicada" "Duplicate": "Duplicada",
"daily-mail-quota": "Cuota diaria de correos electrónicos"
}, },
"report-heading": { "report-heading": {
"Sr.No": "Nº", "Sr.No": "Nº",
@@ -765,8 +767,6 @@
"term-cond-p3": "Recibirá y firmará documentos electrónicamente a través de {{appName}}.", "term-cond-p3": "Recibirá y firmará documentos electrónicamente a través de {{appName}}.",
"term-cond-p4": "Su firma electrónica es jurídicamente vinculante y equivalente a una firma manuscrita.", "term-cond-p4": "Su firma electrónica es jurídicamente vinculante y equivalente a una firma manuscrita.",
"term-cond-h2": "2. Consentimiento para el uso de registros y firmas electrónicas", "term-cond-h2": "2. Consentimiento para el uso de registros y firmas electrónicas",
"js-snippet-msg-2": "Administrar plantillas",
"js-snippet-msg-3": "página.",
"term-cond-p5": "Al aceptar esta Divulgación:", "term-cond-p5": "Al aceptar esta Divulgación:",
"term-cond-p6": "Usted acepta realizar transacciones electrónicas con el Remitente utilizando {{appName}} y comprende que este consentimiento es válido hasta que se retire.", "term-cond-p6": "Usted acepta realizar transacciones electrónicas con el Remitente utilizando {{appName}} y comprende que este consentimiento es válido hasta que se retire.",
"term-cond-p7": "Usted acepta revisar, firmar y devolver documentos electrónicamente utilizando {{appName}}.", "term-cond-p7": "Usted acepta revisar, firmar y devolver documentos electrónicamente utilizando {{appName}}.",
@@ -802,6 +802,8 @@
"term-cond-p30": " o correo electrónico", "term-cond-p30": " o correo electrónico",
"js-snippet-msg": "Para integrar plantillas {{appName}} en sus sitios web HTML o páginas de destino, puede utilizar el siguiente código:", "js-snippet-msg": "Para integrar plantillas {{appName}} en sus sitios web HTML o páginas de destino, puede utilizar el siguiente código:",
"js-snippet-msg-1": "Puede obtener el TemplateId en la página Administrar plantillas", "js-snippet-msg-1": "Puede obtener el TemplateId en la página Administrar plantillas",
"js-snippet-msg-2": "Administrar plantillas",
"js-snippet-msg-3": "página.",
"agrrement-alert": "Para continuar, debe consentir la divulgación de registros y firmas electrónicas.", "agrrement-alert": "Para continuar, debe consentir la divulgación de registros y firmas electrónicas.",
"webhook-already-exists": "¡La URL ya existe! Pruebe con uno diferente.", "webhook-already-exists": "¡La URL ya existe! Pruebe con uno diferente.",
"webhook-must-be-secure": "La URL del webhook debe ser segura y utilizar https://", "webhook-must-be-secure": "La URL del webhook debe ser segura y utilizar https://",
@@ -848,7 +850,11 @@
"agreement-note": "Nota: Aceptar esto no significa que esté firmando el documento de inmediato. Esto solo le permite revisar el documento electrónicamente. Tendrá la oportunidad de leerlo en su totalidad y decidir si desea firmarlo después.", "agreement-note": "Nota: Aceptar esto no significa que esté firmando el documento de inmediato. Esto solo le permite revisar el documento electrónicamente. Tendrá la oportunidad de leerlo en su totalidad y decidir si desea firmarlo después.",
"draft-template-info-p1": "Para hacer que tu plantilla sea pública, debe contener un único rol o, si incluye múltiples roles, todos los roles adicionales deben estar ya asignados a firmantes. El rol público no asignado debe permanecer vacío y debe estar en la primera posición.", "draft-template-info-p1": "Para hacer que tu plantilla sea pública, debe contener un único rol o, si incluye múltiples roles, todos los roles adicionales deben estar ya asignados a firmantes. El rol público no asignado debe permanecer vacío y debe estar en la primera posición.",
"visit-below-link": "Visita el siguiente enlace para saber más -", "visit-below-link": "Visita el siguiente enlace para saber más -",
"upgrade-to-team-plan": "Actualizar a team plan",
"storage-help": "Habilitar BYOC te permite conectar tu propio almacenamiento S3 para que tus archivos permanezcan completamente bajo tu control, sin copias externas retenidas. Si la autonomía de los datos es importante para ti, considera actualizar a Teams para desbloquear esta función.", "storage-help": "Habilitar BYOC te permite conectar tu propio almacenamiento S3 para que tus archivos permanezcan completamente bajo tu control, sin copias externas retenidas. Si la autonomía de los datos es importante para ti, considera actualizar a Teams para desbloquear esta función.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com." "daily-quota-reached": "Ha alcanzado su cuota diaria. Para obtener ayuda, comuníquese con quotas@opensignlabs.com.",
"enabled-signature-type": "Tipos de firma habilitados",
"enabled-signature-type-help": "La configuración de 'Tipos de firma habilitados' determina qué opciones de firma están disponibles en su organización. Por ejemplo, si desactiva la opción 'Dibujar', los miembros de su organización no la verán en el widget de firma, mientras que las otras tres opciones seguirán siendo accesibles.",
"indexing-public-profile": "Permitir la indexación del perfil público por los motores de búsqueda",
"user-created-successfully": "Usuario creado con éxito.",
"only-15-reminder-allowed": "Puede configurar hasta 15 recordatorios automáticos. Por ejemplo, si 'TimeToComplete' es de 15 días y 'RemindOnceInEvery' es de 1 día, alcanzará el límite máximo de 15 recordatorios. Ajuste su configuración en consecuencia."
} }
@@ -79,6 +79,7 @@
"Storage": "Stockage", "Storage": "Stockage",
"Signing certificate": "Certificat de signature", "Signing certificate": "Certificat de signature",
"Teams": "Équipes", "Teams": "Équipes",
"General": "Général",
"Teams-Children": { "Teams-Children": {
"Organizations": "Organisations", "Organizations": "Organisations",
"OrgAdmins": "OrgAdmins" "OrgAdmins": "OrgAdmins"
@@ -170,7 +171,8 @@
"Copy Public URL": "Copier l'URL publique", "Copy Public URL": "Copier l'URL publique",
"extend-expiry-date": "Prolonger la date d'expiration", "extend-expiry-date": "Prolonger la date d'expiration",
"Duplicate Template": "dupliquer le modèle", "Duplicate Template": "dupliquer le modèle",
"Duplicate": "Double" "Duplicate": "Double",
"daily-mail-quota": "Quota d'e-mails quotidien"
}, },
"report-help": { "report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.", "Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -450,6 +452,7 @@
"add-recipients": "Ajouter des destinataires", "add-recipients": "Ajouter des destinataires",
"loading-mssg": "Cela pourrait prendre du temps", "loading-mssg": "Cela pourrait prendre du temps",
"send-mail": "Envoyer un mail", "send-mail": "Envoyer un mail",
"signature-field-widget": "Au moins un champ de signature doit être ajouté pour chaque utilisateur. Vous n'avez pas ajouté de champs de signature pour {{signersName}}",
"placeholder-alert-1": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.", "placeholder-alert-1": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
"placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.", "placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.",
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ", "placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
@@ -557,7 +560,8 @@
"do-not-access-contact-admin": "Vous n'y avez pas accès, veuillez contacter l'administrateur.", "do-not-access-contact-admin": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
"filed-required-correctly": "Veuillez remplir correctement les informations requises.", "filed-required-correctly": "Veuillez remplir correctement les informations requises.",
"admin-created": "Administrateur créé", "admin-created": "Administrateur créé",
"invalid-masterkey": "Clé principale invalide", "master-key": "La clef maitresse", "invalid-masterkey": "Clé principale invalide",
"master-key": "La clef maitresse",
"profile-update-alert": "Mise à jour du profil réussie.", "profile-update-alert": "Mise à jour du profil réussie.",
"date": "Date", "date": "Date",
"report-not-found": "Rapport introuvable", "report-not-found": "Rapport introuvable",
@@ -847,5 +851,10 @@
"draft-template-info-p1": "Pour rendre votre modèle public, il doit contenir un seul rôle ou, s'il inclut plusieurs rôles, tous les rôles supplémentaires doivent déjà être attribués aux signataires. Le rôle public non attribué doit rester vide et être placé en première position.", "draft-template-info-p1": "Pour rendre votre modèle public, il doit contenir un seul rôle ou, s'il inclut plusieurs rôles, tous les rôles supplémentaires doivent déjà être attribués aux signataires. Le rôle public non attribué doit rester vide et être placé en première position.",
"visit-below-link": "Visitez le lien ci-dessous pour en savoir plus -", "visit-below-link": "Visitez le lien ci-dessous pour en savoir plus -",
"storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité.", "storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com." "daily-quota-reached": "Vous avez atteint votre quota quotidien. Pour obtenir de l'aide, veuillez contacter quotas@opensignlabs.com.",
"enabled-signature-type": "Types de signature activés",
"enabled-signature-type-help": "Le paramètre 'Types de signature activés' détermine quelles options de signature sont disponibles dans votre organisation. Par exemple, si vous désactivez l'option 'Dessiner', les membres de votre organisation ne la verront pas dans le widget de signature, tandis que les trois autres options resteront accessibles.",
"indexing-public-profile": "Autoriser l'indexation du profil public par les moteurs de recherche",
"user-created-successfully": "Utilisateur créé avec succès.",
"only-15-reminder-allowed": "Vous pouvez définir jusqu'à 15 rappels automatiques. Par exemple, si 'TimeToComplete' est de 15 jours et 'RemindOnceInEvery' est de 1 jour, vous atteindrez la limite maximale de 15 rappels. Ajustez vos paramètres en conséquence."
} }
@@ -79,6 +79,7 @@
"Storage": "Archiviazione", "Storage": "Archiviazione",
"Signing certificate": "Certificato di firma", "Signing certificate": "Certificato di firma",
"Teams": "Squadre", "Teams": "Squadre",
"General": "Generale",
"Teams-Children": { "Teams-Children": {
"Organizations": "Organizzazioni", "Organizations": "Organizzazioni",
"OrgAdmins": "OrgAdmins" "OrgAdmins": "OrgAdmins"
@@ -149,7 +150,8 @@
"Copy Public URL": "Copia URL pubblico", "Copy Public URL": "Copia URL pubblico",
"extend-expiry-date": "Estendi data di scadenza", "extend-expiry-date": "Estendi data di scadenza",
"Duplicate Template": "Duplica modello", "Duplicate Template": "Duplica modello",
"Duplicate": "Duplica" "Duplicate": "Duplica",
"daily-mail-quota": "Quota e-mail giornaliera"
}, },
"report-heading": { "report-heading": {
"Sr.No": "Nr.", "Sr.No": "Nr.",
@@ -450,6 +452,7 @@
"add-recipients": "Aggiungi destinatari", "add-recipients": "Aggiungi destinatari",
"loading-mssg": "Questo potrebbe richiedere del tempo", "loading-mssg": "Questo potrebbe richiedere del tempo",
"send-mail": "Invia Mail", "send-mail": "Invia Mail",
"signature-field-widget":"È necessario aggiungere almeno un campo firma per ogni utente. Non hai aggiunto campi firma per {{signersName}}",
"placeholder-alert-1": "Assicurati che sia stato aggiunto almeno un widget per la firma per tutti i destinatari.", "placeholder-alert-1": "Assicurati che sia stato aggiunto almeno un widget per la firma per tutti i destinatari.",
"placeholder-alert-2": "Conferma di aver compilato il campo di testo.", "placeholder-alert-2": "Conferma di aver compilato il campo di testo.",
"placeholder-alert-3": "Sei sicuro di voler inviare questo documento per le firme?", "placeholder-alert-3": "Sei sicuro di voler inviare questo documento per le firme?",
@@ -848,5 +851,10 @@
"draft-template-info-p1": "Per rendere il tuo modello pubblico, deve contenere un solo ruolo oppure, se include più ruoli, tutti i ruoli aggiuntivi devono essere già assegnati ai firmatari. Il ruolo pubblico non assegnato deve rimanere vuoto e deve essere posizionato per primo.", "draft-template-info-p1": "Per rendere il tuo modello pubblico, deve contenere un solo ruolo oppure, se include più ruoli, tutti i ruoli aggiuntivi devono essere già assegnati ai firmatari. Il ruolo pubblico non assegnato deve rimanere vuoto e deve essere posizionato per primo.",
"visit-below-link": "Visita il link qui sotto per saperne di più -", "visit-below-link": "Visita il link qui sotto per saperne di più -",
"storage-help": "Abilitare BYOC ti consente di collegare il tuo archivio S3 in modo che i tuoi file rimangano completamente sotto il tuo controllo, senza copie esterne conservate. Se l'autonomia dei dati è importante per te, considera l'upgrade a Teams per sbloccare questa funzionalità.", "storage-help": "Abilitare BYOC ti consente di collegare il tuo archivio S3 in modo che i tuoi file rimangano completamente sotto il tuo controllo, senza copie esterne conservate. Se l'autonomia dei dati è importante per te, considera l'upgrade a Teams per sbloccare questa funzionalità.",
"daily-quota-reached": "Youve reached your daily quota. For assistance, please contact quotas@opensignlabs.com." "daily-quota-reached": "Hai raggiunto la tua quota giornaliera. Per assistenza, contatta quotas@opensignlabs.com.",
"enabled-signature-type": "Tipi di firma abilitati",
"enabled-signature-type-help": "L'impostazione 'Tipi di firma abilitati' determina quali opzioni di firma sono disponibili nella tua organizzazione. Ad esempio, se disabiliti l'opzione 'Disegna', i membri della tua organizzazione non la vedranno nel widget della firma, mentre le altre tre opzioni resteranno accessibili.",
"indexing-public-profile": "Consenti l'indicizzazione del profilo pubblico dai motori di ricerca",
"user-created-successfully": "Utente creato con successo.",
"only-15-reminder-allowed": "Puoi impostare fino a 15 promemoria automatici. Ad esempio, se 'TimeToComplete' è di 15 giorni e 'RemindOnceInEvery' è di 1 giorno, raggiungerai il limite massimo di 15 promemoria. Regola le tue impostazioni di conseguenza."
} }
+271 -283
View File
@@ -2,13 +2,8 @@ import React, { useEffect, useState } from "react";
import Parse from "parse"; import Parse from "parse";
import Title from "./Title"; import Title from "./Title";
import Loader from "../primitives/Loader"; import Loader from "../primitives/Loader";
import { import { copytoData, usertimezone } from "../constant/Utils";
copytoData, import { emailRegex } from "../constant/const";
usertimezone
} from "../constant/Utils";
import {
emailRegex,
} from "../constant/const";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
function generatePassword(length) { function generatePassword(length) {
const characters = const characters =
@@ -46,9 +41,9 @@ const AddUser = (props) => {
if (teamRes.length > 0) { if (teamRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamRes)); const _teamRes = JSON.parse(JSON.stringify(teamRes));
setTeamList(_teamRes); setTeamList(_teamRes);
const allUserId = const allUserId =
_teamRes.find((x) => x.Name === "All Users")?.objectId || ""; _teamRes.find((x) => x.Name === "All Users")?.objectId || "";
setFormdata((prev) => ({ ...prev, team: allUserId })); setFormdata((prev) => ({ ...prev, team: allUserId }));
} }
}; };
const checkUserExist = async () => { const checkUserExist = async () => {
@@ -76,167 +71,167 @@ const AddUser = (props) => {
setIsFormLoader(true); setIsFormLoader(true);
const res = await checkUserExist(); const res = await checkUserExist();
if (res) { if (res) {
props.setIsAlert({ type: "danger", msg: t("user-already-exist") }); props.showAlert("danger", t("user-already-exist"));
setIsFormLoader(false); setIsFormLoader(false);
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1000);
} else { } else {
try { if (localStorage.getItem("TenantId")) {
const extUser = new Parse.Object("contracts_Users");
extUser.set("Name", formdata.name);
if (formdata.phone) {
extUser.set("Phone", formdata.phone);
}
extUser.set("Email", formdata.email);
extUser.set("UserRole", `contracts_${formdata.role}`);
if (formdata?.team) {
extUser.set("TeamIds", [
{
__type: "Pointer",
className: "contracts_Teams",
objectId: formdata.team
}
]);
}
if (localUser && localUser.OrganizationId) {
extUser.set("OrganizationId", {
__type: "Pointer",
className: "contracts_Organizations",
objectId: localUser.OrganizationId.objectId
});
}
if (localUser && localUser.Company) {
extUser.set("Company", localUser.Company);
}
if (localStorage.getItem("TenantId")) {
extUser.set("TenantId", {
__type: "Pointer",
className: "partners_Tenant",
objectId: localStorage.getItem("TenantId")
});
}
const timezone = usertimezone;
if (timezone) {
extUser.set("Timezone", timezone);
}
try { try {
const _users = Parse.Object.extend("User"); const extUser = new Parse.Object("contracts_Users");
const _user = new _users(); extUser.set("Name", formdata.name);
_user.set("name", formdata.name);
_user.set("username", formdata.email);
_user.set("email", formdata.email);
_user.set("password", formdata.password);
if (formdata.phone) { if (formdata.phone) {
_user.set("phone", formdata.phone); extUser.set("Phone", formdata.phone);
}
extUser.set("Email", formdata.email);
extUser.set("UserRole", `contracts_${formdata.role}`);
if (formdata?.team) {
extUser.set("TeamIds", [
{
__type: "Pointer",
className: "contracts_Teams",
objectId: formdata.team
}
]);
}
if (localUser && localUser.OrganizationId) {
extUser.set("OrganizationId", {
__type: "Pointer",
className: "contracts_Organizations",
objectId: localUser.OrganizationId.objectId
});
}
if (localUser && localUser.Company) {
extUser.set("Company", localUser.Company);
} }
const user = await _user.save(); if (localStorage.getItem("TenantId")) {
if (user) { extUser.set("TenantId", {
const currentUser = Parse.User.current(); __type: "Pointer",
extUser.set( className: "partners_Tenant",
"CreatedBy", objectId: localStorage.getItem("TenantId")
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
}); });
} }
const timezone = usertimezone;
if (timezone) {
extUser.set("Timezone", timezone);
}
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
_user.set("name", formdata.name);
_user.set("username", formdata.email);
_user.set("email", formdata.email);
_user.set("password", formdata.password);
if (formdata.phone) {
_user.set("phone", formdata.phone);
}
const user = await _user.save();
if (user) {
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", user);
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
props.showAlert("success", t("user-created-successfully"));
}
} catch (err) {
console.log("err ", err);
if (err.code === 202) {
const params = { email: formdata.email };
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
props.showAlert("success", t("user-created-successfully"));
} else {
setIsFormLoader(false);
props.showAlert("danger", t("something-went-wrong-mssg"));
}
}
} catch (err) { } catch (err) {
console.log("err ", err); console.log("err", err);
if (err.code === 202) { setIsFormLoader(false);
const params = { email: formdata.email }; props.showAlert("danger", t("something-went-wrong-mssg"));
const userRes = await Parse.Cloud.run("getUserId", params);
const currentUser = Parse.User.current();
extUser.set(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
extUser.set("UserId", {
__type: "Pointer",
className: "_User",
objectId: userRes.id
});
const acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
acl.setReadAccess(currentUser.id, true);
acl.setWriteAccess(currentUser.id, true);
extUser.setACL(acl);
const res = await extUser.save();
const parseData = JSON.parse(JSON.stringify(res));
if (props.closePopup) {
props.closePopup();
}
if (props.handleUserData) {
if (formdata?.team) {
const team = teamList.find(
(x) => x.objectId === formdata.team
);
parseData.TeamIds = parseData.TeamIds.map((y) =>
y.objectId === team.objectId ? team : y
);
}
props.handleUserData(parseData);
}
setIsFormLoader(false);
setFormdata({
name: "",
email: "",
phone: "",
team: "",
role: ""
});
}
} }
} catch (err) { } else {
console.log("err", err); props.showAlert("danger", t("something-went-wrong-mssg"));
setIsFormLoader(false);
props.setIsAlert({
type: "danger",
msg: t("something-went-wrong-mssg")
});
} finally {
setTimeout(
() => props.setIsAlert({ type: "success", msg: "" }),
1500
);
} }
} }
} }
@@ -259,8 +254,7 @@ const AddUser = (props) => {
const copytoclipboard = (text) => { const copytoclipboard = (text) => {
copytoData(text); copytoData(text);
props.setIsAlert({ type: "success", msg: t("copied") }); props.showAlert("success", t("copied"));
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
}; };
return ( return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative"> <div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
@@ -270,126 +264,120 @@ const AddUser = (props) => {
<Loader /> <Loader />
</div> </div>
)} )}
<div className="w-full mx-auto"> <div className="w-full mx-auto">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<div className="mb-3"> <div className="mb-3">
<label <label
htmlFor="name" htmlFor="name"
className="block text-xs text-gray-700 font-semibold" className="block text-xs text-gray-700 font-semibold"
> >
{t("name")} {t("name")}
<span className="text-[red] text-[13px]"> *</span> <span className="text-[red] text-[13px]"> *</span>
</label> </label>
<input <input
type="text" type="text"
name="name" name="name"
value={formdata.name} value={formdata.name}
onChange={(e) => handleChange(e)} onChange={(e) => handleChange(e)}
onInvalid={(e) => onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
e.target.setCustomValidity(t("input-required")) onInput={(e) => e.target.setCustomValidity("")}
} required
onInput={(e) => e.target.setCustomValidity("")} className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
required />
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" </div>
/> <div className="mb-3">
</div> <label
<div className="mb-3"> htmlFor="email"
<label className="block text-xs text-gray-700 font-semibold"
htmlFor="email" >
className="block text-xs text-gray-700 font-semibold" {t("email")}
> <span className="text-[red] text-[13px]"> *</span>
{t("email")} </label>
<span className="text-[red] text-[13px]"> *</span> <input
</label> type="email"
<input name="email"
type="email" value={formdata.email}
name="email" onChange={(e) => handleChange(e)}
value={formdata.email} required
onChange={(e) => handleChange(e)} onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
required onInput={(e) => e.target.setCustomValidity("")}
onInvalid={(e) => className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
e.target.setCustomValidity(t("input-required")) />
} </div>
onInput={(e) => e.target.setCustomValidity("")} <div className="mb-3">
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" <label className="block text-xs text-gray-700 font-semibold">
/> {t("password")}
</div> </label>
<div className="mb-3"> <div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
<label className="block text-xs text-gray-700 font-semibold"> <div className="break-all">{formdata?.password}</div>
{t("password")} <i
</label> onClick={() => copytoclipboard(formdata?.password)}
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]"> className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
<div className="break-all">{formdata?.password}</div> ></i>
<i </div>
onClick={() => copytoclipboard(formdata?.password)} <div className="text-[12px] ml-2 mb-0 text-[red] select-none">
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer " {t("password-generateed")}
></i> </div>
</div> </div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none"> <div className="mb-3">
{t("password-generateed")} <label
</div> htmlFor="phone"
</div> className="block text-xs text-gray-700 font-semibold"
<div className="mb-3"> >
<label {t("phone")}
htmlFor="phone" {/* <span className="text-[red] text-[13px]"> *</span> */}
className="block text-xs text-gray-700 font-semibold" </label>
> <input
{t("phone")} type="text"
{/* <span className="text-[red] text-[13px]"> *</span> */} name="phone"
</label> placeholder={t("phone-optional")}
<input value={formdata.phone}
type="text" onChange={(e) => handleChange(e)}
name="phone" className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("phone-optional")} />
value={formdata.phone} </div>
onChange={(e) => handleChange(e)} <div className="mb-3">
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" <label
/> htmlFor="phone"
</div> className="block text-xs text-gray-700 font-semibold"
<div className="mb-3"> >
<label {t("Role")}
htmlFor="phone" <span className="text-[red] text-[13px]"> *</span>
className="block text-xs text-gray-700 font-semibold" </label>
> <select
{t("Role")} value={formdata.role}
<span className="text-[red] text-[13px]"> *</span> onChange={(e) => handleChange(e)}
</label> name="role"
<select className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
value={formdata.role} onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onChange={(e) => handleChange(e)} onInput={(e) => e.target.setCustomValidity("")}
name="role" required
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs" >
onInvalid={(e) => <option defaultValue={""} value={""}>
e.target.setCustomValidity(t("input-required")) {t("Select")}
} </option>
onInput={(e) => e.target.setCustomValidity("")} {role.length > 0 &&
required role.map((x) => (
> <option key={x} value={x}>
<option defaultValue={""} value={""}> {x}
{t("Select")} </option>
</option> ))}
{role.length > 0 && </select>
role.map((x) => ( </div>
<option key={x} value={x}> <div className="flex items-center mt-3 gap-2 text-white">
{x} <button type="submit" className="op-btn op-btn-primary">
</option> {t("submit")}
))} </button>
</select> <div
</div> type="button"
<div className="flex items-center mt-3 gap-2 text-white"> onClick={() => handleReset()}
<button type="submit" className="op-btn op-btn-primary"> className="op-btn op-btn-secondary"
{t("submit")} >
</button> {t("cancel")}
<div </div>
type="button" </div>
onClick={() => handleReset()} </form>
className="op-btn op-btn-secondary" </div>
>
{t("cancel")}
</div>
</div>
</form>
</div>
</div> </div>
); );
}; };
@@ -1,19 +1,11 @@
import React, { import React, { useState } from "react";
useState, import { getFileName } from "../../constant/Utils";
} from "react";
import {
getFileName
} from "../../constant/Utils";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Tooltip } from "react-tooltip"; import { Tooltip } from "react-tooltip";
import SignersInput from "../shared/fields/SignersInput"; import SignersInput from "../shared/fields/SignersInput";
const EditTemplate = ({ const EditTemplate = ({ template, onSuccess }) => {
template, const appName = "OpenSign™";
onSuccess,
}) => {
const appName =
"OpenSign™";
const { t } = useTranslation(); const { t } = useTranslation();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
Name: template?.Name || "", Name: template?.Name || "",
@@ -64,6 +56,13 @@ const EditTemplate = ({
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false; const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
const allowModify = formData?.AllowModifications || false; const allowModify = formData?.AllowModifications || false;
let reminderDate = {}; let reminderDate = {};
const remindOnceInEvery = formData?.RemindOnceInEvery;
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
if (AutoReminder) { if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery); const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
const ReminderDate = new Date(template?.createdAt); const ReminderDate = new Date(template?.createdAt);
@@ -247,11 +246,7 @@ const EditTemplate = ({
</Tooltip> </Tooltip>
</label> </label>
<div className="flex flex-col md:flex-row md:gap-4"> <div className="flex flex-col md:flex-row md:gap-4">
<div <div className={`flex items-center gap-2 ml-2 mb-1`}>
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input <input
className="mr-[2px] op-radio op-radio-xs" className="mr-[2px] op-radio op-radio-xs"
type="radio" type="radio"
@@ -260,11 +255,7 @@ const EditTemplate = ({
/> />
<div className="text-center">{t("yes")}</div> <div className="text-center">{t("yes")}</div>
</div> </div>
<div <div className={`flex items-center gap-2 ml-2 mb-1`}>
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input <input
className="mr-[2px] op-radio op-radio-xs" className="mr-[2px] op-radio op-radio-xs"
type="radio" type="radio"
@@ -1,25 +1,19 @@
import React, { useState } from "react"; import React, { useState } from "react";
import axios from "axios";
import { handleToPrint } from "../../constant/Utils"; import { handleToPrint } from "../../constant/Utils";
import { themeColor, emailRegex } from "../../constant/const"; import { emailRegex } from "../../constant/const";
import Loader from "../../primitives/Loader"; import Loader from "../../primitives/Loader";
import ModalUi from "../../primitives/ModalUi"; import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Parse from "parse";
function EmailComponent({ function EmailComponent({
isEmail, isEmail,
pdfUrl,
setIsEmail, setIsEmail,
setSuccessEmail, setSuccessEmail,
pdfDetails, pdfDetails,
sender,
setIsAlert, setIsAlert,
extUserId,
setIsDownloadModal setIsDownloadModal
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const appName =
"OpenSign™";
const [emailList, setEmailList] = useState([]); const [emailList, setEmailList] = useState([]);
const [emailValue, setEmailValue] = useState(""); const [emailValue, setEmailValue] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -28,73 +22,11 @@ function EmailComponent({
const isAndroid = /Android/i.test(navigator.userAgent); const isAndroid = /Android/i.test(navigator.userAgent);
//function for send email //function for send email
const sendEmail = async () => { const sendEmail = async () => {
const pdfName = pdfDetails[0]?.Name;
setIsLoading(true); setIsLoading(true);
let sendMail; const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
const docId = pdfDetails?.[0]?.objectId || ""; const sendmail = await Parse.Cloud.run("forwarddoc", params);
let presignedUrl = pdfUrl; console.log("sendmail ", sendmail);
try { if (sendmail?.status === "success") {
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{
url: pdfUrl,
docId: docId,
},
{
headers: {
"content-type": "Application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
}
}
);
presignedUrl = axiosRes.data.result;
} catch (err) {
console.log("err in getsignedurl", err);
}
for (let i = 0; i < emailList.length; i++) {
try {
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
sessionToken: localStorage.getItem("accesstoken")
};
const logo =
`<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>`;
const opurl =
` <a href='www.opensignlabs.com' target=_blank>here</a>`;
let params = {
extUserId: extUserId,
pdfName: pdfName,
url: presignedUrl,
recipient: emailList[i],
subject: `${sender.name} has signed the doc - ${pdfName}`,
replyto:
pdfDetails?.[0]?.ExtUserPtr?.Email ||
"",
from:
sender.email,
html:
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'><div>` +
`${logo}</div><div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document Copy</p></div><div>` +
`<p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>${pdfName}</strong> is attached to this email. Kindly download the document from the attachment.</p>` +
`</div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, please contact the sender ${sender.email} directly. ` +
`If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`
};
sendMail = await axios.post(url, params, { headers: headers });
} catch (error) {
console.log("error", error);
setIsLoading(false);
setIsEmail(false);
setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
}
if (sendMail?.data?.result?.status === "success") {
setSuccessEmail(true); setSuccessEmail(true);
setIsEmail(false); setIsEmail(false);
setTimeout(() => { setTimeout(() => {
@@ -103,8 +35,7 @@ function EmailComponent({
setEmailList([]); setEmailList([]);
}, 1500); }, 1500);
setIsLoading(false); setIsLoading(false);
} } else {
else {
setIsLoading(false); setIsLoading(false);
setIsEmail(false); setIsEmail(false);
setIsAlert({ setIsAlert({
@@ -76,6 +76,7 @@ const changeDateToMomentFormat = (format) => {
}; };
//function to get default format //function to get default format
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy"; const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
//function to convert formated date to new Date() format //function to convert formated date to new Date() format
const getDefaultDate = (dateStr, format) => { const getDefaultDate = (dateStr, format) => {
//get valid date format for moment to convert formated date to new Date() format //get valid date format for moment to convert formated date to new Date() format
@@ -90,6 +91,7 @@ const getDefaultDate = (dateStr, format) => {
return date; return date;
} }
}; };
function Placeholder(props) { function Placeholder(props) {
//'isTouchDevice' is used to detect whether a device has a touchscreen or is mouse-based //'isTouchDevice' is used to detect whether a device has a touchscreen or is mouse-based
const isTouchDevice = navigator.maxTouchPoints > 0; const isTouchDevice = navigator.maxTouchPoints > 0;
@@ -437,7 +439,9 @@ function Placeholder(props) {
//checking widget's type and open widget copy modal for required widgets //checking widget's type and open widget copy modal for required widgets
if ( if (
["signature", textWidget, "stamp", "initials"].includes(props.pos.type) ["signature", textInputWidget, textWidget, "stamp", "initials"].includes(
props.pos.type
)
) { ) {
props.setIsPageCopy(true); props.setIsPageCopy(true);
props.setSignKey(props.pos.key); props.setSignKey(props.pos.key);
@@ -1083,7 +1087,7 @@ function Placeholder(props) {
isSignYourself={props.isSignYourself} isSignYourself={props.isSignYourself}
isSelfSign={props.isSelfSign} isSelfSign={props.isSelfSign}
signerObjId={props.signerObjId} signerObjId={props.signerObjId}
handleUserName={props.handleUserName} calculateFontsize={props.calculateFontsize}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]} pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign} isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate} setSelectDate={setSelectDate}
@@ -30,6 +30,7 @@ function PlaceholderType(props) {
const inputRef = useRef(null); const inputRef = useRef(null);
const [textValue, setTextValue] = useState(); const [textValue, setTextValue] = useState();
const [selectedCheckbox, setSelectedCheckbox] = useState([]); const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const [hint, setHint] = useState("");
const years = range(1950, getYear(new Date()) + 16, 1); const years = range(1950, getYear(new Date()) + 16, 1);
const fontSize = props.calculateFont(props.pos.options?.fontSize); const fontSize = props.calculateFont(props.pos.options?.fontSize);
const fontColor = props.pos.options?.fontColor || "black"; const fontColor = props.pos.options?.fontColor || "black";
@@ -150,6 +151,11 @@ function PlaceholderType(props) {
if (defaultData) { if (defaultData) {
setTextValue(defaultData); setTextValue(defaultData);
} }
if (props.pos?.options?.hint) {
setHint(props.pos?.options.hint);
} else {
setHint(props.pos?.type);
}
} else if ([textInputWidget].includes(props.pos?.type)) { } else if ([textInputWidget].includes(props.pos?.type)) {
const defaultData = props.pos?.options?.defaultValue; const defaultData = props.pos?.options?.defaultValue;
if (defaultData) { if (defaultData) {
@@ -158,7 +164,6 @@ function PlaceholderType(props) {
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos?.options?.defaultValue]); }, [props.pos?.options?.defaultValue]);
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => ( const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
<div <div
style={{ style={{
@@ -327,13 +332,20 @@ function PlaceholderType(props) {
/> />
) : ( ) : (
<div className={widgetCls}> <div className={widgetCls}>
{props?.handleUserName && {props.pos.type && (
props?.handleUserName( <div
props?.data?.Id, style={{
props?.data?.Role, fontSize: props.pos
widgetTypeTraslation, ? props.calculateFontsize(props.pos)
props.pos : "11px"
)} }}
className="font-medium"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div> </div>
); );
case "stamp": case "stamp":
@@ -346,13 +358,20 @@ function PlaceholderType(props) {
/> />
) : ( ) : (
<div className={widgetCls}> <div className={widgetCls}>
{props?.handleUserName && {props.pos.type && (
props?.handleUserName( <div
props?.data?.Id, style={{
props?.data?.Role, fontSize: props.pos
widgetTypeTraslation, ? props.calculateFontsize(props.pos)
props.pos : "11px"
)} }}
className="font-medium"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div> </div>
); );
case "checkbox": case "checkbox":
@@ -533,13 +552,20 @@ function PlaceholderType(props) {
/> />
) : ( ) : (
<div className={widgetCls}> <div className={widgetCls}>
{props?.handleUserName && {props.pos.type && (
props?.handleUserName( <div
props?.data?.Id, style={{
props?.data?.Role, fontSize: props.pos
widgetTypeTraslation, ? props.calculateFontsize(props.pos)
props.pos : "11px"
)} }}
className="font-medium text-center"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div> </div>
); );
case "name": case "name":
@@ -548,7 +574,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? ( (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea <textarea
ref={inputRef} ref={inputRef}
placeholder={t("widgets-name.name")} placeholder={hint}
rows={1} rows={1}
onKeyDown={handleEnterPress} onKeyDown={handleEnterPress}
value={textValue} value={textValue}
@@ -580,7 +606,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? ( (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea <textarea
ref={inputRef} ref={inputRef}
placeholder={t("widgets-name.company")} placeholder={hint}
rows={1} rows={1}
onKeyDown={handleEnterPress} onKeyDown={handleEnterPress}
value={textValue} value={textValue}
@@ -611,7 +637,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? ( (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea <textarea
ref={inputRef} ref={inputRef}
placeholder={t("widgets-name.job title")} placeholder={hint}
rows={1} rows={1}
onKeyDown={handleEnterPress} onKeyDown={handleEnterPress}
value={textValue} value={textValue}
@@ -712,13 +738,20 @@ function PlaceholderType(props) {
/> />
) : ( ) : (
<div className={widgetCls}> <div className={widgetCls}>
{props?.handleUserName && {props.pos.type && (
props?.handleUserName( <div
props?.data?.Id, style={{
props?.data?.Role, fontSize: props.pos
widgetTypeTraslation, ? props.calculateFontsize(props.pos)
props.pos : "11px"
)} }}
className="font-medium text-center"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div> </div>
); );
case "email": case "email":
@@ -727,7 +760,7 @@ function PlaceholderType(props) {
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? ( (props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea <textarea
ref={inputRef} ref={inputRef}
placeholder={t("widgets-name.email")} placeholder={hint}
rows={1} rows={1}
onKeyDown={(e) => { onKeyDown={(e) => {
// Prevent new line on Enter key press // Prevent new line on Enter key press
@@ -848,13 +881,20 @@ function PlaceholderType(props) {
) : ( ) : (
<div className={widgetCls}> <div className={widgetCls}>
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>} {props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
{props?.handleUserName && {props.pos.type && (
props?.handleUserName( <div
props?.data?.Id, style={{
props?.data?.Role, fontSize: props.pos
null, ? props.calculateFontsize(props.pos)
props.pos : "11px"
)} }}
className="font-medium"
>
{props.isNeedSign
? props.pos?.options?.hint || widgetTypeTraslation
: widgetTypeTraslation}
</div>
)}
</div> </div>
); );
} }
+9 -101
View File
@@ -89,35 +89,6 @@ function RenderPdf(props) {
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
) )
: []; : [];
const handleAllUserName = (Id, Role, type, pos) => {
return (
<React.Fragment>
<div className="text-black text-[8px] font-bold">
{
props.pdfDetails[0].Signers?.find(
(signer) => signer.objectId === data.signerObjId
)?.Name
}
</div>
{type && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
className="font-bold"
>
{type}
</div>
)}
{Role && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
className="text-black font-medium"
>
{`(${Role})`}
</div>
)}
</React.Fragment>
);
};
return ( return (
checkSign.length === 0 && checkSign.length === 0 &&
data?.placeHolder?.map((placeData, key) => { data?.placeHolder?.map((placeData, key) => {
@@ -148,7 +119,6 @@ function RenderPdf(props) {
isSignYourself={false} isSignYourself={false}
posWidth={posWidth} posWidth={posWidth}
posHeight={posHeight} posHeight={posHeight}
handleUserName={handleAllUserName}
isDragging={props.isDragging} isDragging={props.isDragging}
pdfDetails={props.pdfDetails} pdfDetails={props.pdfDetails}
setIsInitial={props.setIsInitial} setIsInitial={props.setIsInitial}
@@ -185,6 +155,7 @@ function RenderPdf(props) {
fontColor={props.fontColor} fontColor={props.fontColor}
setFontColor={props.setFontColor} setFontColor={props.setFontColor}
setRequestSignTour={props.setRequestSignTour} setRequestSignTour={props.setRequestSignTour}
calculateFontsize={calculateFontsize}
/> />
</React.Fragment> </React.Fragment>
) )
@@ -206,73 +177,6 @@ function RenderPdf(props) {
return `${width / 10}px`; return `${width / 10}px`;
} }
}; };
//function for render placeholder block over pdf document
const handleUserName = (Id, Role, type, pos) => {
if (Id) {
const checkSign = props.signersdata.find((sign) => sign.Id === Id);
if (checkSign?.Name) {
return (
<>
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "8px"
}}
className="text-black font-medium"
>
{checkSign?.Name}
</div>
{type && (
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "11px"
}}
className=" font-bold"
>
{type}
</div>
)}
{Role && (
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "8px"
}}
className="text-black text-[8px] font-medium"
>
{`(${Role})`}
</div>
)}
</>
);
} else {
return (
<>
{type && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
className=" font-bold"
>
{type}
</div>
)}
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
className="text-black font-medium"
>
{Role}
</div>
</>
);
}
} else {
return (
<>
{type && <div className="text-[11px] font-bold">{type}</div>}
<div className="text-black text-[8px] font-medium">{Role}</div>
</>
);
}
};
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`; const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
//calculate render height of pdf in mobile view //calculate render height of pdf in mobile view
const handlePageLoadSuccess = (page) => { const handlePageLoadSuccess = (page) => {
@@ -361,7 +265,6 @@ function RenderPdf(props) {
handleLinkUser={ handleLinkUser={
props.handleLinkUser props.handleLinkUser
} }
handleUserName={handleUserName}
isSignYourself={false} isSignYourself={false}
posWidth={posWidth} posWidth={posWidth}
posHeight={posHeight} posHeight={posHeight}
@@ -403,6 +306,9 @@ function RenderPdf(props) {
props.unSignedWidgetId props.unSignedWidgetId
} }
isFreeResize={true} isFreeResize={true}
calculateFontsize={
calculateFontsize
}
/> />
</React.Fragment> </React.Fragment>
); );
@@ -448,7 +354,6 @@ function RenderPdf(props) {
setWidgetType={props.setWidgetType} setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId} setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId} selectWidgetId={props.selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={props.setIsCheckbox} setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert} setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={ setCurrWidgetsDetails={
@@ -468,6 +373,7 @@ function RenderPdf(props) {
setIsResize={props.setIsResize} setIsResize={props.setIsResize}
isFreeResize={false} isFreeResize={false}
isOpenSignPad={true} isOpenSignPad={true}
calculateFontsize={calculateFontsize}
/> />
) )
); );
@@ -573,7 +479,6 @@ function RenderPdf(props) {
handleLinkUser={ handleLinkUser={
props.handleLinkUser props.handleLinkUser
} }
handleUserName={handleUserName}
isSignYourself={false} isSignYourself={false}
posWidth={posWidth} posWidth={posWidth}
posHeight={posHeight} posHeight={posHeight}
@@ -615,6 +520,9 @@ function RenderPdf(props) {
props.unSignedWidgetId props.unSignedWidgetId
} }
isFreeResize={true} isFreeResize={true}
calculateFontsize={
calculateFontsize
}
/> />
</React.Fragment> </React.Fragment>
); );
@@ -665,7 +573,6 @@ function RenderPdf(props) {
setWidgetType={props.setWidgetType} setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId} setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId} selectWidgetId={props.selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={props.setIsCheckbox} setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert} setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={ setCurrWidgetsDetails={
@@ -686,6 +593,7 @@ function RenderPdf(props) {
setIsResize={props.setIsResize} setIsResize={props.setIsResize}
isFreeResize={false} isFreeResize={false}
isOpenSignPad={true} isOpenSignPad={true}
calculateFontsize={calculateFontsize}
/> />
</React.Fragment> </React.Fragment>
); );
@@ -31,7 +31,11 @@ const WidgetNameModal = (props) => {
name: props.defaultdata?.options?.name || "", name: props.defaultdata?.options?.name || "",
defaultValue: props.defaultdata?.options?.defaultValue || "", defaultValue: props.defaultdata?.options?.defaultValue || "",
status: props.defaultdata?.options?.status || "required", status: props.defaultdata?.options?.status || "required",
hint: props.defaultdata?.options?.hint || "", hint:
props.defaultdata?.options?.hint ||
(props.defaultdata?.type === textInputWidget
? "Enter text"
: `Enter ${props.defaultdata?.options?.name}`),
textvalidate: textvalidate:
props.defaultdata?.options?.validation?.type === "regex" props.defaultdata?.options?.validation?.type === "regex"
? props.defaultdata?.options?.validation?.pattern ? props.defaultdata?.options?.validation?.pattern
@@ -113,7 +117,6 @@ const WidgetNameModal = (props) => {
} }
} }
const handleCheckboxChange = (index) => { const handleCheckboxChange = (index) => {
// Update the state with the modified array // Update the state with the modified array
setSignatureType((prev) => setSignatureType((prev) =>
@@ -272,12 +275,13 @@ const WidgetNameModal = (props) => {
</div> </div>
</div> </div>
)} )}
{props.defaultdata?.type === textInputWidget && ( {props.defaultdata?.type !== textWidget && (
<div className="mb-[0.75rem]"> <div className="mb-[0.75rem]">
<label htmlFor="hint" className="text-[13px]"> <label htmlFor="hint" className="text-[13px]">
{t("hint")} {t("hint")}
</label> </label>
<input <input
maxLength={40}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs" className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="hint" name="hint"
value={formdata.hint} value={formdata.hint}
@@ -295,7 +299,7 @@ const WidgetNameModal = (props) => {
].includes(props.defaultdata?.type) && ( ].includes(props.defaultdata?.type) && (
<div className="flex flex-col md:flex-row md:items-center gap-3 mb-3"> <div className="flex flex-col md:flex-row md:items-center gap-3 mb-3">
<div className="flex items-center gap-2 "> <div className="flex items-center gap-2 ">
<span>{t("font-size")}:</span> <span className="whitespace-nowrap">{t("font-size")}: </span>
<select <select
className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs" className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
value={ value={
+60 -86
View File
@@ -7,9 +7,7 @@ import { appInfo } from "./appinfo";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
import printModule from "print-js"; import printModule from "print-js";
import fontkit from "@pdf-lib/fontkit"; import fontkit from "@pdf-lib/fontkit";
import { import { themeColor } from "./const";
themeColor
} from "./const";
export const fontsizeArr = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]; export const fontsizeArr = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28];
export const fontColorArr = ["red", "black", "blue", "yellow"]; export const fontColorArr = ["red", "black", "blue", "yellow"];
@@ -136,14 +134,12 @@ export const pdfNewWidthFun = (divRef) => {
}; };
//`contractUsers` function is used to get contract_User details //`contractUsers` function is used to get contract_User details
export const contractUsers = async ( export const contractUsers = async () => {
) => {
try { try {
const url = `${localStorage.getItem("baseUrl")}functions/getUserDetails`; const url = `${localStorage.getItem("baseUrl")}functions/getUserDetails`;
const parseAppId = localStorage.getItem("parseAppId"); const parseAppId = localStorage.getItem("parseAppId");
const accesstoken = localStorage.getItem("accesstoken"); const accesstoken = localStorage.getItem("accesstoken");
const token = const token = { "X-Parse-Session-Token": accesstoken };
{ "X-Parse-Session-Token": accesstoken };
const headers = { const headers = {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -495,7 +491,7 @@ export const signPdfFun = async (
documentId, documentId,
signerObjectId, signerObjectId,
objectId, objectId,
widgets, widgets
) => { ) => {
let isCustomCompletionMail = false; let isCustomCompletionMail = false;
try { try {
@@ -504,10 +500,7 @@ export const signPdfFun = async (
if (tenantDetails && tenantDetails === "user does not exist!") { if (tenantDetails && tenantDetails === "user does not exist!") {
return { status: "error", message: "User does not exist." }; return { status: "error", message: "User does not exist." };
} else { } else {
if ( if (tenantDetails?.CompletionBody && tenantDetails?.CompletionSubject) {
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject
) {
isCustomCompletionMail = true; isCustomCompletionMail = true;
} }
} }
@@ -642,7 +635,13 @@ export const createDocument = async (
...Bcc, ...Bcc,
...RedirectUrl ...RedirectUrl
}; };
const remindOnceInEvery = Doc?.RemindOnceInEvery;
const TimeToCompleteDays = parseInt(Doc?.TimeToCompleteDays);
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
const AutomaticReminders = Doc.autoreminder;
if (AutomaticReminders && reminderCount > 15) {
return { status: "error", id: "only-15-reminder-allowed" };
}
try { try {
const res = await axios.post( const res = await axios.post(
`${localStorage.getItem("baseUrl")}classes/contracts_Document`, `${localStorage.getItem("baseUrl")}classes/contracts_Document`,
@@ -660,7 +659,7 @@ export const createDocument = async (
} }
} catch (err) { } catch (err) {
console.log("axois err ", err); console.log("axois err ", err);
return { status: "error", id: "Something Went Wrong!" }; return { status: "error", id: "something-went-wrong-mssg" };
} }
} }
}; };
@@ -985,7 +984,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
...item, ...item,
options: { options: {
...item.options, ...item.options,
defaultValue: widgetData defaultValue: item?.options?.defaultValue || widgetData
} }
// Width: // Width:
// calculateInitialWidthHeight(item.type, widgetData).getWidth || // calculateInitialWidthHeight(item.type, widgetData).getWidth ||
@@ -1005,8 +1004,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
//function for embed document id //function for embed document id
export const embedDocId = async (pdfDoc, documentId, allPages) => { export const embedDocId = async (pdfDoc, documentId, allPages) => {
const appName = const appName = "OpenSign™";
"OpenSign™";
// `fontBytes` is used to embed custom font in pdf // `fontBytes` is used to embed custom font in pdf
const fontBytes = await fileasbytes( const fontBytes = await fileasbytes(
"https://cdn.opensignlabs.com/webfonts/times.ttf" "https://cdn.opensignlabs.com/webfonts/times.ttf"
@@ -1868,12 +1866,9 @@ export const contactBook = async (objectId) => {
}; };
//function for getting document details from contract_Documents class //function for getting document details from contract_Documents class
export const contractDocument = async ( export const contractDocument = async (documentId) => {
documentId,
) => {
const data = { docId: documentId }; const data = { docId: documentId };
const token = const token = { sessionToken: localStorage.getItem("accesstoken") };
{ sessionToken: localStorage.getItem("accesstoken") };
const documentDeatils = await axios const documentDeatils = await axios
.post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, { .post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, {
headers: { headers: {
@@ -2057,36 +2052,31 @@ export const getFileName = (fileUrl) => {
//fetch tenant app logo from `partners_Tenant` class by domain name //fetch tenant app logo from `partners_Tenant` class by domain name
export const getAppLogo = async () => { export const getAppLogo = async () => {
const domain = window.location.host; const domain = window.location.host;
try { try {
const tenant = await Parse.Cloud.run("getlogobydomain", { const tenant = await Parse.Cloud.run("getlogobydomain", {
domain: domain domain: domain
}); });
if (tenant) { if (tenant) {
localStorage.setItem("appname", "OpenSign™"); localStorage.setItem("appname", "OpenSign™");
return { logo: tenant?.logo, user: tenant?.user }; return { logo: tenant?.logo, user: tenant?.user };
}
} catch (err) {
console.log("err in getlogo ", err);
if (err?.message?.includes("valid JSON")) {
return { logo: appInfo.applogo, user: "exist", error: "invalid_json" };
} else {
return { logo: appInfo.applogo, user: "exist" };
}
} }
} catch (err) {
console.log("err in getlogo ", err);
if (err?.message?.includes("valid JSON")) {
return { logo: appInfo.applogo, user: "exist", error: "invalid_json" };
} else {
return { logo: appInfo.applogo, user: "exist" };
}
}
}; };
export const getTenantDetails = async ( export const getTenantDetails = async (objectId, contactId) => {
objectId,
contactId
) => {
try { try {
const url = `${localStorage.getItem("baseUrl")}functions/gettenant`; const url = `${localStorage.getItem("baseUrl")}functions/gettenant`;
const parseAppId = localStorage.getItem("parseAppId"); const parseAppId = localStorage.getItem("parseAppId");
const accesstoken = localStorage.getItem("accesstoken"); const accesstoken = localStorage.getItem("accesstoken");
const token = const token = { "X-Parse-Session-Token": accesstoken };
{ "X-Parse-Session-Token": accesstoken }; const data = { userId: objectId, contactId: contactId };
const data =
{ userId: objectId, contactId: contactId };
const res = await axios.post(url, data, { const res = await axios.post(url, data, {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
@@ -2173,8 +2163,7 @@ export const handleSendOTP = async (email) => {
} }
}; };
export const fetchUrl = async (url, pdfName) => { export const fetchUrl = async (url, pdfName) => {
const appName = const appName = "OpenSign™";
"OpenSign™";
try { try {
const response = await fetch(url); const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
@@ -2188,11 +2177,7 @@ export const fetchUrl = async (url, pdfName) => {
console.error("Error downloading the file:", error); console.error("Error downloading the file:", error);
} }
}; };
export const getSignedUrl = async ( export const getSignedUrl = async (pdfUrl, docId, templateId) => {
pdfUrl,
docId,
templateId
) => {
//use only axios here due to public template sign //use only axios here due to public template sign
const axiosRes = await axios.post( const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`, `${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
@@ -2253,10 +2238,7 @@ export const handleDownloadPdf = async (
setIsDownloading && setIsDownloading("pdf"); setIsDownloading && setIsDownloading("pdf");
const docId = pdfDetails?.[0]?.objectId || ""; const docId = pdfDetails?.[0]?.objectId || "";
try { try {
const url = await getSignedUrl( const url = await getSignedUrl(pdfUrl, docId);
pdfUrl,
docId,
);
await fetchUrl(url, pdfName); await fetchUrl(url, pdfName);
setIsDownloading && setIsDownloading(""); setIsDownloading && setIsDownloading("");
} catch (err) { } catch (err) {
@@ -2286,7 +2268,7 @@ export const handleToPrint = async (event, setIsDownloading, pdfDetails) => {
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`, `${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ {
url: pdfUrl, url: pdfUrl,
docId: docId, docId: docId
}, },
{ {
headers: { headers: {
@@ -2330,8 +2312,7 @@ export const handleDownloadCertificate = async (
setIsDownloading, setIsDownloading,
isZip isZip
) => { ) => {
const appName = const appName = "OpenSign™";
"OpenSign™";
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) { if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
try { try {
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl); await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
@@ -2415,14 +2396,13 @@ export const handleDownloadCertificate = async (
export function escapeRegExp(string) { export function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // Escape special characters return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // Escape special characters
} }
export async function findContact( export async function findContact(value) {
value,
) {
try { try {
const baseURL = localStorage.getItem("baseUrl"); const baseURL = localStorage.getItem("baseUrl");
const url = `${baseURL}functions/getsigners`; const url = `${baseURL}functions/getsigners`;
const token = const token = {
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") }; "X-Parse-Session-Token": localStorage.getItem("accesstoken")
};
const headers = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"), "X-Parse-Application-Id": localStorage.getItem("parseAppId"),
@@ -2634,23 +2614,20 @@ export function base64ToArrayBuffer(base64) {
return bytes.buffer; return bytes.buffer;
} }
export const convertBase64ToFile = async ( export const convertBase64ToFile = async (pdfName, pdfBase64) => {
pdfName,
pdfBase64,
) => {
const fileName = sanitizeFileName(pdfName) + ".pdf"; const fileName = sanitizeFileName(pdfName) + ".pdf";
try { try {
const pdfFile = new Parse.File(fileName, { base64: pdfBase64 }); const pdfFile = new Parse.File(fileName, { base64: pdfBase64 });
// Save the Parse File if needed // Save the Parse File if needed
const pdfData = await pdfFile.save(); const pdfData = await pdfFile.save();
const pdfUrl = pdfData.url(); const pdfUrl = pdfData.url();
const fileRes = await getSecureUrl(pdfUrl); const fileRes = await getSecureUrl(pdfUrl);
if (fileRes?.url) { if (fileRes?.url) {
return fileRes.url; return fileRes.url;
}
} catch (e) {
console.log("error in convertbase64tofile", e);
} }
} catch (e) {
console.log("error in convertbase64tofile", e);
}
}; };
export const onClickZoomIn = (scale, zoomPercent, setScale, setZoomPercent) => { export const onClickZoomIn = (scale, zoomPercent, setScale, setZoomPercent) => {
setScale(scale + 0.1 * scale); setScale(scale + 0.1 * scale);
@@ -2949,13 +2926,10 @@ export const flattenPdf = async (pdfFile) => {
}; };
export const mailTemplate = (param) => { export const mailTemplate = (param) => {
const appName = const appName = "OpenSign™";
"OpenSign™"; const logo = `<div style='padding:10px'><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' /></div>`;
const logo =
`<div style='padding:10px'><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' /></div>`;
const opurl = const opurl = ` <a href='https://www.opensignlabs.com' target=_blank>here</a>.</p></div></div></body></html>`;
` <a href='https://www.opensignlabs.com' target=_blank>here</a>.</p></div></div></body></html>`;
const subject = `${param.senderName} has requested you to sign "${param.title}"`; const subject = `${param.senderName} has requested you to sign "${param.title}"`;
const body = const body =
+222 -213
View File
@@ -36,8 +36,7 @@ function Form() {
} }
const Forms = (props) => { const Forms = (props) => {
const appName = const appName = "OpenSign™";
"OpenSign™";
const { t } = useTranslation(); const { t } = useTranslation();
const maxFileSize = 20; const maxFileSize = 20;
const abortController = new AbortController(); const abortController = new AbortController();
@@ -81,6 +80,15 @@ const Forms = (props) => {
const extUserData = const extUserData =
localStorage.getItem("Extand_Class") && localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const sendinorder =
extUserData?.SendinOrder !== undefined && extUserData?.SendinOrder === false
? "false"
: "true";
const istourenabled =
extUserData?.IsTourEnabled !== undefined &&
extUserData?.IsTourEnabled === false
? "false"
: "true";
useEffect(() => { useEffect(() => {
handleReset(); handleReset();
return () => abortController.abort(); return () => abortController.abort();
@@ -92,7 +100,12 @@ const Forms = (props) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const initializeValues = async () => { const initializeValues = async () => {
setFormData((obj) => ({ ...obj, NotifyOnSignatures: true })); setFormData((obj) => ({
...obj,
NotifyOnSignatures: true,
SendinOrder: sendinorder,
IsTourEnabled: istourenabled
}));
}; };
function getFileAsArrayBuffer(file) { function getFileAsArrayBuffer(file) {
@@ -130,17 +143,88 @@ const Forms = (props) => {
const name = generatePdfName(16); const name = generatePdfName(16);
const pdfName = `${name?.split(".")[0]}.pdf`; const pdfName = `${name?.split(".")[0]}.pdf`;
setfileload(true); setfileload(true);
try { try {
const res = await getFileAsArrayBuffer(files[0]); const res = await getFileAsArrayBuffer(files[0]);
const flatPdf = await flattenPdf(res); const flatPdf = await flattenPdf(res);
const parseFile = new Parse.File( const parseFile = new Parse.File(
pdfName, pdfName,
[...flatPdf], [...flatPdf],
"application/pdf" "application/pdf"
); );
try {
const response = await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round(
(loaded * 100) / total
);
setpercentage(percentCompleted);
}
}
});
// The response object will contain information about the uploaded file
// You can access the URL of the uploaded file using response.url()
if (response.url()) {
const fileRes = await getSecureUrl(response.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
setfileload(false);
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile(e);
}
} else {
removeFile(e);
}
} catch (error) {
removeFile(e);
console.error("Error uploading file:", error);
}
} catch (err) {
if (err?.message?.includes("is encrypted")) {
try { try {
const response = await parseFile.save({ await Parse.Cloud.run("encryptedpdf", {
email: Parse.User.current().getEmail()
});
} catch (err) {
console.log("err in sending posthog encryptedpdf", err);
}
try {
setIsDecrypting(true);
const size = files?.[0].size;
const name = generatePdfName(16);
const url = "https://ai.nxglabs.in/decryptpdf"; //
let formData = new FormData();
formData.append("file", files[0]);
formData.append("password", "");
const config = {
headers: { "content-type": "multipart/form-data" },
responseType: "blob"
};
const response = await axios.post(url, formData, config);
const pdfBlob = new Blob([response.data], {
type: "application/pdf"
});
const pdfFile = new File([pdfBlob], name, {
type: "application/pdf"
});
setIsDecrypting(false);
setfileload(true);
const res = await getFileAsArrayBuffer(pdfFile);
const flatPdf = await flattenPdf(res);
// Upload the file to Parse Server
const parseFile = new Parse.File(
name,
[...flatPdf],
"application/pdf"
);
await parseFile.save({
progress: (progressValue, loaded, total, { type }) => { progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) { if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round( const percentCompleted = Math.round(
@@ -150,16 +234,16 @@ const Forms = (props) => {
} }
} }
}); });
// The response object will contain information about the uploaded file
// You can access the URL of the uploaded file using response.url() // Retrieve the URL of the uploaded file
if (response.url()) { if (parseFile.url()) {
const fileRes = await getSecureUrl(response.url()); const fileRes = await getSecureUrl(parseFile.url());
if (fileRes.url) { if (fileRes.url) {
setFileUpload(fileRes.url); setFileUpload(fileRes.url);
setfileload(false); removeFile();
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name); const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title })); setFormData((obj) => ({ ...obj, Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes.url, tenantId); SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url; return fileRes.url;
} else { } else {
@@ -168,95 +252,22 @@ const Forms = (props) => {
} else { } else {
removeFile(e); removeFile(e);
} }
} catch (error) { } catch (err) {
removeFile(e); removeFile();
console.error("Error uploading file:", error); if (err?.response?.status === 401) {
} setIsPassword(true);
} catch (err) { } else {
if (err?.message?.includes("is encrypted")) { console.log("Error uploading file: ", err?.response);
try {
await Parse.Cloud.run("encryptedpdf", {
email: Parse.User.current().getEmail()
});
} catch (err) {
console.log("err in sending posthog encryptedpdf", err);
}
try {
setIsDecrypting(true);
const size = files?.[0].size;
const name = generatePdfName(16);
const url = "https://ai.nxglabs.in/decryptpdf"; //
let formData = new FormData();
formData.append("file", files[0]);
formData.append("password", "");
const config = {
headers: { "content-type": "multipart/form-data" },
responseType: "blob"
};
const response = await axios.post(url, formData, config);
const pdfBlob = new Blob([response.data], {
type: "application/pdf"
});
const pdfFile = new File([pdfBlob], name, {
type: "application/pdf"
});
setIsDecrypting(false); setIsDecrypting(false);
setfileload(true); e.target.value = "";
const res = await getFileAsArrayBuffer(pdfFile);
const flatPdf = await flattenPdf(res);
// Upload the file to Parse Server
const parseFile = new Parse.File(
name,
[...flatPdf],
"application/pdf"
);
await parseFile.save({
progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round(
(loaded * 100) / total
);
setpercentage(percentCompleted);
}
}
});
// Retrieve the URL of the uploaded file
if (parseFile.url()) {
const fileRes = await getSecureUrl(parseFile.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
removeFile();
const title = generateTitleFromFilename(
files?.[0]?.name
);
setFormData((obj) => ({ ...obj, Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile(e);
}
} else {
removeFile(e);
}
} catch (err) {
removeFile();
if (err?.response?.status === 401) {
setIsPassword(true);
} else {
console.log("Error uploading file: ", err?.response);
setIsDecrypting(false);
e.target.value = "";
}
} }
} else {
console.log("err ", err);
setFileUpload("");
removeFile(e);
} }
} else {
console.log("err ", err);
setFileUpload("");
removeFile(e);
} }
}
} else { } else {
const isImage = files?.[0]?.type.includes("image/"); const isImage = files?.[0]?.type.includes("image/");
if (isImage) { if (isImage) {
@@ -281,50 +292,50 @@ const Forms = (props) => {
}); });
const size = files?.[0]?.size; const size = files?.[0]?.size;
const name = generatePdfName(16); const name = generatePdfName(16);
const getFile = await pdfDoc.save({ const getFile = await pdfDoc.save({
useObjectStreams: false useObjectStreams: false
}); });
setfileload(true); setfileload(true);
const pdfName = `${name?.split(".")[0]}.pdf`; const pdfName = `${name?.split(".")[0]}.pdf`;
const parseFile = new Parse.File( const parseFile = new Parse.File(
pdfName, pdfName,
[...getFile], [...getFile],
"application/pdf" "application/pdf"
); );
try { try {
const response = await parseFile.save({ const response = await parseFile.save({
progress: (progressValue, loaded, total, { type }) => { progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) { if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round( const percentCompleted = Math.round(
(loaded * 100) / total (loaded * 100) / total
); );
setpercentage(percentCompleted); setpercentage(percentCompleted);
}
}
});
// The response object will contain information about the uploaded file
// You can access the URL of the uploaded file using response.url()
if (response.url()) {
const fileRes = await getSecureUrl(response.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
setfileload(false);
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile(e);
} }
}
});
// The response object will contain information about the uploaded file
// You can access the URL of the uploaded file using response.url()
if (response.url()) {
const fileRes = await getSecureUrl(response.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
setfileload(false);
const tenantId = localStorage.getItem("TenantId");
const title = generateTitleFromFilename(files?.[0]?.name);
setFormData((obj) => ({ ...obj, Name: title }));
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else { } else {
removeFile(e); removeFile(e);
} }
} catch (error) { } else {
removeFile(e); removeFile(e);
console.error("Error uploading file:", error);
} }
} catch (error) {
removeFile(e);
console.error("Error uploading file:", error);
}
} }
} }
} }
@@ -374,19 +385,24 @@ const Forms = (props) => {
const isChecked = formData.SendinOrder === "false" ? false : true; const isChecked = formData.SendinOrder === "false" ? false : true;
const isTourEnabled = const isTourEnabled =
formData?.IsTourEnabled === "false" ? false : true; formData?.IsTourEnabled === "false" ? false : true;
const remindOnceInEvery = parseInt(formData.remindOnceInEvery);
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
const AutomaticReminders = formData.autoreminder;
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutomaticReminders && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
object.set("SendinOrder", isChecked); object.set("SendinOrder", isChecked);
object.set("AutomaticReminders", formData.autoreminder); object.set("AutomaticReminders", AutomaticReminders);
object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery)); object.set("RemindOnceInEvery", remindOnceInEvery);
object.set("IsTourEnabled", isTourEnabled); object.set("IsTourEnabled", isTourEnabled);
object.set( object.set("TimeToCompleteDays", TimeToCompleteDays);
"TimeToCompleteDays", object.set("AllowModifications", false);
parseInt(formData?.TimeToCompleteDays) object.set("IsEnableOTP", false);
); if (formData.NotifyOnSignatures !== undefined) {
object.set("AllowModifications", false); object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
object.set("IsEnableOTP", false); }
if (formData.NotifyOnSignatures !== undefined) {
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
}
if (formData?.RedirectUrl) { if (formData?.RedirectUrl) {
object.set("RedirectUrl", formData.RedirectUrl); object.set("RedirectUrl", formData.RedirectUrl);
} }
@@ -425,10 +441,9 @@ const Forms = (props) => {
setSigners([]); setSigners([]);
setBcc([]); setBcc([]);
setFolder({ ObjectId: "", Name: "" }); setFolder({ ObjectId: "", Name: "" });
const notifySign = const notifySign = extUserData?.NotifyOnSignatures
extUserData?.NotifyOnSignatures ? extUserData?.NotifyOnSignatures
? extUserData?.NotifyOnSignatures : true;
: true;
setFormData({ setFormData({
Name: "", Name: "",
Description: "", Description: "",
@@ -437,14 +452,14 @@ const Forms = (props) => {
? "Note to myself" ? "Note to myself"
: "Please review and sign this document", : "Please review and sign this document",
TimeToCompleteDays: 15, TimeToCompleteDays: 15,
SendinOrder: "true", SendinOrder: sendinorder,
password: "", password: "",
file: "", file: "",
NotifyOnSignatures: notifySign, NotifyOnSignatures: notifySign,
remindOnceInEvery: 5, remindOnceInEvery: 5,
autoreminder: false, autoreminder: false,
IsEnableOTP: "false", IsEnableOTP: "false",
IsTourEnabled: "true", IsTourEnabled: istourenabled,
RedirectUrl: "", RedirectUrl: "",
AllowModifications: false AllowModifications: false
}); });
@@ -454,7 +469,17 @@ const Forms = (props) => {
} }
} catch (err) { } catch (err) {
console.log("err ", err); console.log("err ", err);
setIsAlert({ type: "danger", message: t("something-went-wrong-mssg") }); if (err.message === "only 15 reminder allowed") {
setIsAlert({
type: "danger",
message: t("only-15-reminder-allowed")
});
} else {
setIsAlert({
type: "danger",
message: t("something-went-wrong-mssg")
});
}
} finally { } finally {
setTimeout(() => setIsAlert({ type: "success", message: "" }), 1000); setTimeout(() => setIsAlert({ type: "success", message: "" }), 1000);
setIsSubmit(false); setIsSubmit(false);
@@ -494,10 +519,9 @@ const Forms = (props) => {
setSigners([]); setSigners([]);
setBcc([]); setBcc([]);
setFolder({ ObjectId: "", Name: "" }); setFolder({ ObjectId: "", Name: "" });
const notifySign = const notifySign = extUserData?.NotifyOnSignatures
extUserData?.NotifyOnSignatures ? extUserData?.NotifyOnSignatures
? extUserData?.NotifyOnSignatures : true;
: true;
let obj = { let obj = {
Name: "", Name: "",
Description: "", Description: "",
@@ -506,13 +530,13 @@ const Forms = (props) => {
? "Note to myself" ? "Note to myself"
: "Please review and sign this document", : "Please review and sign this document",
TimeToCompleteDays: 15, TimeToCompleteDays: 15,
SendinOrder: "true", SendinOrder: sendinorder,
password: "", password: "",
file: "", file: "",
remindOnceInEvery: 5, remindOnceInEvery: 5,
autoreminder: false, autoreminder: false,
IsEnableOTP: "false", IsEnableOTP: "false",
IsTourEnabled: "true", IsTourEnabled: istourenabled,
NotifyOnSignatures: notifySign, NotifyOnSignatures: notifySign,
RedirectUrl: "", RedirectUrl: "",
AllowModifications: false AllowModifications: false
@@ -551,36 +575,28 @@ const Forms = (props) => {
type: "application/pdf" type: "application/pdf"
}); });
setIsDecrypting(false); setIsDecrypting(false);
const res = await getFileAsArrayBuffer(pdfFile); const res = await getFileAsArrayBuffer(pdfFile);
const flatPdf = await flattenPdf(res); const flatPdf = await flattenPdf(res);
const parseFile = new Parse.File(name, [...flatPdf], "application/pdf"); const parseFile = new Parse.File(name, [...flatPdf], "application/pdf");
await parseFile.save({ await parseFile.save({
progress: (progressValue, loaded, total, { type }) => { progress: (progressValue, loaded, total, { type }) => {
if (type === "upload" && progressValue !== null) { if (type === "upload" && progressValue !== null) {
const percentCompleted = Math.round((loaded * 100) / total); const percentCompleted = Math.round((loaded * 100) / total);
setpercentage(percentCompleted); setpercentage(percentCompleted);
}
}
});
// Retrieve the URL of the uploaded file
if (parseFile.url()) {
const fileRes = await getSecureUrl(parseFile.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
removeFile();
const title = generateTitleFromFilename(formData?.file?.name);
setFormData((obj) => ({ ...obj, password: "", Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else {
removeFile();
setFormData((prev) => ({ ...prev, password: "" }));
setIsDecrypting(false);
if (inputFileRef.current) {
inputFileRef.current.value = ""; // Set file input value to empty string
}
} }
}
});
// Retrieve the URL of the uploaded file
if (parseFile.url()) {
const fileRes = await getSecureUrl(parseFile.url());
if (fileRes.url) {
setFileUpload(fileRes.url);
removeFile();
const title = generateTitleFromFilename(formData?.file?.name);
setFormData((obj) => ({ ...obj, password: "", Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes.url, tenantId);
return fileRes.url;
} else { } else {
removeFile(); removeFile();
setFormData((prev) => ({ ...prev, password: "" })); setFormData((prev) => ({ ...prev, password: "" }));
@@ -589,6 +605,14 @@ const Forms = (props) => {
inputFileRef.current.value = ""; // Set file input value to empty string inputFileRef.current.value = ""; // Set file input value to empty string
} }
} }
} else {
removeFile();
setFormData((prev) => ({ ...prev, password: "" }));
setIsDecrypting(false);
if (inputFileRef.current) {
inputFileRef.current.value = ""; // Set file input value to empty string
}
}
} catch (err) { } catch (err) {
removeFile(); removeFile();
if (err?.response?.status === 401) { if (err?.response?.status === 401) {
@@ -710,9 +734,7 @@ const Forms = (props) => {
)} )}
<div className="text-xs"> <div className="text-xs">
<label className="block"> <label className="block">
{`${`${t("report-heading.File")} (${t("file-type")}`}${ {`${`${t("report-heading.File")} (${t("file-type")}`}${")"}`}
")"
}`}
<span className="text-red-500 text-[13px]">*</span> <span className="text-red-500 text-[13px]">*</span>
</label> </label>
{fileupload.length > 0 ? ( {fileupload.length > 0 ? (
@@ -736,9 +758,7 @@ const Forms = (props) => {
className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs" className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs"
onChange={(e) => handleFileInput(e)} onChange={(e) => handleFileInput(e)}
ref={inputFileRef} ref={inputFileRef}
accept={ accept={"application/pdf,image/png,image/jpeg"}
"application/pdf,image/png,image/jpeg"
}
onInvalid={(e) => onInvalid={(e) =>
e.target.setCustomValidity(t("input-required")) e.target.setCustomValidity(t("input-required"))
} }
@@ -1028,10 +1048,7 @@ const Forms = (props) => {
{isAdvanceOpt && ( {isAdvanceOpt && (
<div <div
style={{ style={{
height: height: props.title === "New Template" ? "100px" : "280px"
props.title === "New Template"
? "100px"
: "280px"
}} }}
className="w-[1px] bg-gray-300 m-auto hidden md:inline-block" className="w-[1px] bg-gray-300 m-auto hidden md:inline-block"
></div> ></div>
@@ -1140,11 +1157,7 @@ const Forms = (props) => {
</Tooltip> </Tooltip>
</label> </label>
<div className="flex flex-col md:flex-row md:gap-4"> <div className="flex flex-col md:flex-row md:gap-4">
<div <div className={`flex items-center gap-2 ml-2 mb-1`}>
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input <input
className="mr-[2px] op-radio op-radio-xs" className="mr-[2px] op-radio op-radio-xs"
type="radio" type="radio"
@@ -1153,11 +1166,7 @@ const Forms = (props) => {
/> />
<div className="text-center">{t("yes")}</div> <div className="text-center">{t("yes")}</div>
</div> </div>
<div <div className={`flex items-center gap-2 ml-2 mb-1`}>
className={
`flex items-center gap-2 ml-2 mb-1`
}
>
<input <input
className="mr-[2px] op-radio op-radio-xs" className="mr-[2px] op-radio op-radio-xs"
type="radio" type="radio"
+65 -80
View File
@@ -68,8 +68,8 @@ import AddContact from "../primitives/AddContact";
function PlaceHolderSign() { function PlaceHolderSign() {
const { t } = useTranslation(); const { t } = useTranslation();
const appName = const copyUrlRef = useRef(null);
"OpenSign™"; const appName = "OpenSign™";
const editorRef = useRef(); const editorRef = useRef();
const { state } = useLocation(); const { state } = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -863,11 +863,7 @@ function PlaceHolderSign() {
scale scale
); );
const pdfName = generatePdfName(16); const pdfName = generatePdfName(16);
const pdfUrl = await convertBase64ToFile( const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64, "");
pdfName,
pdfBase64,
"",
);
const tenantId = localStorage.getItem("TenantId"); const tenantId = localStorage.getItem("TenantId");
const buffer = atob(pdfBase64); const buffer = atob(pdfBase64);
SaveFileSize(buffer.length, pdfUrl, tenantId); SaveFileSize(buffer.length, pdfUrl, tenantId);
@@ -879,11 +875,7 @@ function PlaceHolderSign() {
} else if (pdfBase64Url) { } else if (pdfBase64Url) {
try { try {
const pdfName = generatePdfName(16); const pdfName = generatePdfName(16);
const pdfUrl = await convertBase64ToFile( const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
pdfName,
pdfBase64Url,
"",
);
return pdfUrl; return pdfUrl;
} catch (err) { } catch (err) {
console.log("error to convertBase64ToFile in placeholder flow", err); console.log("error to convertBase64ToFile in placeholder flow", err);
@@ -989,11 +981,7 @@ function PlaceHolderSign() {
let pdfUrl; let pdfUrl;
if (isUploadPdf) { if (isUploadPdf) {
const pdfName = generatePdfName(16); const pdfName = generatePdfName(16);
pdfUrl = await convertBase64ToFile( pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
pdfName,
pdfBase64Url,
"",
);
} }
try { try {
const docCls = new Parse.Object("contracts_Document"); const docCls = new Parse.Object("contracts_Document");
@@ -1091,6 +1079,9 @@ function PlaceHolderSign() {
const copytoclipboard = (text) => { const copytoclipboard = (text) => {
copytoData(text); copytoData(text);
if (copyUrlRef.current) {
copyUrlRef.current.textContent = text; // Update text safely
}
setCopied(true); setCopied(true);
setTimeout(() => setCopied(false), 1500); // Reset copied state after 1.5 seconds setTimeout(() => setCopied(false), 1500); // Reset copied state after 1.5 seconds
}; };
@@ -1149,8 +1140,7 @@ function PlaceHolderSign() {
year: "numeric" year: "numeric"
}); });
let senderEmail = let senderEmail = pdfDetails?.[0]?.ExtUserPtr?.Email;
pdfDetails?.[0]?.ExtUserPtr?.Email;
let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone; let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone;
let signerMail = signersdata.slice(); let signerMail = signersdata.slice();
@@ -1176,16 +1166,11 @@ function PlaceHolderSign() {
const orgName = pdfDetails[0]?.ExtUserPtr.Company const orgName = pdfDetails[0]?.ExtUserPtr.Company
? pdfDetails[0].ExtUserPtr.Company ? pdfDetails[0].ExtUserPtr.Company
: ""; : "";
const senderName = const senderName = pdfDetails?.[0].ExtUserPtr.Name;
pdfDetails?.[0].ExtUserPtr.Name;
const documentName = `${pdfDetails?.[0].Name}`; const documentName = `${pdfDetails?.[0].Name}`;
let replaceVar; let replaceVar;
if ( if (requestBody && requestSubject && isCustomize) {
requestBody &&
requestSubject &&
isCustomize
) {
const replacedRequestBody = requestBody.replace(/"/g, "'"); const replacedRequestBody = requestBody.replace(/"/g, "'");
htmlReqBody = htmlReqBody =
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" + "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
@@ -1209,10 +1194,7 @@ function PlaceHolderSign() {
htmlReqBody, htmlReqBody,
variables variables
); );
} else if ( } else if (tenantMailTemplate?.body && tenantMailTemplate?.subject) {
tenantMailTemplate?.body &&
tenantMailTemplate?.subject
) {
const mailBody = tenantMailTemplate?.body; const mailBody = tenantMailTemplate?.body;
const mailSubject = tenantMailTemplate?.subject; const mailSubject = tenantMailTemplate?.subject;
const replacedRequestBody = mailBody.replace(/"/g, "'"); const replacedRequestBody = mailBody.replace(/"/g, "'");
@@ -1249,8 +1231,7 @@ function PlaceHolderSign() {
? replaceVar?.subject ? replaceVar?.subject
: mailTemplate(mailparam).subject, : mailTemplate(mailparam).subject,
replyto: senderEmail, replyto: senderEmail,
from: from: senderEmail,
senderEmail,
html: replaceVar?.body html: replaceVar?.body
? replaceVar?.body ? replaceVar?.body
: mailTemplate(mailparam).body : mailTemplate(mailparam).body
@@ -1265,20 +1246,13 @@ function PlaceHolderSign() {
setMailStatus("success"); setMailStatus("success");
try { try {
let data; let data;
if ( if (requestBody && requestSubject && isCustomize) {
requestBody &&
requestSubject &&
isCustomize
) {
data = { data = {
RequestBody: htmlReqBody, RequestBody: htmlReqBody,
RequestSubject: requestSubject, RequestSubject: requestSubject,
SendMail: true SendMail: true
}; };
} else if ( } else if (tenantMailTemplate?.body && tenantMailTemplate?.subject) {
tenantMailTemplate?.body &&
tenantMailTemplate?.subject
) {
data = { data = {
RequestBody: tenantMailTemplate?.body, RequestBody: tenantMailTemplate?.body,
RequestSubject: tenantMailTemplate?.subject, RequestSubject: tenantMailTemplate?.subject,
@@ -1311,8 +1285,7 @@ function PlaceHolderSign() {
setIsMailSend(true); setIsMailSend(true);
setIsLoading({ isLoad: false }); setIsLoading({ isLoad: false });
setIsUiLoading(false); setIsUiLoading(false);
} } else if (sendMail?.data?.result?.status === "quota-reached") {
else if (sendMail?.data?.result?.status === "quota-reached") {
setMailStatus("quotareached"); setMailStatus("quotareached");
setIsSend(true); setIsSend(true);
setIsMailSend(true); setIsMailSend(true);
@@ -1558,8 +1531,7 @@ function PlaceHolderSign() {
status: defaultdata?.status || "required", status: defaultdata?.status || "required",
hint: defaultdata?.hint || "", hint: defaultdata?.hint || "",
defaultValue: defaultdata?.defaultValue || "", defaultValue: defaultdata?.defaultValue || "",
validation: validation: {},
{},
fontSize: fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor: fontColor:
@@ -1569,6 +1541,15 @@ function PlaceHolderSign() {
isReadOnly: defaultdata?.isReadOnly || false isReadOnly: defaultdata?.isReadOnly || false
} }
}; };
} else if (["signature"].includes(position.type)) {
return {
...position,
options: {
...position.options,
name: defaultdata.name,
hint: defaultdata?.hint || ""
}
};
} else { } else {
return { return {
...position, ...position,
@@ -1577,6 +1558,7 @@ function PlaceHolderSign() {
name: defaultdata.name, name: defaultdata.name,
status: defaultdata.status, status: defaultdata.status,
defaultValue: defaultdata.defaultValue, defaultValue: defaultdata.defaultValue,
hint: defaultdata?.hint || "",
fontSize: fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor: fontColor:
@@ -2024,30 +2006,26 @@ function PlaceHolderSign() {
{!isCustomize && ( {!isCustomize && (
<span>{t("placeholder-alert-3")}</span> <span>{t("placeholder-alert-3")}</span>
)} )}
{ {isCustomize && (
isCustomize && ( <>
<> <EmailBody
<EmailBody editorRef={editorRef}
editorRef={editorRef} requestBody={requestBody}
requestBody={requestBody} requestSubject={requestSubject}
requestSubject={requestSubject} handleOnchangeRequest={handleOnchangeRequest}
handleOnchangeRequest={ setRequestSubject={setRequestSubject}
handleOnchangeRequest />
} <div
setRequestSubject={setRequestSubject} className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary"
/> onClick={() => {
<div setRequestBody(defaultBody);
className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary" setRequestSubject(defaultSubject);
onClick={() => { }}
setRequestBody(defaultBody); >
setRequestSubject(defaultSubject); <span>{t("reset-to-default")}</span>
}} </div>
> </>
<span>{t("reset-to-default")}</span> )}
</div>
</>
)
}
<div className="flex flex-row items-center gap-2 md:gap-6 mt-2"> <div className="flex flex-row items-center gap-2 md:gap-6 mt-2">
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<button <button
@@ -2065,16 +2043,14 @@ function PlaceHolderSign() {
</button> </button>
)} )}
</div> </div>
{ {!isCustomize && (
!isCustomize && ( <span
<span className="op-link op-link-accent text-sm"
className="op-link op-link-accent text-sm" onClick={() => setIsCustomize(!isCustomize)}
onClick={() => setIsCustomize(!isCustomize)} >
> {t("cutomize-email")}
{t("cutomize-email")} </span>
</span> )}
)
}
</div> </div>
</> </>
)} )}
@@ -2086,6 +2062,11 @@ function PlaceHolderSign() {
<span className="h-[1px] w-[20%] bg-[#ccc]"></span> <span className="h-[1px] w-[20%] bg-[#ccc]"></span>
</div> </div>
<div className="my-3">{handleShareList()}</div> <div className="my-3">{handleShareList()}</div>
<p
id="copyUrl"
ref={copyUrlRef}
className="hidden"
></p>
</> </>
)} )}
</div> </div>
@@ -2127,7 +2108,11 @@ function PlaceHolderSign() {
</div> </div>
) : ( ) : (
<div className="mb-[10px]"> <div className="mb-[10px]">
<p>{t("placeholder-alert-6")}</p> {mailStatus === "dailyquotareached" ? (
<p>{t("daily-quota-reached")}</p>
) : (
<p>{t("placeholder-alert-6")}</p>
)}
{isCurrUser && ( {isCurrUser && (
<p className="mt-1">{t("placeholder-alert-5")}</p> <p className="mt-1">{t("placeholder-alert-5")}</p>
)} )}
+296 -145
View File
@@ -22,8 +22,7 @@ import EditorToolbar, {
} from "../components/pdf/EditorToolbar"; } from "../components/pdf/EditorToolbar";
const Preferences = () => { const Preferences = () => {
const appName = const appName = "OpenSign™";
"OpenSign™";
const { t } = useTranslation(); const { t } = useTranslation();
const editorRef = useRef(); const editorRef = useRef();
const editorRefCom = useRef(); const editorRefCom = useRef();
@@ -46,6 +45,8 @@ const Preferences = () => {
const [activeTab, setactiveTab] = useState(0); const [activeTab, setactiveTab] = useState(0);
const generaltab = { title: t("general"), icon: "fa-light fa-gears" }; const generaltab = { title: t("general"), icon: "fa-light fa-gears" };
const [tab, setTab] = useState([generaltab]); const [tab, setTab] = useState([generaltab]);
const [sendinOrder, setSendinOrder] = useState(true);
const [isTourEnabled, setIsTourEnabled] = useState(false);
useEffect(() => { useEffect(() => {
fetchSignType(); fetchSignType();
@@ -90,6 +91,14 @@ const Preferences = () => {
const SignatureType = _getUser?.SignatureType || signatureTypes; const SignatureType = _getUser?.SignatureType || signatureTypes;
setSignatureType(SignatureType); setSignatureType(SignatureType);
} }
const sendinorder =
_getUser?.SendinOrder !== undefined ? _getUser?.SendinOrder : true;
setSendinOrder(sendinorder);
const istourenabled =
_getUser?.IsTourEnabled !== undefined
? _getUser?.IsTourEnabled
: true;
setIsTourEnabled(istourenabled);
} }
} catch (err) { } catch (err) {
console.log("err while getting user details", err); console.log("err while getting user details", err);
@@ -142,6 +151,11 @@ const Preferences = () => {
params = { ...params, NotifyOnSignatures: isNotifyOnSignatures }; params = { ...params, NotifyOnSignatures: isNotifyOnSignatures };
} }
try { try {
params = {
...params,
SendinOrder: sendinOrder,
IsTourEnabled: isTourEnabled
};
const updateRes = await Parse.Cloud.run("updatepreferences", params); const updateRes = await Parse.Cloud.run("updatepreferences", params);
if (updateRes) { if (updateRes) {
setIsAlert({ type: "success", msg: "Saved successfully." }); setIsAlert({ type: "success", msg: "Saved successfully." });
@@ -150,6 +164,8 @@ const Preferences = () => {
JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (extUser && extUser?.objectId) { if (extUser && extUser?.objectId) {
extUser.NotifyOnSignatures = isNotifyOnSignatures; extUser.NotifyOnSignatures = isNotifyOnSignatures;
extUser.SendinOrder = sendinOrder;
extUser.IsTourEnabled = isTourEnabled;
const _extUser = JSON.parse(JSON.stringify(extUser)); const _extUser = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("Extand_Class", JSON.stringify([_extUser])); localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
} }
@@ -285,6 +301,9 @@ const Preferences = () => {
} }
}; };
const handleTourInput = () => setIsTourEnabled(!isTourEnabled);
const handleSendinOrderInput = () => setSendinOrder(!sendinOrder);
return ( return (
<React.Fragment> <React.Fragment>
<Title title={t("Preferences")} /> <Title title={t("Preferences")} />
@@ -302,7 +321,7 @@ const Preferences = () => {
) : ( ) : (
<div className="relative bg-base-100 text-base-content flex flex-col justify-center shadow-md rounded-box mb-3"> <div className="relative bg-base-100 text-base-content flex flex-col justify-center shadow-md rounded-box mb-3">
{isLoader && ( {isLoader && (
<div className="flex justify-center items-center absolute w-full h-full rounded-box bg-black/30"> <div className="flex z-[100] justify-center items-center absolute w-full h-full rounded-box bg-black/30">
<Loader /> <Loader />
</div> </div>
)} )}
@@ -322,7 +341,9 @@ const Preferences = () => {
onClick={() => setactiveTab(ind)} onClick={() => setactiveTab(ind)}
key={ind} key={ind}
role="tab" role="tab"
className={`${activeTab === ind ? "op-tab-active" : ""} op-tab text-xs md:text-base pb-2 md:pb-0 }`} className={`${
activeTab === ind ? "op-tab-active" : ""
} op-tab text-xs md:text-base pb-2 md:pb-0 }`}
> >
<i className={tabData.icon}></i> <i className={tabData.icon}></i>
<span className="ml-1 md:ml-2">{tabData.title}</span> <span className="ml-1 md:ml-2">{tabData.title}</span>
@@ -332,154 +353,272 @@ const Preferences = () => {
</div> </div>
<div className="flex justify-center md:justify-start mt-3 md:mt-4 break-all"> <div className="flex justify-center md:justify-start mt-3 md:mt-4 break-all">
{activeTab === 0 ? ( {activeTab === 0 ? (
<> <div className="ml-4 mt-1 mb-2 flex flex-col">
<div className="ml-4 mt-1 mb-2 flex flex-col"> <div className="mb-[0.75rem]">
<div className="mb-[0.75rem]"> <label
<label className="mb-[0.7rem] text-[12px]"
htmlFor="signaturetype" htmlFor="signaturetype"
className="text-[14px] mb-[0.7rem] font-medium" >
> <span className="font-medium text-[14px]">
{t("allowed-signature-types")} {t("allowed-signature-types")}
<a </span>
data-tooltip-id="signtypes-tooltip" <a data-tooltip-id="signtypes-tooltip" className="ml-1">
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> </sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i> </a>
</sup> <ReactTooltip
</a> id="signtypes-tooltip"
<ReactTooltip className="z-[999]"
id="signtypes-tooltip" >
className="z-[999]" <div className="max-w-[200px] md:max-w-[450px]">
> <p className="font-bold">
<div className="max-w-[200px] md:max-w-[450px] text-[11px]"> {t("allowed-signature-types")}
<p className="font-bold"> </p>
{t("allowed-signature-types")} <p>{t("allowed-signature-types-help.p1")}</p>
</p> <p className="p-[5px] ml-2">
<p>{t("allowed-signature-types-help.p1")}</p> <ol className="list-disc">
<p className="p-[5px] ml-2"> <li>
<ol className="list-disc"> <span className="font-bold">Draw: </span>
<li> <span>
<span className="font-bold">Draw: </span> {t("allowed-signature-types-help.l1")}
<span> </span>
{t("allowed-signature-types-help.l1")} </li>
</span> <li>
</li> <span className="font-bold">Type: </span>
<li> <span>
<span className="font-bold">Type: </span> {t("allowed-signature-types-help.l2")}
<span> </span>
{t("allowed-signature-types-help.l2")} </li>
</span> <li>
</li> <span className="font-bold">Upload: </span>
<li> <span>
<span className="font-bold">Upload: </span> {t("allowed-signature-types-help.l3")}
<span> </span>
{t("allowed-signature-types-help.l3")} </li>
</span> <li>
</li> <span className="font-bold">Default: </span>
<li> <span>
<span className="font-bold">Default: </span> {t("allowed-signature-types-help.l4")}
<span> </span>
{t("allowed-signature-types-help.l4")} </li>
</span> </ol>
</li> </p>
</ol> </div>
</p> </ReactTooltip>
</div> </label>
</ReactTooltip> <div className="ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
</label> {signatureType.map((type, i) => (
<div className=" ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
{signatureType.map((type, i) => (
<div
key={i}
className="flex flex-row gap-[5px] items-center"
>
<input
className="mr-[2px] op-checkbox op-checkbox-xs"
type="checkbox"
name="signaturetype"
onChange={() => handleCheckboxChange(i)}
checked={type.enabled}
/>
<div
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default capitalize"
title={`Enabling this allow signers to ${type.name} signature`}
>
{type?.name === "typed" ? "type" : type?.name}
</div>
</div>
))}
</div>
</div>
<div className="mb-[0.75rem]">
<label className="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>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div <div
className={ key={i}
"flex items-center gap-2 ml-2 mb-1" className="flex flex-row gap-[5px] items-center"
}
> >
<input <input
className="mr-[2px] op-radio op-radio-xs" className="mr-[2px] op-checkbox op-checkbox-xs"
type="radio" type="checkbox"
onChange={() => handleNotifySignChange(true)} name="signaturetype"
checked={isNotifyOnSignatures === true} onChange={() => handleCheckboxChange(i)}
checked={type.enabled}
/> />
<div className="text-[13px] font-medium cursor-default capitalize"> <div
{t("yes")} className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default capitalize"
title={`Enabling this allow signers to ${type.name} signature`}
>
{type?.name === "typed" ? "type" : type?.name}
</div> </div>
</div> </div>
<div className="flex flex-col md:flex-row md:gap-4"> ))}
<div </div>
className={ </div>
"flex items-center gap-2 ml-2 mb-1" <div className="mb-[0.75rem]">
} <label className="mb-[0.7rem] text-[12px]">
> <span className="text-[14px] font-medium">
<input {t("notify-on-signatures")}
className="mr-[2px] op-radio op-radio-xs" </span>
type="radio" <a data-tooltip-id="nos-tooltip" className="ml-1">
onChange={() => handleNotifySignChange(false)} <sup>
checked={isNotifyOnSignatures === false} <i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
/> </sup>
<div className="text-[13px] font-medium cursor-default capitalize"> </a>
{t("no")} <ReactTooltip id="nos-tooltip" className="z-[999]">
</div> <div className="max-w-[200px] md:max-w-[450px]">
</div> <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>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className={"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] cursor-default capitalize">
{t("yes")}
</div>
</div>
<div className={"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] cursor-default capitalize">
{t("no")}
</div> </div>
</div> </div>
</div> </div>
<div className="mb-[0.75rem]"> </div>
<TimezoneSelector <div className="mb-[0.75rem]">
timezone={timezone} <TimezoneSelector
setTimezone={setTimezone} timezone={timezone}
/> setTimezone={setTimezone}
</div> />
<div className="mb-[0.75rem]"> </div>
<button <div className="mb-[0.75rem] text-[12px]">
className="op-btn op-btn-primary" <label className="block mb-[0.7rem]">
onClick={handleSave} <span className="text-[14px] font-medium">
{t("send-in-order")}
</span>
<a
data-tooltip-id="sendInOrder-tooltip"
className="ml-1"
> >
{t("save")} <sup>
</button> <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="sendInOrder-tooltip"
className="z-[999]"
>
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("send-in-order")}</p>
<p>{t("send-in-order-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
{t("yes")}:{" "}
</span>
<span>{t("send-in-order-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("send-in-order-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("send-in-order-help.p4")}</p>
</div>
</ReactTooltip>
</label>
<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="SendinOrder"
checked={sendinOrder}
onChange={handleSendinOrderInput}
/>
<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="SendinOrder"
className="op-radio op-radio-xs"
checked={!sendinOrder}
onChange={handleSendinOrderInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div> </div>
</div> </div>
</> <div className="mb-[0.75rem] text-[12px]">
<label className="block mb-[0.7rem]">
<span className="text-[14px] font-medium">
{t("enable-tour")}
</span>
<a
data-tooltip-id="istourenabled-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="istourenabled-tooltip"
className="z-[999]"
>
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("enable-tour")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">
{t("yes")}:{" "}
</span>
<span>{t("istourenabled-help.p1")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("istourenabled-help.p2")}</span>
</li>
</ol>
</p>
<p>
{t("istourenabled-help.p3", {
appName: appName
})}
</p>
</div>
</ReactTooltip>
</label>
<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={isTourEnabled}
onChange={handleTourInput}
/>
<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={!isTourEnabled}
onChange={handleTourInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
</div>
<div className="mb-[0.75rem]">
<button
className="op-btn op-btn-primary w-[110px]"
onClick={handleSave}
>
{t("save")}
</button>
</div>
</div>
) : ( ) : (
<div className="flex flex-col mx-4 mb-4"> <div className="flex flex-col mx-4 mb-4">
<div className="flex flex-col"> <div className="flex flex-col">
@@ -496,7 +635,9 @@ const Preferences = () => {
{t("subject")} {t("subject")}
<Tooltip <Tooltip
id={"request-sub-tooltip"} id={"request-sub-tooltip"}
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`} message={`${t(
"variables-use"
)}: {{sender_name}} {{document_title}}`}
/> />
</label> </label>
<input <input
@@ -505,7 +646,9 @@ const Preferences = () => {
onChange={(e) => onChange={(e) =>
setRequestSubject(e.target.value) setRequestSubject(e.target.value)
} }
placeholder={`{{sender_name}} ${t("send-to-sign")} {{document_title}}`} placeholder={`{{sender_name}} ${t(
"send-to-sign"
)} {{document_title}}`}
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs" className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
/> />
</div> </div>
@@ -514,7 +657,9 @@ const Preferences = () => {
{t("body")} {t("body")}
<Tooltip <Tooltip
id={"request-body-tooltip"} id={"request-body-tooltip"}
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`} message={`${t(
"variables-use"
)}: {{sender_name}} {{document_title}}`}
/> />
</label> </label>
<EditorToolbar containerId="toolbar1" /> <EditorToolbar containerId="toolbar1" />
@@ -560,7 +705,9 @@ const Preferences = () => {
{t("subject")} {t("subject")}
<Tooltip <Tooltip
id={"complete-sub-tooltip"} id={"complete-sub-tooltip"}
message={`${t("variables-use")}:{{sender_name}} {{document_title}}`} message={`${t(
"variables-use"
)}:{{sender_name}} {{document_title}}`}
/> />
</label> </label>
<input <input
@@ -569,7 +716,9 @@ const Preferences = () => {
onChange={(e) => onChange={(e) =>
setCompletionSubject(e.target.value) setCompletionSubject(e.target.value)
} }
placeholder={`{{sender_name}} ${t("send-to-sign")} {{document_title}}`} placeholder={`{{sender_name}} ${t(
"send-to-sign"
)} {{document_title}}`}
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs" className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
/> />
</div> </div>
@@ -578,7 +727,9 @@ const Preferences = () => {
{t("body")} {t("body")}
<Tooltip <Tooltip
id={"complete-body-tooltip"} id={"complete-body-tooltip"}
message={`${t("variables-use")}:{{sender_name}} {{document_title}} {{signing_url}}`} message={`${t(
"variables-use"
)}:{{sender_name}} {{document_title}} {{signing_url}}`}
/> />
</label> </label>
<EditorToolbar containerId="toolbar2" /> <EditorToolbar containerId="toolbar2" />
+5 -18
View File
@@ -66,8 +66,7 @@ import LoaderWithMsg from "../primitives/LoaderWithMsg";
function SignYourSelf() { function SignYourSelf() {
const { t } = useTranslation(); const { t } = useTranslation();
const { docId } = useParams(); const { docId } = useParams();
const appName = const appName = "OpenSign™";
"OpenSign™";
const divRef = useRef(null); const divRef = useRef(null);
const nodeRef = useRef(null); const nodeRef = useRef(null);
const imageRef = useRef(null); const imageRef = useRef(null);
@@ -124,7 +123,6 @@ function SignYourSelf() {
const [pdfLoad, setPdfLoad] = useState(false); const [pdfLoad, setPdfLoad] = useState(false);
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" }); const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isDontShow, setIsDontShow] = useState(false); const [isDontShow, setIsDontShow] = useState(false);
const [extUserId, setExtUserId] = useState("");
const [isCompleted, setIsCompleted] = useState(false); const [isCompleted, setIsCompleted] = useState(false);
const [isCelebration, setIsCelebration] = useState(false); const [isCelebration, setIsCelebration] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState(""); const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
@@ -207,7 +205,6 @@ function SignYourSelf() {
if (documentData && documentData.length > 0) { if (documentData && documentData.length > 0) {
setPdfDetails(documentData); setPdfDetails(documentData);
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
const placeholders = const placeholders =
documentData[0]?.Placeholders?.length > 0 documentData[0]?.Placeholders?.length > 0
? documentData[0]?.Placeholders ? documentData[0]?.Placeholders
@@ -564,11 +561,7 @@ function SignYourSelf() {
let pdfUrl; let pdfUrl;
if (isUploadPdf) { if (isUploadPdf) {
const pdfName = generatePdfName(16); const pdfName = generatePdfName(16);
pdfUrl = await convertBase64ToFile( pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
pdfName,
pdfBase64Url,
"",
);
} }
const widgetsType = ["signature", "stamp", "image", "initials"]; const widgetsType = ["signature", "stamp", "image", "initials"];
let updatedXYPosition; let updatedXYPosition;
@@ -754,10 +747,7 @@ function SignYourSelf() {
if (tenantDetails && tenantDetails === "user does not exist!") { if (tenantDetails && tenantDetails === "user does not exist!") {
alert(t("user-not-exist")); alert(t("user-not-exist"));
} else { } else {
if ( if (tenantDetails?.CompletionBody && tenantDetails?.CompletionSubject) {
tenantDetails?.CompletionBody &&
tenantDetails?.CompletionSubject
) {
isCustomCompletionMail = true; isCustomCompletionMail = true;
} }
} }
@@ -793,7 +783,7 @@ function SignYourSelf() {
pdfFile: base64Url, pdfFile: base64Url,
docId: documentId, docId: documentId,
isCustomCompletionMail: isCustomCompletionMail, isCustomCompletionMail: isCustomCompletionMail,
signature: suffixbase64, signature: suffixbase64
}; };
const resSignPdf = await Parse.Cloud.run("signPdf", params); const resSignPdf = await Parse.Cloud.run("signPdf", params);
if (resSignPdf) { if (resSignPdf) {
@@ -1371,13 +1361,10 @@ function SignYourSelf() {
{/*render email component to send email after finish signature on document */} {/*render email component to send email after finish signature on document */}
<EmailComponent <EmailComponent
isEmail={isEmail} isEmail={isEmail}
pdfUrl={pdfUrl}
setIsEmail={setIsEmail} setIsEmail={setIsEmail}
pdfDetails={pdfDetails}
setSuccessEmail={setSuccessEmail} setSuccessEmail={setSuccessEmail}
sender={jsonSender} pdfDetails={pdfDetails}
setIsAlert={setIsAlert} setIsAlert={setIsAlert}
extUserId={extUserId}
setIsDownloadModal={setIsDownloadModal} setIsDownloadModal={setIsDownloadModal}
/> />
{/* pdf header which contain funish back button */} {/* pdf header which contain funish back button */}
+23 -19
View File
@@ -165,7 +165,6 @@ const TemplatePlaceholder = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current, isHeader]); }, [divRef.current, isHeader]);
//function to fetch tenant Details //function to fetch tenant Details
const fetchTenantDetails = async () => { const fetchTenantDetails = async () => {
const user = JSON.parse( const user = JSON.parse(
@@ -778,11 +777,7 @@ const TemplatePlaceholder = () => {
let pdfUrl; let pdfUrl;
if (isUploadPdf) { if (isUploadPdf) {
const pdfName = generatePdfName(16); const pdfName = generatePdfName(16);
pdfUrl = await convertBase64ToFile( pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
pdfName,
pdfBase64Url,
"",
);
} }
if (signersdata?.length > 0) { if (signersdata?.length > 0) {
signersdata.forEach((x) => { signersdata.forEach((x) => {
@@ -849,11 +844,7 @@ const TemplatePlaceholder = () => {
scale scale
); );
const pdfName = generatePdfName(16); const pdfName = generatePdfName(16);
const pdfUrl = await convertBase64ToFile( const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64, "");
pdfName,
pdfBase64,
"",
);
const tenantId = localStorage.getItem("TenantId"); const tenantId = localStorage.getItem("TenantId");
const buffer = atob(pdfBase64); const buffer = atob(pdfBase64);
SaveFileSize(buffer.length, pdfUrl, tenantId); SaveFileSize(buffer.length, pdfUrl, tenantId);
@@ -865,11 +856,7 @@ const TemplatePlaceholder = () => {
} else if (pdfBase64Url) { } else if (pdfBase64Url) {
try { try {
const pdfName = generatePdfName(16); const pdfName = generatePdfName(16);
const pdfUrl = await convertBase64ToFile( const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
pdfName,
pdfBase64Url,
"",
);
return pdfUrl; return pdfUrl;
} catch (err) { } catch (err) {
console.log("error to convertBase64ToFile in placeholder flow", err); console.log("error to convertBase64ToFile in placeholder flow", err);
@@ -881,6 +868,14 @@ const TemplatePlaceholder = () => {
}; };
const handleSaveTemplate = async () => { const handleSaveTemplate = async () => {
if (signersdata?.length) { if (signersdata?.length) {
const remindOnceInEvery = parseInt(pdfDetails[0]?.RemindOnceInEvery);
const TimeToCompleteDays = parseInt(pdfDetails[0]?.TimeToCompleteDays);
const AutomaticReminders = pdfDetails[0]?.AutomaticReminders;
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
if (AutomaticReminders && reminderCount > 15) {
alert(t("only-15-reminder-allowed"));
return;
}
setIsLoading({ isLoad: true, message: t("loading-mssg") }); setIsLoading({ isLoad: true, message: t("loading-mssg") });
setIsSendAlert(false); setIsSendAlert(false);
let signers = [], let signers = [],
@@ -1086,7 +1081,7 @@ const TemplatePlaceholder = () => {
}); });
setIsCreateDoc(false); setIsCreateDoc(false);
} else { } else {
setHandleError(t("something-went-wrong-mssg")); setHandleError(t(res.id));
setIsCreateDoc(false); setIsCreateDoc(false);
} }
}; };
@@ -1461,8 +1456,7 @@ const TemplatePlaceholder = () => {
status: defaultdata?.status || "required", status: defaultdata?.status || "required",
hint: defaultdata?.hint || "", hint: defaultdata?.hint || "",
defaultValue: defaultdata?.defaultValue || "", defaultValue: defaultdata?.defaultValue || "",
validation: validation: {},
{},
fontSize: fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor: fontColor:
@@ -1471,6 +1465,15 @@ const TemplatePlaceholder = () => {
"black" "black"
} }
}; };
} else if (["signature"].includes(position.type)) {
return {
...position,
options: {
...position.options,
name: defaultdata.name,
hint: defaultdata?.hint || ""
}
};
} else { } else {
return { return {
...position, ...position,
@@ -1479,6 +1482,7 @@ const TemplatePlaceholder = () => {
name: defaultdata.name, name: defaultdata.name,
status: defaultdata.status, status: defaultdata.status,
defaultValue: defaultdata.defaultValue, defaultValue: defaultdata.defaultValue,
hint: defaultdata?.hint || "",
fontSize: fontSize:
fontSize || currWidgetsDetails?.options?.fontSize || 12, fontSize || currWidgetsDetails?.options?.fontSize || 12,
fontColor: fontColor:
+188 -197
View File
@@ -10,7 +10,6 @@ import AddUser from "../components/AddUser";
import Title from "../components/Title"; import Title from "../components/Title";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"]; const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"];
// const actions = [];
const UserList = () => { const UserList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [userList, setUserList] = useState([]); const [userList, setUserList] = useState([]);
@@ -114,9 +113,8 @@ const UserList = () => {
setUserList(_userRes); setUserList(_userRes);
} catch (err) { } catch (err) {
console.log("Err in fetch userlist", err); console.log("Err in fetch userlist", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") }); showAlert("danger", t("something-went-wrong-mssg"));
} finally { } finally {
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
setIsLoader(false); setIsLoader(false);
} }
} }
@@ -173,17 +171,15 @@ const UserList = () => {
extUser.id = user.objectId; extUser.id = user.objectId;
extUser.set("IsDisabled", !IsDisabled); extUser.set("IsDisabled", !IsDisabled);
await extUser.save(); await extUser.save();
setIsAlert({ showAlert(
type: !IsDisabled === true ? "danger" : "success", !IsDisabled === true ? "danger" : "success",
msg: !IsDisabled === true ? t("user-deactivated") : t("user-activated")
!IsDisabled === true ? t("user-deactivated") : t("user-activated") );
});
} catch (err) { } catch (err) {
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") }); showAlert("danger", t("something-went-wrong-mssg"));
console.log("err in disable team", err); console.log("err in disable team", err);
} finally { } finally {
setIsActLoader({}); setIsActLoader({});
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
} }
} }
}; };
@@ -191,6 +187,11 @@ const UserList = () => {
setIsActiveModal({ [user.objectId]: true }); setIsActiveModal({ [user.objectId]: true });
}; };
// `showAlert` handle show/hide alert
const showAlert = (type, msg) => {
setIsAlert({ type, msg });
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
};
return ( return (
<div className="relative"> <div className="relative">
<Title title={isAdmin ? "Users" : "Page not found"} /> <Title title={isAdmin ? "Users" : "Page not found"} />
@@ -205,197 +206,187 @@ const UserList = () => {
</div> </div>
)} )}
{ {!isLoader && (
!isLoader && ( <>
<> {isAdmin ? (
{isAdmin ? ( <div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg"> {isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
{isAlert.msg && ( <div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
<Alert type={isAlert.type}>{isAlert.msg}</Alert> <div className="font-light">
)} {t("report-name.Users")}{" "}
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]"> <span className="text-xs md:text-[13px] font-normal">
<div className="font-light"> <Tooltip message={t("users-from-teams")} />
{t("report-name.Users")}{" "} </span>
<span className="text-xs md:text-[13px] font-normal">
<Tooltip message={t("users-from-teams")} />
</span>
</div>
<div className="flex flex-row gap-2 items-center">
<div
className="cursor-pointer"
onClick={() => handleModal("form")}
>
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
</div>
</div>
</div>
<div className="overflow-x-auto w-full">
<table className="op-table border-collapse w-full">
<thead className="text-[14px]">
<tr className="border-y-[1px]">
{heading?.map((item, index) => (
<th key={index} className="px-4 py-2">
{t(`report-heading.${item}`)}
</th>
))}
</tr>
</thead>
<tbody className="text-[12px]">
{userList?.length > 0 && (
<>
{currentList.map((item, index) => (
<tr className="border-y-[1px]" key={index}>
{heading.includes("Sr.No") && (
<th className="px-4 py-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="px-4 py-2">
{item?.UserRole?.split("_").pop() || "-"}
</td>
<td className="px-4 py-2">
{formatRow(item.TeamIds)}
</td>
{item.UserRole !== "contracts_Admin" && (
<td className="px-4 py-2 font-semibold">
<label className="cursor-pointer relative block items-center mb-0">
<input
type="checkbox"
className="op-toggle transition-all op-toggle-secondary"
checked={item?.IsDisabled !== true}
onChange={() => handleToggleBtn(item)}
/>
</label>
{isActiveModal[item.objectId] && (
<ModalUi
isOpen
title={t("user-status")}
handleClose={handleClose}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("are-you-sure")}{" "}
{item?.IsDisabled
? t("activate")
: t("deactivate")}{" "}
{t("this-user")}?
</div>
<hr className="bg-[#ccc] mt-4 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() =>
handleToggleSubmit(item)
}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
)}
</td>
)}
</tr>
))}
</>
)}
</tbody>
</table>
</div>
<div className="flex flex-row justify-between items-center text-xs font-medium">
<div className="op-join flex flex-wrap items-center p-2">
{userList.length > recordperPage && (
<button
onClick={() => paginateBack()}
className="op-join-item op-btn op-btn-sm"
>
{t("prev")}
</button>
)}
{pageNumbers.map((x, i) => (
<button
key={i}
onClick={() => setCurrentPage(x)}
disabled={x === "..."}
className={`${
x === currentPage ? "op-btn-active" : ""
} op-join-item op-btn op-btn-sm`}
>
{x}
</button>
))}
{userList.length > recordperPage && (
<button
onClick={() => paginateFront()}
className="op-join-item op-btn op-btn-sm"
>
{t("next")}
</button>
)}
</div>
</div>
{userList?.length <= 0 && (
<div
className={`${
isDashboard ? "h-[317px]" : ""
} flex flex-col items-center justify-center w-ful bg-base-100 text-base-content rounded-xl py-4`}
>
<div className="w-[60px] h-[60px] overflow-hidden">
<img
className="w-full h-full object-contain"
src={pad}
alt="img"
/>
</div>
<div className="text-sm font-semibold">
{t("no-data-avaliable")}
</div>
</div>
)}
<ModalUi
isOpen={isModal.form}
title={formHeader}
handleClose={() => handleModal("form")}
>
<AddUser
setIsAlert={setIsAlert}
handleUserData={handleUserData}
closePopup={() => handleModal("form")}
setFormHeader={setFormHeader}
/>
</ModalUi>
</div> </div>
) : ( <div className="flex flex-row gap-2 items-center">
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box"> <div
<div className="text-center"> className="cursor-pointer"
<h1 className="text-[60px] lg:text-[120px] font-semibold"> onClick={() => handleModal("form")}
404 >
</h1> <i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
<p className="text-[30px] lg:text-[50px]"> </div>
{t("page-not-found")} </div>
</p> </div>
<div className="w-full overflow-x-auto">
<table className="op-table border-collapse w-full mb-[50px]">
<thead className="text-[14px]">
<tr className="border-y-[1px]">
{heading?.map((item, index) => (
<th key={index} className="px-4 py-2">
{t(`report-heading.${item}`)}
</th>
))}
</tr>
</thead>
{userList?.length > 0 && (
<tbody className="text-[12px]">
{currentList.map((item, index) => (
<tr className="border-y-[1px]" key={index}>
{heading.includes("Sr.No") && (
<th className="px-4 py-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="px-4 py-2">
{item?.UserRole?.split("_").pop() || "-"}
</td>
<td className="px-4 py-2">
{formatRow(item.TeamIds)}
</td>
{item.UserRole !== "contracts_Admin" ? (
<td className="px-4 py-2 font-semibold">
<label className="cursor-pointer relative block items-center mb-0">
<input
type="checkbox"
className="op-toggle transition-all op-toggle-secondary"
checked={item?.IsDisabled !== true}
onChange={() => handleToggleBtn(item)}
/>
</label>
{isActiveModal[item.objectId] && (
<ModalUi
isOpen
title={t("user-status")}
handleClose={handleClose}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-black">
{t("are-you-sure")}{" "}
{item?.IsDisabled
? t("activate")
: t("deactivate")}{" "}
{t("this-user")}?
</div>
<hr className="bg-[#ccc] mt-4 " />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={() => handleToggleSubmit(item)}
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={handleClose}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
)}
</td>
) : (
<td className="px-4 py-2 font-semibold"></td>
)}
</tr>
))}
</tbody>
)}
</table>
</div>
<div className="flex flex-row justify-between items-center text-xs font-medium">
<div className="op-join flex flex-wrap items-center p-2">
{userList.length > recordperPage && (
<button
onClick={() => paginateBack()}
className="op-join-item op-btn op-btn-sm"
>
{t("prev")}
</button>
)}
{pageNumbers.map((x, i) => (
<button
key={i}
onClick={() => setCurrentPage(x)}
disabled={x === "..."}
className={`${
x === currentPage ? "op-btn-active" : ""
} op-join-item op-btn op-btn-sm`}
>
{x}
</button>
))}
{userList.length > recordperPage && (
<button
onClick={() => paginateFront()}
className="op-join-item op-btn op-btn-sm"
>
{t("next")}
</button>
)}
</div>
</div>
{userList?.length <= 0 && (
<div
className={`${
isDashboard ? "h-[317px]" : ""
} flex flex-col items-center justify-center w-ful bg-base-100 text-base-content rounded-xl py-4`}
>
<div className="w-[60px] h-[60px] overflow-hidden">
<img
className="w-full h-full object-contain"
src={pad}
alt="img"
/>
</div>
<div className="text-sm font-semibold">
{t("no-data-avaliable")}
</div> </div>
</div> </div>
)} )}
</> <ModalUi
) isOpen={isModal.form}
} title={formHeader}
handleClose={() => handleModal("form")}
>
<AddUser
showAlert={showAlert}
handleUserData={handleUserData}
closePopup={() => handleModal("form")}
setFormHeader={setFormHeader}
/>
</ModalUi>
</div>
) : (
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
<div className="text-center">
<h1 className="text-[60px] lg:text-[120px] font-semibold">
404
</h1>
<p className="text-[30px] lg:text-[50px]">
{t("page-not-found")}
</p>
</div>
</div>
)}
</>
)}
</div> </div>
); );
}; };
@@ -1,12 +1,10 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useRef } from "react";
import pad from "../assets/images/pad.svg"; import pad from "../assets/images/pad.svg";
import { useLocation, useNavigate } from "react-router"; import { useLocation, useNavigate } from "react-router";
import axios from "axios"; import axios from "axios";
import ModalUi from "./ModalUi"; import ModalUi from "./ModalUi";
import AddSigner from "../components/AddSigner"; import AddSigner from "../components/AddSigner";
import { import { emailRegex } from "../constant/const";
emailRegex,
} from "../constant/const";
import Alert from "./Alert"; import Alert from "./Alert";
import Tooltip from "./Tooltip"; import Tooltip from "./Tooltip";
import { RWebShare } from "react-web-share"; import { RWebShare } from "react-web-share";
@@ -38,8 +36,8 @@ import * as XLSX from "xlsx";
import EditContactForm from "../components/EditContactForm"; import EditContactForm from "../components/EditContactForm";
const ReportTable = (props) => { const ReportTable = (props) => {
const appName = const copyUrlRef = useRef(null);
"OpenSign™"; const appName = "OpenSign™";
const drivename = appName === "OpenSign™" ? "OpenSign™" : ""; const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -156,14 +154,14 @@ const ReportTable = (props) => {
const teamtRes = await Parse.Cloud.run("getteams", { active: true }); const teamtRes = await Parse.Cloud.run("getteams", { active: true });
if (teamtRes.length > 0) { if (teamtRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamtRes)); const _teamRes = JSON.parse(JSON.stringify(teamtRes));
const selected = _teamRes.map( const selected = _teamRes.map(
(x) => (x) =>
x.Name === "All Users" && { x.Name === "All Users" && {
label: x.Name, label: x.Name,
value: x.objectId value: x.objectId
} }
); );
setSelectedTeam(selected); setSelectedTeam(selected);
} }
} }
} catch (err) { } catch (err) {
@@ -224,7 +222,7 @@ const ReportTable = (props) => {
const handleURL = async (item, act) => { const handleURL = async (item, act) => {
if (props.ReportName === "Templates") { if (props.ReportName === "Templates") {
if (act.hoverLabel === "Edit") { if (act.hoverLabel === "Edit") {
navigate(`/${act.redirectUrl}/${item.objectId}`); navigate(`/${act.redirectUrl}/${item.objectId}`);
} else { } else {
setActLoader({ [`${item.objectId}_${act.btnId}`]: true }); setActLoader({ [`${item.objectId}_${act.btnId}`]: true });
try { try {
@@ -410,8 +408,7 @@ const ReportTable = (props) => {
setSelectedTeam(formatedList); setSelectedTeam(formatedList);
} }
setIsShareWith({ [item.objectId]: true }); setIsShareWith({ [item.objectId]: true });
} } else if (act.action === "duplicate") {
else if (act.action === "duplicate") {
setIsModal({ [`duplicate_${item.objectId}`]: true }); setIsModal({ [`duplicate_${item.objectId}`]: true });
} else if (act.action === "rename") { } else if (act.action === "rename") {
setIsModal({ [`rename_${item.objectId}`]: true }); setIsModal({ [`rename_${item.objectId}`]: true });
@@ -495,8 +492,7 @@ const ReportTable = (props) => {
setActLoader({}); setActLoader({});
} }
}; };
const handleClose = ( const handleClose = () => {
) => {
setIsRevoke({}); setIsRevoke({});
setIsDeleteModal({}); setIsDeleteModal({});
setReason(""); setReason("");
@@ -527,6 +523,9 @@ const ReportTable = (props) => {
const copytoclipboard = (share) => { const copytoclipboard = (share) => {
copytoData(share.url); copytoData(share.url);
if (copyUrlRef.current) {
copyUrlRef.current.textContent = share.url; // Update text safely
}
setCopied({ ...copied, [share.email]: true }); setCopied({ ...copied, [share.email]: true });
}; };
//function to handle revoke/decline docment //function to handle revoke/decline docment
@@ -652,11 +651,7 @@ const ReportTable = (props) => {
if (isCompleted) { if (isCompleted) {
setIsDownloadModal({ [item.objectId]: true }); setIsDownloadModal({ [item.objectId]: true });
} else { } else {
const signedUrl = await getSignedUrl( const signedUrl = await getSignedUrl(url, docId, templateId);
url,
docId,
templateId
);
await fetchUrl(signedUrl, pdfName); await fetchUrl(signedUrl, pdfName);
} }
setActLoader({}); setActLoader({});
@@ -683,10 +678,8 @@ const ReportTable = (props) => {
const signPdf = `${window.location.origin}/login/${encodeBase64}`; const signPdf = `${window.location.origin}/login/${encodeBase64}`;
const variables = { const variables = {
document_title: doc.Name, document_title: doc.Name,
sender_name: sender_name: doc.ExtUserPtr.Name,
doc.ExtUserPtr.Name, sender_mail: doc.ExtUserPtr.Email,
sender_mail:
doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr?.Phone || "", sender_phone: doc.ExtUserPtr?.Phone || "",
receiver_name: userDetails?.Name || "", receiver_name: userDetails?.Name || "",
receiver_email: userDetails?.Email, receiver_email: userDetails?.Email,
@@ -714,10 +707,8 @@ const ReportTable = (props) => {
const signPdf = `${window.location.origin}/login/${encodeBase64}`; const signPdf = `${window.location.origin}/login/${encodeBase64}`;
const variables = { const variables = {
document_title: doc.Name, document_title: doc.Name,
sender_name: sender_name: doc.ExtUserPtr.Name,
doc.ExtUserPtr.Name, sender_mail: doc.ExtUserPtr.Email,
sender_mail:
doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr?.Phone || "", sender_phone: doc.ExtUserPtr?.Phone || "",
receiver_name: userDetails?.Name || "", receiver_name: userDetails?.Name || "",
receiver_email: userDetails?.Email || "", receiver_email: userDetails?.Email || "",
@@ -757,10 +748,8 @@ const ReportTable = (props) => {
const signPdf = `${window.location.origin}/login/${encodeBase64}`; const signPdf = `${window.location.origin}/login/${encodeBase64}`;
const variables = { const variables = {
document_title: doc.Name, document_title: doc.Name,
sender_name: sender_name: doc.ExtUserPtr.Name,
doc.ExtUserPtr.Name, sender_mail: doc.ExtUserPtr.Email,
sender_mail:
doc.ExtUserPtr.Email,
sender_phone: doc.ExtUserPtr?.Phone || "", sender_phone: doc.ExtUserPtr?.Phone || "",
receiver_name: user?.signerPtr?.Name || "", receiver_name: user?.signerPtr?.Name || "",
receiver_email: user?.email ? user?.email : user?.signerPtr?.Email, receiver_email: user?.email ? user?.email : user?.signerPtr?.Email,
@@ -790,14 +779,11 @@ const ReportTable = (props) => {
sessionToken: localStorage.getItem("accesstoken") sessionToken: localStorage.getItem("accesstoken")
}; };
let params = { let params = {
replyto: replyto: doc?.ExtUserPtr?.Email || "",
doc?.ExtUserPtr?.Email ||
"",
extUserId: doc?.ExtUserPtr?.objectId, extUserId: doc?.ExtUserPtr?.objectId,
recipient: userDetails?.Email, recipient: userDetails?.Email,
subject: mail.subject, subject: mail.subject,
from: from: doc?.ExtUserPtr?.Email,
doc?.ExtUserPtr?.Email,
html: mail.body html: mail.body
}; };
try { try {
@@ -806,8 +792,7 @@ const ReportTable = (props) => {
setIsAlert(true); setIsAlert(true);
setAlertMsg({ type: "success", message: t("mail-sent-alert") }); setAlertMsg({ type: "success", message: t("mail-sent-alert") });
setIsResendMail({}); setIsResendMail({});
} } else {
else {
setIsAlert(true); setIsAlert(true);
setAlertMsg({ setAlertMsg({
type: "danger", type: "danger",
@@ -1741,11 +1726,7 @@ const ReportTable = (props) => {
<i className={act.btnIcon}></i> <i className={act.btnIcon}></i>
{act.btnLabel && ( {act.btnLabel && (
<span className="uppercase font-medium"> <span className="uppercase font-medium">
{ {`${t(`btnLabel.${act.btnLabel}`)}`}
`${t(
`btnLabel.${act.btnLabel}`
)}`
}
</span> </span>
)} )}
{/* template report */} {/* template report */}
@@ -1825,7 +1806,9 @@ const ReportTable = (props) => {
title={t(`btnLabel.${act.hoverLabel}`)} title={t(`btnLabel.${act.hoverLabel}`)}
className={ className={
act.action !== "option" act.action !== "option"
? `${act?.btnColor || ""} op-btn op-btn-sm mr-1` ? `${
act?.btnColor || ""
} op-btn op-btn-sm mr-1`
: "text-base-content focus:outline-none text-lg mr-2 relative" : "text-base-content focus:outline-none text-lg mr-2 relative"
} }
> >
@@ -1912,28 +1895,26 @@ const ReportTable = (props) => {
{isShareWith[item.objectId] && ( {isShareWith[item.objectId] && (
<div className="op-modal op-modal-open"> <div className="op-modal op-modal-open">
<div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative"> <div className="max-h-90 bg-base-100 w-[95%] md:max-w-[500px] rounded-box relative">
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]"> <h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{t("share-with")} {t("share-with")}
</h3> </h3>
<div <div
className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40" className="op-btn op-btn-sm op-btn-circle op-btn-ghost text-base-content absolute right-2 top-2 z-40"
onClick={() => setIsShareWith({})} onClick={() => setIsShareWith({})}
> >
</div> </div>
<div className="px-2 mt-3 w-full h-full"> <div className="px-2 mt-3 w-full h-full">
<div className="op-input op-input-bordered op-input-sm w-full h-full text-[13px] break-all"> <div className="op-input op-input-bordered op-input-sm w-full h-full text-[13px] break-all">
{selectedTeam?.[0]?.label} {selectedTeam?.[0]?.label}
</div> </div>
</div> </div>
<button <button
onClick={(e) => onClick={(e) => handleShareWith(e, item)}
handleShareWith(e, item) className="op-btn op-btn-primary ml-[10px] my-3"
} >
className="op-btn op-btn-primary ml-[10px] my-3" {t("submit")}
> </button>
{t("submit")}
</button>
</div> </div>
</div> </div>
)} )}
@@ -2026,9 +2007,7 @@ const ReportTable = (props) => {
{isBulkSend[item.objectId] && ( {isBulkSend[item.objectId] && (
<ModalUi <ModalUi
isOpen isOpen
title={ title={t("quick-send")}
t("quick-send")
}
handleClose={() => setIsBulkSend({})} handleClose={() => setIsBulkSend({})}
> >
{isLoader[item.objectId] ? ( {isLoader[item.objectId] ? (
@@ -2088,6 +2067,11 @@ const ReportTable = (props) => {
</div> </div>
</div> </div>
))} ))}
<p
id="copyUrl"
ref={copyUrlRef}
className="hidden"
></p>
</div> </div>
</ModalUi> </ModalUi>
)} )}
+5 -1
View File
@@ -1,5 +1,9 @@
# Use an official Node runtime as the base image # Use an official Node runtime as the base image
FROM node:20 FROM node:22.14.0
# install java
RUN wget https://download.oracle.com/java/23/latest/jdk-23_linux-x64_bin.deb \
&& dpkg -i jdk-23_linux-x64_bin.deb
# Set the working directory inside the container # Set the working directory inside the container
WORKDIR /usr/src/app WORKDIR /usr/src/app
Binary file not shown.
+3 -132
View File
@@ -5,8 +5,7 @@ import { PDFDocument } from 'pdf-lib';
dotenv.config(); dotenv.config();
export const cloudServerUrl = 'http://localhost:8080/app'; export const cloudServerUrl = 'http://localhost:8080/app';
export const appName = export const appName = 'OpenSign™';
'OpenSign™';
export function customAPIurl() { export function customAPIurl() {
const url = new URL(cloudServerUrl); const url = new URL(cloudServerUrl);
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin; return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
@@ -139,131 +138,6 @@ export const updateMailCount = async (extUserId, plan, monthchange) => {
} }
}; };
export function formatWidgetOptions(type, options) {
const colorsArr = ['red', 'black', 'blue', 'yellow'];
const fontSizes = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28];
const status = options?.required === true ? 'required' : 'optional' || 'required';
const defaultValue = options?.default || '';
const values = options?.values || [];
const color = options?.color ? options.color : 'black';
const fontColor = colorsArr.includes(color) ? color : 'black';
const size = options?.fontsize ? parseInt(options.fontsize) : 12;
const fontSize = fontSizes.includes(size) ? size : 12;
switch (type) {
case 'signature':
return { name: 'signature', status: 'required' };
case 'stamp':
return { status: status, name: 'stamp' };
case 'initials':
return { status: status, name: options.name || 'initials' };
case 'image':
return { status: status, name: options.name || 'image' };
case 'email':
return {
status: status,
name: options.name || 'email',
validation: { type: 'email' },
fontColor: fontColor,
fontSize: fontSize,
};
case 'name':
return {
status: status,
name: options.name || 'name',
fontColor: fontColor,
fontSize: fontSize,
};
case 'job title':
return {
status: status,
name: options.name || 'job title',
fontColor: fontColor,
fontSize: fontSize,
};
case 'company':
return {
status: status,
name: options.name || 'company',
fontColor: fontColor,
fontSize: fontSize,
};
case 'date': {
let today = new Date();
let dd = String(today.getDate()).padStart(2, '0');
let mm = String(today.getMonth() + 1).padStart(2, '0'); // January is 0!
let yyyy = today.getFullYear();
today = dd + '-' + mm + '-' + yyyy;
let dateFormat = options?.format;
dateFormat = dateFormat.replace(/m/g, 'M');
return {
status: status,
name: options.name || 'date',
response: defaultValue || today,
validation: { format: dateFormat || 'dd-MM-yyyy', type: 'date-format' },
fontColor: fontColor,
fontSize: fontSize,
};
}
case 'textbox':
return {
status: status,
name: 'textbox',
defaultValue: defaultValue,
hint: options.hint,
validation: { type: 'regex', pattern: options?.regularexpression || '/^[a-zA-Z0-9s]+$/' },
fontColor: fontColor,
fontSize: fontSize,
isReadOnly: options?.readonly || false,
};
case 'checkbox': {
const arr = options?.values;
let selectedvalues = [];
for (const obj of options.selectedvalues) {
const index = arr.indexOf(obj);
selectedvalues.push(index);
}
return {
status: status,
name: options.name || 'checkbox',
values: values,
isReadOnly: options?.readonly || false,
isHideLabel: options?.hidelabel || false,
validation: {
minRequiredCount: options?.validation?.minselections || 0,
maxRequiredCount: options?.validation?.maxselections || 0,
},
defaultValue: selectedvalues || [],
fontColor: fontColor,
fontSize: fontSize,
};
}
case 'radio button': {
return {
status: status,
name: options.name || 'radio',
values: values,
isReadOnly: options?.readonly || false,
isHideLabel: options?.hidelabel || false,
defaultValue: defaultValue,
fontColor: fontColor,
fontSize: fontSize,
};
}
case 'dropdown':
return {
status: status,
name: options.name || 'dropdown',
values: values,
defaultValue: defaultValue,
fontColor: fontColor,
fontSize: fontSize,
isReadOnly: options?.readonly || false,
};
default:
break;
}
}
export function sanitizeFileName(fileName) { export function sanitizeFileName(fileName) {
// Remove spaces and invalid characters // Remove spaces and invalid characters
const file = fileName.replace(/[^a-zA-Z0-9._-]/g, ''); const file = fileName.replace(/[^a-zA-Z0-9._-]/g, '');
@@ -276,7 +150,6 @@ export const smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '46
export const smtpenable = export const smtpenable =
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false; process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false;
// `generateId` is used to unique Id for fileAdapter // `generateId` is used to unique Id for fileAdapter
export function generateId(length) { export function generateId(length) {
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
@@ -356,11 +229,9 @@ export const flattenPdf = async pdfFile => {
export const mailTemplate = param => { export const mailTemplate = param => {
const themeColor = '#47a3ad'; const themeColor = '#47a3ad';
const subject = `${param.senderName} has requested you to sign "${param.title}"`; const subject = `${param.senderName} has requested you to sign "${param.title}"`;
const logo = const logo = `<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' />`;
`<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' />`;
const opurl = const opurl = ` <a href='www.opensignlabs.com' target=_blank>here</a>`;
` <a href='www.opensignlabs.com' target=_blank>here</a>`;
const body = 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='background:white;padding-bottom:20px'><div style='padding:10px'>" + "<html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background:white;padding-bottom:20px'><div style='padding:10px'>" +
+2 -2
View File
@@ -1,4 +1,3 @@
import PDF from './parsefunction/pdf/PDF.js'; import PDF from './parsefunction/pdf/PDF.js';
import sendmailv3 from './parsefunction/sendMailv3.js'; import sendmailv3 from './parsefunction/sendMailv3.js';
import usersignup from './parsefunction/usersignup.js'; import usersignup from './parsefunction/usersignup.js';
@@ -51,7 +50,7 @@ import generateCertificatebydocId from './parsefunction/generateCertificatebydoc
import fileUpload from './parsefunction/fileUpload.js'; import fileUpload from './parsefunction/fileUpload.js';
import getUserListByOrg from './parsefunction/getUserListByOrg.js'; import getUserListByOrg from './parsefunction/getUserListByOrg.js';
import editContact from './parsefunction/editContact.js'; import editContact from './parsefunction/editContact.js';
import forwardDoc from './parsefunction/ForwardDoc.js';
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic. // 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); Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
@@ -112,3 +111,4 @@ Parse.Cloud.define('generatecertificate', generateCertificatebydocId);
Parse.Cloud.define('fileupload', fileUpload); Parse.Cloud.define('fileupload', fileUpload);
Parse.Cloud.define('getuserlistbyorg', getUserListByOrg); Parse.Cloud.define('getuserlistbyorg', getUserListByOrg);
Parse.Cloud.define('editcontact', editContact); Parse.Cloud.define('editcontact', editContact);
Parse.Cloud.define('forwarddoc', forwardDoc);
@@ -1,4 +1,13 @@
async function DocumentBeforesave(request) { async function DocumentBeforesave(request) {
if (!request.original) {
const TimeToCompleteDays = request.object.get('TimeToCompleteDays') || 15;
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
const reminderCount = TimeToCompleteDays / RemindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'only 15 reminder allowed');
}
}
try { try {
// below code is used to update document when user sent document or self signed // below code is used to update document when user sent document or self signed
const document = request.object; const document = request.object;
@@ -0,0 +1,81 @@
import axios from 'axios';
import { appName, cloudServerUrl } from '../../Utils.js';
export default async function forwardDoc(request) {
try {
if (!request.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'unauthorized.');
}
const { docId, recipients } = request.params;
const isReceipents = recipients?.length > 0 && recipients?.length <= 10;
if (docId && isReceipents) {
const userPtr = { __type: 'Pointer', className: '_User', objectId: request.user.id };
const docQuery = new Parse.Query('contracts_Document');
docQuery
.equalTo('objectId', docId)
.equalTo('CreatedBy', userPtr)
.notEqualTo('IsArchive', true)
.notEqualTo('IsDeclined', true)
.include('Signers')
.include('ExtUserPtr')
.include('Placeholders.signerPtr')
.include('ExtUserPtr.TenantId');
const docRes = await docQuery.first({ useMasterKey: true });
if (!docRes) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
}
const _docRes = docRes?.toJSON();
const docName = _docRes.Name;
const fileAdapterId = _docRes?.FileAdapterId || '';
const extUserId = _docRes?.ExtUserPtr?.objectId;
const from = _docRes?.ExtUserPtr?.Email;
const replyTo = _docRes?.ExtUserPtr?.Email;
const senderName = _docRes?.ExtUserPtr?.Name;
try {
let mailRes;
for (let i = 0; i < recipients.length; i++) {
const logo = `<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>`;
const opurl = ` <a href='www.opensignlabs.com' target=_blank>here</a>`;
const themeColor = '#47a3ad';
let params = {
extUserId: extUserId,
pdfName: docName,
url: _docRes?.SignedUrl || '',
recipient: recipients[i],
subject: `${senderName} has signed the doc - ${docName}`,
replyto: replyTo || '',
from: from,
html:
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'><div>` +
`${logo}</div><div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document Copy</p></div><div>` +
`<p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>${docName}</strong> is attached to this email. Kindly download the document from the attachment.</p>` +
`</div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, please contact the sender ${replyTo} directly. ` +
`If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`,
};
mailRes = await axios.post(`${cloudServerUrl}/functions/sendmailv3`, params, {
headers: {
'Content-Type': 'application/json',
'X-Parse-Application-Id': process.env.APP_ID,
'X-Parse-Master-Key': process.env.MASTER_KEY,
},
});
}
return mailRes.data?.result;
} catch (error) {
const msg =
error?.response?.data?.error ||
error?.response?.data ||
error?.message ||
'Something went wrong.';
throw new Parse.Error(400, msg);
}
} else {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'please provide parameters.');
}
} catch (err) {
console.log('Err in forwardDoc', err);
throw err;
}
}
@@ -4,7 +4,7 @@ export default async function Newsletter(request) {
const email = request.params.email; const email = request.params.email;
const domain = request.params.domain; const domain = request.params.domain;
try { try {
const envAppId = process.env.REACT_APP_APPID || 'legadranaxn'; const envAppId = process.env.REACT_APP_APPID || 'opensign';
const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': envAppId }; const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': envAppId };
const envProdServer = process.env.REACT_APP_SERVERURL || 'https://app.opensignlabs.com/api/app'; const envProdServer = process.env.REACT_APP_SERVERURL || 'https://app.opensignlabs.com/api/app';
const newsletter = await axios.post( const newsletter = await axios.post(
@@ -11,7 +11,7 @@ async function getDocument(docId) {
query.include('Placeholders'); query.include('Placeholders');
query.notEqualTo('IsArchive', true); query.notEqualTo('IsArchive', true);
const res = await query.first({ useMasterKey: true }); const res = await query.first({ useMasterKey: true });
const _res = res.toJSON(); const _res = res?.toJSON();
return _res?.ExtUserPtr?.objectId; return _res?.ExtUserPtr?.objectId;
} catch (err) { } catch (err) {
console.log('err ', err); console.log('err ', err);
@@ -1,4 +1,13 @@
async function TemplateBeforeSave(request) { async function TemplateBeforeSave(request) {
if (!request.original) {
const TimeToCompleteDays = request.object.get('TimeToCompleteDays') || 15;
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
const reminderCount = TimeToCompleteDays / RemindOnceInEvery;
if (AutoReminder && reminderCount > 15) {
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'only 15 reminder allowed');
}
}
try { try {
if (!request.original) { if (!request.original) {
// below code is used to update template when user sent template or self signed // below code is used to update template when user sent template or self signed
@@ -12,21 +12,18 @@ const eSignName = 'OpenSign';
const eSigncontact = 'hello@opensignlabs.com'; const eSigncontact = 'hello@opensignlabs.com';
// `uploadFile` is used to create url in from pdfFile // `uploadFile` is used to create url in from pdfFile
async function uploadFile( async function uploadFile(pdfName, filepath) {
pdfName,
filepath,
) {
try { try {
const filedata = fs.readFileSync(filepath); const filedata = fs.readFileSync(filepath);
let fileUrl; let fileUrl;
const file = new Parse.File(pdfName, [...filedata], 'application/pdf'); const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
const fileRes = getSecureUrl(file.url()); const fileRes = getSecureUrl(file.url());
fileUrl = fileRes.url; fileUrl = fileRes.url;
return { imageUrl: fileUrl }; return { imageUrl: fileUrl };
} catch (err) { } catch (err) {
console.log('Err ', err); console.log('Err ', err);
// `unlinkCertificate` is used to remove exported signed pdf file from exports folder // `unlinkCertificate` is used to remove exported certificate file from exports folder
unlinkCertificate(filepath); unlinkCertificate(filepath);
} }
} }
@@ -86,10 +83,7 @@ export default async function generateCertificatebydocId(req) {
//below is used to save signed certificate in exports folder //below is used to save signed certificate in exports folder
fs.writeFileSync(certificatePath, signedCertificate); fs.writeFileSync(certificatePath, signedCertificate);
const file = await uploadFile( const file = await uploadFile('certificate.pdf', certificatePath);
'certificate.pdf',
certificatePath,
);
const updateDoc = new Parse.Object('contracts_Document'); const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = doc.objectId; updateDoc.id = doc.objectId;
updateDoc.set('CertificateUrl', file.imageUrl); updateDoc.set('CertificateUrl', file.imageUrl);
@@ -0,0 +1,35 @@
import { exec } from 'child_process';
export default function DigitalSign(pdf, pfx, details) {
const signcmd = `java -jar PDFDigitalSigner.jar "${pdf}" "${pfx.name}" "${pfx.passphrase}" "${details.name}" "${details.location}" "${details.reason}"`;
// const signcmd = `java -jar PDFDigitalSigner.jar "${pdf}" keystore.pfx opensign "${details.name}" "${details.location}" "${details.reason}"`;
return new Promise((resolve, reject) => {
exec(signcmd, (error, stdout, stderr) => {
if (error) {
reject(`Error: ${error.message}`);
}
if (stderr) {
reject(`stderr: ${stderr}`);
}
// Resolve the promise with the output
resolve(stdout.trim());
});
});
}
export async function retryAsync(fn, args = [], retries = 3, delay = 2000) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fn(...args); // Try executing the async function
if (response) return { response, attempt }; // Stop retrying if response is received
} catch (error) {
if (attempt < retries) {
console.log(`Attempt ${attempt} failed. Retrying in ${delay / 1000} seconds...\n`);
await new Promise(resolve => setTimeout(resolve, delay)); // Wait before retrying
} else {
return { error, attempt };
// throw new Error(error);
}
}
}
}
@@ -1,8 +1,5 @@
import fs from 'node:fs'; import fs from 'node:fs';
import axios from 'axios'; import axios from 'axios';
import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
import { PDFDocument } from 'pdf-lib'; import { PDFDocument } from 'pdf-lib';
import { import {
cloudServerUrl, cloudServerUrl,
@@ -12,24 +9,29 @@ import {
appName, appName,
} from '../../../Utils.js'; } from '../../../Utils.js';
import GenerateCertificate from './GenerateCertificate.js'; import GenerateCertificate from './GenerateCertificate.js';
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
import { Placeholder } from './Placeholder.js'; import { Placeholder } from './Placeholder.js';
import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
const serverUrl = cloudServerUrl; // process.env.SERVER_URL; const serverUrl = cloudServerUrl; // process.env.SERVER_URL;
const APPID = process.env.APP_ID; const APPID = process.env.APP_ID;
const masterKEY = process.env.MASTER_KEY; const masterKEY = process.env.MASTER_KEY;
const eSignName = 'OpenSign'; const eSignName = 'OpenSign';
const eSigncontact = 'hello@opensignlabs.com'; const eSigncontact = 'hello@opensignlabs.com';
const logo =
"<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>";
const opurl = ` <a href=www.opensignlabs.com target=_blank>here</a>`;
// `updateDoc` is used to create url in from pdfFile // `updateDoc` is used to create url in from pdfFile
async function uploadFile( async function uploadFile(pdfName, filepath) {
pdfName,
filepath,
) {
try { try {
const filedata = fs.readFileSync(filepath); const filedata = fs.readFileSync(filepath);
let fileUrl; let fileUrl;
const file = new Parse.File(pdfName, [...filedata], 'application/pdf'); const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
await file.save({ useMasterKey: true }); await file.save({ useMasterKey: true });
const fileRes = getSecureUrl(file.url()); const fileRes = getSecureUrl(file.url());
fileUrl = fileRes.url; fileUrl = fileRes.url;
return { imageUrl: fileUrl }; return { imageUrl: fileUrl };
} catch (err) { } catch (err) {
@@ -104,14 +106,10 @@ async function sendNotifyMail(doc, signUser, mailProvider) {
const signerName = signUser.Name; const signerName = signUser.Name;
const signerEmail = signUser.Email; const signerEmail = signUser.Email;
const viewDocUrl = `${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`; const viewDocUrl = `${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
const logo =
`<div><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px' /></div>`;
const opurl =
` <a href=www.opensignlabs.com target=_blank>here</a>`;
const subject = `Document "${pdfName}" has been signed by ${signerName}`; const subject = `Document "${pdfName}" has been signed by ${signerName}`;
const body = 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='background-color:white'>" + "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
`${logo}<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>${logo}</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>` + `</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 ${appName}. For any queries regarding this email, ` + `<p><a href=${viewDocUrl} target=_blank>View Document</a></p></div></div><div><p>This is an automated email from ${appName}. 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 ${appName}${opurl}.</p></div></div></body></html>`; `please contact the sender ${creatorEmail} directly. If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`;
@@ -154,17 +152,13 @@ async function sendCompletedMail(obj) {
signersMail = sender.Email; signersMail = sender.Email;
} }
const recipient = signersMail; const recipient = signersMail;
const logo =
`<div><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/></div>`;
const opurl =
` <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>`;
let subject = `Document "${pdfName}" has been signed by all parties`; let subject = `Document "${pdfName}" has been signed by all parties`;
let body = let 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='background-color:white'>" + "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
`${logo}<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 successfully</p></div><div>` + `<div>${logo}</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 successfully</p></div><div>` +
`<p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document <b>"${pdfName}"</b>. Kindly download the document from the attachment.</p>` + `<p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document <b>"${pdfName}"</b>. Kindly download the document from the attachment.</p>` +
`</div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, please contact the sender ${sender.Email} directly.` + `</div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, please contact the sender ${sender.Email} directly.` +
`If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}`; `If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`;
if (obj?.isCustomMail) { if (obj?.isCustomMail) {
const tenant = sender?.TenantId; const tenant = sender?.TenantId;
@@ -202,8 +196,7 @@ async function sendCompletedMail(obj) {
const variables = { const variables = {
document_title: pdfName, document_title: pdfName,
sender_name: sender_name: sender.Name,
sender.Name,
sender_mail: doc?.SenderMail || sender.Email, sender_mail: doc?.SenderMail || sender.Email,
sender_phone: sender?.Phone || '', sender_phone: sender?.Phone || '',
receiver_name: sender.Name, receiver_name: sender.Name,
@@ -220,45 +213,40 @@ async function sendCompletedMail(obj) {
const params = { const params = {
extUserId: sender.objectId, extUserId: sender.objectId,
url: url, url: url,
from: from: appName,
appName, replyto: doc?.ExtUserPtr?.Email || '',
replyto:
doc?.ExtUserPtr?.Email ||
'',
recipient: recipient, recipient: recipient,
subject: subject, subject: subject,
pdfName: pdfName, pdfName: pdfName,
html: body, html: body,
mailProvider: obj.mailProvider, mailProvider: obj.mailProvider,
bcc: Bcc, bcc: Bcc,
certificatePath: `./exports/certificate_${doc.objectId}.pdf`, certificatePath: `./exports/signed_certificate_${doc.objectId}.pdf`,
filename: obj?.filename, filename: obj?.filename,
}; };
const res = await axios.post(serverUrl + '/functions/sendmailv3', params, { try {
headers: { const res = await axios.post(serverUrl + '/functions/sendmailv3', params, {
'Content-Type': 'application/json', headers: {
'X-Parse-Application-Id': APPID, 'Content-Type': 'application/json',
'X-Parse-Master-Key': masterKEY, 'X-Parse-Application-Id': APPID,
}, 'X-Parse-Master-Key': masterKEY,
}); },
});
// console.log('res', res.data.result);
if (res.data?.result?.status !== 'success') {
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
}
} catch (err) {
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
}
} }
// `sendMailsaveCertifcate` is used send completion mail and update complete status of document // `sendMailsaveCertifcate` is used send completion mail and update complete status of document
async function sendMailsaveCertifcate( async function sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, filename) {
doc,
P12Buffer,
isCustomMail,
mailProvider,
filename,
) {
const certificate = await GenerateCertificate(doc); const certificate = await GenerateCertificate(doc);
const certificatePdf = await PDFDocument.load(certificate); const certificatePdf = await PDFDocument.load(certificate);
let passphrase = process.env.PASS_PHRASE; const P12Buffer = fs.readFileSync(pfx.name);
if (doc?.ExtUserPtr?.TenantId?.PfxFile?.password) { const p12 = new P12Signer(P12Buffer, { passphrase: pfx.passphrase || null });
passphrase = doc?.ExtUserPtr?.TenantId?.PfxFile?.password;
}
const p12 = new P12Signer(P12Buffer, { passphrase: passphrase || null });
// `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate // `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate
pdflibAddPlaceholder({ pdflibAddPlaceholder({
pdfDoc: certificatePdf, pdfDoc: certificatePdf,
@@ -274,13 +262,11 @@ async function sendMailsaveCertifcate(
const certificateOBJ = new SignPdf(); const certificateOBJ = new SignPdf();
// `signedCertificate` is used to sign certificate digitally // `signedCertificate` is used to sign certificate digitally
const signedCertificate = await certificateOBJ.sign(CertificateBuffer, p12); const signedCertificate = await certificateOBJ.sign(CertificateBuffer, p12);
const certificatePath = `./exports/certificate_${doc.objectId}.pdf`; const certificatePath = `./exports/signed_certificate_${doc.objectId}.pdf`;
//below is used to save signed certificate in exports folder //below is used to save signed certificate in exports folder
fs.writeFileSync(certificatePath, signedCertificate); fs.writeFileSync(certificatePath, signedCertificate);
const file = await uploadFile( const file = await uploadFile('certificate.pdf', certificatePath);
'certificate.pdf',
certificatePath,
);
const body = { CertificateUrl: file.imageUrl }; const body = { CertificateUrl: file.imageUrl };
await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, { await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, {
headers: { headers: {
@@ -296,6 +282,7 @@ async function sendMailsaveCertifcate(
sendCompletedMail({ isCustomMail, doc, mailProvider, filename }); sendCompletedMail({ isCustomMail, doc, mailProvider, filename });
} }
saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId); saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId);
fs.unlinkSync(pfx.name);
} }
/** /**
* *
@@ -304,9 +291,11 @@ async function sendMailsaveCertifcate(
* @returns if success {status, data} else {status, message} * @returns if success {status, data} else {status, message}
*/ */
async function PDF(req) { async function PDF(req) {
const docId = req.params.docId;
const randomNumber = Math.floor(Math.random() * 5000);
const pfxname = `keystore_${randomNumber}.pfx`;
try { try {
const userIP = req.headers['x-real-ip']; // client IPaddress const userIP = req.headers['x-real-ip']; // client IPaddress
const docId = req.params.docId;
const reqUserId = req.params.userId; const reqUserId = req.params.userId;
const isCustomMail = req.params.isCustomCompletionMail || false; const isCustomMail = req.params.isCustomCompletionMail || false;
const mailProvider = req.params.mailProvider || ''; const mailProvider = req.params.mailProvider || '';
@@ -354,9 +343,9 @@ async function PDF(req) {
pfxFile = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.base64; pfxFile = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.base64;
passphrase = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.password; passphrase = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.password;
} }
// const P12Buffer = fs.readFileSync(); const pfx = { name: pfxname, passphrase: passphrase };
const P12Buffer = Buffer.from(pfxFile, 'base64'); const P12Buffer = Buffer.from(pfxFile, 'base64');
const p12Cert = new P12Signer(P12Buffer, { passphrase: passphrase || null }); fs.writeFileSync(pfxname, P12Buffer);
const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId }; const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId };
const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP }; const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP };
let updateAuditTrail; let updateAuditTrail;
@@ -375,12 +364,12 @@ async function PDF(req) {
} else { } else {
isCompleted = true; 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 // 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 docName = _resDoc?.Name?.replace(/[^a-zA-Z0-9._-]/g, '_')?.toLowerCase();
const filename = docName?.length > 100 ? docName?.slice(0, 100) : docName; const filename = docName?.length > 100 ? docName?.slice(0, 100) : docName;
const name = `signed_${filename}_${randomNumber}.pdf`; const name = `${filename}_${randomNumber}.pdf`;
const filePath = `./exports/${name}`; let filePath = `./exports/${name}`;
let signedFilePath = `./exports/signed_${name}`;
let pdfSize = PdfBuffer.length; let pdfSize = PdfBuffer.length;
if (isCompleted) { if (isCompleted) {
const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>'); const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>');
@@ -394,6 +383,8 @@ async function PDF(req) {
form.updateFieldAppearances(); form.updateFieldAppearances();
// Flattens the form, converting all form fields into non-editable, static content // Flattens the form, converting all form fields into non-editable, static content
form.flatten(); form.flatten();
const p12Cert = new P12Signer(P12Buffer, { passphrase: passphrase || null });
signedFilePath = `./exports/signed_${name}`;
Placeholder({ Placeholder({
pdfDoc: pdfDoc, pdfDoc: pdfDoc,
reason: `Digitally signed by ${eSignName} for ${reason}`, reason: `Digitally signed by ${eSignName} for ${reason}`,
@@ -410,19 +401,18 @@ async function PDF(req) {
const signedDocs = await OBJ.sign(PdfBuffer, p12Cert); const signedDocs = await OBJ.sign(PdfBuffer, p12Cert);
//`saveUrl` is used to save signed pdf in exports folder //`saveUrl` is used to save signed pdf in exports folder
fs.writeFileSync(filePath, signedDocs); fs.writeFileSync(signedFilePath, signedDocs);
pdfSize = signedDocs.length; pdfSize = signedDocs.length;
console.log(`✅ PDF digitally signed created: ${signedFilePath} \n`);
} else { } else {
//`saveUrl` is used to save signed pdf in exports folder //`saveUrl` is used to save signed pdf in exports folder
fs.writeFileSync(filePath, PdfBuffer); fs.writeFileSync(signedFilePath, PdfBuffer);
pdfSize = PdfBuffer.length; pdfSize = PdfBuffer.length;
console.log(`New Signed PDF created called: ${signedFilePath}`);
} }
// `uploadFile` is used to upload pdf to aws s3 and get it's url // `uploadFile` is used to upload pdf to aws s3 and get it's url
const data = await uploadFile( const data = await uploadFile(`signed_${name}`, signedFilePath);
name,
filePath,
);
if (data && data.imageUrl) { if (data && data.imageUrl) {
// `axios` is used to update signed pdf url in contracts_Document classes for given DocId // `axios` is used to update signed pdf url in contracts_Document classes for given DocId
@@ -439,17 +429,13 @@ async function PDF(req) {
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId); saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
if (updatedDoc && updatedDoc.isCompleted) { if (updatedDoc && updatedDoc.isCompleted) {
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl }; const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
sendMailsaveCertifcate( sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, `signed_${name}`);
doc, } else {
P12Buffer, fs.unlinkSync(pfxname);
isCustomMail,
mailProvider,
name,
);
} }
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder // `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
fs.unlinkSync(filePath); fs.unlinkSync(signedFilePath);
console.log(`New Signed PDF created called: ${filePath}`); // console.log(`New Signed PDF created called: ${filePath}`);
if (updatedDoc.message === 'success') { if (updatedDoc.message === 'success') {
return { status: 'success', data: data.imageUrl }; return { status: 'success', data: data.imageUrl };
} else { } else {
@@ -465,6 +451,15 @@ async function PDF(req) {
} }
} catch (err) { } catch (err) {
console.log('Err in signpdf', err); console.log('Err in signpdf', err);
const body = { DebugginLog: err?.message };
try {
await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, {
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
});
} catch (err) {
console.log('err in saving debugginglog', err);
}
fs.unlinkSync(pfxname);
throw err; throw err;
} }
} }
@@ -42,6 +42,12 @@ export default async function updatePreferences(request) {
newOrg.set('SignatureType', SignatureType); newOrg.set('SignatureType', SignatureType);
} }
} }
if (request.params.SendinOrder !== undefined) {
newOrg.set('SendinOrder', request.params.SendinOrder);
}
if (request.params.IsTourEnabled !== undefined) {
newOrg.set('IsTourEnabled', request.params.IsTourEnabled);
}
const updateUserRes = await newOrg.save(null, { useMasterKey: true }); const updateUserRes = await newOrg.save(null, { useMasterKey: true });
if (updateUserRes) { if (updateUserRes) {
const _updateUserRes = JSON.parse(JSON.stringify(updateUserRes)); const _updateUserRes = JSON.parse(JSON.stringify(updateUserRes));
Binary file not shown.
+4 -4
View File
@@ -47,7 +47,7 @@
"mongodb-runner": "^5.7.1", "mongodb-runner": "^5.7.1",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"nyc": "^17.1.0", "nyc": "^17.1.0",
"prettier": "^3.5.3" "prettier": "^3.5.0"
}, },
"engines": { "engines": {
"node": "18 || 20 || 22" "node": "18 || 20 || 22"
@@ -16933,9 +16933,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.5.3", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.0.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", "integrity": "sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
+1 -1
View File
@@ -57,7 +57,7 @@
"mongodb-runner": "^5.7.1", "mongodb-runner": "^5.7.1",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
"nyc": "^17.1.0", "nyc": "^17.1.0",
"prettier": "^3.5.3" "prettier": "^3.5.0"
}, },
"overrides": { "overrides": {
"ws": "$ws", "ws": "$ws",
Binary file not shown.
Binary file not shown.