mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-03 08:18:50 +02:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8833018027 |
@@ -241,15 +241,6 @@
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "ehowe",
|
||||
"name": "Eugene Howe",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/756984?v=4",
|
||||
"profile": "https://github.com/ehowe",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -21,46 +21,34 @@ jobs:
|
||||
dockerfile: apps/OpenSign/Dockerhubfile
|
||||
- image: opensign/opensignserver
|
||||
dockerfile: apps/OpenSignServer/Dockerhubfile
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# 👇 Enable emulation so we can build for arm64 on amd64 runners
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
# 👇 Enable Buildx for multi-arch builds
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
-
|
||||
name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: ${{ matrix.image }}
|
||||
|
||||
- name: Debug - List files opensign
|
||||
-
|
||||
name: Debug - List files opensign
|
||||
run: ls -R apps/OpenSign/ # Adjust the path as needed
|
||||
|
||||
- name: Debug - List files opensignserver
|
||||
-
|
||||
name: Debug - List files opensignserver
|
||||
run: ls -R apps/OpenSignServer/ # Adjust the path as needed
|
||||
|
||||
- name: Build and push (multi-arch)
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.dockerfile }}
|
||||
push: true
|
||||
# 👇 Build for both AMD64 & ARM64
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
@@ -165,7 +165,6 @@ We would like to thank all our contributors and users for their support and feed
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/daniel-mutwiri"><img src="https://avatars.githubusercontent.com/u/8936960?v=4?s=100" width="100px;" alt="Daniel Mutwiri"/><br /><sub><b>Daniel Mutwiri</b></sub></a><br /><a href="#code-daniel-mutwiri" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Zathiel"><img src="https://avatars.githubusercontent.com/u/26553418?v=4?s=100" width="100px;" alt="Zathiel"/><br /><sub><b>Zathiel</b></sub></a><br /><a href="#code-Zathiel" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/1024mb"><img src="https://avatars.githubusercontent.com/u/9301204?v=4?s=100" width="100px;" alt="1024mb"/><br /><sub><b>1024mb</b></sub></a><br /><a href="#translation-1024mb" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ehowe"><img src="https://avatars.githubusercontent.com/u/756984?v=4?s=100" width="100px;" alt="Eugene Howe"/><br /><sub><b>Eugene Howe</b></sub></a><br /><a href="#code-ehowe" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Generated
-17180
File diff suppressed because it is too large
Load Diff
+20
-19
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "open_sign",
|
||||
"version": "2.30.0",
|
||||
"version": "2.26.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.9.0",
|
||||
@@ -8,19 +8,19 @@
|
||||
"@lottiefiles/dotlottie-react": "^0.16.2",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.2.1",
|
||||
"@reduxjs/toolkit": "^2.10.1",
|
||||
"axios": "^1.13.2",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"axios": "^1.12.2",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^25.6.3",
|
||||
"i18next": "^25.4.0",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"parse": "^7.0.2",
|
||||
"parse": "^6.1.1",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pkijs": "^3.3.3",
|
||||
"pkijs": "^3.0.8",
|
||||
"print-js": "^1.6.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"quill-html-edit-button": "^3.0.0",
|
||||
@@ -28,25 +28,26 @@
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-datepicker": "^8.9.0",
|
||||
"react-datepicker": "^8.7.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-multi-backend": "^9.0.0",
|
||||
"react-dnd-touch-backend": "^16.0.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.7.4",
|
||||
"react-i18next": "^15.7.3",
|
||||
"react-konva": "^18.2.12",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-quill-new": "^3.6.0",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.9.6",
|
||||
"react-router": "^7.9.1",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.10.2",
|
||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||
"react-timezone-select": "^3.2.8",
|
||||
"react-tooltip": "^5.30.0",
|
||||
"react-tooltip": "^5.29.1",
|
||||
"reactour": "^1.19.4",
|
||||
"redux": "^5.0.1",
|
||||
"regex-parser": "^2.3.1",
|
||||
@@ -95,31 +96,31 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/runtime-corejs2": "^7.28.3",
|
||||
"@testing-library/jest-dom": "^6.8.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/react": "^19.2.6",
|
||||
"@types/react": "^19.1.13",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"autoprefixer": "^10.4.22",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-loader": "^10.0.0",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.24",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"jsdom": "^27.2.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"jsdom": "^27.0.0",
|
||||
"lint-staged": "^16.1.6",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"pretty-quick": "^4.2.2",
|
||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||
"tailwindcss": "^3.4.18",
|
||||
"vite": "^7.2.4",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^7.1.4",
|
||||
"vite-plugin-svgr": "^4.5.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.4"
|
||||
@@ -127,4 +128,4 @@
|
||||
"engines": {
|
||||
"node": "18 || 20 || 22"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,6 @@
|
||||
"login-page": "Anmeldeseite",
|
||||
"or": "ODER",
|
||||
"signup-page": "Registrierungsseite",
|
||||
"password-match-length": "Neues Passwort und Bestätigungspasswort sollten übereinstimmen",
|
||||
"password-length": "Das Passwort sollte 8 Zeichen lang sein",
|
||||
"password-case": "Das Passwort sollte einen Großbuchstaben, einen Kleinbuchstaben und eine Zahl enthalten",
|
||||
"password-special-char": "Das Passwort sollte ein Sonderzeichen enthalten",
|
||||
@@ -121,8 +120,7 @@
|
||||
"Teams": "Teams",
|
||||
"Team-update-successfully": "Team erfolgreich aktualisiert.",
|
||||
"Users": "Benutzer"
|
||||
},
|
||||
"Global SMTP": "Globales SMTP"
|
||||
}
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "Benötigt Ihre Unterschrift",
|
||||
@@ -167,8 +165,7 @@
|
||||
"daily-mail-quota": "Tägliches E-Mail-Kontingent",
|
||||
"Save as template": "Als Vorlage speichern",
|
||||
"Fix & resend": "Korrigieren und erneut senden",
|
||||
"Kiosk Mode": "Kiosk Modus",
|
||||
"Reset password": "Passwort zurücksetzen"
|
||||
"Kiosk Mode": "Kiosk Modus"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nr.",
|
||||
@@ -388,8 +385,7 @@
|
||||
"dropdown": "Dropdown",
|
||||
"radio button": "Radiobutton",
|
||||
"image": "Bild",
|
||||
"email": "E-Mail",
|
||||
"number": "Zahl"
|
||||
"email": "E-Mail"
|
||||
},
|
||||
"fields": "Felder",
|
||||
"recipients": "Empfänger",
|
||||
@@ -446,19 +442,6 @@
|
||||
"maximum-check": "Maximale Anzahl",
|
||||
"cell-count": "Zellzahl",
|
||||
"default-value": "Standardwert",
|
||||
"formula": "Formel",
|
||||
"formula-placeholder": "Erstellen Sie eine Formel mit Zahlen-Widgets",
|
||||
"formula-no-widgets": "Für diesen Unterzeichner sind noch keine weiteren Zahlen-Widgets vorhanden.",
|
||||
"supported-operators": "Unterstützte Operatoren:",
|
||||
"formula-helper": "Klicken Sie auf einen Zahlen-Widget-Chip, um ihn einzufügen (z. B. {{number-1}} + {{number-2}}).",
|
||||
"formula-error-letter": "Widget-Namen müssen in doppelte geschweifte Klammern gesetzt werden (z. B. {{number-1}}).",
|
||||
"formula-error-characters": "Außerhalb der Klammern sind nur Zahlen sowie +, -, *, /, (, ) erlaubt.",
|
||||
"formula-error-braces": "Jede öffnende {{ muss eine passende }} haben.",
|
||||
"formula-error-operators": "Verwenden Sie bei Operatoren oder Klammern mindestens zwei Werte.",
|
||||
"formula-error-leading-operator": "Formeln dürfen nicht mit Operatoren beginnen.",
|
||||
"decimal-places": "Dezimalstellen",
|
||||
"number-of-decimal-places": "Anzahl der Dezimalstellen",
|
||||
"decimal-places-helper": "Auf 0 setzen für ganze Zahlen",
|
||||
"select": "Auswählen",
|
||||
"read-only": "Ist schreibgeschützt",
|
||||
"hide-labels": "Labels ausblenden",
|
||||
@@ -918,10 +901,6 @@
|
||||
"thanks-for-feedback": "Danke für Ihr Feedback 🙏",
|
||||
"share-your-feedback": "Teilen Sie Ihr Feedback",
|
||||
"share-your-review": "Teilen Sie Ihre Bewertung",
|
||||
"please-select-rating": "Bitte wählen Sie eine Bewertung aus",
|
||||
"feedback-optional": "Feedback (optional)",
|
||||
"feedback-saved": "Feedback wurde gespeichert.",
|
||||
"feedback-save-error": "Feedback konnte nicht gespeichert werden, bitte versuchen Sie es erneut.",
|
||||
"date-format": "Datumsformat",
|
||||
"document-deleted": "Das Dokument wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
|
||||
"save-as-template-?": "Sind Sie sicher, dass Sie dieses Dokument als Vorlage speichern möchten?",
|
||||
@@ -1237,42 +1216,6 @@
|
||||
"p1": "Dies stellt sicher, dass das Abschlusszertifikat im endgültigen PDF-Dokument enthalten ist. Bitte beachten Sie jedoch, dass das Zertifikat nach dem Zusammenführen nicht mehr vom Hauptdokument getrennt werden kann.",
|
||||
"p2": "Wenn Sie sich entscheiden, nicht zusammenzuführen, wird das Abschlusszertifikat als separate PDF-Datei zusammen mit dem unterzeichneten Dokument bereitgestellt."
|
||||
},
|
||||
"read-only-date-error": "Das schreibgeschützte Datums-Widget muss ein Standarddatum haben.",
|
||||
"set-date": "Datum festlegen",
|
||||
"set-today": "Unterzeichnungsdatum",
|
||||
"enter-name": "Name eingeben",
|
||||
"enter-email": "E-Mail eingeben",
|
||||
"subscribe-to-opensign-msg": "Abonnieren Sie {{appName}} und genießen Sie unbegrenzt kostenlose digitale Signaturen.",
|
||||
"duplicate-subscribe-msg": "Mit Duplizieren können Sie eine exakte Kopie der ausgewählten Vorlage erstellen, die Sie wiederverwenden oder ändern können, ohne das Original zu beeinflussen.",
|
||||
"save-as-template-msg": "Speichern Sie dieses Dokument als wiederverwendbare Vorlage, die Sie für zukünftige Dokumente erneut nutzen können.",
|
||||
"public-credit-alert": "Das Signieren über diesen Link verbraucht Ihre kostenlosen E-Mail-Credits. Als kostenloser Nutzer erhalten Sie 15 E-Mail-Credits pro Monat, um zu verhindern, dass Spammer unsere Systeme missbrauchen. Um höhere Limits und ununterbrochenen Zugriff zu genießen, abonnieren Sie die <1>OpenSign™-Bezahlpläne.</1>",
|
||||
"know-more-about": "Mehr erfahren über",
|
||||
"free-unlimited-signatures": "Kostenlose unbegrenzte Signaturen",
|
||||
"email-quota-updated": "Tägliches E-Mail-Kontingent erfolgreich aktualisiert.",
|
||||
"password-has-been-reset": "Passwort wurde zurückgesetzt.",
|
||||
"Unauthorized to reset your own password.": "Nicht berechtigt, Ihr eigenes Passwort zurückzusetzen.",
|
||||
"User not found or not allowed.": "Benutzer nicht gefunden oder nicht erlaubt.",
|
||||
"User not found.": "Benutzer nicht gefunden.",
|
||||
"Admin user tenant not found.": "Admin-Benutzer-Tenant nicht gefunden.",
|
||||
"User is not authenticated.": "Benutzer ist nicht authentifiziert.",
|
||||
"Please provide required parameters.": "Bitte geben Sie die erforderlichen Parameter an.",
|
||||
"reset-password": "Benutzerpasswort zurücksetzen",
|
||||
"enter-strong-password": "Geben Sie ein sicheres Passwort ein oder generieren Sie eines automatisch.",
|
||||
"autogenerate": "Automatisch generieren",
|
||||
"extend": "Verlängern",
|
||||
"console-general-help": "Um einzelnen Benutzern zu ermöglichen, ihre eigenen E-Mail-Vorlagen anzupassen, navigieren Sie zu",
|
||||
"console-mail-help": "Um unternehmensweite E-Mail-Vorlagen zu aktualisieren, gehen Sie zu",
|
||||
"admin-setting-disabled": "Diese Einstellung ist deaktiviert. Bitte wenden Sie sich für Unterstützung an den Administrator.",
|
||||
"company-email-settings": "Unternehmens-E-Mail-Einstellungen",
|
||||
"allow-email-template-individually": "E-Mail-Vorlagenanpassung für Benutzer individuell erlauben ",
|
||||
"user-email-template-general-help": "Wenn diese Option deaktiviert ist, können Benutzer ihre E-Mail-Vorlagen nicht mehr anpassen.",
|
||||
"disable-individual-user-smtp": "individuelle benutzer-smtp-einstellungen deaktivieren",
|
||||
"enable-individual-user-smtp": "individuelle benutzer-smtp-einstellungen aktivieren",
|
||||
"user-smtp-general-help": "Wenn deaktiviert, können Benutzer ihre eigenen SMTP-Einstellungen nicht konfigurieren. Alle E-Mails verwenden dann die globale SMTP-Konfiguration.",
|
||||
"global-smtp-settings": "Globale SMTP-Einstellungen",
|
||||
"global-smtp-settings-help-p1": "Diese Einstellungen gelten global für Ihr gesamtes {{appName}}-Konto. Wenn ein Benutzer keine eigenen E-Mail-Zustellungseinstellungen konfiguriert hat, verwendet das System automatisch die hier definierten globalen SMTP-Einstellungen.",
|
||||
"global-smtp-settings-help-p2": "Um eine bessere Zustellbarkeit aller Signaturanfragen sicherzustellen, können Sie Ihren bevorzugten E-Mail-Anbieter verbinden. Wählen Sie eine der folgenden Optionen:",
|
||||
"global-connect-to-gmail-help": "Verwenden Sie ein Gmail-Konto, um Signaturanfragen direkt aus dem Posteingang Ihrer Organisation zu senden. Dies verbessert die Zustellrate, Reputation und Zuverlässigkeit.",
|
||||
"global-connect-to-smtp-help": "Verwenden Sie Ihren eigenen SMTP-Server, um E-Mails über Ihre Domain zu senden. Dies bietet volle Kontrolle über Ihre E-Mail-Infrastruktur, verbessert die Zustellbarkeit und sorgt für konsistentes Branding.",
|
||||
"global-smtp-settings-help-p3": "Wenn keine individuellen Benutzereinstellungen konfiguriert sind, werden automatisch diese globalen Einstellungen angewendet."
|
||||
}
|
||||
"enter-email": "E-Mail eingeben"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
"login-page": "Login P+age",
|
||||
"or": "OR",
|
||||
"signup-page": "Signup page",
|
||||
"password-match-length": "New password and confirmation password should match",
|
||||
"password-length": "Password should be 8 characters long",
|
||||
"password-case": "Password should contain uppercase letter, lowercase letter, digit",
|
||||
"password-special-char": " Password should contain special character",
|
||||
@@ -121,8 +120,7 @@
|
||||
"Teams": "Teams",
|
||||
"Team-update-successfully": "Team Updated successfully.",
|
||||
"Users": "Users"
|
||||
},
|
||||
"Global SMTP": "Global SMTP"
|
||||
}
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "Need your signature",
|
||||
@@ -167,8 +165,7 @@
|
||||
"daily-mail-quota": "Daily Email Quota",
|
||||
"Save as template": "Save as template",
|
||||
"Fix & resend": "Fix & Resend",
|
||||
"Kiosk Mode": "Kiosk Mode",
|
||||
"Reset password": "Reset password"
|
||||
"Kiosk Mode": "Kiosk Mode"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Sr.No",
|
||||
@@ -388,8 +385,7 @@
|
||||
"dropdown": "dropdown",
|
||||
"radio button": "radio button",
|
||||
"image": "image",
|
||||
"email": "email",
|
||||
"number": "number"
|
||||
"email": "email"
|
||||
},
|
||||
"fields": "Fields",
|
||||
"recipients": "Recipients",
|
||||
@@ -447,19 +443,6 @@
|
||||
"maximum-check": "Maximum check",
|
||||
"cell-count": "Cell count",
|
||||
"default-value": "Default value",
|
||||
"formula": "Formula",
|
||||
"formula-placeholder": "Build a formula using number widgets",
|
||||
"formula-no-widgets": "No other number widgets are assigned to this signer yet.",
|
||||
"supported-operators": "Supported operators:",
|
||||
"formula-helper": "Click a number widget chip to insert it (e.g. {{number-1}} + {{number-2}}).",
|
||||
"formula-error-letter": "Wrap widget names in double curly braces (e.g. {{number-1}}).",
|
||||
"formula-error-characters": "Only numbers and +, -, *, /, (, ) are allowed outside of braces.",
|
||||
"formula-error-braces": "Each opening {{ must have a matching }}.",
|
||||
"formula-error-operators": "Add at least two values when using operators or parentheses.",
|
||||
"formula-error-leading-operator": "Formulas cannot start with operators.",
|
||||
"decimal-places": "Decimal Places",
|
||||
"number-of-decimal-places": "Number of decimal places",
|
||||
"decimal-places-helper": "Set to 0 for whole numbers",
|
||||
"select": "Select",
|
||||
"read-only": "read only",
|
||||
"hide-labels": "Hide labels",
|
||||
@@ -919,10 +902,6 @@
|
||||
"thanks-for-feedback": "Thanks for your feedback 🙏",
|
||||
"share-your-feedback": "Share your feedback",
|
||||
"share-your-review": "Share your review",
|
||||
"please-select-rating": "Please select a rating",
|
||||
"feedback-optional": "Feedback (Optional)",
|
||||
"feedback-saved": "Feedback has been saved.",
|
||||
"feedback-save-error": "Unable to save feedback, please try again.",
|
||||
"date-format": "Date format",
|
||||
"document-deleted": "The document has been deleted or you don't have access. Please contact the sender.",
|
||||
"save-as-template-?": "Are you sure you want to save this document as template?",
|
||||
@@ -1229,8 +1208,8 @@
|
||||
"enter-region-of-space": "Enter region of space",
|
||||
"enter-access-key": "Enter access key",
|
||||
"enter-secret-access-key": "Enter secret access key",
|
||||
"otp-email": "We've sent a verification code to",
|
||||
"save-as-temp-warn": "Note: This document includes prefilled widgets, which will be automatically removed as they are already incorporated into the base document.",
|
||||
"otp-email":"We’ve sent a verification code to",
|
||||
"save-as-temp-warn":"Note: This document includes prefilled widgets, which will be automatically removed as they are already incorporated into the base document.",
|
||||
"edit-document": "Edit document",
|
||||
"modify": "Modify",
|
||||
"merge-certificate-to-pdf": "Merge Certificate to PDF",
|
||||
@@ -1238,42 +1217,6 @@
|
||||
"p1": "This will ensure that the completion certificate is included in the final PDF document. However, please note that once merged, the certificate cannot be separated from the main document.",
|
||||
"p2": "If you choose not to merge, the completion certificate will be provided as a separate PDF file along with the signed document."
|
||||
},
|
||||
"read-only-date-error": "Read-only date widget must have a default date.",
|
||||
"set-date": "Set Date",
|
||||
"set-today": "Signing Date",
|
||||
"enter-name": "Enter name",
|
||||
"enter-email": "Enter email",
|
||||
"subscribe-to-opensign-msg": "Subscribe to {{appName}} and enjoy unlimited free digital signatures.",
|
||||
"duplicate-subscribe-msg": "Duplicate lets you create an exact copy of the selected template, allowing you to reuse or modify it without affecting the original.",
|
||||
"save-as-template-msg": "Save this document as a reusable template that you can use again for future documents.",
|
||||
"public-credit-alert": "Signing through this link consumes your free email credits. As a Free user, you are allotted 15 email credits per month to prevent spammers from abusing our systems. To enjoy higher limits and uninterrupted access, Subscribe to <1>OpenSign™ Paid plans.</1>",
|
||||
"know-more-about": "Know more about",
|
||||
"free-unlimited-signatures": "Free Unlimited Signatures",
|
||||
"email-quota-updated": "Daily email quota updated successfully.",
|
||||
"password-has-been-reset": "Password has been reset.",
|
||||
"Unauthorized to reset your own password.": "Unauthorized to reset your own password.",
|
||||
"User not found or not allowed.": "User not found or not allowed.",
|
||||
"User not found.": "User not found.",
|
||||
"Admin user tenant not found.": "Admin user tenant not found.",
|
||||
"User is not authenticated.": "User is not authenticated.",
|
||||
"Please provide required parameters.": "Please provide required parameters.",
|
||||
"reset-password": "Reset User Password",
|
||||
"enter-strong-password": "Enter a strong password or auto-generate one.",
|
||||
"autogenerate": "Autogenerate",
|
||||
"extend": "Extend",
|
||||
"console-general-help": "To let individual users customize their own email templates, navigate to",
|
||||
"console-mail-help": "To update company-wide email templates, go to",
|
||||
"admin-setting-disabled": "This setting is disabled. Please contact admin for assistance.",
|
||||
"company-email-settings": "Company email settings",
|
||||
"allow-email-template-individually": "Allow email template customization for users individually ",
|
||||
"user-email-template-general-help": "Disabling this will prevent users from customizing their email templates.",
|
||||
"disable-individual-user-smtp": "Disable individual user smtp settings",
|
||||
"enable-individual-user-smtp": "Enable individual user smtp settings",
|
||||
"user-smtp-general-help": "When disabled, users will not be able to configure their own SMTP settings. All emails will use the global SMTP configuration instead.",
|
||||
"global-smtp-settings": "Global SMTP Settings",
|
||||
"global-smtp-settings-help-p1": "These settings apply globally across your entire {{appName}} account. If an individual user has not configured their own email delivery settings, the system will automatically use the Global SMTP Settings defined here.",
|
||||
"global-smtp-settings-help-p2": "To ensure better inbox deliverability for all signature request emails, you can connect your preferred email provider. Choose one of the following options:",
|
||||
"global-connect-to-gmail-help": "Use a Gmail account to send signature request emails directly from your organization's inbox. This improves delivery rates, reputation, and overall reliability.",
|
||||
"global-connect-to-smtp-help": "Use your own SMTP server to send emails through your domain. This provides full control over your email infrastructure, enhances deliverability, and maintains consistent branding.",
|
||||
"global-smtp-settings-help-p3": "If no individual user-level settings are configured, these global settings will be applied automatically."
|
||||
}
|
||||
"enter-email": "Enter email"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
"login-page": "Página de inicio de sesión",
|
||||
"or": "O",
|
||||
"signup-page": "Página de registro",
|
||||
"password-match-length": "La nueva contraseña y la confirmación deben coincidir",
|
||||
"password-length": "La contraseña debe tener 8 caracteres",
|
||||
"password-case": "La contraseña debe incluir mayúsculas, minúsculas y números",
|
||||
"password-special-char": " La contraseña debe incluir caracteres especiales",
|
||||
@@ -121,8 +120,7 @@
|
||||
"Teams": "Equipos",
|
||||
"Team-update-successfully": "Equipo actualizado correctamente.",
|
||||
"Users": "Usuarios"
|
||||
},
|
||||
"Global SMTP": "SMTP Global"
|
||||
}
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "Necesitan tu firma",
|
||||
@@ -167,8 +165,7 @@
|
||||
"daily-mail-quota": "Cuota diaria de correos electrónicos",
|
||||
"Save as template": "Guardar como plantilla",
|
||||
"Fix & resend": "Corregir y reenviar",
|
||||
"Kiosk Mode": "Modo Kiosco",
|
||||
"Reset password": "Restablecer contraseña"
|
||||
"Kiosk Mode": "Modo Kiosco"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nº",
|
||||
@@ -388,8 +385,7 @@
|
||||
"dropdown": "desplegable",
|
||||
"radio button": "botón de radio",
|
||||
"image": "imagen",
|
||||
"email": "correo",
|
||||
"number": "número"
|
||||
"email": "correo"
|
||||
},
|
||||
"fields": "Campos",
|
||||
"recipients": "Destinatarios",
|
||||
@@ -447,19 +443,6 @@
|
||||
"maximum-check": "Chequeo máximo",
|
||||
"cell-count": "recuento de células",
|
||||
"default-value": "Valor por defecto",
|
||||
"formula": "Fórmula",
|
||||
"formula-placeholder": "Crea una fórmula utilizando widgets numéricos",
|
||||
"formula-no-widgets": "Todavía no hay otros widgets numéricos asignados a este firmante.",
|
||||
"supported-operators": "Operadores compatibles:",
|
||||
"formula-helper": "Haz clic en una ficha de widget numérico para insertarla (p. ej. {{number-1}} + {{number-2}}).",
|
||||
"formula-error-letter": "Envuelve los nombres de los widgets entre dobles llaves (p. ej. {{number-1}}).",
|
||||
"formula-error-characters": "Fuera de las llaves solo se permiten números y +, -, *, /, (, ).",
|
||||
"formula-error-braces": "Cada {{ de apertura debe tener una }} correspondiente.",
|
||||
"formula-error-operators": "Cuando uses operadores o paréntesis, agrega al menos dos valores.",
|
||||
"formula-error-leading-operator": "Las fórmulas no pueden comenzar con operadores.",
|
||||
"decimal-places": "Decimales",
|
||||
"number-of-decimal-places": "Número de decimales",
|
||||
"decimal-places-helper": "Establecer en 0 para números enteros",
|
||||
"select": "Seleccionar",
|
||||
"read-only": "Es de solo lectura",
|
||||
"hide-labels": "Esconder etiquetas",
|
||||
@@ -919,10 +902,6 @@
|
||||
"thanks-for-feedback": "Gracias por su comentario 🙏",
|
||||
"share-your-feedback": "Comparta sus comentarios",
|
||||
"share-your-review": "Comparta su reseña",
|
||||
"please-select-rating": "Por favor seleccione una calificación",
|
||||
"feedback-optional": "Comentarios (opcional)",
|
||||
"feedback-saved": "Los comentarios han sido guardados.",
|
||||
"feedback-save-error": "No se pudieron guardar los comentarios, inténtelo de nuevo.",
|
||||
"date-format": "Formato de fecha",
|
||||
"document-deleted": "El documento ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
|
||||
"save-as-template-?": "¿Está seguro de que desea guardar este documento como plantilla?",
|
||||
@@ -1238,42 +1217,6 @@
|
||||
"p1": "Esto garantizará que el certificado de finalización esté incluido en el documento PDF final. Sin embargo, tenga en cuenta que una vez combinado, el certificado no se podrá separar del documento principal.",
|
||||
"p2": "Si elige no combinar, el certificado de finalización se proporcionará como un archivo PDF separado junto con el documento firmado."
|
||||
},
|
||||
"read-only-date-error": "El widget de fecha de solo lectura debe tener una fecha predeterminada.",
|
||||
"set-date": "Establecer fecha",
|
||||
"set-today": "Fecha de firma",
|
||||
"enter-name": "Ingrese nombre",
|
||||
"enter-email": "Ingrese correo electrónico",
|
||||
"subscribe-to-opensign-msg": "Suscríbase a {{appName}} y disfrute de firmas digitales gratuitas ilimitadas.",
|
||||
"duplicate-subscribe-msg": "Duplicar le permite crear una copia exacta de la plantilla seleccionada, lo que le permite reutilizarla o modificarla sin afectar al original.",
|
||||
"save-as-template-msg": "Guarde este documento como una plantilla reutilizable que podrá usar nuevamente para futuros documentos.",
|
||||
"public-credit-alert": "Firmar a través de este enlace consumirá sus créditos de correo electrónico gratuitos. Como usuario gratuito, se le asignan 15 créditos de correo electrónico por mes para evitar que los spammers abusen de nuestros sistemas. Para disfrutar de límites más altos y acceso ininterrumpido, suscríbase a los planes de pago de <1>OpenSign™.</1>",
|
||||
"know-more-about": "Saber más sobre",
|
||||
"free-unlimited-signatures": "¿Firmas ilimitadas gratis",
|
||||
"email-quota-updated": "La cuota diaria de correos se actualizó correctamente.",
|
||||
"password-has-been-reset": "La contraseña ha sido restablecida.",
|
||||
"Unauthorized to reset your own password.": "No está autorizado para restablecer su propia contraseña.",
|
||||
"User not found or not allowed.": "Usuario no encontrado o no autorizado.",
|
||||
"User not found.": "Usuario no encontrado.",
|
||||
"Admin user tenant not found.": "Inquilino de usuario administrador no encontrado.",
|
||||
"User is not authenticated.": "El usuario no está autenticado.",
|
||||
"Please provide required parameters.": "Por favor, proporcione los parámetros requeridos.",
|
||||
"reset-password": "Restablecer contraseña de usuario",
|
||||
"enter-strong-password": "Ingrese una contraseña segura o genere una automáticamente.",
|
||||
"autogenerate": "Generar automáticamente",
|
||||
"extend": "Extender",
|
||||
"console-general-help": "Para permitir que los usuarios individuales personalicen sus propias plantillas de correo electrónico, vaya a",
|
||||
"console-mail-help": "Para actualizar las plantillas de correo electrónico de toda la empresa, vaya a",
|
||||
"admin-setting-disabled": "Esta configuración está deshabilitada. Comuníquese con el administrador para obtener ayuda.",
|
||||
"company-email-settings": "Configuración de correo electrónico de la empresa",
|
||||
"allow-email-template-individually": "Permitir la personalización individual de plantillas de correo electrónico para los usuarios ",
|
||||
"user-email-template-general-help": "Si se desactiva, los usuarios no podrán personalizar sus plantillas de correo electrónico.",
|
||||
"disable-individual-user-smtp": "desactivar la configuración smtp individual del usuario",
|
||||
"enable-individual-user-smtp": "activar la configuración smtp individual del usuario",
|
||||
"user-smtp-general-help": "Cuando está desactivado, los usuarios no podrán configurar su propio SMTP. Todos los correos utilizarán la configuración SMTP global.",
|
||||
"global-smtp-settings": "Configuración SMTP Global",
|
||||
"global-smtp-settings-help-p1": "Estas configuraciones se aplican globalmente en toda su cuenta de {{appName}}. Si un usuario no ha configurado sus propios ajustes de entrega de correo, el sistema utilizará automáticamente la configuración SMTP global definida aquí.",
|
||||
"global-smtp-settings-help-p2": "Para garantizar una mejor entregabilidad de todos los correos de solicitud de firma, puede conectar su proveedor de correo preferido. Elija una de las siguientes opciones:",
|
||||
"global-connect-to-gmail-help": "Use una cuenta de Gmail para enviar correos de solicitud de firma directamente desde la bandeja de entrada de su organización. Esto mejora la entregabilidad, la reputación y la fiabilidad.",
|
||||
"global-connect-to-smtp-help": "Use su propio servidor SMTP para enviar correos a través de su dominio. Esto proporciona control total sobre su infraestructura de correo, mejora la entregabilidad y mantiene la coherencia de marca.",
|
||||
"global-smtp-settings-help-p3": "Si no se configuran ajustes a nivel de usuario, se aplicarán automáticamente estos ajustes globales."
|
||||
}
|
||||
"enter-email": "Ingrese correo electrónico"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
"login-page": "Page de connexion",
|
||||
"or": "OU",
|
||||
"signup-page": "Page d'inscription",
|
||||
"password-match-length": "Le nouveau mot de passe et la confirmation doivent correspondre",
|
||||
"password-length": "Le mot de passe doit être de plus de 8 caractères",
|
||||
"password-case": "Le mot de passe doit contenir une lettre majuscule, minuscule et un chiffre ",
|
||||
"password-special-char": "Le mot de passe doit contenir un caractère spécial",
|
||||
@@ -121,8 +120,7 @@
|
||||
"Teams": "Équipe",
|
||||
"Team-update-successfully": "Équipe mise à jour avec succès.",
|
||||
"Users": "Utilisateurs"
|
||||
},
|
||||
"Global SMTP": "SMTP Global"
|
||||
}
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "Besoin de votre signature",
|
||||
@@ -202,8 +200,7 @@
|
||||
"daily-mail-quota": "Quota d'e-mails quotidien",
|
||||
"Save as template": "Enregistrer comme modèle",
|
||||
"Fix & resend": "Corriger et renvoyer",
|
||||
"Kiosk Mode": "Mode Kiosque",
|
||||
"Reset password": "Réinitialiser le mot de passe"
|
||||
"Kiosk Mode": "Mode Kiosque"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
@@ -388,8 +385,7 @@
|
||||
"dropdown": "dérouler",
|
||||
"radio button": "bouton radio",
|
||||
"image": "image",
|
||||
"email": "e-mail",
|
||||
"number": "nombre"
|
||||
"email": "e-mail"
|
||||
},
|
||||
"fields": "Des champs",
|
||||
"recipients": "Destinataires",
|
||||
@@ -430,7 +426,7 @@
|
||||
"initial-alert": "Mon initiale est introuvable",
|
||||
"copy-title": "Copier le widget vers",
|
||||
"contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?",
|
||||
"reset-password-alert-1": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie",
|
||||
"reset-password": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie",
|
||||
"reset-password-alert-2": "Veuillez configurer l'adaptateur de messagerie",
|
||||
"reset-password-alert-3": "Réinitialisez votre mot de passe",
|
||||
"faild-animation": "Échec du chargement de l'animation",
|
||||
@@ -446,19 +442,6 @@
|
||||
"maximum-check": "Contrôle maximum",
|
||||
"cell-count": "numération cellulaire",
|
||||
"default-value": "Valeur par défaut",
|
||||
"formula": "Formule",
|
||||
"formula-placeholder": "Créez une formule à l'aide des widgets numériques",
|
||||
"formula-no-widgets": "Aucun autre widget numérique n'est encore attribué à ce signataire.",
|
||||
"supported-operators": "Opérateurs pris en charge :",
|
||||
"formula-helper": "Cliquez sur une puce de widget numérique pour l'insérer (ex. {{number-1}} + {{number-2}}).",
|
||||
"formula-error-letter": "Encadrez les noms de widget avec des doubles accolades (ex. {{number-1}}).",
|
||||
"formula-error-characters": "En dehors des accolades, seuls les chiffres et +, -, *, /, (, ) sont autorisés.",
|
||||
"formula-error-braces": "Chaque {{ ouvrante doit avoir une }} correspondante.",
|
||||
"formula-error-operators": "Lorsque vous utilisez des opérateurs ou des parenthèses, ajoutez au moins deux valeurs.",
|
||||
"formula-error-leading-operator": "Les formules ne peuvent pas commencer par des opérateurs.",
|
||||
"decimal-places": "Décimales",
|
||||
"number-of-decimal-places": "Nombre de décimales",
|
||||
"decimal-places-helper": "Définir sur 0 pour les nombres entiers",
|
||||
"select": "Sélectionner",
|
||||
"read-only": "Est en lecture seule",
|
||||
"hide-labels": "Masquer les étiquettes",
|
||||
@@ -918,10 +901,6 @@
|
||||
"thanks-for-feedback": "Merci pour votre retour 🙏",
|
||||
"share-your-feedback": "Partagez votre avis",
|
||||
"share-your-review": "Partagez votre avis",
|
||||
"please-select-rating": "Veuillez sélectionner une note",
|
||||
"feedback-optional": "Retour (optionnel)",
|
||||
"feedback-saved": "Le retour a été enregistré.",
|
||||
"feedback-save-error": "Impossible d'enregistrer le retour, veuillez réessayer.",
|
||||
"date-format": "Format de date",
|
||||
"document-deleted": "Le document a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
|
||||
"save-as-template-?": "Êtes-vous sûr de vouloir enregistrer ce document comme modèle ?",
|
||||
@@ -1237,42 +1216,6 @@
|
||||
"p1": "Cela garantira que le certificat de finalisation est inclus dans le document PDF final. Cependant, veuillez noter qu'une fois fusionné, le certificat ne peut plus être séparé du document principal.",
|
||||
"p2": "Si vous choisissez de ne pas fusionner, le certificat de finalisation sera fourni sous forme de fichier PDF distinct accompagné du document signé."
|
||||
},
|
||||
"read-only-date-error": "Le widget de date en lecture seule doit avoir une date par défaut.",
|
||||
"set-date": "Définir la date",
|
||||
"set-today": "Date de signature",
|
||||
"enter-name": "Saisir le nom",
|
||||
"enter-email": "Saisir l'e-mail",
|
||||
"subscribe-to-opensign-msg": "Abonnez-vous à {{appName}} et profitez de signatures numériques gratuites illimitées.",
|
||||
"duplicate-subscribe-msg": "Dupliquer vous permet de créer une copie exacte du modèle sélectionné, que vous pouvez réutiliser ou modifier sans affecter l'original.",
|
||||
"save-as-template-msg": "Enregistrez ce document comme modèle réutilisable que vous pourrez utiliser à nouveau pour de futurs documents.",
|
||||
"public-credit-alert": "Signer via ce lien consomme vos crédits e-mail gratuits. En tant qu'utilisateur gratuit, vous disposez de 15 crédits e-mail par mois afin d’empêcher les spammeurs d’abuser de nos systèmes. Pour profiter de limites plus élevées et d'un accès ininterrompu, abonnez-vous aux forfaits payants de <1>OpenSign™.</1>",
|
||||
"know-more-about": "En savoir plus sur",
|
||||
"free-unlimited-signatures": "Signatures illimitées gratuites",
|
||||
"email-quota-updated": "Quota quotidien d'e-mails mis à jour avec succès.",
|
||||
"password-has-been-reset": "Le mot de passe a été réinitialisé.",
|
||||
"Unauthorized to reset your own password.": "Non autorisé à réinitialiser votre propre mot de passe.",
|
||||
"User not found or not allowed.": "Utilisateur introuvable ou non autorisé.",
|
||||
"User not found.": "Utilisateur introuvable.",
|
||||
"Admin user tenant not found.": "Locataire administrateur introuvable.",
|
||||
"User is not authenticated.": "L'utilisateur n'est pas authentifié.",
|
||||
"Please provide required parameters.": "Veuillez fournir les paramètres requis.",
|
||||
"reset-password": "Réinitialiser le mot de passe utilisateur",
|
||||
"enter-strong-password": "Entrez un mot de passe sécurisé ou générez-en un automatiquement.",
|
||||
"autogenerate": "Générer automatiquement",
|
||||
"extend": "Prolonger",
|
||||
"console-general-help": "Pour permettre aux utilisateurs individuels de personnaliser leurs propres modèles d'e-mails, accédez à",
|
||||
"console-mail-help": "Pour mettre à jour les modèles d'e-mails à l'échelle de l'entreprise, allez dans",
|
||||
"admin-setting-disabled": "Ce paramètre est désactivé. Veuillez contacter l'administrateur pour obtenir de l'aide.",
|
||||
"company-email-settings": "Paramètres de messagerie de l'entreprise",
|
||||
"allow-email-template-individually": "Autoriser la personnalisation individuelle des modèles d'e-mails pour chaque utilisateur ",
|
||||
"user-email-template-general-help": "Si cette option est désactivée, les utilisateurs ne pourront plus personnaliser leurs modèles d'e-mails.",
|
||||
"disable-individual-user-smtp": "désactiver les paramètres smtp individuels de l'utilisateur",
|
||||
"enable-individual-user-smtp": "activer les paramètres smtp individuels de l'utilisateur",
|
||||
"user-smtp-general-help": "Lorsqu'elle est désactivée, les utilisateurs ne pourront pas configurer leur propre SMTP. Tous les e-mails utiliseront la configuration SMTP globale.",
|
||||
"global-smtp-settings": "Paramètres SMTP globaux",
|
||||
"global-smtp-settings-help-p1": "Ces paramètres s'appliquent globalement à l'ensemble de votre compte {{appName}}. Si un utilisateur n'a pas configuré ses propres paramètres de messagerie, le système utilisera automatiquement les paramètres SMTP globaux définis ici.",
|
||||
"global-smtp-settings-help-p2": "Pour assurer une meilleure délivrabilité de toutes les demandes de signature, vous pouvez connecter votre fournisseur de messagerie préféré. Choisissez l'une des options suivantes :",
|
||||
"global-connect-to-gmail-help": "Utilisez un compte Gmail pour envoyer des demandes de signature directement depuis la boîte de réception de votre organisation. Cela améliore la délivrabilité, la réputation et la fiabilité.",
|
||||
"global-connect-to-smtp-help": "Utilisez votre propre serveur SMTP pour envoyer des e-mails via votre domaine. Cela offre un contrôle total sur votre infrastructure de messagerie, améliore la délivrabilité et garantit une cohérence de marque.",
|
||||
"global-smtp-settings-help-p3": "Si aucun paramètre utilisateur individuel n'est configuré, ces paramètres globaux seront appliqués automatiquement."
|
||||
}
|
||||
"enter-email": "Saisir l'e-mail"
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
"login-page": "लॉगिन पृष्ठ",
|
||||
"or": "या",
|
||||
"signup-page": "साइनअप पृष्ठ",
|
||||
"password-match-length": "नया पासवर्ड और पुष्टि पासवर्ड मेल खाना चाहिए",
|
||||
"password-length": "पासवर्ड 8 वर्ण लंबा होना चाहिए",
|
||||
"password-case": "पासवर्ड में अपरकेस अक्षर, लोअरकेस अक्षर, अंक होना चाहिए",
|
||||
"password-special-char": " पासवर्ड में विशेष वर्ण होना चाहिए",
|
||||
@@ -121,8 +120,7 @@
|
||||
"Teams": "टीमें",
|
||||
"Team-update-successfully": "टीम सफलतापूर्वक अपडेट की गई।",
|
||||
"Users": "उपयोगकर्ता"
|
||||
},
|
||||
"Global SMTP": "ग्लोबल SMTP"
|
||||
}
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "आपके हस्ताक्षर की आवश्यकता है",
|
||||
@@ -167,8 +165,7 @@
|
||||
"daily-mail-quota": "दैनिक ईमेल कोटा",
|
||||
"Save as template": "टेम्पलेट के रूप में सहेजें",
|
||||
"Fix & resend": "ठीक करें और पुनः भेजें",
|
||||
"Kiosk Mode": "कियोस्क मोड",
|
||||
"Reset password": "पासवर्ड रीसेट करें"
|
||||
"Kiosk Mode": "कियोस्क मोड"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "क्रमांक",
|
||||
@@ -388,8 +385,7 @@
|
||||
"dropdown": "ड्रॉपडाउन",
|
||||
"radio button": "रेडियो बटन",
|
||||
"image": "छवि",
|
||||
"email": "ईमेल",
|
||||
"number": "संख्या"
|
||||
"email": "ईमेल"
|
||||
},
|
||||
"fields": "क्षेत्र",
|
||||
"recipients": "प्राप्तकर्ता",
|
||||
@@ -446,19 +442,6 @@
|
||||
"maximum-check": "अधिकतम जाँच",
|
||||
"cell-count": "सेल्स संख्या",
|
||||
"default-value": "डिफ़ॉल्ट मान",
|
||||
"formula": "सूत्र",
|
||||
"formula-placeholder": "संख्या विजेट्स का उपयोग करके एक सूत्र बनाएं",
|
||||
"formula-no-widgets": "इस हस्ताक्षरकर्ता को अभी तक कोई अन्य संख्या विजेट असाइन नहीं किया गया है।",
|
||||
"supported-operators": "समर्थित ऑपरेटर:",
|
||||
"formula-helper": "सूची में से संख्या विजेट चिप पर क्लिक करके उसे जोड़ें (उदा. {{number-1}} + {{number-2}})।",
|
||||
"formula-error-letter": "विजेट नामों को दोहरी घुंघराली ब्रैकेट में रखें (उदा. {{number-1}})।",
|
||||
"formula-error-characters": "ब्रैकेट के बाहर केवल संख्याएँ और +, -, *, /, (, ) की अनुमति है।",
|
||||
"formula-error-braces": "हर खुलने वाली {{ के लिए एक मिलान करने वाली }} होना चाहिए।",
|
||||
"formula-error-operators": "ऑपरेटर या कोष्ठक का उपयोग करते समय कम से कम दो मान जोड़ें।",
|
||||
"formula-error-leading-operator": "सूत्र ऑपरेटर से शुरू नहीं हो सकते।",
|
||||
"decimal-places": "दशमलव स्थान",
|
||||
"number-of-decimal-places": "दशमलव स्थानों की संख्या",
|
||||
"decimal-places-helper": "पूर्णांक के लिए 0 पर सेट करें",
|
||||
"select": "चुनें",
|
||||
"read-only": "सिर्फ पढ़ने के लिए है",
|
||||
"hide-labels": "लेबल छिपाएँ",
|
||||
@@ -918,10 +901,6 @@
|
||||
"thanks-for-feedback": "आपकी प्रतिक्रिया के लिए धन्यवाद 🙏",
|
||||
"share-your-feedback": "अपनी प्रतिक्रिया साझा करें",
|
||||
"share-your-review": "अपनी समीक्षा साझा करें",
|
||||
"please-select-rating": "कृपया एक रेटिंग चुनें",
|
||||
"feedback-optional": "प्रतिक्रिया (वैकल्पिक)",
|
||||
"feedback-saved": "प्रतिक्रिया सहेज ली गई है।",
|
||||
"feedback-save-error": "प्रतिक्रिया सहेजी नहीं जा सकी, कृपया पुनः प्रयास करें।",
|
||||
"date-format": "दिनांक प्रारूप",
|
||||
"document-deleted": "दस्तावेज़ हटा दिया गया है या आपके पास पहुंच नहीं है। कृपया प्रेषक से संपर्क करें।",
|
||||
"save-as-template-?": "क्या आप वाकई इस दस्तावेज़ को टेम्पलेट के रूप में सहेजना चाहते हैं?",
|
||||
@@ -1237,42 +1216,7 @@
|
||||
"p1": "यह सुनिश्चित करेगा कि पूर्णता प्रमाणपत्र अंतिम PDF दस्तावेज़ में शामिल हो। हालाँकि, कृपया ध्यान दें कि एक बार मिलाने के बाद, प्रमाणपत्र को मुख्य दस्तावेज़ से अलग नहीं किया जा सकता।",
|
||||
"p2": "यदि आप मिलाना नहीं चुनते हैं, तो पूर्णता प्रमाणपत्र हस्ताक्षरित दस्तावेज़ के साथ एक अलग PDF फ़ाइल के रूप में प्रदान किया जाएगा।"
|
||||
},
|
||||
"read-only-date-error": "रीड-ओनली दिनांक विजेट में एक डिफ़ॉल्ट दिनांक होना आवश्यक है।",
|
||||
"set-date": "दिनांक सेट करें",
|
||||
"set-today": "हस्ताक्षर की तिथि",
|
||||
"enter-name": "नाम दर्ज करें",
|
||||
"enter-email": "ईमेल दर्ज करें",
|
||||
"subscribe-to-opensign-msg": "{{appName}} की सदस्यता लें और असीमित निःशुल्क डिजिटल हस्ताक्षरों का आनंद लें।",
|
||||
"duplicate-subscribe-msg": "डुप्लिकेट करने से आप चयनित टेम्पलेट की एक सटीक प्रति बना सकते हैं, जिसे आप पुनः उपयोग या संशोधित कर सकते हैं बिना मूल को प्रभावित किए।",
|
||||
"save-as-template-msg": "इस दस्तावेज़ को एक पुन: प्रयोज्य टेम्पलेट के रूप में सहेजें जिसे आप भविष्य के दस्तावेज़ों के लिए फिर से उपयोग कर सकते हैं।",
|
||||
"public-credit-alert": "इस लिंक के माध्यम से हस्ताक्षर करने पर आपके निःशुल्क ईमेल क्रेडिट का उपभोग होगा। एक निःशुल्क उपयोगकर्ता के रूप में, आपको स्पैमर्स को हमारी प्रणाली का दुरुपयोग करने से रोकने के लिए प्रति माह 15 ईमेल क्रेडिट आवंटित किए जाते हैं। उच्च सीमा और निर्बाध पहुँच का आनंद लेने के लिए, <1>OpenSign™ पेड प्लान</1> की सदस्यता लें।",
|
||||
"know-more-about": "इसके बारे में और जानें",
|
||||
"free-unlimited-signatures": "मुफ़्त असीमित हस्ताक्षर",
|
||||
"email-quota-updated": "दैनिक ईमेल कोटा सफलतापूर्वक अपडेट किया गया।",
|
||||
"password-has-been-reset": "पासवर्ड रीसेट कर दिया गया है।",
|
||||
"Unauthorized to reset your own password.": "अपना पासवर्ड रीसेट करने के लिए अधिकृत नहीं है।",
|
||||
"User not found or not allowed.": "उपयोगकर्ता नहीं मिला या अनुमति नहीं है।",
|
||||
"User not found.": "उपयोगकर्ता नहीं मिला।",
|
||||
"Admin user tenant not found.": "एडमिन उपयोगकर्ता टेनेंट नहीं मिला।",
|
||||
"User is not authenticated.": "उपयोगकर्ता प्रमाणीकृत नहीं है।",
|
||||
"Please provide required parameters.": "कृपया आवश्यक पैरामीटर प्रदान करें।",
|
||||
"reset-password": "उपयोगकर्ता पासवर्ड रीसेट करें",
|
||||
"enter-strong-password": "एक मज़बूत पासवर्ड दर्ज करें या स्वचालित रूप से उत्पन्न करें।",
|
||||
"autogenerate": "स्वतः उत्पन्न करें",
|
||||
"extend": "बढ़ाएँ",
|
||||
"console-general-help": "व्यक्तिगत उपयोगकर्ताओं को अपनी स्वयं की ईमेल टेम्पलेट्स अनुकूलित करने की अनुमति देने के लिए, जाएँ",
|
||||
"console-mail-help": "कंपनी-व्यापी ईमेल टेम्पलेट्स अपडेट करने के लिए, जाएँ",
|
||||
"admin-setting-disabled": "यह सेटिंग अक्षम है। सहायता के लिए कृपया व्यवस्थापक से संपर्क करें।",
|
||||
"company-email-settings": "कंपनी ईमेल सेटिंग्स",
|
||||
"allow-email-template-individually": "उपयोगकर्ताओं को व्यक्तिगत रूप से ईमेल टेम्पलेट अनुकूलित करने की अनुमति दें ",
|
||||
"user-email-template-general-help": "इसे निष्क्रिय करने पर उपयोगकर्ता अपनी ईमेल टेम्पलेट्स को अनुकूलित नहीं कर पाएंगे।",
|
||||
"disable-individual-user-smtp": "व्यक्तिगत उपयोगकर्ता SMTP सेटिंग्स अक्षम करें",
|
||||
"enable-individual-user-smtp": "व्यक्तिगत उपयोगकर्ता SMTP सेटिंग्स सक्षम करें",
|
||||
"user-smtp-general-help": "जब यह निष्क्रिय हो, तो उपयोगकर्ता अपनी स्वयं की SMTP सेटिंग्स कॉन्फ़िगर नहीं कर पाएंगे। सभी ईमेल ग्लोबल SMTP कॉन्फ़िगरेशन का उपयोग करेंगे।",
|
||||
"global-smtp-settings": "ग्लोबल SMTP सेटिंग्स",
|
||||
"global-smtp-settings-help-p1": "ये सेटिंग्स आपके पूरे {{appName}} खाते में वैश्विक रूप से लागू होती हैं। यदि किसी उपयोगकर्ता ने अपनी स्वयं की ईमेल डिलीवरी सेटिंग्स कॉन्फ़िगर नहीं की हैं, तो सिस्टम स्वचालित रूप से यहाँ परिभाषित ग्लोबल SMTP सेटिंग्स का उपयोग करेगा।",
|
||||
"global-smtp-settings-help-p2": "सभी हस्ताक्षर अनुरोध ईमेल की बेहतर इनबॉक्स डिलीवरी सुनिश्चित करने के लिए, आप अपना पसंदीदा ईमेल प्रदाता जोड़ सकते हैं। निम्न विकल्पों में से एक चुनें:",
|
||||
"global-connect-to-gmail-help": "अपनी संस्था की इनबॉक्स से सीधे हस्ताक्षर अनुरोध ईमेल भेजने के लिए Gmail खाते का उपयोग करें। यह डिलीवरी दर, प्रतिष्ठा और विश्वसनीयता में सुधार करता है।",
|
||||
"global-connect-to-smtp-help": "अपने डोमेन के माध्यम से ईमेल भेजने के लिए अपना स्वयं का SMTP सर्वर उपयोग करें। यह आपकी ईमेल संरचना पर पूर्ण नियंत्रण प्रदान करता है, डिलीवरी को बेहतर बनाता है और ब्रांड की निरंतरता बनाए रखता है।",
|
||||
"global-smtp-settings-help-p3": "यदि कोई व्यक्तिगत उपयोगकर्ता-स्तरीय सेटिंग्स कॉन्फ़िगर नहीं की गई हैं, तो ये वैश्विक सेटिंग्स स्वचालित रूप से लागू होंगी।"
|
||||
}
|
||||
"enter-email": "ईमेल दर्ज करें"
|
||||
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
"login-page": "Pagina di accesso",
|
||||
"or": "O",
|
||||
"signup-page": "Pagina di registrazione",
|
||||
"password-match-length": "La nuova password e la conferma devono corrispondere",
|
||||
"password-length": "La password deve contenere almeno 8 caratteri",
|
||||
"password-case": "La password deve contenere una lettera maiuscola, una minuscola e un numero",
|
||||
"password-special-char": "La password deve contenere un carattere speciale",
|
||||
@@ -121,8 +120,7 @@
|
||||
"Teams": "Team",
|
||||
"Team-update-successfully": "Team aggiornato con successo.",
|
||||
"Users": "Utenti"
|
||||
},
|
||||
"Global SMTP": "SMTP Globale"
|
||||
}
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "Necessita della tua firma",
|
||||
@@ -167,8 +165,7 @@
|
||||
"daily-mail-quota": "Quota e-mail giornaliera",
|
||||
"Save as template": "Salva come modello",
|
||||
"Fix & resend": "Correggi e reinvia",
|
||||
"Kiosk Mode": "Modalità Kiosk",
|
||||
"Reset password": "Reimposta password"
|
||||
"Kiosk Mode": "Modalità Kiosk"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nr.",
|
||||
@@ -265,7 +262,7 @@
|
||||
"my-signature": "La mia firma",
|
||||
"signature": "Firma",
|
||||
"upload-image": "Carica immagine",
|
||||
"clear": "Cancella",
|
||||
"clear": "Chiara",
|
||||
"upload-signature/Image": "Carica firma/immagine",
|
||||
"initials": "Iniziali",
|
||||
"API": "API",
|
||||
@@ -388,8 +385,7 @@
|
||||
"dropdown": "menu a tendina",
|
||||
"radio button": "pulsante di opzione",
|
||||
"image": "immagine",
|
||||
"email": "email",
|
||||
"number": "numero"
|
||||
"email": "email"
|
||||
},
|
||||
"fields": "Campi",
|
||||
"recipients": "Destinatari",
|
||||
@@ -446,19 +442,6 @@
|
||||
"maximum-check": "Controllo massimo",
|
||||
"cell-count": "conteggio delle cellule",
|
||||
"default-value": "Valore predefinita",
|
||||
"formula": "Formula",
|
||||
"formula-placeholder": "Crea una formula utilizzando i widget numerici",
|
||||
"formula-no-widgets": "Non ci sono ancora altri widget numerici assegnati a questo firmatario.",
|
||||
"supported-operators": "Operatori supportati:",
|
||||
"formula-helper": "Fai clic su una chip di widget numerico per inserirla (es. {{number-1}} + {{number-2}}).",
|
||||
"formula-error-letter": "Racchiudi i nomi dei widget tra doppie parentesi graffe (es. {{number-1}}).",
|
||||
"formula-error-characters": "Al di fuori delle parentesi sono consentiti solo numeri e +, -, *, /, (, ).",
|
||||
"formula-error-braces": "Ogni {{ di apertura deve avere una }} corrispondente.",
|
||||
"formula-error-operators": "Quando usi operatori o parentesi, aggiungi almeno due valori.",
|
||||
"formula-error-leading-operator": "Le formule non possono iniziare con operatori.",
|
||||
"decimal-places": "Cifre decimali",
|
||||
"number-of-decimal-places": "Numero di cifre decimali",
|
||||
"decimal-places-helper": "Imposta su 0 per numeri interi",
|
||||
"select": "Seleziona",
|
||||
"read-only": "È di sola lettura",
|
||||
"hide-labels": "Nascondi etichette",
|
||||
@@ -918,10 +901,6 @@
|
||||
"thanks-for-feedback": "Grazie per il tuo feedback 🙏",
|
||||
"share-your-feedback": "Condividi il tuo feedback",
|
||||
"share-your-review": "Condividi la tua recensione",
|
||||
"please-select-rating": "Si prega di selezionare una valutazione",
|
||||
"feedback-optional": "Feedback (opzionale)",
|
||||
"feedback-saved": "Il feedback è stato salvato.",
|
||||
"feedback-save-error": "Impossibile salvare il feedback, riprova.",
|
||||
"date-format": "Formato data",
|
||||
"document-deleted": "Il documento è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
|
||||
"save-as-template-?": "Sei sicuro di voler salvare questo documento come modello?",
|
||||
@@ -1237,42 +1216,6 @@
|
||||
"p1": "Questo garantirà che il certificato di completamento sia incluso nel documento PDF finale. Tuttavia, si noti che una volta unito, il certificato non può essere separato dal documento principale.",
|
||||
"p2": "Se scegli di non unire, il certificato di completamento sarà fornito come file PDF separato insieme al documento firmato."
|
||||
},
|
||||
"read-only-date-error": "Il widget data di sola lettura deve avere una data predefinita.",
|
||||
"set-date": "Imposta data",
|
||||
"set-today": "Data di firma",
|
||||
"enter-name": "Inserisci nome",
|
||||
"enter-email": "Inserisci e-mail",
|
||||
"subscribe-to-opensign-msg": "Abbonati a {{appName}} e goditi firme digitali gratuite illimitate.",
|
||||
"duplicate-subscribe-msg": "Duplicare consente di creare una copia esatta del modello selezionato, permettendoti di riutilizzarlo o modificarlo senza influire sull'originale.",
|
||||
"save-as-template-msg": "Salva questo documento come modello riutilizzabile che potrai usare di nuovo per documenti futuri.",
|
||||
"public-credit-alert": "Firmare tramite questo link consumerà i tuoi crediti e-mail gratuiti. Come utente gratuito, ti vengono assegnati 15 crediti e-mail al mese per impedire agli spammer di abusare dei nostri sistemi. Per usufruire di limiti più elevati e accesso ininterrotto, abbonati ai piani a pagamento di <1>OpenSign™.</1>",
|
||||
"know-more-about": "Scopri di più su",
|
||||
"free-unlimited-signatures": "Firme illimitate gratuite",
|
||||
"email-quota-updated": "Quota e-mail giornaliera aggiornata con successo.",
|
||||
"password-has-been-reset": "La password è stata reimpostata.",
|
||||
"Unauthorized to reset your own password.": "Non sei autorizzato a reimpostare la tua password.",
|
||||
"User not found or not allowed.": "Utente non trovato o non autorizzato.",
|
||||
"User not found.": "Utente non trovato.",
|
||||
"Admin user tenant not found.": "Tenant utente amministratore non trovato.",
|
||||
"User is not authenticated.": "Utente non autenticato.",
|
||||
"Please provide required parameters.": "Fornisci i parametri richiesti.",
|
||||
"reset-password": "Reimposta password utente",
|
||||
"enter-strong-password": "Inserisci una password sicura o generane una automaticamente.",
|
||||
"autogenerate": "Genera automaticamente",
|
||||
"extend": "Estendi",
|
||||
"console-general-help": "Per consentire ai singoli utenti di personalizzare i propri modelli di e-mail, vai a",
|
||||
"console-mail-help": "Per aggiornare i modelli di e-mail aziendali, vai a",
|
||||
"admin-setting-disabled": "Questa impostazione è disabilitata. Contatta l'amministratore per assistenza.",
|
||||
"company-email-settings": "Impostazioni e-mail aziendali",
|
||||
"allow-email-template-individually": "Consenti la personalizzazione individuale dei modelli di e-mail per gli utenti ",
|
||||
"user-email-template-general-help": "Disabilitando questa opzione, gli utenti non potranno più personalizzare i loro modelli di e-mail.",
|
||||
"disable-individual-user-smtp": "disabilita le impostazioni smtp individuali dell'utente",
|
||||
"enable-individual-user-smtp": "abilita le impostazioni smtp individuali dell'utente",
|
||||
"user-smtp-general-help": "Quando disattivato, gli utenti non potranno configurare il proprio SMTP. Tutte le e-mail utilizzeranno la configurazione SMTP globale.",
|
||||
"global-smtp-settings": "Impostazioni SMTP Globali",
|
||||
"global-smtp-settings-help-p1": "Queste impostazioni si applicano globalmente all'intero account {{appName}}. Se un utente non ha configurato le proprie impostazioni di consegna e-mail, il sistema utilizzerà automaticamente le impostazioni SMTP globali definite qui.",
|
||||
"global-smtp-settings-help-p2": "Per garantire una migliore consegna delle e-mail di richiesta firma, puoi collegare il tuo provider di posta preferito. Scegli una delle seguenti opzioni:",
|
||||
"global-connect-to-gmail-help": "Utilizza un account Gmail per inviare e-mail di richiesta firma direttamente dalla casella di posta della tua organizzazione. Migliora la consegna, la reputazione e l'affidabilità.",
|
||||
"global-connect-to-smtp-help": "Utilizza il tuo server SMTP per inviare e-mail tramite il tuo dominio. Questo offre pieno controllo sulla tua infrastruttura e-mail, migliora la consegna e mantiene la coerenza del marchio.",
|
||||
"global-smtp-settings-help-p3": "Se non sono configurate impostazioni a livello utente, verranno applicate automaticamente queste impostazioni globali."
|
||||
}
|
||||
"enter-email": "Inserisci e-mail"
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import Loader from "./primitives/Loader";
|
||||
import UserList from "./pages/UserList";
|
||||
import { serverUrl_fn } from "./constant/appinfo";
|
||||
import DocSuccessPage from "./pages/DocSuccessPage";
|
||||
import ValidateSession from "./primitives/ValidateSession";
|
||||
import DragProvider from "./components/DragProivder";
|
||||
import Title from "./components/Title";
|
||||
const DebugPdf = lazyWithRetry(() => import("./pages/DebugPdf"));
|
||||
@@ -87,7 +88,13 @@ function App() {
|
||||
path="/forgetpassword"
|
||||
element={<Lazy Page={ForgetPassword} />}
|
||||
/>
|
||||
<Route element={<HomeLayout />}>
|
||||
<Route
|
||||
element={
|
||||
<ValidateSession>
|
||||
<HomeLayout />
|
||||
</ValidateSession>
|
||||
}
|
||||
>
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route
|
||||
path="/changepassword"
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Parse from "parse";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { emailRegex } from "../constant/const";
|
||||
|
||||
const AddSigner = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [addYourself, setAddYourself] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [isUserExist, setIsUserExist] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
checkUserExist();
|
||||
}, []);
|
||||
// Load user details from localStorage when the component mounts
|
||||
useEffect(() => {
|
||||
const savedUserDetails = JSON.parse(
|
||||
localStorage.getItem("UserInformation")
|
||||
);
|
||||
if (savedUserDetails && addYourself) {
|
||||
setName(savedUserDetails.name);
|
||||
setPhone(savedUserDetails?.phone || "");
|
||||
setEmail(savedUserDetails.email?.toLowerCase()?.replace(/\s/g, ""));
|
||||
}
|
||||
}, [addYourself]);
|
||||
|
||||
const checkUserExist = async () => {
|
||||
const user = Parse.User.current();
|
||||
try {
|
||||
const query = new Parse.Query("contracts_Contactbook");
|
||||
query.equalTo("CreatedBy", user);
|
||||
query.notEqualTo("IsDeleted", true);
|
||||
query.equalTo("Email", user.getEmail());
|
||||
const res = await query.first();
|
||||
// console.log(res);
|
||||
if (!res) {
|
||||
setIsUserExist(true);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
}
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!emailRegex.test(email)) {
|
||||
alert(t("valid-email-alert"));
|
||||
} else {
|
||||
setIsLoader(true);
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
try {
|
||||
const user = Parse.User.current();
|
||||
const query = new Parse.Query("contracts_Contactbook");
|
||||
query.equalTo("CreatedBy", user);
|
||||
query.notEqualTo("IsDeleted", true);
|
||||
query.equalTo("Email", email);
|
||||
const res = await query.first();
|
||||
// console.log(res);
|
||||
if (!res) {
|
||||
const contactQuery = new Parse.Object("contracts_Contactbook");
|
||||
contactQuery.set("Name", name);
|
||||
if (phone) {
|
||||
contactQuery.set("Phone", phone);
|
||||
}
|
||||
contactQuery.set("Email", email);
|
||||
contactQuery.set("UserRole", "contracts_Guest");
|
||||
|
||||
contactQuery.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", name);
|
||||
_user.set("username", email);
|
||||
_user.set("email", email);
|
||||
_user.set("password", email);
|
||||
if (phone) {
|
||||
_user.set("phone", phone);
|
||||
}
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey],
|
||||
email: parseData?.Email
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const currentUser = Parse.User.current();
|
||||
contactQuery.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
contactQuery.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);
|
||||
|
||||
contactQuery.setACL(acl);
|
||||
const res = await contactQuery.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.details) {
|
||||
props.details({
|
||||
value: parseData[props.valueKey],
|
||||
label: parseData[props.displayKey],
|
||||
email: parseData?.Email
|
||||
});
|
||||
}
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsLoader(false);
|
||||
// Reset the form fields
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
alert(t("add-signer-alert"));
|
||||
setIsLoader(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in fetch contact", err);
|
||||
setIsLoader(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
const handleAddYourselfChange = () => {
|
||||
if (addYourself) {
|
||||
setAddYourself(false);
|
||||
setName("");
|
||||
setPhone("");
|
||||
setEmail("");
|
||||
} else {
|
||||
setAddYourself(true);
|
||||
}
|
||||
};
|
||||
const handleReset = () => {
|
||||
setAddYourself(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full p-[20px]">
|
||||
{isLoader && (
|
||||
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto p-2 text-base-content">
|
||||
<p className="mb-3 text-xs text-gray-500">{t("add-signer-note")}</p>
|
||||
{isUserExist && (
|
||||
<div className="mb-3 flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="addYourself"
|
||||
checked={addYourself}
|
||||
onChange={handleAddYourselfChange}
|
||||
className="op-checkbox op-checkbox-sm"
|
||||
/>
|
||||
<label htmlFor="addYourself" className="ml-2 mb-0">
|
||||
{t("add-yourself")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="name" className="block text-xs font-semibold">
|
||||
{t("name")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
required
|
||||
disabled={addYourself}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-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"
|
||||
placeholder={t("enter-name")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="block text-xs font-semibold">
|
||||
{t("email")}
|
||||
<span className="text-[red] text-[13px]"> *</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) =>
|
||||
setEmail(e.target.value?.toLowerCase()?.replace(/\s/g, ""))
|
||||
}
|
||||
required
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
disabled={addYourself}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
placeholder={t("enter-email")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="phone" className="block text-xs font-semibold">
|
||||
{t("phone")}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="phone"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
disabled={addYourself}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
placeholder={t("phone-optional")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 flex gap-x-2 justify-start">
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleReset()}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
{t("reset")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddSigner;
|
||||
@@ -42,7 +42,7 @@ const ColumnSelector = ({
|
||||
type="checkbox"
|
||||
checked={selected.includes(col)}
|
||||
onChange={() => handleChange(col)}
|
||||
className="mb-0 cursor-pointer"
|
||||
className="mb-0"
|
||||
/>
|
||||
<span className="whitespace-nowrap ml-1">
|
||||
{t(`report-heading.${col}`, { defaultValue: col })}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { TouchBackend } from "react-dnd-touch-backend";
|
||||
import {
|
||||
DndProvider,
|
||||
MouseTransition
|
||||
TouchTransition,
|
||||
MouseTransition,
|
||||
Preview
|
||||
} from "react-dnd-multi-backend";
|
||||
import DragElement from "./pdf/DragElement";
|
||||
import LazyPage from "../primitives/LazyPage";
|
||||
import { GuidelinesProvider } from "../context/GuidelinesContext";
|
||||
|
||||
@@ -12,13 +16,33 @@ const HTML5toTouch = {
|
||||
id: "html5",
|
||||
backend: HTML5Backend,
|
||||
transition: MouseTransition
|
||||
},
|
||||
{
|
||||
id: "touch",
|
||||
backend: TouchBackend,
|
||||
options: { enableMouseEvents: true },
|
||||
preview: true,
|
||||
transition: TouchTransition
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/* This handling in only for devices which support touch */
|
||||
const generatePreview = (props) => {
|
||||
const { item, style } = props;
|
||||
const newStyle = { ...style };
|
||||
|
||||
return (
|
||||
<div style={newStyle}>
|
||||
<DragElement {...item} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function DragProvider({ Page, lazy = false }) {
|
||||
return (
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Preview>{generatePreview}</Preview>
|
||||
<GuidelinesProvider>
|
||||
{lazy ? <LazyPage Page={Page} /> : <Page />}
|
||||
</GuidelinesProvider>
|
||||
|
||||
@@ -13,8 +13,7 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSidebar } from "../redux/reducers/sidebarReducer";
|
||||
import { sessionStatus } from "../redux/reducers/userReducer";
|
||||
import { toggleSidebar } from "../redux/reducers/sidebarReducer.js";
|
||||
|
||||
const Header = ({ isConsole, setIsLoggingOut }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
@@ -68,8 +67,6 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("Err while logging out", err);
|
||||
} finally {
|
||||
dispatch(sessionStatus(true));
|
||||
}
|
||||
let appdata = localStorage.getItem("userSettings");
|
||||
let applogo = localStorage.getItem("appLogo");
|
||||
@@ -141,10 +138,7 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 ml-2">
|
||||
<div
|
||||
onClick={() => navigate("/dashboard/35KBoSgoAK")}
|
||||
className="h-[25px] md:h-[40px] w-auto overflow-hidden cursor-pointer"
|
||||
>
|
||||
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
||||
{applogo && (
|
||||
<img
|
||||
className="object-contain h-full w-auto"
|
||||
|
||||
@@ -7,6 +7,7 @@ import Loader from "../../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function DashboardReport(props) {
|
||||
const { t } = useTranslation();
|
||||
// console.log("props ", props)
|
||||
const [List, setList] = useState([]);
|
||||
const [isLoader, setIsLoader] = useState(true);
|
||||
const [reportName, setReportName] = useState("");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, Suspense } from "react";
|
||||
import React, { Suspense } from "react";
|
||||
import { lazyWithRetry } from "../../utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const DashboardButton = lazyWithRetry(() => import("./DashboardButton"));
|
||||
@@ -20,7 +20,6 @@ const buttonList = [
|
||||
];
|
||||
const GetDashboard = (props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const Button = ({ label, redirectId, redirectType, icon }) => (
|
||||
<DashboardButton
|
||||
Icon={icon}
|
||||
@@ -60,9 +59,7 @@ const GetDashboard = (props) => {
|
||||
<div data-tut={col.widget.data.tourSection}>
|
||||
<Suspense fallback={<div>please wait</div>}>
|
||||
<div className="mb-3 md:mb-0">
|
||||
<DashboardReport
|
||||
Record={col.widget}
|
||||
/>
|
||||
<DashboardReport Record={col.widget} />
|
||||
</div>
|
||||
</Suspense>
|
||||
</div>
|
||||
@@ -96,9 +93,7 @@ const GetDashboard = (props) => {
|
||||
return (
|
||||
<Suspense fallback={<div>please wait</div>}>
|
||||
<div className="mb-3 md:mb-0">
|
||||
<DashboardReport
|
||||
Record={col.widget}
|
||||
/>
|
||||
<DashboardReport Record={col.widget} />
|
||||
</div>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
@@ -71,9 +71,11 @@ function DriveBody(props) {
|
||||
});
|
||||
props.setPdfData(updatedData);
|
||||
props.sortingData(null, null, updatedData);
|
||||
try {
|
||||
await axios.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${docId}`,
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem(
|
||||
"baseUrl"
|
||||
)}classes/contracts_Document/${docId}`,
|
||||
updateName,
|
||||
{
|
||||
headers: {
|
||||
@@ -82,14 +84,18 @@ function DriveBody(props) {
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
)
|
||||
.then(() => {
|
||||
// const res = result.data;
|
||||
// console.log("res", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Err ", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -239,6 +245,7 @@ function DriveBody(props) {
|
||||
)
|
||||
|
||||
.then((Listdata) => {
|
||||
// console.log("Listdata ", Listdata);
|
||||
const res = Listdata.data;
|
||||
if (res) {
|
||||
const updatedData = props.pdfData.filter(
|
||||
|
||||
@@ -3,8 +3,6 @@ import ModalUi from "../../primitives/ModalUi";
|
||||
import { EmailBody } from "./EmailBody";
|
||||
import {
|
||||
contractDocument,
|
||||
defaultMailBody,
|
||||
defaultMailSubject,
|
||||
sendEmailToSigners
|
||||
} from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -103,10 +101,7 @@ function CustomizeMail(props) {
|
||||
<div
|
||||
className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary"
|
||||
onClick={() => {
|
||||
props?.setCustomizeMail({
|
||||
subject: defaultMailSubject,
|
||||
body: defaultMailBody
|
||||
});
|
||||
props?.setCustomizeMail(props?.defaultMail);
|
||||
}}
|
||||
>
|
||||
<span>{t("reset-to-default")}</span>
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { useDragLayer } from "react-dnd";
|
||||
import { defaultWidthHeight } from "../../constant/Utils";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
function DragGuideLinesLayer(props) {
|
||||
const { showGuidelines } = props;
|
||||
const prevDragState = useRef({ isDragging: false, x: 0, y: 0 });
|
||||
const { isDraggingWidgets, offset, itemType } = useDragLayer((monitor) => ({
|
||||
isDraggingWidgets: monitor.isDragging(),
|
||||
offset: monitor.getClientOffset(),
|
||||
itemType: monitor.getItem()
|
||||
}));
|
||||
useEffect(() => {
|
||||
if (!offset) return;
|
||||
//getting container by id
|
||||
const container = document.getElementById("container");
|
||||
if (!container) return;
|
||||
if (itemType?.text) {
|
||||
// Calculate the position of the dragged element relative to the container
|
||||
// Get the container's bounding rectangle (position and size in the viewport)
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
|
||||
// Compute the X and Y coordinates of the dragged item inside the container
|
||||
// by subtracting the container's top-left offset from the current drag offset
|
||||
const x = offset.x - containerRect.left;
|
||||
const y = offset.y - containerRect.top;
|
||||
|
||||
// Check if there’s any change in the dragging state or the dragged element’s position
|
||||
// Compare the current drag state with the previously stored one
|
||||
const hasStateChanged =
|
||||
prevDragState.current.isDragging !== isDraggingWidgets || // Drag started or stopped
|
||||
prevDragState.current.x !== x || // X position changed
|
||||
prevDragState.current.y !== y; // Y position changed
|
||||
|
||||
if (hasStateChanged) {
|
||||
// Update the previous drag state with the latest values
|
||||
prevDragState.current = { isDragging: isDraggingWidgets, x, y };
|
||||
|
||||
if (isDraggingWidgets) {
|
||||
// Adjust the current drag coordinates relative to the initial widgets button position
|
||||
const getXPosition = props?.signBtnPosition?.[0]
|
||||
? x - props.signBtnPosition[0].xPos
|
||||
: x;
|
||||
const getYPosition = props?.signBtnPosition?.[0]
|
||||
? y - props.signBtnPosition[0].yPos
|
||||
: y;
|
||||
|
||||
// Get the default width and height of the dragged checkbox/radio widget type
|
||||
const el = document.getElementById("checkbox&radio-preview");
|
||||
let rect;
|
||||
if (el) {
|
||||
rect = el.getBoundingClientRect();
|
||||
}
|
||||
const widget = {
|
||||
type: itemType?.text,
|
||||
width: defaultWidthHeight(itemType?.text).width,
|
||||
height: defaultWidthHeight(itemType?.text).height
|
||||
};
|
||||
|
||||
const getWidth =
|
||||
rect?.width || props?.posWidth(widget, props?.isSignYourself);
|
||||
const getHeight =
|
||||
rect?.height || props?.posHeight(widget, props?.isSignYourself);
|
||||
|
||||
// Display the alignment guidelines based on the current drag position and widget size
|
||||
showGuidelines(true, getXPosition, getYPosition, getWidth, getHeight);
|
||||
} else {
|
||||
// Hide guidelines when dragging stops
|
||||
showGuidelines(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isDraggingWidgets, offset, itemType, showGuidelines]);
|
||||
}
|
||||
|
||||
export default DragGuideLinesLayer;
|
||||
@@ -31,6 +31,7 @@ function EmailComponent({
|
||||
setIsLoading(true);
|
||||
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
|
||||
const sendmail = await Parse.Cloud.run("forwarddoc", params);
|
||||
console.log("sendmail ", sendmail);
|
||||
if (sendmail?.status === "success") {
|
||||
setSuccessEmail(true);
|
||||
setIsEmail(false);
|
||||
|
||||
@@ -75,12 +75,9 @@ function Header(props) {
|
||||
alert(t("only-pdf-allowed"));
|
||||
return;
|
||||
}
|
||||
const fileSize =
|
||||
maxFileSize;
|
||||
const pdfsize = file?.size;
|
||||
const fileSizeBytes = fileSize * 1024 * 1024;
|
||||
if (pdfsize > fileSizeBytes) {
|
||||
alert(`${t("file-alert-1")} ${fileSize} MB`);
|
||||
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
@@ -137,13 +134,6 @@ function Header(props) {
|
||||
useObjectStreams: false
|
||||
});
|
||||
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
|
||||
const pdfsize = pdfBuffer?.byteLength;
|
||||
const fileSizeBytes = fileSize * 1024 * 1024;
|
||||
if (pdfsize > fileSizeBytes) {
|
||||
alert(`${t("file-alert-1")} ${fileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
props.setPdfArrayBuffer(pdfBuffer);
|
||||
props.setPdfBase64Url(pdfBase64);
|
||||
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||
|
||||
@@ -67,12 +67,9 @@ function PdfTools(props) {
|
||||
alert(t("only-pdf-allowed"));
|
||||
return;
|
||||
}
|
||||
const fileSize =
|
||||
maxFileSize;
|
||||
const pdfsize = file?.size;
|
||||
const fileSizeBytes = fileSize * 1024 * 1024;
|
||||
if (pdfsize > fileSizeBytes) {
|
||||
alert(`${t("file-alert-1")} ${fileSize} MB`);
|
||||
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
@@ -129,13 +126,6 @@ function PdfTools(props) {
|
||||
useObjectStreams: false
|
||||
});
|
||||
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
|
||||
const pdfsize = pdfBuffer?.byteLength;
|
||||
const fileSizeBytes = fileSize * 1024 * 1024;
|
||||
if (pdfsize > fileSizeBytes) {
|
||||
alert(`${t("file-alert-1")} ${fileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
props.setPdfArrayBuffer(pdfBuffer);
|
||||
props.setPdfBase64Url(pdfBase64);
|
||||
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, forwardRef } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import BorderResize from "./BorderResize";
|
||||
import { Rnd } from "react-rnd";
|
||||
import {
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
fontColorArr,
|
||||
fontsizeArr,
|
||||
getContainerScale,
|
||||
handleCopyNextToWidget,
|
||||
handleHeighlightWidget,
|
||||
onChangeInput,
|
||||
radioButtonWidget,
|
||||
@@ -14,13 +15,7 @@ import {
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
selectFormat,
|
||||
getYear,
|
||||
getMonth,
|
||||
months,
|
||||
years,
|
||||
getDefaultDate,
|
||||
getDefaultFormat,
|
||||
handleBackground
|
||||
randomId
|
||||
} from "../../constant/Utils";
|
||||
import PlaceholderType from "./PlaceholderType";
|
||||
import moment from "moment";
|
||||
@@ -28,36 +23,53 @@ import "../../styles/opensigndrive.css";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
|
||||
import {
|
||||
setIsShowModal,
|
||||
setPrefillImg
|
||||
} from "../../redux/reducers/widgetSlice";
|
||||
import { themeColor } from "../../constant/const";
|
||||
import { useGuidelinesContext } from "../../context/GuidelinesContext";
|
||||
import DatePicker from "react-datepicker";
|
||||
|
||||
//function to get default format
|
||||
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||
|
||||
//function to convert formated date to new Date() format
|
||||
const getDefaultDate = (dateStr, format) => {
|
||||
//get valid date format for moment to convert formated date to new Date() format
|
||||
const formats = changeDateToMomentFormat(format);
|
||||
const parsedDate = moment(dateStr, formats);
|
||||
let date;
|
||||
if (parsedDate.isValid()) {
|
||||
date = new Date(parsedDate.toISOString());
|
||||
return date;
|
||||
} else {
|
||||
date = new Date();
|
||||
return date;
|
||||
}
|
||||
};
|
||||
|
||||
function Placeholder(props) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const prefillImg = useSelector((state) => state.widget.prefillImg);
|
||||
const { showGuidelines } = useGuidelinesContext();
|
||||
const statusArr = ["Required", "Optional"];
|
||||
const widgetData =
|
||||
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
||||
const [isDateModal, setIsDateModal] = useState(false);
|
||||
const [containerScale, setContainerScale] = useState();
|
||||
const [selectDate, setSelectDate] = useState({});
|
||||
const [dateFormatList, setDateFormatList] = useState([]);
|
||||
const [dateFormat, setDateFormat] = useState([]);
|
||||
const [clickonWidget, setClickonWidget] = useState({});
|
||||
const [formdata, setFormdata] = useState({
|
||||
status: props?.pos?.options?.status || "required",
|
||||
isReadOnly: props?.pos?.options?.isReadOnly || false
|
||||
});
|
||||
const [isToday, setIsToday] = useState(
|
||||
props.pos?.options?.response === "today" ? true : false
|
||||
const [isDateReadOnly, setIsDateReadOnly] = useState(
|
||||
props?.pos?.options?.isReadOnly || false
|
||||
);
|
||||
const startDate = props?.pos?.options?.response
|
||||
? getDefaultDate(
|
||||
props?.pos?.options?.response,
|
||||
props.pos?.options?.validation?.format
|
||||
)
|
||||
: "";
|
||||
: new Date();
|
||||
|
||||
useEffect(() => {
|
||||
const getPdfPageWidth = props.pdfOriginalWH.find(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
@@ -90,87 +102,29 @@ function Placeholder(props) {
|
||||
}
|
||||
}, [widgetData]);
|
||||
|
||||
//function is used to convert date according to selected format
|
||||
const handleGetFormatDate = (data, isDateChange) => {
|
||||
const isSpecialDateFormat =
|
||||
data?.format &&
|
||||
["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(data?.format);
|
||||
let updateDate = data.date;
|
||||
let date = "";
|
||||
if (isSpecialDateFormat && updateDate) {
|
||||
date = isDateChange
|
||||
? moment(updateDate).format(changeDateToMomentFormat(data.format))
|
||||
: updateDate;
|
||||
} else if (updateDate) {
|
||||
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
|
||||
const newDate = new Date(updateDate);
|
||||
date = moment(newDate.getTime()).format(
|
||||
changeDateToMomentFormat(data?.format)
|
||||
);
|
||||
}
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
return date;
|
||||
};
|
||||
const saveDateSetting = (data) => {
|
||||
if (formdata?.isReadOnly && !selectDate.date && !isToday) {
|
||||
alert(t("read-only-date-error"));
|
||||
return;
|
||||
}
|
||||
handleSaveDate(data);
|
||||
setIsDateModal(false);
|
||||
props.setFontColor("");
|
||||
props.setFontSize("");
|
||||
};
|
||||
//function to save date and format on local array onchange date and onclick format
|
||||
const handleSaveDate = (data, isDateChange) => {
|
||||
const date = isToday ? "today" : handleGetFormatDate(data, isDateChange);
|
||||
//`onChangeInput` is used to save data related to date in a placeholder field
|
||||
onChangeInput(
|
||||
date,
|
||||
props.pos,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
data?.format,
|
||||
props.fontSize || props.pos?.options?.fontSize || 12,
|
||||
props.fontColor || props.pos?.options?.fontColor || "black",
|
||||
formdata
|
||||
);
|
||||
};
|
||||
|
||||
//Date-picker- accept date - Thu Dec 30 2025 00:00:00 GMT+0530 (India Standard Time)
|
||||
//if date have in this format - "30/12/2025"
|
||||
//then first need to be convert in this format - Tue Dec 30 2025 00:00:00 GMT+0530 (India Standard Time)
|
||||
//Using moment - moment("30/12/2025", "DD/MM/YYYY").toDate();
|
||||
|
||||
//moment package where we show date according to format in list
|
||||
//it support date in this type - "2025-09-11"// ISO string, "2025-09-11T14:30:00" // ISO datetime
|
||||
//function change format array list with selected date and format
|
||||
const changeDateFormat = () => {
|
||||
const updateDate = [];
|
||||
dateFormatArr.map((data) => {
|
||||
let date = selectDate?.date || new Date();
|
||||
const isString = typeof date === "string";
|
||||
if (selectDate.format === "dd-MM-yyyy" && isString) {
|
||||
const [day, month, year] = date.split("-");
|
||||
let date;
|
||||
if (selectDate && selectDate.format === "dd-MM-yyyy") {
|
||||
const [day, month, year] = selectDate.date.split("-");
|
||||
date = new Date(`${year}-${month}-${day}`);
|
||||
} else if (selectDate.format === "dd.MM.yyyy" && isString) {
|
||||
const [day, month, year] = date.split(".");
|
||||
} else if (selectDate && selectDate.format === "dd.MM.yyyy") {
|
||||
const [day, month, year] = selectDate.date.split(".");
|
||||
date = new Date(`${year}.${month}.${day}`);
|
||||
} else if (selectDate.format === "dd/MM/yyyy" && isString) {
|
||||
const [day, month, year] = date.split("/");
|
||||
} else if (selectDate && selectDate.format === "dd/MM/yyyy") {
|
||||
const [day, month, year] = selectDate.date.split("/");
|
||||
date = new Date(`${year}/${month}/${day}`);
|
||||
} else {
|
||||
date = selectDate?.date ? new Date(selectDate?.date) : new Date();
|
||||
date = new Date(selectDate?.date);
|
||||
}
|
||||
const milliseconds = date.getTime();
|
||||
const newDate = moment(milliseconds).format(data);
|
||||
const dateObj = { date: newDate, format: selectFormat(data) };
|
||||
updateDate.push(dateObj);
|
||||
});
|
||||
setDateFormatList(updateDate);
|
||||
setDateFormat(updateDate);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -262,8 +216,7 @@ function Placeholder(props) {
|
||||
(props.isSignYourself || props.isSelfSign)
|
||||
) {
|
||||
props.handleTextSettingModal(true);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
props?.handleNameModal && props?.handleNameModal(true);
|
||||
}
|
||||
|
||||
@@ -301,8 +254,72 @@ function Placeholder(props) {
|
||||
) {
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
}
|
||||
props.setIsPageCopy(true);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
|
||||
//checking widget's type and open widget copy modal for required widgets
|
||||
if (
|
||||
["signature", textInputWidget, textWidget, "stamp", "initials"].includes(
|
||||
props.pos.type
|
||||
)
|
||||
) {
|
||||
props.setIsPageCopy(true);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
} else {
|
||||
const newId = randomId();
|
||||
//function to create new widget next to just widget
|
||||
handleCopyNextToWidget(
|
||||
newId,
|
||||
props.pos,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data?.Id
|
||||
);
|
||||
//condiiton is used to store copied prefill image base64 url in redux for display image
|
||||
if (props?.data?.Role === "prefill") {
|
||||
const getPrefillImg = prefillImg?.find((x) => x.id === props.pos.key);
|
||||
dispatch(
|
||||
setPrefillImg({
|
||||
id: newId,
|
||||
base64: getPrefillImg?.base64
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//function to save date and format on local array onchange date and onclick format
|
||||
const handleSaveDate = (data, isDateChange) => {
|
||||
const isSpecialDateFormat =
|
||||
data?.format &&
|
||||
["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(data?.format);
|
||||
let updateDate = data.date;
|
||||
let date;
|
||||
if (isSpecialDateFormat) {
|
||||
date = isDateChange
|
||||
? moment(updateDate).format(changeDateToMomentFormat(data.format))
|
||||
: updateDate;
|
||||
} else {
|
||||
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
|
||||
const newDate = new Date(updateDate);
|
||||
date = moment(newDate.getTime()).format(
|
||||
changeDateToMomentFormat(data?.format)
|
||||
);
|
||||
}
|
||||
//`onChangeInput` is used to save data related to date in a placeholder field
|
||||
onChangeInput(
|
||||
date,
|
||||
props.pos,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
data?.format,
|
||||
props.fontSize || props.pos?.options?.fontSize || 12,
|
||||
props.fontColor || props.pos?.options?.fontColor || "black",
|
||||
isDateReadOnly || false
|
||||
);
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
};
|
||||
|
||||
const setCellCount = (key, newCount) => {
|
||||
@@ -566,7 +583,7 @@ function Placeholder(props) {
|
||||
}
|
||||
}
|
||||
};
|
||||
//function to calculate font size
|
||||
//function to calculate font size
|
||||
const calculateFont = (size, isMinHeight) => {
|
||||
const containerScale = getContainerScale(
|
||||
props.pdfOriginalWH,
|
||||
@@ -596,6 +613,22 @@ function Placeholder(props) {
|
||||
}
|
||||
};
|
||||
|
||||
//function to handle widget background color
|
||||
const handleBackground = () => {
|
||||
if (props.data) {
|
||||
if (props.isNeedSign) {
|
||||
if (props.data?.Id === props?.uniqueId) {
|
||||
return props.data?.blockColor + "b0";
|
||||
} else {
|
||||
return "#dedddc";
|
||||
}
|
||||
} else {
|
||||
return props.data?.blockColor + "b0";
|
||||
}
|
||||
} else {
|
||||
return "rgba(203, 233, 237, 0.69)";
|
||||
}
|
||||
};
|
||||
const fontSize = calculateFont(props.pos.options?.fontSize);
|
||||
const fontColor = props.pos.options?.fontColor || "black";
|
||||
|
||||
@@ -617,40 +650,6 @@ function Placeholder(props) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
className="border-gray-400 rounded-[50px] border-[1px] px-3 md:ml-2 text-xs py-2 focus:outline-none hover:border-base-content "
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
{value}
|
||||
<i className="fa-light fa-calendar ml-[5px]"></i>
|
||||
</div>
|
||||
));
|
||||
ExampleCustomInput.displayName = "ExampleCustomInput";
|
||||
const handleChangeFormat = (e) => {
|
||||
const selectedIndex = e.target.value;
|
||||
e.stopPropagation();
|
||||
if (
|
||||
props?.isPlaceholder &&
|
||||
props?.data?.Role !== "prefill" &&
|
||||
!selectDate?.date
|
||||
) {
|
||||
setSelectDate((prev) => ({
|
||||
...prev,
|
||||
format: dateFormatList[selectedIndex]?.format
|
||||
}));
|
||||
} else {
|
||||
setSelectDate(dateFormatList[selectedIndex]);
|
||||
}
|
||||
};
|
||||
const handleClearDate = () => {
|
||||
setIsToday(false);
|
||||
setSelectDate((prev) => ({
|
||||
...prev,
|
||||
date: ""
|
||||
}));
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{/* Check if a text widget (prefill type) exists. Once the user enters a value and clicks outside or the widget becomes non-selectable, it should appear as plain text (just like embedded text in a document). When the user clicks on the text again, it should become editable. */}
|
||||
@@ -729,11 +728,7 @@ function Placeholder(props) {
|
||||
background:
|
||||
props?.data?.Role === "prefill"
|
||||
? "transparent"
|
||||
: handleBackground(
|
||||
props?.data,
|
||||
props?.isNeedSign,
|
||||
props?.uniqueId
|
||||
) //handle block color of widget for
|
||||
: handleBackground() //handle block color of widget for
|
||||
}}
|
||||
onDrag={(_, d) => {
|
||||
props?.handleTabDrag?.(props.pos.key);
|
||||
@@ -894,8 +889,10 @@ function Placeholder(props) {
|
||||
(props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key))
|
||||
}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
calculateFont={calculateFont}
|
||||
setCellCount={setCellCount}
|
||||
@@ -905,18 +902,22 @@ function Placeholder(props) {
|
||||
)}
|
||||
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
|
||||
<div className="text-base-content h-[100%] p-[20px]">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col md:items-center md:flex-row gap-y-3">
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-y-3">
|
||||
<div className="flex flex-row items-center gap-x-1">
|
||||
<span className="capitalize">{t("format")} :</span>
|
||||
<select
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs md:ml-4"
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
defaultValue={""}
|
||||
onChange={(e) => handleChangeFormat(e)}
|
||||
onChange={(e) => {
|
||||
const selectedIndex = e.target.value;
|
||||
e.stopPropagation();
|
||||
setSelectDate(dateFormat[selectedIndex]);
|
||||
}}
|
||||
>
|
||||
<option value="" disabled>
|
||||
{t("select-date-format")}
|
||||
</option>
|
||||
{dateFormatList.map((data, ind) => {
|
||||
{dateFormat.map((data, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={ind} key={ind}>
|
||||
{data?.date ? data?.date : "nodata"}
|
||||
@@ -924,190 +925,79 @@ function Placeholder(props) {
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<span className="text-xs text-gray-400 ml-1">
|
||||
{selectDate.format}
|
||||
</span>
|
||||
</div>
|
||||
{props?.data?.Role !== "prefill" && props?.isPlaceholder && (
|
||||
<>
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-2">
|
||||
<span>{t("set-date")} :</span>
|
||||
<DatePicker
|
||||
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
||||
<div className="flex justify-start md:ml-2 ">
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={months[getMonth(date)]}
|
||||
onChange={({ target: { value } }) =>
|
||||
changeMonth(months.indexOf(value))
|
||||
}
|
||||
>
|
||||
{months.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={getYear(date)}
|
||||
onChange={({ target: { value } }) =>
|
||||
changeYear(value)
|
||||
}
|
||||
>
|
||||
{years.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
closeOnScroll={true}
|
||||
selected={getDefaultDate(
|
||||
selectDate?.date,
|
||||
selectDate?.format
|
||||
)}
|
||||
popperPlacement="top-end"
|
||||
customInput={<ExampleCustomInput />}
|
||||
onChange={(date) => {
|
||||
setIsToday(false);
|
||||
const isDateChange = true;
|
||||
const dateObj = {
|
||||
date: date,
|
||||
format: selectDate.format
|
||||
};
|
||||
handleGetFormatDate(dateObj, isDateChange);
|
||||
}}
|
||||
dateFormat={
|
||||
selectDate
|
||||
? selectDate?.format
|
||||
: props.pos?.options?.validation?.format
|
||||
? props.pos?.options?.validation?.format
|
||||
: "MM/dd/yyyy"
|
||||
}
|
||||
portalId="root-portal"
|
||||
/>
|
||||
<label className=" flex items-center gap-1 cursor-pointer">
|
||||
<input
|
||||
checked={isToday}
|
||||
type="checkbox"
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
onClick={() => {
|
||||
setSelectDate((prev) => ({
|
||||
...prev,
|
||||
date: ""
|
||||
}));
|
||||
setIsToday(!isToday);
|
||||
}}
|
||||
/>
|
||||
<span className="ml-[2px]">{t("set-today")}</span>
|
||||
</label>
|
||||
<span
|
||||
onClick={() => handleClearDate()}
|
||||
className="underline text-blue-500 cursor-pointer md:ml-2"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-row items-center gap-[10px]">
|
||||
{statusArr.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
name="status"
|
||||
onChange={() =>
|
||||
setFormdata({
|
||||
...formdata,
|
||||
status: data.toLowerCase()
|
||||
})
|
||||
}
|
||||
checked={
|
||||
formdata.status.toLowerCase() === data.toLowerCase()
|
||||
}
|
||||
/>
|
||||
<div className="text-[13px]">
|
||||
{t(`widget-status.${data}`)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col md:flex-row gap-y-2 md:gap-y-0 gap-x-2">
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="capitalize">{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={
|
||||
props.fontSize || clickonWidget.options?.fontSize || 12
|
||||
}
|
||||
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-row gap-1 items-center">
|
||||
<span className="capitalize">{t("color")} :</span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor ||
|
||||
clickonWidget.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
className="ml-[4px] md:ml[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
>
|
||||
{fontColorArr.map((color, ind) => (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span
|
||||
style={{
|
||||
background:
|
||||
props.fontColor || props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
className="w-5 h-[19px] ml-1"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
{props?.isPlaceholder && props?.data?.Role !== "prefill" && (
|
||||
<label className="flex items-center gap-1 cursor-pointer">
|
||||
<input
|
||||
name="isReadOnly"
|
||||
type="checkbox"
|
||||
checked={formdata?.isReadOnly}
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
onChange={() =>
|
||||
setFormdata({
|
||||
...formdata,
|
||||
isReadOnly: !formdata?.isReadOnly
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="capitalize ml-[2px]">{t("read-only")}</span>
|
||||
</label>
|
||||
)}
|
||||
<span className="text-xs text-gray-400 ml-1">
|
||||
{selectDate.format}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-y-2 md:gap-y-0 gap-x-2 mt-3">
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="capitalize">{t("font-size")} :</span>
|
||||
<select
|
||||
className="ml-[3px] md:ml:[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={props.fontSize || clickonWidget.options?.fontSize || 12}
|
||||
onChange={(e) => props.setFontSize(parseInt(e.target.value))}
|
||||
>
|
||||
{fontsizeArr.map((size, ind) => (
|
||||
<option className="text-[13px]" value={size} key={ind}>
|
||||
{size}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-row gap-1 items-center">
|
||||
<span className="capitalize">{t("color")} :</span>
|
||||
<select
|
||||
value={
|
||||
props.fontColor || clickonWidget.options?.fontColor || "black"
|
||||
}
|
||||
onChange={(e) => props.setFontColor(e.target.value)}
|
||||
className="ml-[4px] md:ml[7px] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
>
|
||||
{fontColorArr.map((color, ind) => (
|
||||
<option value={color} key={ind}>
|
||||
{t(`color-type.${color}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span
|
||||
style={{
|
||||
background:
|
||||
props.fontColor || props.pos.options?.fontColor || "black"
|
||||
}}
|
||||
className="w-5 h-[19px] ml-1"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
{props?.isPlaceholder && props?.data?.Role !== "prefill" && (
|
||||
<div className="flex items-center mt-3">
|
||||
<input
|
||||
id="isReadOnly"
|
||||
name="isReadOnly"
|
||||
type="checkbox"
|
||||
checked={
|
||||
isDateReadOnly || props.pos.options?.isReadOnly || false
|
||||
}
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
onChange={() => setIsDateReadOnly(!isDateReadOnly)}
|
||||
/>
|
||||
<label
|
||||
className="ml-1.5 mb-0 capitalize text-[13px]"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
onClick={() => {
|
||||
saveDateSetting(selectDate);
|
||||
setIsDateModal(false);
|
||||
handleSaveDate(selectDate);
|
||||
props.setFontColor("");
|
||||
props.setFontSize("");
|
||||
}}
|
||||
>
|
||||
{t("save")}
|
||||
|
||||
@@ -2,12 +2,9 @@ import { useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { handleCopyNextToWidget, randomId } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setPrefillImg } from "../../redux/reducers/widgetSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
function PlaceholderCopy(props) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const copyType = [
|
||||
{ id: 1, type: "All pages" },
|
||||
{ id: 2, type: "All pages but last" },
|
||||
@@ -124,7 +121,7 @@ function PlaceholderCopy(props) {
|
||||
);
|
||||
for (let i = 0; i < props.allPages; i++) {
|
||||
const newId = randomId();
|
||||
const nameId = randomId(2);
|
||||
const nameId = randomId(2)
|
||||
const widgetName = `${currentPlaceholder?.options?.name}${nameId}`;
|
||||
const newPlaceholder = {
|
||||
...currentPlaceholder,
|
||||
@@ -137,6 +134,7 @@ function PlaceholderCopy(props) {
|
||||
);
|
||||
const existPlaceholderPosition =
|
||||
existPlaceholder[0] && existPlaceholder[0].pos;
|
||||
|
||||
//function for get copy to requested location of placeholder position
|
||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||
type,
|
||||
@@ -148,18 +146,6 @@ function PlaceholderCopy(props) {
|
||||
newPlaceholderPosition.push(getPlaceholderObj);
|
||||
}
|
||||
newPageNumber++;
|
||||
if (
|
||||
filterSignerPosition[0]?.Role === "prefill" &&
|
||||
currentPlaceholder?.options?.response &&
|
||||
currentPlaceholder?.type === "image"
|
||||
) {
|
||||
dispatch(
|
||||
setPrefillImg({
|
||||
id: newId,
|
||||
base64: currentPlaceholder?.options?.response
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
let updatedSignerPlaceholder;
|
||||
if (props?.signerObjId) {
|
||||
@@ -254,18 +240,6 @@ function PlaceholderCopy(props) {
|
||||
props.setXyPosition,
|
||||
props?.Id
|
||||
);
|
||||
if (
|
||||
filterSignerPosition[0]?.Role === "prefill" &&
|
||||
currentXYposition?.options?.response &&
|
||||
currentXYposition?.type === "image"
|
||||
) {
|
||||
dispatch(
|
||||
setPrefillImg({
|
||||
id: newId,
|
||||
base64: currentXYposition?.options?.response
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const getIndex = props?.xyPosition.findIndex(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState, forwardRef } from "react";
|
||||
import { useEffect, useState, forwardRef } from "react";
|
||||
import {
|
||||
getMonth,
|
||||
getYear,
|
||||
@@ -10,8 +10,7 @@ import {
|
||||
years,
|
||||
selectCheckbox,
|
||||
checkRegularExpress,
|
||||
isBase64,
|
||||
getSignerPages
|
||||
isBase64
|
||||
} from "../../constant/Utils";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
@@ -20,7 +19,6 @@ import { useTranslation } from "react-i18next";
|
||||
import CellsWidget from "./CellsWidget";
|
||||
import { useSelector } from "react-redux";
|
||||
import Loader from "../../primitives/Loader";
|
||||
|
||||
const textWidgetCls =
|
||||
"w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap";
|
||||
const widgetCls =
|
||||
@@ -36,9 +34,8 @@ function PlaceholderType(props) {
|
||||
((props.isSelfSign || props.isNeedSign) &&
|
||||
props.data?.signerObjId === props.signerObjId);
|
||||
const isReadOnly =
|
||||
props?.data?.Role !== "prefill" &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId);
|
||||
props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId;
|
||||
// prefer the latest response value over any default value
|
||||
const widgetData =
|
||||
props.pos?.options?.response ?? props.pos?.options?.defaultValue ?? "";
|
||||
@@ -67,21 +64,18 @@ function PlaceholderType(props) {
|
||||
props?.pos?.options?.defaultValue ||
|
||||
[]
|
||||
);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// keep displayed value in sync with the stored response
|
||||
setwidgetValue(widgetData);
|
||||
}
|
||||
if (props.pos?.options?.hint) {
|
||||
setHint(props.pos?.options.hint);
|
||||
} else if (props.pos?.options?.validation?.type) {
|
||||
checkRegularExpress(props.pos?.options?.validation?.type, setHint);
|
||||
checkRegularExpress(props.pos?.options?.validation?.type, setHint);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.pos, widgetData, type]);
|
||||
|
||||
|
||||
}, [props.pos]);
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
style={{
|
||||
@@ -89,7 +83,8 @@ function PlaceholderType(props) {
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif"
|
||||
}}
|
||||
className={`${isReadOnly ? `select-none opacity-25` : ``} ${selectWidgetCls} overflow-hidden`}
|
||||
className={`${isReadOnly ? `select-none` : ``} ${selectWidgetCls} overflow-hidden`}
|
||||
disabled={isReadOnly}
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
@@ -404,9 +399,11 @@ function PlaceholderType(props) {
|
||||
popperPlacement="top-end"
|
||||
customInput={<ExampleCustomInput />}
|
||||
dateFormat={
|
||||
props?.selectDate?.format ||
|
||||
props.pos?.options?.validation?.format ||
|
||||
"MM/dd/yyyy"
|
||||
props.selectDate
|
||||
? props.selectDate?.format
|
||||
: props.pos?.options?.validation?.format
|
||||
? props.pos?.options?.validation?.format
|
||||
: "MM/dd/yyyy"
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
@@ -415,9 +412,11 @@ function PlaceholderType(props) {
|
||||
className="select-none-cls overflow-hidden"
|
||||
>
|
||||
<span>
|
||||
{props?.selectDate?.format ||
|
||||
props.pos?.options?.validation?.format ||
|
||||
"MM/dd/yyyy"}
|
||||
{props.selectDate
|
||||
? props.selectDate?.format
|
||||
: props.pos?.options?.validation?.format
|
||||
? props.pos?.options?.validation?.format
|
||||
: "MM/dd/yyyy"}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -28,8 +28,6 @@ import {
|
||||
} from "../../redux/reducers/widgetSlice";
|
||||
import * as utils from "../../utils";
|
||||
|
||||
const widgetTitle = "font-medium";
|
||||
|
||||
const ShowTextWidget = ({ position, handleWidgetDetails }) => {
|
||||
const inputRef = useRef(null);
|
||||
const [inputValue, setInputValue] = useState(position.options.response || "");
|
||||
@@ -48,77 +46,14 @@ const ShowTextWidget = ({ position, handleWidgetDetails }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ImageComponent = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const prefillImg = useSelector((state) => state.widget.prefillImg);
|
||||
const imageRefs = useRef({});
|
||||
|
||||
let imgUrl = "";
|
||||
const isBase64Url = isBase64(props?.position?.SignUrl);
|
||||
if (isBase64Url) {
|
||||
imgUrl = props?.position?.SignUrl;
|
||||
} else {
|
||||
const getPrefillImg = prefillImg?.find(
|
||||
(x) => x.id === props?.position?.key
|
||||
);
|
||||
imgUrl = getPrefillImg?.base64;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={widgetTitle}>{props?.position.options?.name}</span>
|
||||
{imgUrl ? (
|
||||
<>
|
||||
<div className="cursor-pointer op-card border-[1px] border-gray-400 flex flex-col w-full h-full justify-center items-center ">
|
||||
<img
|
||||
alt="print img"
|
||||
ref={(el) => (imageRefs.current[props?.position.key] = el)} // Assign ref dynamicallys
|
||||
src={imgUrl}
|
||||
draggable="false"
|
||||
className="object-contain h-full w-full aspect-[5/2]"
|
||||
onLoad={() => props?.handleImageLoaded?.(props?.position.key)}
|
||||
onError={() => props?.handleImageLoaded?.(props?.position.key)}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
onClick={() => props?.handleClearImage(props?.position)}
|
||||
className="flex justify-start text-blue-500 underline cursor-pointer"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className="cursor-pointer op-card border-[1px] op-border-hover flex flex-col overflow-hidden w-full h-full aspect-[5/2] justify-center items-center"
|
||||
onClick={() => imageRefs.current[props?.position.key]?.click()}
|
||||
>
|
||||
{props?.imageLoaders[props?.position?.key] && (
|
||||
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-white/30 z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => props?.onImageChange?.(e, props?.position)}
|
||||
className="filetype"
|
||||
accept="image/png,image/jpeg"
|
||||
ref={(el) => (imageRefs.current[props?.position.key] = el)} // Assign ref dynamically
|
||||
hidden
|
||||
/>
|
||||
<i className="fa-light text-base-content fa-cloud-upload-alt text-[25px]"></i>
|
||||
<div className="text-[10px] text-base-content">{t("upload")}</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function PrefillWidgetModal(props) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
// Track already loaded image keys so they don't increment multiple times
|
||||
const loadedSet = useRef(new Set());
|
||||
const initializedRef = useRef(false); // prevent rerun on state updates
|
||||
const prefillImg = useSelector((state) => state.widget.prefillImg);
|
||||
const [uniqueWidget, setUniqueWidget] = useState([]);
|
||||
const [image, setImage] = useState(null);
|
||||
const [imageLoaders, setImageLoaders] = useState({});
|
||||
const [currentWidget, setCurrentWidget] = useState("");
|
||||
@@ -127,12 +62,13 @@ function PrefillWidgetModal(props) {
|
||||
const [loadedImages, setLoadedImages] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const years = range(1950, getYear(new Date()) + 16, 1);
|
||||
const widgetTitle = "font-medium";
|
||||
const isAnyLoaderActive = Object.values(imageLoaders).some(
|
||||
(val) => val === true
|
||||
);
|
||||
|
||||
// useMemo to memoize the calculation of unique widgets
|
||||
const uniqueWidget = useMemo(() => {
|
||||
const memoizedUniqueWidget = useMemo(() => {
|
||||
//functions to used remove duplicate name values across all pages
|
||||
if (!props.prefillData) return [];
|
||||
//This will help us track which name values have already been encountered across all pages.
|
||||
@@ -158,7 +94,6 @@ function PrefillWidgetModal(props) {
|
||||
|
||||
return flatArray || [];
|
||||
}, [props.prefillData]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(resetWidgetState([]));
|
||||
}, []);
|
||||
@@ -184,6 +119,10 @@ function PrefillWidgetModal(props) {
|
||||
savePrefillImg();
|
||||
}, [props.xyPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
setUniqueWidget(memoizedUniqueWidget);
|
||||
}, [memoizedUniqueWidget]);
|
||||
|
||||
useEffect(() => {
|
||||
if (totalImages > 0 && loadedImages === totalImages) {
|
||||
setLoading(false);
|
||||
@@ -229,7 +168,67 @@ function PrefillWidgetModal(props) {
|
||||
}
|
||||
return date;
|
||||
};
|
||||
const ImageComponent = (props) => {
|
||||
const imageRefs = useRef([]);
|
||||
let imgUrl = "";
|
||||
const isBase64Url = isBase64(props?.position?.SignUrl);
|
||||
if (isBase64Url) {
|
||||
imgUrl = props?.position?.SignUrl;
|
||||
} else {
|
||||
const getPrefillImg = prefillImg?.find(
|
||||
(x) => x.id === props?.position?.key
|
||||
);
|
||||
imgUrl = getPrefillImg?.base64;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={widgetTitle}>{props?.position.options?.name}</span>
|
||||
{imgUrl ? (
|
||||
<>
|
||||
<div className="cursor-pointer op-card border-[1px] border-gray-400 flex flex-col w-full h-full justify-center items-center ">
|
||||
<img
|
||||
alt="print img"
|
||||
ref={(el) => (imageRefs.current[props?.position.key] = el)} // Assign ref dynamicallys
|
||||
src={imgUrl}
|
||||
draggable="false"
|
||||
className="object-contain h-full w-full aspect-[5/2]"
|
||||
onLoad={() => handleImageLoaded?.(props?.position.key)}
|
||||
onError={() => handleImageLoaded?.(props?.position.key)}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
onClick={() => handleClearImage(props?.position)}
|
||||
className="flex justify-start text-blue-500 underline cursor-pointer"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className="cursor-pointer op-card border-[1px] op-border-hover flex flex-col overflow-hidden w-full h-full aspect-[5/2] justify-center items-center"
|
||||
onClick={() => imageRefs.current[props?.position.key]?.click()}
|
||||
>
|
||||
{imageLoaders[props?.position?.key] && (
|
||||
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-white/30 z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => onImageChange(e, props?.position)}
|
||||
className="filetype"
|
||||
accept="image/png,image/jpeg"
|
||||
ref={(el) => (imageRefs.current[props?.position.key] = el)} // Assign ref dynamically
|
||||
hidden
|
||||
/>
|
||||
<i className="fa-light text-base-content fa-cloud-upload-alt text-[25px]"></i>
|
||||
<div className="text-[10px] text-base-content">{t("upload")}</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
style={{ fontFamily: "Arial, sans-serif" }}
|
||||
@@ -279,6 +278,7 @@ function PrefillWidgetModal(props) {
|
||||
image.src,
|
||||
image.imgType
|
||||
);
|
||||
setImageLoaders({});
|
||||
if (imageUrl) {
|
||||
return imageUrl;
|
||||
}
|
||||
@@ -419,7 +419,6 @@ function PrefillWidgetModal(props) {
|
||||
// (tracks how many images have finished loading)
|
||||
setLoadedImages((prev) => prev + 1);
|
||||
}
|
||||
setImageLoaders({});
|
||||
};
|
||||
const handleWidgetType = (position) => {
|
||||
switch (position?.type) {
|
||||
@@ -438,9 +437,9 @@ function PrefillWidgetModal(props) {
|
||||
className="mt-[2px] op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
checked={selectCheckbox(ind, position)}
|
||||
onChange={(e) =>
|
||||
handleCheckboxValue(e.target.checked, ind, position)
|
||||
}
|
||||
onChange={(e) => {
|
||||
handleCheckboxValue(e.target.checked, ind, position);
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`checkbox-${position.key + ind}`}
|
||||
@@ -473,7 +472,9 @@ function PrefillWidgetModal(props) {
|
||||
value={
|
||||
position?.options?.response || position?.options?.defaultValue
|
||||
}
|
||||
onChange={(e) => handleWidgetDetails(position, e.target.value)}
|
||||
onChange={(e) => {
|
||||
handleWidgetDetails(position, e.target.value);
|
||||
}}
|
||||
>
|
||||
{/* Default/Title option */}
|
||||
<option value="" disabled hidden>
|
||||
@@ -523,7 +524,9 @@ function PrefillWidgetModal(props) {
|
||||
)}
|
||||
closeOnScroll={true}
|
||||
selected={handleDate(position)}
|
||||
onChange={(date) => handleOnDateChange(date, position)}
|
||||
onChange={(date) => {
|
||||
handleOnDateChange(date, position);
|
||||
}}
|
||||
customInput={<ExampleCustomInput />}
|
||||
dateFormat={position?.options?.validation?.format || "MM/dd/yyyy"}
|
||||
/>
|
||||
@@ -533,10 +536,7 @@ function PrefillWidgetModal(props) {
|
||||
return (
|
||||
<ImageComponent
|
||||
position={position}
|
||||
imageLoaders={imageLoaders}
|
||||
onImageChange={onImageChange}
|
||||
handleImageLoaded={handleImageLoaded}
|
||||
handleClearImage={handleClearImage}
|
||||
docId={props?.docId}
|
||||
/>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
@@ -554,7 +554,9 @@ function PrefillWidgetModal(props) {
|
||||
className="mt-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
checked={handleRadioCheck(data, position)}
|
||||
onChange={() => handleWidgetDetails(position, data)}
|
||||
onChange={() => {
|
||||
handleWidgetDetails(position, data);
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`radio-${position.key + ind}`}
|
||||
@@ -700,9 +702,9 @@ function PrefillWidgetModal(props) {
|
||||
multiValueLabel: () => "mb-[2px]",
|
||||
menu: () =>
|
||||
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content absolute z-9999",
|
||||
menuList: () => "shadow-md rounded-lg",
|
||||
menuList: () => "shadow-md rounded-lg ",
|
||||
option: () =>
|
||||
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2",
|
||||
"bg-base-200 text-base-content rounded-lg m-1 hover:bg-base-300 p-2 ",
|
||||
noOptionsMessage: () =>
|
||||
"p-2 bg-base-200 rounded-lg m-1 p-2"
|
||||
}}
|
||||
@@ -713,7 +715,7 @@ function PrefillWidgetModal(props) {
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => handleCreateNew(e, field.value)}
|
||||
className="op-btn op-btn-accent op-btn-outline op-btn-sm"
|
||||
className="op-btn op-btn-accent op-btn-outline op-btn-sm "
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>
|
||||
</button>
|
||||
|
||||
@@ -85,12 +85,9 @@ function RenderAllPdfPage(props) {
|
||||
alert(t("only-pdf-allowed"));
|
||||
return;
|
||||
}
|
||||
const fileSize =
|
||||
maxFileSize;
|
||||
const pdfsize = file?.size;
|
||||
const fileSizeBytes = fileSize * 1024 * 1024;
|
||||
if (pdfsize > fileSizeBytes) {
|
||||
alert(`${t("file-alert-1")} ${fileSize} MB`);
|
||||
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
@@ -147,13 +144,6 @@ function RenderAllPdfPage(props) {
|
||||
useObjectStreams: false
|
||||
});
|
||||
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
|
||||
const pdfsize = pdfBuffer?.byteLength;
|
||||
const fileSizeBytes = fileSize * 1024 * 1024;
|
||||
if (pdfsize > fileSizeBytes) {
|
||||
alert(`${t("file-alert-1")} ${fileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
props.setPdfArrayBuffer(pdfBuffer);
|
||||
props.setPdfBase64Url(pdfBase64);
|
||||
props.setIsUploadPdf && props.setIsUploadPdf(true);
|
||||
|
||||
@@ -22,25 +22,19 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import Guidelines from "./Guidelines";
|
||||
import { useGuidelinesContext } from "../../context/GuidelinesContext";
|
||||
import { toggleSidebar } from "../../redux/reducers/sidebarReducer";
|
||||
import DragGuidelinesLayer from "./DragGridLinesLayer";
|
||||
import { useDrop } from "react-dnd";
|
||||
import WidgetsDragPreview from "./WidgetsDragPreview";
|
||||
|
||||
function RenderPdf(props) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
drop: (item, monitor) => props?.addPositionOfSignature(item, monitor)
|
||||
});
|
||||
const [scaledHeight, setScaledHeight] = useState();
|
||||
const { guideline, showGuidelines } = useGuidelinesContext();
|
||||
const { guideline } = useGuidelinesContext();
|
||||
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
|
||||
const isGuestSigner = localStorage.getItem("isGuestSigner");
|
||||
const scrollTriggerId = useSelector((state) => state.widget.scrollTriggerId);
|
||||
const isOpen = useSelector((state) => state.sidebar.isOpen);
|
||||
const scrollRef = useRef(null);
|
||||
const pdfContainerRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(toggleSidebar(false));
|
||||
return () => {
|
||||
@@ -277,7 +271,6 @@ function RenderPdf(props) {
|
||||
setIsReqSignTourDisabled={props.setIsReqSignTourDisabled}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={props?.currWidgetsDetails}
|
||||
handleNameModal={props?.handleNameModal}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
@@ -341,7 +334,7 @@ function RenderPdf(props) {
|
||||
}}
|
||||
ref={(node) => {
|
||||
pdfContainerRef.current = node;
|
||||
drop && drop(node);
|
||||
props.drop && props.drop(node);
|
||||
}}
|
||||
id="container"
|
||||
>
|
||||
@@ -491,8 +484,6 @@ function RenderPdf(props) {
|
||||
setIsReqSignTourDisabled={
|
||||
props.setIsReqSignTourDisabled
|
||||
}
|
||||
unSignedWidgetId={props.unSignedWidgetId}
|
||||
handleNameModal={props.handleNameModal}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
@@ -538,25 +529,6 @@ function RenderPdf(props) {
|
||||
y2={guideline.y2}
|
||||
/>
|
||||
)}
|
||||
<DragGuidelinesLayer
|
||||
signBtnPosition={props.signBtnPosition}
|
||||
showGuidelines={showGuidelines}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isSignYourself={props.signerPos?.length > 0 ? true : false}
|
||||
/>
|
||||
<WidgetsDragPreview
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
isSignYourself={props.signerPos?.length > 0 ? true : false}
|
||||
uniqueId={props?.uniqueId}
|
||||
data={props?.signerPos?.find((x) => x.Id === props?.uniqueId)}
|
||||
isNeedSign={props?.pdfRequest}
|
||||
pdfOriginalWH={props.pdfOriginalWH}
|
||||
pageNumber={props.pageNumber}
|
||||
containerWH={props.containerWH}
|
||||
scale={props?.scale}
|
||||
/>
|
||||
</div>
|
||||
</RSC>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import RecipientList from "./RecipientList";
|
||||
import { useDrag } from "react-dnd";
|
||||
import WidgetList from "./WidgetList";
|
||||
import {
|
||||
isMobile,
|
||||
@@ -10,54 +11,106 @@ import {
|
||||
textWidget,
|
||||
widgets
|
||||
} from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useWidgetDrag } from "../../hook/useWidgetDrag";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
function WidgetComponent(props) {
|
||||
const { t } = useTranslation();
|
||||
const signRef = useRef(null);
|
||||
const userInformation = localStorage.getItem("UserInformation");
|
||||
const [isSignersModal, setIsSignersModal] = useState(false);
|
||||
// Define all draggable widget configurations
|
||||
const draggableItems = [
|
||||
{ id: 1, text: "signature" },
|
||||
{ id: 2, text: "stamp" },
|
||||
{ id: 3, text: "initials" },
|
||||
{ id: 4, text: textInputWidget },
|
||||
{ id: 6, text: "name" },
|
||||
{ id: 7, text: "job title" },
|
||||
{ id: 8, text: "company" },
|
||||
{ id: 9, text: "email" },
|
||||
{ id: 10, text: "date" },
|
||||
{ id: 11, text: textWidget },
|
||||
{ id: 12, text: cellsWidget },
|
||||
{ id: 13, text: "checkbox" },
|
||||
{ id: 14, text: "dropdown" },
|
||||
{ id: 15, text: radioButtonWidget },
|
||||
{ id: 16, text: "image" }
|
||||
];
|
||||
|
||||
// Create all drag refs in one go
|
||||
const widgetRefs = draggableItems.map((item) =>
|
||||
useWidgetDrag({ type: "BOX", ...item })
|
||||
);
|
||||
|
||||
// Map your widgets with the generated dragRefs
|
||||
const [widgetList, setWidgetList] = useState([]);
|
||||
const [, signature] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 1, text: "signature" }
|
||||
});
|
||||
const [, stamp] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 2, text: "stamp" }
|
||||
});
|
||||
const [, dropdown] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 5, text: "dropdown" }
|
||||
});
|
||||
const [, checkbox] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 6, text: "checkbox" }
|
||||
});
|
||||
const [, textInput] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 7, text: textInputWidget }
|
||||
});
|
||||
const [, cells] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 17, text: cellsWidget }
|
||||
});
|
||||
const [, initials] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 8, text: "initials" }
|
||||
});
|
||||
const [, name] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 9, text: "name" }
|
||||
});
|
||||
const [, company] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 10, text: "company" }
|
||||
});
|
||||
const [, jobTitle] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 11, text: "job title" }
|
||||
});
|
||||
const [, date] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 12, text: "date" }
|
||||
});
|
||||
const [, image] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 13, text: "image" }
|
||||
});
|
||||
const [, email] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 14, text: "email" }
|
||||
});
|
||||
const [, radioButton] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 15, text: radioButtonWidget }
|
||||
});
|
||||
const [, text] = useDrag({
|
||||
type: "BOX",
|
||||
item: { type: "BOX", id: 16, text: textWidget }
|
||||
});
|
||||
const [widget, setWidget] = useState([]);
|
||||
const handleModal = () => {
|
||||
setIsSignersModal(!isSignersModal);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const updated = widgets.map((obj, index) => ({
|
||||
...obj,
|
||||
ref: widgetRefs[index]?.dragRef || null
|
||||
}));
|
||||
setWidgetList(updated);
|
||||
const widgetRef = [
|
||||
signature,
|
||||
stamp,
|
||||
initials,
|
||||
textInput,
|
||||
name,
|
||||
jobTitle,
|
||||
company,
|
||||
date,
|
||||
text,
|
||||
cells,
|
||||
checkbox,
|
||||
dropdown,
|
||||
radioButton,
|
||||
image,
|
||||
email
|
||||
];
|
||||
const getWidgetArray = widgets;
|
||||
const newUpdateSigner = getWidgetArray.map((obj, ind) => {
|
||||
return { ...obj, ref: widgetRef[ind] };
|
||||
});
|
||||
|
||||
setWidget(newUpdateSigner);
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
// allow only (signature, stamp, initials, text, name, job title, company, email, cells) widget when isAllowModification true and user have session token
|
||||
const modifiedWidgets = widgetList.filter(
|
||||
//allow only (signature,stamp,initials,text,name, job title, company,email) widget when isAllowModification true and user have session token
|
||||
const modifiedWidgets = widget.filter(
|
||||
(data) =>
|
||||
![
|
||||
"dropdown",
|
||||
@@ -68,8 +121,8 @@ function WidgetComponent(props) {
|
||||
"checkbox"
|
||||
].includes(data.type)
|
||||
);
|
||||
// allow only (signature, stamp, initials, text, cells) widget when isAllowModification true and user does not have session token
|
||||
const unlogedInUserWidgets = widgetList.filter(
|
||||
//allow only (signature,stamp,initials,text) widget when isAllowModification true and user does not have session token
|
||||
const unlogedInUserWidgets = widget.filter(
|
||||
(data) =>
|
||||
![
|
||||
"dropdown",
|
||||
@@ -84,13 +137,13 @@ function WidgetComponent(props) {
|
||||
"company"
|
||||
].includes(data.type)
|
||||
);
|
||||
const selfSignWidgets = widgetList.filter(
|
||||
const selfSignWidgets = widget.filter(
|
||||
(data) =>
|
||||
!["dropdown", radioButtonWidget, textInputWidget].includes(data.type)
|
||||
);
|
||||
//if user select prefill role then allow only date,image,text,checkbox,radio,dropdownAdd commentMore actions
|
||||
//dropdown widget should only be show in template flow
|
||||
const prefillAllowWidgets = widgetList.filter((data) =>
|
||||
const prefillAllowWidgets = widget.filter((data) =>
|
||||
(props.isPrefillDropdown ? ["dropdown"] : [])
|
||||
.concat([radioButtonWidget, textWidget, "date", "image", "checkbox"])
|
||||
.includes(data.type)
|
||||
@@ -108,7 +161,7 @@ function WidgetComponent(props) {
|
||||
return unlogedInUserWidgets;
|
||||
}
|
||||
} else if (props?.roleName !== "prefill") {
|
||||
return widgetList.filter((data) => ![textWidget].includes(data.type));
|
||||
return widget.filter((data) => ![textWidget].includes(data.type));
|
||||
}
|
||||
};
|
||||
const handleSelectRecipient = () => {
|
||||
@@ -210,12 +263,7 @@ function WidgetComponent(props) {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="p-[12px] grid lg:grid-cols-2 gap-x-2 lg:gap-y-1.5 pt-3"
|
||||
data-tut="addWidgets"
|
||||
role="list"
|
||||
aria-label="Add widgets"
|
||||
>
|
||||
<div className="p-[15px] flex flex-col pt-4" data-tut="addWidgets">
|
||||
<WidgetList
|
||||
updateWidgets={handleWidgetType}
|
||||
handleDivClick={props.handleDivClick}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import "../../styles/AddUser.css";
|
||||
import RegexParser from "regex-parser";
|
||||
@@ -21,30 +21,11 @@ const WidgetNameModal = (props) => {
|
||||
hint: "",
|
||||
textvalidate: "",
|
||||
isReadOnly: false,
|
||||
cellCount: 5,
|
||||
cellCount: 5
|
||||
});
|
||||
const [isValid, setIsValid] = useState(true);
|
||||
const statusArr = ["Required", "Optional"];
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
const type = props?.defaultdata?.type;
|
||||
const isCellWidget = useMemo(() => type === cellsWidget, [type]);
|
||||
const isSignOrInitials = useMemo(
|
||||
() => ["signature", "initials"].includes(type),
|
||||
[type]
|
||||
);
|
||||
const showFontControls = useMemo(
|
||||
() =>
|
||||
[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.defaultdata?.type),
|
||||
[type]
|
||||
);
|
||||
|
||||
const handleHint = () => {
|
||||
const type = props.defaultdata?.type;
|
||||
@@ -73,13 +54,13 @@ const WidgetNameModal = (props) => {
|
||||
? props.defaultdata?.options?.validation?.pattern
|
||||
: props.defaultdata?.options?.validation?.type || "",
|
||||
isReadOnly: props.defaultdata?.options?.isReadOnly || false,
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5,
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5
|
||||
});
|
||||
} else {
|
||||
setFormdata({
|
||||
...formdata,
|
||||
name: props.defaultdata?.options?.name || "",
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5,
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,13 +68,12 @@ const WidgetNameModal = (props) => {
|
||||
const defaultSignatureType = signTypes || [];
|
||||
setSignatureType(defaultSignatureType);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line
|
||||
}, [props.defaultdata]);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (props.handleData) {
|
||||
if (isSignOrInitials) {
|
||||
if (["signature", "initials"].includes(props.defaultdata?.type)) {
|
||||
const enabledSignTypes = signatureType?.filter((x) => x.enabled);
|
||||
const isDefaultSignTypeOnly =
|
||||
enabledSignTypes?.length === 1 &&
|
||||
@@ -107,11 +87,9 @@ const WidgetNameModal = (props) => {
|
||||
props.handleData(data, props.defaultdata?.type);
|
||||
}
|
||||
} else {
|
||||
|
||||
const isTextInput = [
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
].includes(props.defaultdata?.type);
|
||||
const isTextInput = [textInputWidget, cellsWidget].includes(
|
||||
props.defaultdata?.type
|
||||
);
|
||||
const { isReadOnly, defaultValue, status } = formdata;
|
||||
// If it’s a text‐input widget, enforce that read-only fields have
|
||||
// either a defaultValue or an "optional" status.
|
||||
@@ -132,7 +110,7 @@ const WidgetNameModal = (props) => {
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: "",
|
||||
cellCount: 5,
|
||||
cellCount: 5
|
||||
});
|
||||
setSignatureType(signTypes);
|
||||
}
|
||||
@@ -170,11 +148,10 @@ const WidgetNameModal = (props) => {
|
||||
} else {
|
||||
setIsValid(true);
|
||||
}
|
||||
|
||||
const val = isCellWidget
|
||||
? e.target.value.slice(0, formdata.cellCount)
|
||||
: e.target.value;
|
||||
|
||||
const val =
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? e.target.value.slice(0, formdata.cellCount)
|
||||
: e.target.value;
|
||||
setFormdata({ ...formdata, [e.target.name]: val });
|
||||
};
|
||||
|
||||
@@ -183,7 +160,7 @@ const WidgetNameModal = (props) => {
|
||||
case "email":
|
||||
return "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/";
|
||||
case "number":
|
||||
return "^\\d+(?:\\.\\d+)?$"; // "/^\\d+$/";
|
||||
return "/^\\d+$/";
|
||||
case "text":
|
||||
//allow space in text regex
|
||||
return "/^[a-zA-Z ]+$/";
|
||||
@@ -207,19 +184,23 @@ const WidgetNameModal = (props) => {
|
||||
<ModalUi
|
||||
isOpen={props.isOpen}
|
||||
handleClose={props.handleClose && props.handleClose}
|
||||
title={isSignOrInitials ? t("signature-setting") : t("widget-info")}
|
||||
title={
|
||||
["signature", "initials"].includes(props.defaultdata?.type)
|
||||
? t("signature-setting")
|
||||
: t("widget-info")
|
||||
}
|
||||
>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={`${
|
||||
[textInputWidget, cellsWidget].includes(props.defaultdata?.type)
|
||||
? "pt-0"
|
||||
: isSignOrInitials
|
||||
: ["signature", "initials"].includes(props.defaultdata?.type)
|
||||
? "pt-2"
|
||||
: ""
|
||||
} p-[20px] text-base-content`}
|
||||
>
|
||||
{!isSignOrInitials && (
|
||||
{!["signature", "initials"].includes(props.defaultdata?.type) && (
|
||||
<div className="mb-[0.75rem] text-[13px]">
|
||||
<label htmlFor="name">
|
||||
{t("name")}
|
||||
@@ -236,7 +217,7 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isCellWidget && (
|
||||
{props.defaultdata?.type === cellsWidget && (
|
||||
<div className="mb-[0.75rem] text-[13px]">
|
||||
<label htmlFor="cellCount">{t("cell-count")}</label>
|
||||
<input
|
||||
@@ -250,10 +231,7 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{[
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
].includes(props.defaultdata?.type) &&
|
||||
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) &&
|
||||
props?.roleName !== "prefill" && (
|
||||
<>
|
||||
<div className="mb-[0.75rem]">
|
||||
@@ -266,7 +244,11 @@ const WidgetNameModal = (props) => {
|
||||
value={formdata.defaultValue}
|
||||
onChange={(e) => handledefaultChange(e)}
|
||||
autoComplete="off"
|
||||
maxLength={isCellWidget ? formdata.cellCount : undefined}
|
||||
maxLength={
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? formdata.cellCount
|
||||
: undefined
|
||||
}
|
||||
onBlur={() => {
|
||||
if (isValid === false) {
|
||||
setFormdata({ ...formdata, defaultValue: "" });
|
||||
@@ -280,7 +262,7 @@ const WidgetNameModal = (props) => {
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-exclamation-circle text-[15px] mr-1"
|
||||
className="fa-light fa-exclamation-circle text-[15px]"
|
||||
style={{ color: "#fab005" }}
|
||||
></i>
|
||||
{t("invalid-default-value")}
|
||||
@@ -289,8 +271,7 @@ const WidgetNameModal = (props) => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!props?.isSelfSign &&
|
||||
!isSignOrInitials &&
|
||||
{!["signature", "initials"].includes(props.defaultdata?.type) &&
|
||||
props?.roleName !== "prefill" && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<div className="flex flex-row gap-[10px] mb-[0.5rem]">
|
||||
@@ -321,10 +302,9 @@ const WidgetNameModal = (props) => {
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{[
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
].includes(props.defaultdata?.type) &&
|
||||
{[textInputWidget, cellsWidget].includes(
|
||||
props.defaultdata?.type
|
||||
) &&
|
||||
props?.roleName !== "prefill" && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
@@ -350,7 +330,7 @@ const WidgetNameModal = (props) => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isSignOrInitials && (
|
||||
{["signature", "initials"].includes(props.defaultdata?.type) && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="signaturetype" className="text-[14px] mb-[0.7rem]">
|
||||
{t("allowed-signature-types")}
|
||||
@@ -378,7 +358,7 @@ const WidgetNameModal = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!props?.isSelfSign && props?.roleName !== "prefill" && (
|
||||
{props?.roleName !== "prefill" && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="hint" className="text-[13px]">
|
||||
{t("hint")}
|
||||
@@ -392,7 +372,15 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{showFontControls && (
|
||||
{[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.defaultdata?.type) && (
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3 mb-3">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<span className="whitespace-nowrap">{t("font-size")}: </span>
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import { useDragLayer } from "react-dnd";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
getContainerScale,
|
||||
handleBackground,
|
||||
radioButtonWidget
|
||||
} from "../../constant/Utils";
|
||||
|
||||
export default function WidgetsDragPreview(props) {
|
||||
const { widgetType, isDragging, initialOffset, currentOffset } = useDragLayer(
|
||||
(monitor) => ({
|
||||
widgetType: monitor.getItem(),
|
||||
isDragging: monitor.isDragging(),
|
||||
initialOffset: monitor.getInitialSourceClientOffset(),
|
||||
currentOffset: monitor.getSourceClientOffset()
|
||||
})
|
||||
);
|
||||
const options = [{ name: "Option-1" }, { name: "Option-2" }];
|
||||
if (!isDragging) return null;
|
||||
|
||||
if (!initialOffset || !currentOffset) return null;
|
||||
|
||||
const { x, y } = currentOffset;
|
||||
const getWidgetDetails = defaultWidthHeight(widgetType?.text);
|
||||
const widget = {
|
||||
type: widgetType?.text,
|
||||
width: getWidgetDetails?.width,
|
||||
height: getWidgetDetails?.height
|
||||
};
|
||||
const calculateFont = () => {
|
||||
const containerScale = getContainerScale(
|
||||
props.pdfOriginalWH,
|
||||
props.pageNumber,
|
||||
props.containerWH
|
||||
);
|
||||
const fontSize = 12 * containerScale * props.scale;
|
||||
return fontSize;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 pointer-events-none z-[9999]">
|
||||
<div
|
||||
className="absolute"
|
||||
style={{
|
||||
transform: `translate(${x}px, ${y}px)`
|
||||
}}
|
||||
>
|
||||
{widgetType.text === "checkbox" ||
|
||||
widgetType.text === radioButtonWidget ? (
|
||||
<div
|
||||
id="checkbox&radio-preview"
|
||||
style={{
|
||||
background: handleBackground(
|
||||
props?.data,
|
||||
props?.isNeedSign,
|
||||
props?.uniqueId
|
||||
)
|
||||
}}
|
||||
className="border-red-600 rounded-[2px] border-[1px]"
|
||||
>
|
||||
{options.map((x, id) => {
|
||||
return (
|
||||
<div key={id}>
|
||||
<label
|
||||
style={{ fontSize: calculateFont(), color: "black" }}
|
||||
className={`mb-0 flex items-center gap-1`}
|
||||
>
|
||||
<input
|
||||
style={{
|
||||
width: calculateFont(),
|
||||
height: calculateFont()
|
||||
}}
|
||||
className={`${widgetType.text === "checkbox" ? "op-checkbox" : "op-radio rounded-full border-black appearance-none bg-white inline-block align-middle relative"} rounded-[1px]`}
|
||||
type={
|
||||
widgetType.text === "checkbox" ? "checkbox" : "radio"
|
||||
}
|
||||
/>
|
||||
<span>{x?.name}</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
width: props?.posWidth(widget, props?.isSignYourself),
|
||||
height: props?.posHeight(widget, props?.isSignYourself),
|
||||
background: handleBackground(
|
||||
props?.data,
|
||||
props?.isNeedSign,
|
||||
props?.uniqueId
|
||||
)
|
||||
}}
|
||||
className="border-red-600 rounded-[2px] border-[1px] flex items-center justify-center"
|
||||
>
|
||||
<span className="text-center text-[10px] font-medium">
|
||||
{widgetType?.text}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { removeBackground } from "@imgly/background-removal";
|
||||
@@ -21,10 +21,7 @@ import {
|
||||
convertTextToImg,
|
||||
convertJpegToPng,
|
||||
convertBase64ToFile,
|
||||
generatePdfName,
|
||||
getDefaultDate,
|
||||
getDefaultFormat,
|
||||
getSignerPages
|
||||
generatePdfName
|
||||
} from "../../constant/Utils";
|
||||
import CellsWidget from "./CellsWidget";
|
||||
import DatePicker from "react-datepicker";
|
||||
@@ -46,10 +43,24 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import { emailRegex } from "../../constant/const";
|
||||
import RegexParser from "regex-parser";
|
||||
import {
|
||||
saveToMySign
|
||||
} from "../../utils/widgetUtils";
|
||||
import { saveToMySign } from "../../utils/widgetUtils";
|
||||
|
||||
//function to get default format
|
||||
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||
//function to convert formatted date to new Date() format
|
||||
const getDefaultDate = (dateStr, format) => {
|
||||
//get valid date format for moment to convert formatted date to new Date() format
|
||||
const formats = changeDateToMomentFormat(format);
|
||||
const parsedDate = moment(dateStr, formats);
|
||||
let date;
|
||||
if (parsedDate.isValid()) {
|
||||
date = new Date(parsedDate.toISOString());
|
||||
return date;
|
||||
} else {
|
||||
date = new Date();
|
||||
return date;
|
||||
}
|
||||
};
|
||||
const fontOptions = [
|
||||
{ value: "Fasthand" },
|
||||
{ value: "Dancing Script" },
|
||||
@@ -114,28 +125,12 @@ function WidgetsValueModal(props) {
|
||||
props?.kiosk_signer || JSON.parse(localStorage.getItem("kiosk_signer"));
|
||||
const jsonSender = senderUser && JSON.parse(senderUser);
|
||||
const currentUserName = jsonSender && jsonSender?.name;
|
||||
const type = currWidgetsDetails?.type;
|
||||
const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`);
|
||||
|
||||
const [widgetValue, setWidgetValue] = useState(() => {
|
||||
if (currWidgetsDetails.type === "checkbox") {
|
||||
return undefined;
|
||||
}
|
||||
return (
|
||||
currWidgetsDetails?.options?.response ||
|
||||
currWidgetsDetails?.options?.defaultValue
|
||||
);
|
||||
});
|
||||
const blocked = [
|
||||
"checkbox",
|
||||
"dropdown",
|
||||
"date",
|
||||
"radio",
|
||||
];
|
||||
|
||||
const showClearbtn =
|
||||
!blocked.includes(type);
|
||||
|
||||
const [widgetValue, setWidgetValue] = useState(
|
||||
currWidgetsDetails.type !== "checkbox" &&
|
||||
(currWidgetsDetails?.options?.response ||
|
||||
currWidgetsDetails?.options?.defaultValue)
|
||||
);
|
||||
const [cellsValue, setCellsValue] = useState(() => {
|
||||
const count = currWidgetsDetails?.options?.cellCount || 5;
|
||||
const val =
|
||||
@@ -180,7 +175,7 @@ function WidgetsValueModal(props) {
|
||||
currWidgetsDetails?.options?.response,
|
||||
currWidgetsDetails?.options?.validation?.format
|
||||
)
|
||||
: ""
|
||||
: new Date()
|
||||
);
|
||||
useEffect(() => {
|
||||
dispatch(setScrollTriggerId(currWidgetsDetails?.key));
|
||||
@@ -211,8 +206,7 @@ function WidgetsValueModal(props) {
|
||||
} else {
|
||||
setHint(currWidgetsDetails?.type);
|
||||
}
|
||||
}
|
||||
else if (isSignOrInitials) {
|
||||
} else if (isSignOrInitials) {
|
||||
setIsTab(currWidgetsDetails?.signatureType);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -262,7 +256,9 @@ function WidgetsValueModal(props) {
|
||||
setXyPosition,
|
||||
uniqueId,
|
||||
false,
|
||||
data?.format
|
||||
data?.format,
|
||||
currWidgetsDetails?.options?.fontSize || 12,
|
||||
currWidgetsDetails?.options?.fontColor || "black"
|
||||
);
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
};
|
||||
@@ -884,23 +880,21 @@ function WidgetsValueModal(props) {
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
{value ? value : "Select date"}
|
||||
{value}
|
||||
<i className="fa-light fa-calendar ml-[5px]"></i>
|
||||
</div>
|
||||
));
|
||||
ExampleCustomInput.displayName = "ExampleCustomInput";
|
||||
//function to set onchange date
|
||||
const handleOnDateChange = (date) => {
|
||||
setWidgetValue(date);
|
||||
setStartDate(date);
|
||||
};
|
||||
const handleOnchangeTextBox = (e) => {
|
||||
// hide any prior validation error while typing
|
||||
setIsShowValidation(false);
|
||||
let value = e.target.value;
|
||||
setWidgetValue(value);
|
||||
setWidgetValue(e.target.value);
|
||||
onChangeInput(
|
||||
value,
|
||||
e.target.value,
|
||||
currWidgetsDetails,
|
||||
xyPosition,
|
||||
props.index,
|
||||
@@ -1476,59 +1470,52 @@ function WidgetsValueModal(props) {
|
||||
);
|
||||
case "date":
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div
|
||||
className={`border-[1px] text-base-content data-[theme=opensigndark]:border-base-content data-[theme=opensigncss]:border-gray-300 rounded-[2px] p-1 px-3`}
|
||||
>
|
||||
<DatePicker
|
||||
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
||||
<div className="flex justify-start ml-2 ">
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={months[getMonth(date)]}
|
||||
onChange={({ target: { value } }) =>
|
||||
changeMonth(months.indexOf(value))
|
||||
}
|
||||
>
|
||||
{months.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={getYear(date)}
|
||||
onChange={({ target: { value } }) => changeYear(value)}
|
||||
>
|
||||
{years.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
closeOnScroll={true}
|
||||
selected={startDate}
|
||||
onChange={(date) => handleOnDateChange(date)}
|
||||
popperPlacement="top-end"
|
||||
customInput={<ExampleCustomInput />}
|
||||
dateFormat={
|
||||
selectDate
|
||||
? selectDate?.format
|
||||
: currWidgetsDetails?.options?.validation?.format
|
||||
? currWidgetsDetails?.options?.validation?.format
|
||||
: "MM/dd/yyyy"
|
||||
}
|
||||
portalId="root-portal"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<span className="text-gray-300">
|
||||
{currWidgetsDetails?.options?.validation?.format}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`border-[1px] text-base-content data-[theme=opensigndark]:border-base-content data-[theme=opensigncss]:border-gray-300 rounded-[2px] p-1 px-3`}
|
||||
>
|
||||
<DatePicker
|
||||
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
||||
<div className="flex justify-start ml-2 ">
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={months[getMonth(date)]}
|
||||
onChange={({ target: { value } }) =>
|
||||
changeMonth(months.indexOf(value))
|
||||
}
|
||||
>
|
||||
{months.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
className="bg-transparent outline-none"
|
||||
value={getYear(date)}
|
||||
onChange={({ target: { value } }) => changeYear(value)}
|
||||
>
|
||||
{years.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
closeOnScroll={true}
|
||||
selected={startDate}
|
||||
onChange={(date) => handleOnDateChange(date)}
|
||||
popperPlacement="top-end"
|
||||
customInput={<ExampleCustomInput />}
|
||||
dateFormat={
|
||||
selectDate
|
||||
? selectDate?.format
|
||||
: currWidgetsDetails?.options?.validation?.format
|
||||
? currWidgetsDetails?.options?.validation?.format
|
||||
: "MM/dd/yyyy"
|
||||
}
|
||||
portalId="root-portal"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case "email":
|
||||
@@ -1608,6 +1595,9 @@ function WidgetsValueModal(props) {
|
||||
const isCheckBox =
|
||||
!currWidgetsDetails.options?.isReadOnly &&
|
||||
currWidgetsDetails.type === "checkbox";
|
||||
const isRadio =
|
||||
!currWidgetsDetails?.options?.isReadOnly &&
|
||||
currWidgetsDetails?.type === radioButtonWidget;
|
||||
if (isCheckBox) {
|
||||
//get minimum required count if exist
|
||||
const minCount =
|
||||
@@ -1739,14 +1729,13 @@ function WidgetsValueModal(props) {
|
||||
} else if (!isSave) {
|
||||
const widgetsPosition = xyPosition?.find((data) => data.Id === uniqueId);
|
||||
let nextWidgetDetails = null;
|
||||
const editableWidgets = (widgetsPosition?.placeHolder ?? []).flatMap(
|
||||
({ pos = [], pageNumber }) =>
|
||||
pos
|
||||
.filter(
|
||||
({ options }) =>
|
||||
!options?.isReadOnly
|
||||
)
|
||||
.map((widget) => ({ widget, pageNumber }))
|
||||
const editableWidgets = widgetsPosition?.placeHolder.flatMap((page) =>
|
||||
page.pos
|
||||
.filter((widget) => !widget.options?.isReadOnly)
|
||||
.map((widget) => ({
|
||||
widget,
|
||||
pageNumber: page.pageNumber
|
||||
}))
|
||||
);
|
||||
//get current index of widget
|
||||
const currentIndex = editableWidgets.findIndex(
|
||||
@@ -1948,11 +1937,13 @@ function WidgetsValueModal(props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-center">
|
||||
{showClearbtn ? (
|
||||
{!["checkbox", "dropdown", "date", "radio"].includes(
|
||||
currWidgetsDetails?.type
|
||||
) ? (
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost op-btn-sm text-base-content mr-1"
|
||||
onClick={handleClear}
|
||||
onClick={() => handleClear()}
|
||||
>
|
||||
{t("clear")}
|
||||
</button>
|
||||
|
||||
@@ -3,20 +3,15 @@ import { isMobile } from "../../constant/Utils";
|
||||
// `getWidgetType` is used to load ui of widget in side list
|
||||
const getWidgetType = (item, widgetName) => {
|
||||
return (
|
||||
<div
|
||||
title={widgetName}
|
||||
className="op-btn w-fit md:w-[100%] op-btn-primary op-btn-outline op-btn-sm focus:outline-none ml-[6px] outline outline-[1px] md:ml-0 p-0 overflow-hidden"
|
||||
>
|
||||
<div className="op-btn w-fit md:w-[100%] op-btn-primary op-btn-outline op-btn-sm focus:outline-none outline outline-[1.5px] ml-[6px] md:ml-0 p-0 overflow-hidden">
|
||||
<div className="w-full h-full flex md:justify-between items-center">
|
||||
<div className="flex justify-start items-center ml-1 overflow-hidden">
|
||||
{!isMobile && (
|
||||
<i className="fa-light fa-grip-vertical ml-[3px] text-[13px] lg:text-[13px]"></i>
|
||||
)}
|
||||
<span className="md:inline-block text-center text-[11px] lg:text-[13px] mx-[3px] truncate font-medium capitalize">
|
||||
<div className="flex justify-start items-center text-[13px] ml-1">
|
||||
{!isMobile && <i className="fa-light fa-grip-vertical ml-[3px]"></i>}
|
||||
<span className="md:inline-block text-center text-[15px] ml-[5px] font-semibold pr-1 md:pr-0">
|
||||
{widgetName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-[13px] lg:text-[14px] op-btn op-btn-primary rounded-none w-[20px] h-full flex justify-center items-center">
|
||||
<div className="text-[20px] op-btn op-btn-primary rounded-none w-[40px] h-full flex justify-center items-center">
|
||||
<i className={item.icon}></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Parse from "parse";
|
||||
import ReactQuill from "react-quill-new";
|
||||
import "../../styles/quill.css";
|
||||
import EditorToolbar, { module1, module2, formats } from "../pdf/EditorToolbar";
|
||||
import Tooltip from "../../primitives/Tooltip";
|
||||
import Alert from "../../primitives/Alert";
|
||||
import Loader from "../../primitives/Loader";
|
||||
|
||||
const MailTemplateEditor = ({
|
||||
info,
|
||||
tenantId,
|
||||
}) => {
|
||||
const appName = localStorage.getItem("appname") || "OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const editorRef = useRef();
|
||||
const editorRefCom = useRef();
|
||||
const [requestBody, setRequestBody] = useState("");
|
||||
const [requestSubject, setRequestSubject] = useState("");
|
||||
const [completionBody, setCompletionBody] = useState("");
|
||||
const [completionSubject, setCompletionSubject] = useState("");
|
||||
const [isDefaultMail, setIsDefaultMail] = useState({
|
||||
requestMail: false,
|
||||
completionMail: false
|
||||
});
|
||||
const [isMailLoader, setIsMailLoader] = useState({
|
||||
request: false,
|
||||
completion: false
|
||||
});
|
||||
const [isalert, setIsAlert] = useState({ type: "success", msg: "" });
|
||||
const defaultRequestSubject = `{{sender_name}} has requested you to sign {{document_title}}`;
|
||||
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign {{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p><a href='{{signing_url}}' rel='noopener noreferrer' target='_blank'>Sign here</a></p><br><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
|
||||
const defaultCompletionSubject = `Document {{document_title}} has been signed by all parties`;
|
||||
const defaultCompletionBody = `<p>Hi {{sender_name}},</p><br><p>All parties have successfully signed the document {{document_title}}. Kindly download the document from the attachment.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
|
||||
const cloudfunction =
|
||||
"updatetenant";
|
||||
|
||||
useEffect(() => {
|
||||
fetchSubscription();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
info
|
||||
]);
|
||||
|
||||
const handleModifyMail = (mode) => {
|
||||
mode === "request"
|
||||
? setIsDefaultMail((p) => ({ ...p, requestMail: !p?.requestMail }))
|
||||
: setIsDefaultMail((p) => ({ ...p, completionMail: !p?.completionMail }));
|
||||
};
|
||||
const fetchSubscription = async () => {
|
||||
await tenantEmailTemplate(info);
|
||||
};
|
||||
|
||||
const tenantEmailTemplate = async (tenantRes) => {
|
||||
if (tenantRes === "user does not exist!") {
|
||||
alert(t("user-not-exist"));
|
||||
} else if (tenantRes) {
|
||||
const updateRes = tenantRes;
|
||||
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign {{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p><a href='{{signing_url}}' rel='noopener noreferrer' target='_blank'>Sign here</a></p><br><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
|
||||
if (updateRes?.RequestBody) {
|
||||
setRequestBody(updateRes?.RequestBody);
|
||||
setRequestSubject(updateRes?.RequestSubject);
|
||||
setIsDefaultMail((prev) => ({ ...prev, requestMail: false }));
|
||||
} else {
|
||||
setRequestBody(defaultRequestBody);
|
||||
setRequestSubject(defaultRequestSubject);
|
||||
setIsDefaultMail((prev) => ({ ...prev, requestMail: true }));
|
||||
}
|
||||
if (updateRes?.CompletionBody) {
|
||||
setCompletionBody(updateRes?.CompletionBody);
|
||||
setCompletionSubject(updateRes?.CompletionSubject);
|
||||
setIsDefaultMail((prev) => ({ ...prev, completionMail: false }));
|
||||
} else {
|
||||
setCompletionBody(defaultCompletionBody);
|
||||
setCompletionSubject(defaultCompletionSubject);
|
||||
setIsDefaultMail((prev) => ({ ...prev, completionMail: true }));
|
||||
}
|
||||
}
|
||||
};
|
||||
//function to save completion email template
|
||||
const handleSaveCompletionEmail = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const replacedHtmlBody = completionBody.replace(/"/g, "'");
|
||||
const htmlBody = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>${replacedHtmlBody}</body></html>`;
|
||||
const updateTenant = await Parse.Cloud.run(cloudfunction, {
|
||||
tenantId: tenantId,
|
||||
details: {
|
||||
CompletionBody: htmlBody,
|
||||
CompletionSubject: completionSubject
|
||||
}
|
||||
});
|
||||
if (updateTenant) {
|
||||
const updateRes = JSON.parse(JSON.stringify(updateTenant));
|
||||
setCompletionBody(updateRes?.CompletionBody);
|
||||
setCompletionSubject(updateRes?.CompletionSubject);
|
||||
setIsAlert({ type: "success", msg: t("saved-successfully") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error while saving completion email template: ", err);
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
}
|
||||
};
|
||||
//function to save request email template
|
||||
const handleSaveRequestEmail = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const replacedHtmlBody = requestBody.replace(/"/g, "'");
|
||||
const htmlBody = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>${replacedHtmlBody}</body></html>`;
|
||||
const updateTenant = await Parse.Cloud.run(cloudfunction, {
|
||||
tenantId: tenantId,
|
||||
details: { RequestBody: htmlBody, RequestSubject: requestSubject }
|
||||
});
|
||||
if (updateTenant) {
|
||||
const updateRes = JSON.parse(JSON.stringify(updateTenant));
|
||||
setRequestBody(updateRes?.RequestBody);
|
||||
setRequestSubject(updateRes?.RequestSubject);
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.RequestBody = updateRes?.RequestBody;
|
||||
extUser.TenantId.RequestSubject = updateRes?.RequestSubject;
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
setIsAlert({ type: "success", msg: t("saved-successfully") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error while saving request email template: ", err);
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
}
|
||||
};
|
||||
|
||||
//function to use reset form
|
||||
const handleReset = async (request, completion) => {
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
handleModifyMail(request);
|
||||
if (request && !isDefaultMail?.requestMail) {
|
||||
setRequestBody(defaultRequestBody);
|
||||
setRequestSubject(defaultRequestSubject);
|
||||
setIsMailLoader((p) => ({ ...p, request: true }));
|
||||
try {
|
||||
await Parse.Cloud.run(cloudfunction, {
|
||||
tenantId: tenantId,
|
||||
details: { RequestBody: "", RequestSubject: "" }
|
||||
});
|
||||
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.RequestBody = "";
|
||||
extUser.TenantId.RequestSubject = "";
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error while resetting request mail: ", err);
|
||||
} finally {
|
||||
setIsMailLoader((p) => ({ ...p, request: false }));
|
||||
}
|
||||
} else if (completion && !isDefaultMail?.completionMail) {
|
||||
setCompletionSubject(defaultCompletionSubject);
|
||||
setCompletionBody(defaultCompletionBody);
|
||||
setIsMailLoader((p) => ({ ...p, completion: true }));
|
||||
try {
|
||||
await Parse.Cloud.run(cloudfunction, {
|
||||
tenantId: tenantId,
|
||||
details: { CompletionBody: "", CompletionSubject: "" }
|
||||
});
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.CompletionBody = "";
|
||||
extUser.TenantId.CompletionSubject = "";
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error while resetting completion mail: ", err);
|
||||
} finally {
|
||||
setIsMailLoader((p) => ({ ...p, completion: false }));
|
||||
}
|
||||
}
|
||||
};
|
||||
//function for handle ontext change and save again text in delta
|
||||
const handleOnchangeRequest = () => {
|
||||
if (editorRef.current) {
|
||||
const html = editorRef.current.editor.root.innerHTML;
|
||||
setRequestBody(html);
|
||||
}
|
||||
};
|
||||
const handleOnchangeCompletion = () => {
|
||||
if (editorRefCom.current) {
|
||||
const html = editorRefCom.current.editor.root.innerHTML;
|
||||
setCompletionBody(html);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{isalert.msg && <Alert type={isalert.type}>{isalert.msg}</Alert>}
|
||||
<div className="flex flex-col mb-4">
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-[14px] mb-[0.7rem] font-medium">
|
||||
{t("request-email")}
|
||||
</h1>
|
||||
<div className="relative mt-2 mb-4">
|
||||
{isMailLoader.request && (
|
||||
<div className="flex z-[100] justify-center items-center absolute w-full h-full rounded-box bg-black/30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
isDefaultMail?.requestMail && (
|
||||
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
|
||||
<button
|
||||
onClick={() => handleModifyMail("request")}
|
||||
className="op-btn op-btn-primary shadow-lg"
|
||||
>
|
||||
{t("modify")}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<form
|
||||
onSubmit={handleSaveRequestEmail}
|
||||
className="p-3 border-[1px] border-base-content rounded-box"
|
||||
>
|
||||
<div className="text-lg font-normal">
|
||||
<label className="text-sm">
|
||||
{t("subject")}{" "}
|
||||
<Tooltip
|
||||
id={"request-sub-tooltip"}
|
||||
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
value={requestSubject}
|
||||
onChange={(e) => setRequestSubject(e.target.value)}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-lg font-normal py-2">
|
||||
<label className="text-sm mt-3">
|
||||
{t("body")}{" "}
|
||||
<Tooltip
|
||||
id={"request-body-tooltip"}
|
||||
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
|
||||
/>
|
||||
</label>
|
||||
<EditorToolbar containerId="toolbar1" />
|
||||
<ReactQuill
|
||||
theme="snow"
|
||||
value={requestBody}
|
||||
placeholder="add body of email"
|
||||
ref={editorRef}
|
||||
modules={module1}
|
||||
formats={formats}
|
||||
onChange={handleOnchangeRequest}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2">
|
||||
<button
|
||||
disabled={!requestBody || !requestSubject}
|
||||
className="op-btn op-btn-primary"
|
||||
type="submit"
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => handleReset("request")}
|
||||
>
|
||||
{t("reset")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<h1 className="text-[14px] mb-[0.7rem] font-medium">
|
||||
{t("completion-email")}
|
||||
</h1>
|
||||
<div className="relative my-2">
|
||||
{isMailLoader.completion && (
|
||||
<div className="flex z-[100] justify-center items-center absolute w-full h-full rounded-box bg-black/30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{
|
||||
isDefaultMail?.completionMail && (
|
||||
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
|
||||
<button
|
||||
onClick={() => handleModifyMail("completion")}
|
||||
className="op-btn op-btn-primary shadow-lg"
|
||||
>
|
||||
{t("modify")}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<form
|
||||
onSubmit={handleSaveCompletionEmail}
|
||||
className="p-3 border-[1px] border-base-content rounded-box"
|
||||
>
|
||||
<div className="text-lg font-normal">
|
||||
<label className="text-sm">
|
||||
{t("subject")}{" "}
|
||||
<Tooltip
|
||||
id={"complete-sub-tooltip"}
|
||||
message={`${t("variables-use")}:{{sender_name}} {{document_title}}`}
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
value={completionSubject}
|
||||
onChange={(e) => setCompletionSubject(e.target.value)}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-lg font-normal py-2">
|
||||
<label className="text-sm mt-3">
|
||||
{t("body")}{" "}
|
||||
<Tooltip
|
||||
id={"complete-body-tooltip"}
|
||||
message={`${t("variables-use")}:{{sender_name}} {{document_title}} {{signing_url}}`}
|
||||
/>
|
||||
</label>
|
||||
<EditorToolbar containerId="toolbar2" />
|
||||
<ReactQuill
|
||||
theme="snow"
|
||||
value={completionBody}
|
||||
placeholder="add body of email"
|
||||
ref={editorRefCom}
|
||||
modules={module2}
|
||||
formats={formats}
|
||||
onChange={handleOnchangeCompletion}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2">
|
||||
<button
|
||||
disabled={!completionBody || !completionSubject}
|
||||
className="op-btn op-btn-primary"
|
||||
type="submit"
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => handleReset(null, "completion")}
|
||||
>
|
||||
{t("reset")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default MailTemplateEditor;
|
||||
+168
-210
@@ -9,7 +9,7 @@ import fontkit from "@pdf-lib/fontkit";
|
||||
import { themeColor } from "./const";
|
||||
import { format, toZonedTime } from "date-fns-tz";
|
||||
import i18n from "../i18n";
|
||||
import { applyNumberFormulasToPages, buildDownloadFilename } from "../utils";
|
||||
import { buildDownloadFilename } from "../utils";
|
||||
|
||||
export const fontsizeArr = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28];
|
||||
export const fontColorArr = ["red", "black", "blue", "yellow"];
|
||||
@@ -327,7 +327,6 @@ export const widgets = [
|
||||
{ type: "name", icon: "fa-light fa-user", iconSize: "21px" },
|
||||
{ type: "job title", icon: "fa-light fa-address-card", iconSize: "17px" },
|
||||
{ type: "company", icon: "fa-light fa-building", iconSize: "25px" },
|
||||
{ type: "email", icon: "fa-light fa-envelope", iconSize: "20px" },
|
||||
{ type: "date", icon: "fa-light fa-calendar-days", iconSize: "20px" },
|
||||
{ type: textWidget, icon: "fa-light fa-text-width", iconSize: "20px" },
|
||||
{ type: cellsWidget, icon: "fa-light fa-table-cells", iconSize: "20px" },
|
||||
@@ -338,7 +337,8 @@ export const widgets = [
|
||||
iconSize: "19px"
|
||||
},
|
||||
{ type: radioButtonWidget, icon: "fa-light fa-circle-dot", iconSize: "20px" },
|
||||
{ type: "image", icon: "fa-light fa-image", iconSize: "20px" }
|
||||
{ type: "image", icon: "fa-light fa-image", iconSize: "20px" },
|
||||
{ type: "email", icon: "fa-light fa-envelope", iconSize: "20px" }
|
||||
];
|
||||
|
||||
export const getDate = (dateformat) => {
|
||||
@@ -410,51 +410,23 @@ export const changeDateToMomentFormat = (format) => {
|
||||
return "L";
|
||||
}
|
||||
};
|
||||
|
||||
export const getSignerPages = (xyPosition = [], currentKey, signerId) => {
|
||||
if (!Array.isArray(xyPosition)) {
|
||||
return [];
|
||||
}
|
||||
const hasSigners = xyPosition.some((item) =>
|
||||
Array.isArray(item?.placeHolder)
|
||||
);
|
||||
if (hasSigners) {
|
||||
const candidates =
|
||||
signerId !== undefined && signerId !== null && signerId !== ""
|
||||
? xyPosition.filter(
|
||||
(item) => item.Id === signerId || item.signerObjId === signerId
|
||||
)
|
||||
: xyPosition;
|
||||
for (const signer of candidates) {
|
||||
const pages = signer?.placeHolder || [];
|
||||
const ownsWidget = pages.some((page) =>
|
||||
(page?.pos || []).some((widget) => widget?.key === currentKey)
|
||||
);
|
||||
if (ownsWidget) {
|
||||
return pages;
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
return xyPosition;
|
||||
};
|
||||
|
||||
export const addWidgetOptions = (
|
||||
type,
|
||||
signer,
|
||||
placeholder,
|
||||
role,
|
||||
widgetValue
|
||||
) => {
|
||||
export const addWidgetOptions = (type, signer, placeholder, widgetValue) => {
|
||||
let defaultOpt;
|
||||
//condition to handle widgets name field
|
||||
if (placeholder) {
|
||||
const countSameWidget = placeholder?.reduce((count, page) => {
|
||||
return count + page.pos.filter((item) => item.type === type).length;
|
||||
}, 0);
|
||||
const count = countSameWidget + 1;
|
||||
defaultOpt = { name: `${type}-${count}`, status: "required" };
|
||||
defaultOpt = {
|
||||
name: `${type}-${count}`,
|
||||
status: "required"
|
||||
};
|
||||
} else {
|
||||
defaultOpt = { name: `${type}-1`, status: "required" };
|
||||
defaultOpt = {
|
||||
name: `${type}-1`,
|
||||
status: "required"
|
||||
};
|
||||
}
|
||||
switch (type) {
|
||||
case "signature":
|
||||
@@ -462,7 +434,11 @@ export const addWidgetOptions = (
|
||||
case "stamp":
|
||||
return defaultOpt;
|
||||
case "checkbox":
|
||||
return { ...defaultOpt, isReadOnly: false, isHideLabel: false };
|
||||
return {
|
||||
...defaultOpt,
|
||||
isReadOnly: false,
|
||||
isHideLabel: false
|
||||
};
|
||||
case textInputWidget:
|
||||
return { ...defaultOpt, isReadOnly: false };
|
||||
case cellsWidget:
|
||||
@@ -476,18 +452,27 @@ export const addWidgetOptions = (
|
||||
case "initials":
|
||||
return defaultOpt;
|
||||
case "name":
|
||||
return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
|
||||
return {
|
||||
...defaultOpt,
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
case "company":
|
||||
return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
|
||||
return {
|
||||
...defaultOpt,
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
case "job title":
|
||||
return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
|
||||
return {
|
||||
...defaultOpt,
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
case "date": {
|
||||
const dateFormat = signer?.DateFormat
|
||||
? selectFormat(signer?.DateFormat)
|
||||
: "MM/dd/yyyy";
|
||||
return {
|
||||
...defaultOpt,
|
||||
response: role === "prefill" ? getDate(signer?.DateFormat) : "",
|
||||
response: getDate(signer?.DateFormat),
|
||||
validation: { format: dateFormat, type: "date-format" }
|
||||
};
|
||||
}
|
||||
@@ -528,9 +513,15 @@ export const addWidgetSelfsignOptions = (
|
||||
return count + page.pos.filter((item) => item.type === type).length;
|
||||
}, 0);
|
||||
const count = countSameWidget + 1;
|
||||
defaultOpt = { name: `${type}-${count}`, status: "required" };
|
||||
defaultOpt = {
|
||||
name: `${type}-${count}`,
|
||||
status: "required"
|
||||
};
|
||||
} else {
|
||||
defaultOpt = { name: `${type}-1`, status: "required" };
|
||||
defaultOpt = {
|
||||
name: `${type}-1`,
|
||||
status: "required"
|
||||
};
|
||||
}
|
||||
switch (type) {
|
||||
case "signature":
|
||||
@@ -1011,7 +1002,7 @@ export const onChangeInput = (
|
||||
dateFormat,
|
||||
fontSize,
|
||||
fontColor,
|
||||
dateDetails
|
||||
isDateReadOnly
|
||||
) => {
|
||||
const isSigners = xyPosition.some(
|
||||
(data) => data.signerPtr || data.Role === "prefill"
|
||||
@@ -1043,18 +1034,9 @@ export const onChangeInput = (
|
||||
options: {
|
||||
...position.options,
|
||||
response: value,
|
||||
fontSize: fontSize ? fontSize : position.options?.fontSize,
|
||||
fontColor: fontColor
|
||||
? fontColor
|
||||
: position.options?.fontColor,
|
||||
isReadOnly:
|
||||
dateDetails?.isReadOnly !== "undefined"
|
||||
? dateDetails?.isReadOnly
|
||||
: position.options?.isReadOnly,
|
||||
status:
|
||||
dateDetails?.status !== "undefined"
|
||||
? dateDetails?.status
|
||||
: position.options?.status,
|
||||
fontSize: fontSize,
|
||||
fontColor: fontColor,
|
||||
isReadOnly: isDateReadOnly || false,
|
||||
validation: {
|
||||
type: "date-format",
|
||||
format: dateFormat // This indicates the required date format explicitly.
|
||||
@@ -1081,11 +1063,9 @@ export const onChangeInput = (
|
||||
return obj;
|
||||
});
|
||||
|
||||
const recalculatedPages = applyNumberFormulasToPages(newUpdateSignPos);
|
||||
|
||||
const newUpdateSigner = xyPosition.map((obj) => {
|
||||
if (obj.Id === userId) {
|
||||
return { ...obj, placeHolder: recalculatedPages };
|
||||
return { ...obj, placeHolder: newUpdateSignPos };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
@@ -1131,7 +1111,7 @@ export const onChangeInput = (
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
setXyPosition(applyNumberFormulasToPages(updatePlaceholder));
|
||||
setXyPosition(updatePlaceholder);
|
||||
}
|
||||
};
|
||||
//function to increase height of text area on press enter
|
||||
@@ -1226,13 +1206,13 @@ export const calculateInitialWidthHeight = (widgetData) => {
|
||||
export const widgetDataValue = (type, value) => {
|
||||
switch (type) {
|
||||
case "name":
|
||||
return value?.Name || value?.name;
|
||||
return value?.Name;
|
||||
case "company":
|
||||
return value?.Company || value?.company;
|
||||
return value?.Company;
|
||||
case "job title":
|
||||
return value?.JobTitle || value?.jobTitle;
|
||||
return value?.JobTitle;
|
||||
case "email":
|
||||
return value?.Email || value?.email;
|
||||
return value?.Email;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -1856,16 +1836,6 @@ export const embedWidgetsToDoc = async (
|
||||
console.log("error in image", e);
|
||||
}
|
||||
for (let [id, position] of widgetsPositionArr.entries()) {
|
||||
const isTextTypeWidget = [
|
||||
textWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"date",
|
||||
"email"
|
||||
].includes(position.type);
|
||||
if (hasError) break; // Stop the inner loop if an error occurred
|
||||
try {
|
||||
let img;
|
||||
@@ -1914,13 +1884,33 @@ export const embedWidgetsToDoc = async (
|
||||
return y;
|
||||
}
|
||||
} else {
|
||||
const yPosition = isTextTypeWidget ? resizePos + 6 : resizePos;
|
||||
const WidgetsTypeTextExist = [
|
||||
textWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"date",
|
||||
"email"
|
||||
].includes(position.type);
|
||||
const yPosition = WidgetsTypeTextExist ? resizePos + 6 : resizePos;
|
||||
return yPosition;
|
||||
}
|
||||
};
|
||||
const color = position?.options?.fontColor;
|
||||
const updateColorInRgb = getWidgetsFontColor(color);
|
||||
const fontSize = parseInt(position?.options?.fontSize || 12);
|
||||
const isTextTypeWidget = [
|
||||
textWidget,
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"date",
|
||||
"email"
|
||||
].includes(position.type);
|
||||
if (position.type === "checkbox") {
|
||||
// Determine layout mode: 'vertical' (default) or 'horizontal'
|
||||
// const isHorizontal = position.layout === "horizontal";
|
||||
@@ -2006,19 +1996,9 @@ export const embedWidgetsToDoc = async (
|
||||
} else if (isTextTypeWidget) {
|
||||
let textContent = "";
|
||||
if (position?.options?.response) {
|
||||
if (
|
||||
position.type === "date" &&
|
||||
position.options?.response === "today"
|
||||
) {
|
||||
const getFormat = changeDateToMomentFormat(
|
||||
position?.options?.validation?.format
|
||||
);
|
||||
textContent = moment().format(getFormat);
|
||||
} else {
|
||||
textContent = position.options?.response;
|
||||
}
|
||||
textContent = position.options?.response;
|
||||
} else if (position?.options?.defaultValue) {
|
||||
textContent = position?.options?.defaultValue?.toString();
|
||||
textContent = position?.options?.defaultValue;
|
||||
}
|
||||
if (position.type === cellsWidget) {
|
||||
const cellCount =
|
||||
@@ -2045,16 +2025,15 @@ export const embedWidgetsToDoc = async (
|
||||
if (ch) page.drawText(ch, textPosition);
|
||||
}
|
||||
} else {
|
||||
const fixedWidth = widgetWidth; // Set your fixed
|
||||
textContent = textContent?.toString();
|
||||
const isNewOnEnterLineExist = textContent?.includes("\n");
|
||||
const fixedWidth = widgetWidth; // Set your fixed width
|
||||
const isNewOnEnterLineExist = textContent.includes("\n");
|
||||
|
||||
// Function to break text into lines based on the fixed width
|
||||
const NewbreakTextIntoLines = (textContent, width) => {
|
||||
const lines = [];
|
||||
let currentLine = "";
|
||||
|
||||
for (const word of textContent?.split(" ")) {
|
||||
for (const word of textContent.split(" ")) {
|
||||
//get text line width
|
||||
const lineWidth = font.widthOfTextAtSize(
|
||||
`${currentLine} ${word}`,
|
||||
@@ -2239,6 +2218,7 @@ export const embedWidgetsToDoc = async (
|
||||
width: widgetWidth,
|
||||
height: widgetHeight
|
||||
};
|
||||
|
||||
const imageOptions = getWidgetPosition(page, signature, 1, getSize);
|
||||
page.drawImage(img, imageOptions);
|
||||
}
|
||||
@@ -2366,34 +2346,56 @@ export const contractDocument = async (documentId, include) => {
|
||||
|
||||
//function for add default signature or image for all requested location
|
||||
export const addDefaultSignatureImg = (xyPosition, defaultSignImg, type) => {
|
||||
let imgWH = { width: "", height: "" };
|
||||
const img = new Image();
|
||||
img.src = defaultSignImg;
|
||||
if (img.complete) {
|
||||
imgWH = { width: img.width, height: img.height };
|
||||
}
|
||||
|
||||
let xyDefaultPos = [];
|
||||
for (let i = 0; i < xyPosition.length; i++) {
|
||||
// let getIMGWH;
|
||||
const getXYdata = xyPosition[i].pos;
|
||||
const getPageNo = xyPosition[i].pageNumber;
|
||||
const getPosData = getXYdata;
|
||||
|
||||
const addSign = getPosData.map((position) => {
|
||||
// getIMGWH = calculateImgAspectRatio(imgWH, position);
|
||||
if (position.type) {
|
||||
if (position?.type === type) {
|
||||
return {
|
||||
...position,
|
||||
SignUrl: defaultSignImg,
|
||||
// Width: getIMGWH.newWidth,
|
||||
// Height: getIMGWH.newHeight,
|
||||
ImageType: "default",
|
||||
options: { ...position.options, response: defaultSignImg }
|
||||
options: {
|
||||
...position.options,
|
||||
response: defaultSignImg
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (position && !position.isStamp) {
|
||||
return {
|
||||
...position,
|
||||
SignUrl: defaultSignImg,
|
||||
// Width: getIMGWH.newWidth,
|
||||
// Height: getIMGWH.newHeight,
|
||||
ImageType: "default",
|
||||
options: { ...position.options, response: defaultSignImg }
|
||||
options: {
|
||||
...position.options,
|
||||
response: defaultSignImg
|
||||
}
|
||||
};
|
||||
}
|
||||
return position;
|
||||
});
|
||||
|
||||
const newXypos = { pageNumber: getPageNo, pos: addSign };
|
||||
const newXypos = {
|
||||
pageNumber: getPageNo,
|
||||
pos: addSign
|
||||
};
|
||||
xyDefaultPos.push(newXypos);
|
||||
}
|
||||
return xyDefaultPos;
|
||||
@@ -2980,34 +2982,34 @@ export const saveLanguageInLocal = (i18n) => {
|
||||
const detectedLanguage = i18n.language || "en";
|
||||
localStorage.setItem("i18nextLng", detectedLanguage);
|
||||
};
|
||||
|
||||
// function to get default signature of current user from `contracts_Signature` class
|
||||
//function to get default signatur eof current user from `contracts_Signature` class
|
||||
export const getDefaultSignature = async (objectId) => {
|
||||
try {
|
||||
if (objectId) {
|
||||
const result = await Parse.Cloud.run("getdefaultsignature", {
|
||||
userId: objectId
|
||||
});
|
||||
if (result) {
|
||||
const res = JSON.parse(JSON.stringify(result));
|
||||
const defaultSignature = res?.ImageURL
|
||||
? await getBase64FromUrl(res?.ImageURL, true)
|
||||
: "";
|
||||
const defaultInitial = res?.Initials
|
||||
? await getBase64FromUrl(res?.Initials, true)
|
||||
: "";
|
||||
const query = new Parse.Query("contracts_Signature");
|
||||
query.equalTo("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: objectId
|
||||
});
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
res: {
|
||||
id: result?.id,
|
||||
defaultSignature: defaultSignature,
|
||||
defaultInitial: defaultInitial
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return { status: "error" };
|
||||
const result = await query.first();
|
||||
if (result) {
|
||||
const res = JSON.parse(JSON.stringify(result));
|
||||
const defaultSignature = res?.ImageURL
|
||||
? await getBase64FromUrl(res?.ImageURL, true)
|
||||
: "";
|
||||
const defaultInitial = res?.Initials
|
||||
? await getBase64FromUrl(res?.Initials, true)
|
||||
: "";
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
res: {
|
||||
id: result?.id,
|
||||
defaultSignature: defaultSignature,
|
||||
defaultInitial: defaultInitial
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(
|
||||
@@ -3333,7 +3335,10 @@ export const handleHeighlightWidget = (
|
||||
...data,
|
||||
pos: data.pos.map((position) => {
|
||||
if (position.key === key) {
|
||||
return { ...position, zIndex: highestZIndex + 1 };
|
||||
return {
|
||||
...position,
|
||||
zIndex: highestZIndex + 1
|
||||
};
|
||||
}
|
||||
return position;
|
||||
})
|
||||
@@ -3430,49 +3435,50 @@ export const updateDateWidgetsRes = (
|
||||
const contactUser = documentData?.Signers?.find(
|
||||
(data) => data.objectId === signerId
|
||||
);
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") || JSON.stringify([contactUser]);
|
||||
const extUser = JSON.stringify([contactUser]);
|
||||
let placeHolders = documentData?.Placeholders;
|
||||
if (isRemovePrefill) {
|
||||
placeHolders = placeHolders?.filter((x) => x.Role !== "prefill");
|
||||
}
|
||||
const userDetails = extUser ? JSON.parse(extUser)[0] : contactUser;
|
||||
return placeHolders?.map((item) => {
|
||||
if (item?.signerObjId === signerId || item?.Id === signerId) {
|
||||
// Sort page number placeholders
|
||||
const sortedPlaceHolder = [...item.placeHolder]
|
||||
.sort((a, b) => a.pageNumber - b.pageNumber)
|
||||
.map((ph) => ({
|
||||
if (item?.signerObjId === signerId) {
|
||||
return {
|
||||
...item,
|
||||
placeHolder: item?.placeHolder?.map((ph) => ({
|
||||
...ph,
|
||||
// Sort positions within each page
|
||||
pos: [...ph.pos]
|
||||
.sort((a, b) => a.yPosition - b.yPosition)
|
||||
.map((widget) => {
|
||||
// Update widget values if needed
|
||||
if (
|
||||
["name", "email", "job title", "company"].includes(
|
||||
widget.type
|
||||
) &&
|
||||
!widget.options.defaultValue &&
|
||||
!widget.options.response &&
|
||||
userDetails
|
||||
) {
|
||||
return {
|
||||
...widget,
|
||||
options: {
|
||||
...widget.options,
|
||||
response: widgetDataValue(widget.type, userDetails)
|
||||
}
|
||||
};
|
||||
}
|
||||
return widget;
|
||||
})
|
||||
}));
|
||||
|
||||
return { ...item, placeHolder: sortedPlaceHolder };
|
||||
pos: ph?.pos?.map((widget) => {
|
||||
// only for date widgets *and* missing response
|
||||
if (widget.type === "date" && !widget.options.response) {
|
||||
return {
|
||||
...widget,
|
||||
options: {
|
||||
...widget.options,
|
||||
response: getDate(
|
||||
changeDateToMomentFormat(widget.options.validation.format)
|
||||
)
|
||||
}
|
||||
};
|
||||
} else if (
|
||||
["name", "email", "job title", "company"].includes(widget.type) &&
|
||||
!widget.options.defaultValue &&
|
||||
!widget.options.response
|
||||
) {
|
||||
return {
|
||||
...widget,
|
||||
options: {
|
||||
...widget.options,
|
||||
response: widgetDataValue(widget.type, userDetails)
|
||||
}
|
||||
};
|
||||
}
|
||||
return widget;
|
||||
})
|
||||
}))
|
||||
};
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3587,7 +3593,6 @@ export const handleCheckResponse = (checkUser, setminRequiredCount) => {
|
||||
//in `defaultValue` variable is used to get how many checkbox checked by default
|
||||
const defaultValue =
|
||||
requiredCheckbox[i].options?.defaultValue?.length;
|
||||
const checkboxValue = response ? response : defaultValue;
|
||||
//condition to check parseMin and parseMax greater than 0 then consider it as a required check box
|
||||
if (
|
||||
parseMin > 0 &&
|
||||
@@ -3602,10 +3607,7 @@ export const handleCheckResponse = (checkUser, setminRequiredCount) => {
|
||||
setminRequiredCount(parseMin);
|
||||
}
|
||||
//else condition to validate minimum required checkbox
|
||||
else if (
|
||||
parseMin > 0 &&
|
||||
(parseMin > checkboxValue || !checkboxValue)
|
||||
) {
|
||||
else if (parseMin > 0 && (parseMin > response || !response)) {
|
||||
if (!showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredCheckbox[i].key;
|
||||
@@ -3804,7 +3806,7 @@ export const sendEmailToSigners = async (
|
||||
htmlReqBody,
|
||||
variables
|
||||
);
|
||||
} else if (defaultMail?.body && defaultMail?.subject) {
|
||||
} else if (defaultMail) {
|
||||
const mailBody = defaultMail?.body;
|
||||
const mailSubject = defaultMail.subject;
|
||||
const replacedRequestBody = mailBody.replace(/"/g, "'");
|
||||
@@ -4032,47 +4034,3 @@ export const handleDeleteWidget = (key, Id, pageNumber, signerPos) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
//function to convert formatted date to new Date() format
|
||||
export const getDefaultDate = (dateStr, format) => {
|
||||
//get valid date format for moment to convert formatted date to new Date() format
|
||||
const formats = changeDateToMomentFormat(format);
|
||||
const parsedDate = moment(dateStr, formats);
|
||||
let date;
|
||||
if (parsedDate.isValid()) {
|
||||
date = new Date(parsedDate.toISOString());
|
||||
return date;
|
||||
} else if (dateStr === "today") {
|
||||
date = new Date();
|
||||
return date;
|
||||
}
|
||||
};
|
||||
//function to get default format
|
||||
export const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||
|
||||
export function generateId(length) {
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
const charactersLength = characters.length;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
//function to handle widget background color
|
||||
export const handleBackground = (data, isNeedSign, uniqueId) => {
|
||||
if (data) {
|
||||
if (isNeedSign) {
|
||||
if (data?.Id === uniqueId) {
|
||||
return data?.blockColor + "b0";
|
||||
} else {
|
||||
return "#dedddc";
|
||||
}
|
||||
} else {
|
||||
return data?.blockColor + "b0";
|
||||
}
|
||||
} else {
|
||||
return "rgba(203, 233, 237, 0.69)";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export const getThemeIconColor = () => {
|
||||
return theme === "opensigndark" ? "#CCCCCC" : "#686968";
|
||||
};
|
||||
export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
export const maxFileSize = 10; // 10MB
|
||||
export const maxTitleLength = 250; // 250 characters
|
||||
export const maxNoteLength = 200; // 200 characters
|
||||
export const maxDescriptionLength = 500; // 500 characters
|
||||
export const maxFileSize = 80; // for cloud 10MB / 80MB for self-hosted
|
||||
|
||||
@@ -4,10 +4,6 @@ const parseAppId = process.env.REACT_APP_APPID
|
||||
? process.env.REACT_APP_APPID
|
||||
: "opensign";
|
||||
const serverUrl = serverUrl_fn();
|
||||
const commonheader = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
};
|
||||
export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
|
||||
//checking server url and save file's size
|
||||
const tenantPtr = {
|
||||
@@ -33,6 +29,7 @@ export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
|
||||
);
|
||||
const response = res.data.results;
|
||||
let data;
|
||||
// console.log("response", response);
|
||||
if (response && response.length > 0) {
|
||||
data = {
|
||||
usedStorage: response[0].usedStorage
|
||||
@@ -42,12 +39,20 @@ export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
|
||||
await axios.put(
|
||||
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
|
||||
data,
|
||||
{ headers: commonheader }
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
data = { usedStorage: size, PartnersTenant: tenantPtr };
|
||||
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
|
||||
headers: commonheader
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -64,9 +69,14 @@ const saveDataFile = async (size, imageUrl, tenantPtr, UserId) => {
|
||||
TenantPtr: tenantPtr,
|
||||
...(UserId ? { UserId: UserId } : {})
|
||||
};
|
||||
|
||||
// console.log("data save",file, data)
|
||||
try {
|
||||
await axios.post(`${serverUrl}/classes/partners_DataFiles`, data, {
|
||||
headers: commonheader
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("err in save usage ", err);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDrag } from "react-dnd";
|
||||
import { getEmptyImage } from "react-dnd-html5-backend";
|
||||
import { useGuidelinesContext } from "../context/GuidelinesContext";
|
||||
|
||||
export const useWidgetDrag = (item) => {
|
||||
const { showGuidelines } = useGuidelinesContext();
|
||||
const [, dragRef, preview] = useDrag({
|
||||
type: "BOX",
|
||||
item,
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging()
|
||||
}),
|
||||
end: () => {
|
||||
showGuidelines(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Hide browser default drag preview
|
||||
useEffect(() => {
|
||||
preview(getEmptyImage(), { captureDraggingState: true });
|
||||
}, [preview]);
|
||||
|
||||
return { dragRef };
|
||||
};
|
||||
@@ -4,22 +4,21 @@ import Footer from "../components/Footer";
|
||||
import Sidebar from "../components/sidebar/Sidebar";
|
||||
import Tour from "../primitives/Tour";
|
||||
import axios from "axios";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useSelector } from "react-redux";
|
||||
import Parse from "parse";
|
||||
import { useNavigate, Outlet } from "react-router";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { useNavigate, useLocation, Outlet } from "react-router";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { sessionStatus } from "../redux/reducers/userReducer";
|
||||
import SessionExpiredModal from "../primitives/SessionExpiredModal";
|
||||
|
||||
const HomeLayout = () => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const location = useLocation();
|
||||
const arr = useSelector((state) => state.TourSteps);
|
||||
const isValidSession = useSelector((state) => state.user.isValidSession);
|
||||
const [isUserValid, setIsUserValid] = useState(true);
|
||||
const [isLoader, setIsLoader] = useState(true);
|
||||
const [isCloseBtn, setIsCloseBtn] = useState(true);
|
||||
const [isTour, setIsTour] = useState(false);
|
||||
@@ -36,31 +35,28 @@ const HomeLayout = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem("accesstoken")) {
|
||||
if (!tenantId) {
|
||||
dispatch(sessionStatus(false));
|
||||
} else {
|
||||
(async () => {
|
||||
try {
|
||||
// Use the session token to validate the user
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
const user = await userQuery.get(Parse?.User?.current()?.id, {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
});
|
||||
if (user) {
|
||||
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
|
||||
dispatch(sessionStatus(true));
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
dispatch(sessionStatus(true));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("catch");
|
||||
// Session token is invalid or there was an error
|
||||
dispatch(sessionStatus(false));
|
||||
if (!tenantId) {
|
||||
setIsUserValid(false);
|
||||
} else {
|
||||
(async () => {
|
||||
try {
|
||||
// Use the session token to validate the user
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
const user = await userQuery.get(Parse?.User?.current()?.id, {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
});
|
||||
if (user) {
|
||||
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
|
||||
setIsUserValid(true);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
setIsUserValid(false);
|
||||
}
|
||||
})();
|
||||
}
|
||||
} catch (error) {
|
||||
// Session token is invalid or there was an error
|
||||
setIsUserValid(false);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -173,56 +169,77 @@ const HomeLayout = () => {
|
||||
}
|
||||
}
|
||||
|
||||
return isValidSession && localStorage.getItem("accesstoken") ? (
|
||||
const handleLoginBtn = async () => {
|
||||
try {
|
||||
await Parse?.User?.logOut();
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
} finally {
|
||||
localStorage.removeItem("accesstoken");
|
||||
navigate("/", { replace: true, state: { from: location } });
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex flex-col h-screen overflow-hidden">
|
||||
{/* HEADER */}
|
||||
<header className="z-[501]">
|
||||
{!isLoader && <Header setIsLoggingOut={setIsLoggingOut} />}
|
||||
</header>
|
||||
{isLoader ? (
|
||||
<div className="flex h-[100vh] justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
) : (
|
||||
{isUserValid ? (
|
||||
<>
|
||||
{isLoggingOut && (
|
||||
<div className="inset-0 bg-black/30 z-[1000] fixed flex justify-center items-center">
|
||||
{isLoader ? (
|
||||
<div className="flex h-[100vh] justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{/* BODY */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* SIDEBAR with width animation */}
|
||||
<Sidebar />
|
||||
{/* MAIN (includes both content + footer in one scrollable column) */}
|
||||
<main
|
||||
id="renderList"
|
||||
className="flex-1 overflow-auto transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<div className="flex flex-col min-h-full">
|
||||
{/* your page content */}
|
||||
<div className="p-3">{<Outlet />}</div>
|
||||
{/* sticky-but-scrollable footer */}
|
||||
<div className="mt-auto z-30">
|
||||
<Footer />
|
||||
) : (
|
||||
<>
|
||||
{isLoggingOut && (
|
||||
<div className="inset-0 bg-black/30 z-[1000] fixed flex justify-center items-center">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{/* BODY */}
|
||||
<div className="flex flex-1 overflow-hidden">
|
||||
{/* SIDEBAR with width animation */}
|
||||
<Sidebar />
|
||||
{/* MAIN (includes both content + footer in one scrollable column) */}
|
||||
<main
|
||||
id="renderList"
|
||||
className="flex-1 overflow-auto transition-all duration-300 ease-in-out"
|
||||
>
|
||||
<div className="flex flex-col min-h-full">
|
||||
{/* your page content */}
|
||||
<div className="p-3">{<Outlet />}</div>
|
||||
{/* sticky-but-scrollable footer */}
|
||||
<div className="mt-auto z-30">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<Tour
|
||||
onRequestClose={closeTour}
|
||||
steps={tourConfigs}
|
||||
isOpen={isTour}
|
||||
disableKeyboardNavigation={["esc"]}
|
||||
// disableInteraction={true}
|
||||
scrollOffset={-100}
|
||||
showCloseButton={isCloseBtn}
|
||||
/>
|
||||
<Tour
|
||||
onRequestClose={closeTour}
|
||||
steps={tourConfigs}
|
||||
isOpen={isTour}
|
||||
disableKeyboardNavigation={["esc"]}
|
||||
// disableInteraction={true}
|
||||
scrollOffset={-100}
|
||||
showCloseButton={isCloseBtn}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<ModalUi showHeader={false} isOpen={true} showClose={false}>
|
||||
<div className="flex flex-col justify-center items-center py-4 md:py-5 gap-5">
|
||||
<p className="text-xl font-medium">{t("session-expired")}</p>
|
||||
<button onClick={handleLoginBtn} className="op-btn op-btn-neutral">
|
||||
{t("login")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<SessionExpiredModal />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -253,7 +253,7 @@ const AddAdmin = () => {
|
||||
/[A-Z]/.test(newPassword) &&
|
||||
/\d/.test(newPassword)
|
||||
);
|
||||
setSpecialCharValid(/[!@#$%^&*()\-_=+{};:,<.>]/.test(newPassword));
|
||||
setSpecialCharValid(/[!@#$%^&*()\-_=+{};:,<.>?]/.test(newPassword));
|
||||
};
|
||||
const subscribeNewsletter = async () => {
|
||||
try {
|
||||
|
||||
@@ -4,74 +4,57 @@ import { Navigate } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function ChangePassword() {
|
||||
const [lengthValid, setLengthValid] = useState(false);
|
||||
const [caseDigitValid, setCaseDigitValid] = useState(false);
|
||||
const [specialCharValid, setSpecialCharValid] = useState(false);
|
||||
const handlePasswordChange = (e) => {
|
||||
const value = e.target.value;
|
||||
setnewpassword(value);
|
||||
setLengthValid(value.length >= 8);
|
||||
setCaseDigitValid(
|
||||
/[a-z]/.test(value) && /[A-Z]/.test(value) && /\d/.test(value)
|
||||
);
|
||||
setSpecialCharValid(/[!@#$%^&*()\-_=+{};:,<.>?]/.test(value));
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
const [currentpassword, setCurrentPassword] = useState("");
|
||||
const [newpassword, setnewpassword] = useState("");
|
||||
const [confirmpassword, setconfirmpassword] = useState("");
|
||||
const [lengthValid, setLengthValid] = useState(false);
|
||||
const [caseDigitValid, setCaseDigitValid] = useState(false);
|
||||
const [specialCharValid, setSpecialCharValid] = useState(false);
|
||||
const [showNewPassword, setNewShowPassword] = useState(false);
|
||||
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||
const toggleNewPasswordVisibility = () => {
|
||||
setNewShowPassword(!showNewPassword);
|
||||
};
|
||||
const toggleConfirmPasswordVisibility = () => {
|
||||
setShowConfirmPassword(!showConfirmPassword);
|
||||
};
|
||||
|
||||
const handlePasswordChange = (e) => {
|
||||
const newPassword = e.target.value;
|
||||
setconfirmpassword(newPassword);
|
||||
// Check conditions separately
|
||||
setLengthValid(newPassword.length >= 8);
|
||||
setCaseDigitValid(
|
||||
/[a-z]/.test(newPassword) &&
|
||||
/[A-Z]/.test(newPassword) &&
|
||||
/\d/.test(newPassword)
|
||||
);
|
||||
setSpecialCharValid(/[!@#$%^&*()\-_=+{};:,<.>]/.test(newPassword));
|
||||
};
|
||||
const handleSubmit = async (evt) => {
|
||||
evt.preventDefault();
|
||||
try {
|
||||
if (newpassword === confirmpassword) {
|
||||
if (lengthValid && caseDigitValid && specialCharValid) {
|
||||
Parse.User.logIn(localStorage.getItem("userEmail"), currentpassword)
|
||||
.then(async (user) => {
|
||||
if (user) {
|
||||
const User = new Parse.User();
|
||||
const query = new Parse.Query(User);
|
||||
await query.get(user.id).then((user) => {
|
||||
// Updates the data we want
|
||||
user.set("password", newpassword);
|
||||
user
|
||||
.save()
|
||||
.then(async () => {
|
||||
let _user = user.toJSON();
|
||||
if (_user) {
|
||||
await Parse.User.become(_user.sessionToken);
|
||||
localStorage.setItem("accesstoken", _user.sessionToken);
|
||||
}
|
||||
setCurrentPassword("");
|
||||
setnewpassword("");
|
||||
setconfirmpassword("");
|
||||
alert(t("password-update-alert-1"));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("err", error);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
alert(t("password-update-alert-2"));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(t("password-update-alert-3"));
|
||||
console.error("Error while logging in user", error);
|
||||
});
|
||||
}
|
||||
Parse.User.logIn(localStorage.getItem("userEmail"), currentpassword)
|
||||
.then(async (user) => {
|
||||
if (user) {
|
||||
const User = new Parse.User();
|
||||
const query = new Parse.Query(User);
|
||||
await query.get(user.id).then((user) => {
|
||||
// Updates the data we want
|
||||
user.set("password", newpassword);
|
||||
user
|
||||
.save()
|
||||
.then(async () => {
|
||||
let _user = user.toJSON();
|
||||
if (_user) {
|
||||
await Parse.User.become(_user.sessionToken);
|
||||
localStorage.setItem("accesstoken", _user.sessionToken);
|
||||
}
|
||||
alert(t("password-update-alert-1"));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("err", error);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
alert(t("password-update-alert-2"));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(t("password-update-alert-3"));
|
||||
console.error("Error while logging in user", error);
|
||||
});
|
||||
} else {
|
||||
alert(t("password-update-alert-4"));
|
||||
}
|
||||
@@ -109,93 +92,47 @@ function ChangePassword() {
|
||||
<label htmlFor="newpassword" className="text-xs block ml-1">
|
||||
{t("new-password")}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showNewPassword ? "text" : "password"}
|
||||
name="newpassword"
|
||||
value={newpassword}
|
||||
onChange={(e) => setnewpassword(e.target.value)}
|
||||
className="op-input op-input-bordered op-input-sm text-xs w-full"
|
||||
placeholder={t("new-password")}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
<span
|
||||
className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer text-base-content`}
|
||||
onClick={toggleNewPasswordVisibility}
|
||||
>
|
||||
{showNewPassword ? (
|
||||
<i className="fa fa-eye-slash" /> // Close eye icon
|
||||
) : (
|
||||
<i className="fa fa-eye" /> // Open eye icon
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="newpassword"
|
||||
value={newpassword}
|
||||
onChange={handlePasswordChange}
|
||||
className="op-input op-input-bordered op-input-sm text-xs w-full"
|
||||
placeholder={t("new-password")}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
{newpassword.length > 0 && (
|
||||
<div className="mt-1 text-[11px]">
|
||||
<p className={lengthValid ? "text-green-600" : "text-red-600"}>
|
||||
{lengthValid ? "✓" : "✗"} Password length ≥ 8
|
||||
</p>
|
||||
<p className={caseDigitValid ? "text-green-600" : "text-red-600"}>
|
||||
{caseDigitValid ? "✓" : "✗"} Uppercase, lowercase, and number
|
||||
</p>
|
||||
<p className={specialCharValid ? "text-green-600" : "text-red-600"}>
|
||||
{specialCharValid ? "✓" : "✗"} Special character
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="confirmpassword" className="text-xs block ml-1">
|
||||
<label htmlFor="newpassword" className="text-xs block ml-1">
|
||||
{t("confirm-password")}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type={showConfirmPassword ? "text" : "password"}
|
||||
name="confirmpassword"
|
||||
className="op-input op-input-bordered op-input-sm text-xs w-full"
|
||||
value={confirmpassword}
|
||||
onChange={handlePasswordChange}
|
||||
placeholder={t("confirm-password")}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
<span
|
||||
className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer text-base-content`}
|
||||
onClick={toggleConfirmPasswordVisibility}
|
||||
>
|
||||
{showConfirmPassword ? (
|
||||
<i className="fa fa-eye-slash" /> // Close eye icon
|
||||
) : (
|
||||
<i className="fa fa-eye" /> // Open eye icon
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
name="confirmpassword"
|
||||
className="op-input op-input-bordered op-input-sm text-xs w-full"
|
||||
value={confirmpassword}
|
||||
onChange={(e) => setconfirmpassword(e.target.value)}
|
||||
placeholder={t("confirm-password")}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{confirmpassword.length > 0 && (
|
||||
<div className="mt-1 text-[11px]">
|
||||
{newpassword.length > 0 && (
|
||||
<p
|
||||
className={`${newpassword === confirmpassword ? "text-green-600" : "text-red-600"} text-[11px] mt-1`}
|
||||
>
|
||||
{newpassword === confirmpassword ? "✓" : "✗"}{" "}
|
||||
{t("password-match-length")}
|
||||
</p>
|
||||
)}
|
||||
<p
|
||||
className={`${lengthValid ? "text-green-600" : "text-red-600"}`}
|
||||
>
|
||||
{lengthValid ? "✓" : "✗"} {t("password-length")}
|
||||
</p>
|
||||
<p
|
||||
className={`${
|
||||
caseDigitValid ? "text-green-600" : "text-red-600"
|
||||
}`}
|
||||
>
|
||||
{caseDigitValid ? "✓" : "✗"} {t("password-case")}
|
||||
</p>
|
||||
<p
|
||||
className={`${
|
||||
specialCharValid ? "text-green-600" : "text-red-600"
|
||||
}`}
|
||||
>
|
||||
{specialCharValid ? "✓" : "✗"} {t("password-special-char")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-primary shadow-md mt-2"
|
||||
|
||||
@@ -27,7 +27,7 @@ function processDimensions(x, y, width, height) {
|
||||
};
|
||||
}
|
||||
const DebugPdf = () => {
|
||||
const { t } = useTranslation();
|
||||
const {t} = useTranslation()
|
||||
const { width } = useWindowSize();
|
||||
const [pdf, setPdf] = useState("");
|
||||
const [isModal, setIsModal] = useState(true);
|
||||
|
||||
@@ -13,6 +13,18 @@ import { useTranslation } from "react-i18next";
|
||||
import Loader from "../primitives/Loader";
|
||||
|
||||
function ForgotPassword() {
|
||||
const [lengthValid, setLengthValid] = useState(false);
|
||||
const [caseDigitValid, setCaseDigitValid] = useState(false);
|
||||
const [specialCharValid, setSpecialCharValid] = useState(false);
|
||||
const handlePasswordChange = (event) => {
|
||||
let value = event.target.value;
|
||||
setState({ ...state, password: value });
|
||||
setLengthValid(value.length >= 8);
|
||||
setCaseDigitValid(
|
||||
/[a-z]/.test(value) && /[A-Z]/.test(value) && /\d/.test(value)
|
||||
);
|
||||
setSpecialCharValid(/[!@#$%^&*()\-_=+{};:,<.>?]/.test(value));
|
||||
};
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
@@ -124,6 +136,28 @@ function ForgotPassword() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-center text-xs font-bold">
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
className="op-input op-input-bordered op-input-sm w-full mt-2"
|
||||
value={state.password}
|
||||
onChange={handlePasswordChange}
|
||||
placeholder="New password"
|
||||
required
|
||||
/>
|
||||
{state.password.length > 0 && (
|
||||
<div className="mt-1 text-[11px]">
|
||||
<p className={lengthValid ? "text-green-600" : "text-red-600"}>
|
||||
{lengthValid ? "✓" : "✗"} Password length ≥ 8
|
||||
</p>
|
||||
<p className={caseDigitValid ? "text-green-600" : "text-red-600"}>
|
||||
{caseDigitValid ? "✓" : "✗"} Uppercase, lowercase, and number
|
||||
</p>
|
||||
<p className={specialCharValid ? "text-green-600" : "text-red-600"}>
|
||||
{specialCharValid ? "✓" : "✗"} Special character
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<button type="submit" className="op-btn op-btn-primary">
|
||||
{t("submit")}
|
||||
</button>
|
||||
|
||||
@@ -30,8 +30,6 @@ import ModalUi from "../primitives/ModalUi";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { sessionStatus } from "../redux/reducers/userReducer";
|
||||
|
||||
// `Form` render all type of Form on this basis of their provided in path
|
||||
function Form() {
|
||||
@@ -46,12 +44,12 @@ function Form() {
|
||||
}
|
||||
|
||||
const Forms = (props) => {
|
||||
const appName = "OpenSign™";
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const abortController = new AbortController();
|
||||
const inputFileRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const [signers, setSigners] = useState([]);
|
||||
const [folder, setFolder] = useState({ ObjectId: "", Name: "" });
|
||||
const [formData, setFormData] = useState({
|
||||
@@ -69,7 +67,7 @@ const Forms = (props) => {
|
||||
NotifyOnSignatures: "",
|
||||
Bcc: [],
|
||||
RedirectUrl: "",
|
||||
AllowModifications: false
|
||||
AllowModifications: false,
|
||||
});
|
||||
const [fileupload, setFileUpload] = useState("");
|
||||
const [selectedFiles, setSelectedFiles] = useState([]);
|
||||
@@ -100,7 +98,6 @@ const Forms = (props) => {
|
||||
extUserData?.IsTourEnabled === false
|
||||
? "false"
|
||||
: "true";
|
||||
const fileSize = maxFileSize;
|
||||
useEffect(() => {
|
||||
handleReset();
|
||||
return () => abortController.abort();
|
||||
@@ -112,12 +109,12 @@ const Forms = (props) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const initializeValues = async () => {
|
||||
setFormData((obj) => ({
|
||||
...obj,
|
||||
NotifyOnSignatures: true,
|
||||
SendinOrder: sendinorder,
|
||||
IsTourEnabled: istourenabled
|
||||
}));
|
||||
setFormData((obj) => ({
|
||||
...obj,
|
||||
NotifyOnSignatures: true,
|
||||
SendinOrder: sendinorder,
|
||||
IsTourEnabled: istourenabled
|
||||
}));
|
||||
};
|
||||
|
||||
// `removeFile` is used to reset progress, percentage and remove file if exists
|
||||
@@ -139,10 +136,11 @@ const Forms = (props) => {
|
||||
return;
|
||||
}
|
||||
// setFormData((prev) => ({ ...prev, file: files[0] }));
|
||||
const totalBytes = Math.round(files.reduce((sum, f) => sum + f.size, 0)); // in bytes
|
||||
const fileSizeBytes = fileSize * 1024 * 1024;
|
||||
if (totalBytes > fileSizeBytes) {
|
||||
alert(`${t("file-alert-1")} ${fileSize} MB`);
|
||||
const totalMb = Math.round(
|
||||
files.reduce((sum, f) => sum + f.size, 0) / Math.pow(1024, 2)
|
||||
);
|
||||
if (totalMb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
removeFile(e);
|
||||
@@ -276,12 +274,9 @@ const Forms = (props) => {
|
||||
setfileload(false);
|
||||
removeFile(e);
|
||||
console.log("err in docx to pdf ", err);
|
||||
const error = t("docx-error");
|
||||
if (err?.code === 209) {
|
||||
dispatch(sessionStatus(false));
|
||||
} else {
|
||||
alert(error);
|
||||
}
|
||||
const error =
|
||||
t("docx-error");
|
||||
alert(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -305,25 +300,25 @@ const Forms = (props) => {
|
||||
const name = generatePdfName(16);
|
||||
const pdfName = `${name}.pdf`;
|
||||
let uploadedUrl = "";
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...pdfBytes],
|
||||
"application/pdf"
|
||||
);
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total) => {
|
||||
if (progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...pdfBytes],
|
||||
"application/pdf"
|
||||
);
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
uploadedUrl = fileRes.url;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
uploadedUrl = fileRes.url;
|
||||
}
|
||||
}
|
||||
if (uploadedUrl) {
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const userId = extUserData?.UserId?.objectId;
|
||||
@@ -339,13 +334,8 @@ const Forms = (props) => {
|
||||
setSelectedFiles([]);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.code === 209) {
|
||||
dispatch(sessionStatus(false));
|
||||
} else {
|
||||
alert(error.message);
|
||||
}
|
||||
alert(error.message);
|
||||
setSelectedFiles([]);
|
||||
removeFile(e);
|
||||
}
|
||||
};
|
||||
// `isValidURL` is used to check valid webhook url
|
||||
@@ -410,11 +400,11 @@ const Forms = (props) => {
|
||||
object.set("RemindOnceInEvery", remindOnceInEvery);
|
||||
object.set("IsTourEnabled", isTourEnabled);
|
||||
object.set("TimeToCompleteDays", TimeToCompleteDays);
|
||||
object.set("AllowModifications", false);
|
||||
object.set("IsEnableOTP", false);
|
||||
if (formData.NotifyOnSignatures !== undefined) {
|
||||
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
|
||||
}
|
||||
object.set("AllowModifications", false);
|
||||
object.set("IsEnableOTP", false);
|
||||
if (formData.NotifyOnSignatures !== undefined) {
|
||||
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
|
||||
}
|
||||
if (formData?.RedirectUrl) {
|
||||
object.set("RedirectUrl", formData.RedirectUrl);
|
||||
}
|
||||
@@ -451,9 +441,9 @@ const Forms = (props) => {
|
||||
setBcc([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
setFormData({
|
||||
Name: "",
|
||||
Description: "",
|
||||
@@ -471,7 +461,7 @@ const Forms = (props) => {
|
||||
IsEnableOTP: "false",
|
||||
IsTourEnabled: istourenabled,
|
||||
RedirectUrl: "",
|
||||
AllowModifications: false
|
||||
AllowModifications: false,
|
||||
});
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
@@ -480,9 +470,7 @@ const Forms = (props) => {
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err?.code === 209) {
|
||||
dispatch(sessionStatus(false));
|
||||
} else if (err.message === "only 15 reminder allowed") {
|
||||
if (err.message === "only 15 reminder allowed") {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
message: t("only-15-reminder-allowed")
|
||||
@@ -533,9 +521,9 @@ const Forms = (props) => {
|
||||
setBcc([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
let obj = {
|
||||
Name: "",
|
||||
Description: "",
|
||||
@@ -553,7 +541,7 @@ const Forms = (props) => {
|
||||
IsTourEnabled: istourenabled,
|
||||
NotifyOnSignatures: notifySign,
|
||||
RedirectUrl: "",
|
||||
AllowModifications: false
|
||||
AllowModifications: false,
|
||||
};
|
||||
setFormData(obj);
|
||||
removeFile();
|
||||
@@ -575,27 +563,35 @@ const Forms = (props) => {
|
||||
const name = generatePdfName(16);
|
||||
const pdfFile = await decryptPdf(formData?.file, formData?.password);
|
||||
setIsDecrypting(false);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
const parseFile = new Parse.File(name, [...flatPdf], "application/pdf");
|
||||
await parseFile.save({
|
||||
progress: (progressValue, loaded, total) => {
|
||||
if (progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
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(formData?.file?.name);
|
||||
setFormData((obj) => ({ ...obj, password: "", Name: title }));
|
||||
SaveFileSize(size, fileRes?.url, tenantId, userId);
|
||||
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 }));
|
||||
SaveFileSize(size, fileRes?.url, tenantId, userId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile();
|
||||
setFormData((prev) => ({ ...prev, password: "" }));
|
||||
@@ -604,19 +600,9 @@ const Forms = (props) => {
|
||||
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) {
|
||||
removeFile();
|
||||
if (err?.code === 209) {
|
||||
dispatch(sessionStatus(false));
|
||||
} else if (err?.response?.status === 401) {
|
||||
if (err?.response?.status === 401) {
|
||||
setIsPassword(true);
|
||||
setIsCorrectPass(false);
|
||||
} else {
|
||||
@@ -1054,7 +1040,10 @@ const Forms = (props) => {
|
||||
{isAdvanceOpt && (
|
||||
<div
|
||||
style={{
|
||||
height: props.title === "New Template" ? "100px" : "280px"
|
||||
height:
|
||||
props.title === "New Template"
|
||||
? "100px"
|
||||
: "280px"
|
||||
}}
|
||||
className="w-[1px] bg-gray-300 m-auto hidden md:inline-block"
|
||||
></div>
|
||||
@@ -1163,7 +1152,11 @@ const Forms = (props) => {
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
||||
<div
|
||||
className={
|
||||
`flex items-center gap-2 ml-2 mb-1`
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
@@ -1172,7 +1165,11 @@ const Forms = (props) => {
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
||||
<div
|
||||
className={
|
||||
`flex items-center gap-2 ml-2 mb-1`
|
||||
}
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
|
||||
@@ -23,6 +23,18 @@ import { useTranslation } from "react-i18next";
|
||||
import SelectLanguage from "../components/pdf/SelectLanguage";
|
||||
|
||||
function Login() {
|
||||
const [lengthValid, setLengthValid] = useState(false);
|
||||
const [caseDigitValid, setCaseDigitValid] = useState(false);
|
||||
const [specialCharValid, setSpecialCharValid] = useState(false);
|
||||
const handlePasswordChange = (event) => {
|
||||
let value = event.target.value;
|
||||
setState({ ...state, password: value });
|
||||
setLengthValid(value.length >= 8);
|
||||
setCaseDigitValid(
|
||||
/[a-z]/.test(value) && /[A-Z]/.test(value) && /\d/.test(value)
|
||||
);
|
||||
setSpecialCharValid(/[!@#$%^&*()\-_=+{};:,<.>?]/.test(value));
|
||||
};
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const { t, i18n } = useTranslation();
|
||||
@@ -47,14 +59,10 @@ function Login() {
|
||||
const [image, setImage] = useState();
|
||||
const [errMsg, setErrMsg] = useState();
|
||||
useEffect(() => {
|
||||
handleUserExist();
|
||||
checkUserExt();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const handleUserExist = async () => {
|
||||
checkUserExt();
|
||||
};
|
||||
|
||||
|
||||
const setLocalVar = (user) => {
|
||||
localStorage.setItem("accesstoken", user.sessionToken);
|
||||
@@ -478,7 +486,7 @@ function Login() {
|
||||
name="password"
|
||||
value={state.password}
|
||||
autoComplete="current-password"
|
||||
onChange={handleChange}
|
||||
onChange={handlePasswordChange}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(
|
||||
t("input-required")
|
||||
@@ -499,6 +507,19 @@ function Login() {
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative mt-1">
|
||||
{state.password.length > 0 && (
|
||||
<div className="mt-1 text-[11px]">
|
||||
<p className={lengthValid ? "text-green-600" : "text-red-600"}>
|
||||
{lengthValid ? "✓" : "✗"} Password length ≥ 8
|
||||
</p>
|
||||
<p className={caseDigitValid ? "text-green-600" : "text-red-600"}>
|
||||
{caseDigitValid ? "✓" : "✗"} Uppercase, lowercase, and number
|
||||
</p>
|
||||
<p className={specialCharValid ? "text-green-600" : "text-red-600"}>
|
||||
{specialCharValid ? "✓" : "✗"} Special character
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<NavLink
|
||||
to="/forgetpassword"
|
||||
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
|
||||
|
||||
@@ -46,9 +46,10 @@ const ManageSign = () => {
|
||||
objectId: User.id
|
||||
};
|
||||
try {
|
||||
const signRes = await Parse.Cloud.run("getdefaultsignature", {
|
||||
userId: User.id
|
||||
});
|
||||
const signCls = "contracts_Signature";
|
||||
const signQuery = new Parse.Query(signCls);
|
||||
signQuery.equalTo("UserId", userId);
|
||||
const signRes = await signQuery.first();
|
||||
if (signRes) {
|
||||
const res = signRes.toJSON();
|
||||
setId(res.objectId);
|
||||
@@ -225,23 +226,44 @@ const ManageSign = () => {
|
||||
};
|
||||
|
||||
const saveEntry = async (obj) => {
|
||||
try {
|
||||
const User = Parse?.User?.current()?.id;
|
||||
const res = await Parse.Cloud.run("managesign", {
|
||||
signature: obj.url,
|
||||
userId: User,
|
||||
initials: obj.initialsUrl,
|
||||
id: id,
|
||||
title: obj.name
|
||||
});
|
||||
setIsAlert({ type: "success", message: t("signature-saved-alert") });
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
setIsAlert({ type: "danger", message: `${err.message}` });
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
setTimeout(() => setIsAlert({}), 2000);
|
||||
const signCls = "contracts_Signature";
|
||||
const User = Parse?.User?.current()?.id;
|
||||
const userId = { __type: "Pointer", className: "_User", objectId: User };
|
||||
if (id) {
|
||||
try {
|
||||
const updateSign = new Parse.Object(signCls);
|
||||
updateSign.id = id;
|
||||
updateSign.set("Initials", obj.initialsUrl ? obj.initialsUrl : "");
|
||||
updateSign.set("ImageURL", obj.url ? obj.url : "");
|
||||
updateSign.set("SignatureName", obj.name);
|
||||
updateSign.set("UserId", userId);
|
||||
const res = await updateSign.save();
|
||||
setIsAlert({ type: "success", message: t("signature-saved-alert") });
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
setIsAlert({ type: "danger", message: `${err.message}` });
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
setTimeout(() => setIsAlert({}), 2000);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const updateSign = new Parse.Object(signCls);
|
||||
updateSign.set("Initials", obj.initialsUrl ? obj.initialsUrl : "");
|
||||
updateSign.set("ImageURL", obj.url);
|
||||
updateSign.set("SignatureName", obj.name);
|
||||
updateSign.set("UserId", userId);
|
||||
const res = await updateSign.save();
|
||||
setIsAlert({ type: "success", message: t("signature-saved-alert") });
|
||||
return res;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
setIsAlert({ type: "success", message: `${err.message}` });
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
setTimeout(() => setIsAlert({}), 2000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { PDFDocument } from "pdf-lib";
|
||||
import "../styles/signature.css";
|
||||
import Parse from "parse";
|
||||
import axios from "axios";
|
||||
import { useDrop } from "react-dnd";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import Tour from "../primitives/Tour";
|
||||
@@ -155,12 +156,16 @@ function PdfRequestFiles(
|
||||
const [assignedWidgetId, setAssignedWidgetId] = useState([]);
|
||||
const [showSignPagenumber, setShowSignPagenumber] = useState([]);
|
||||
const [owner, setOwner] = useState({});
|
||||
const [tenant, setTenant] = useState({});
|
||||
const [tenantMailTemplate, setTenantMailTemplate] = useState({
|
||||
body: "",
|
||||
subject: ""
|
||||
});
|
||||
const [isOptionalDetails, setIsOptionalDetails] = useState(false);
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
drop: (item, monitor) => addPositionOfSignature(item, monitor),
|
||||
collect: (monitor) => ({ isOver: !!monitor.isOver() })
|
||||
});
|
||||
const isMobile = window.innerWidth < 767;
|
||||
let isGuestSignFlow = false;
|
||||
let sendmail;
|
||||
@@ -245,7 +250,6 @@ function PdfRequestFiles(
|
||||
const filterSignTypes = signatureType?.filter(
|
||||
(x) => x.enabled === true
|
||||
);
|
||||
setTenant(tenantDetails || {});
|
||||
if (tenantDetails?.RequestBody) {
|
||||
setTenantMailTemplate({
|
||||
body: tenantDetails?.RequestBody,
|
||||
@@ -336,6 +340,7 @@ function PdfRequestFiles(
|
||||
const getCurrentSigner = getSigners?.find(
|
||||
(data) => data.UserId.objectId === jsonSender?.objectId
|
||||
);
|
||||
|
||||
currUserId = getCurrentSigner?.objectId
|
||||
? getCurrentSigner.objectId
|
||||
: contactBookId ||
|
||||
@@ -765,16 +770,12 @@ function PdfRequestFiles(
|
||||
sendmail !== "false" &&
|
||||
sendInOrder
|
||||
) {
|
||||
const mailBody =
|
||||
tenantMailTemplate?.body;
|
||||
const mailSubject =
|
||||
tenantMailTemplate?.subject;
|
||||
const requestBody =
|
||||
updatedDoc.updatedPdfDetails?.[0]?.RequestBody ||
|
||||
mailBody;
|
||||
tenantMailTemplate?.body;
|
||||
const requestSubject =
|
||||
updatedDoc.updatedPdfDetails?.[0]?.RequestSubject ||
|
||||
mailSubject;
|
||||
tenantMailTemplate?.subject;
|
||||
if (user) {
|
||||
const expireDate = expiry;
|
||||
const newDate = new Date(expireDate);
|
||||
@@ -1098,11 +1099,10 @@ function PdfRequestFiles(
|
||||
const userId =
|
||||
pdfDetails?.[0].Signers?.find((x) => x.objectId === signerObjectId)
|
||||
?.UserId?.objectId || jsonSender?.objectId;
|
||||
|
||||
const params = {
|
||||
docId: pdfDetails?.[0].objectId,
|
||||
reason: reason,
|
||||
userId: userId,
|
||||
userId: userId
|
||||
};
|
||||
await axios
|
||||
.post(`${localStorage.getItem("baseUrl")}functions/declinedoc`, params, {
|
||||
@@ -1488,10 +1488,7 @@ function PdfRequestFiles(
|
||||
const widgetHeight =
|
||||
defaultWidthHeight(dragTypeValue).height * containerScale;
|
||||
const extUser = localStorage.getItem("Extand_Class");
|
||||
let parseUser =
|
||||
(extUser && JSON.parse(extUser)[0]) ||
|
||||
JSON.parse(localStorage.getItem("signer"));
|
||||
|
||||
const parseUser = extUser && JSON.parse(extUser)[0];
|
||||
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
|
||||
//adding and updating drop position in array when user drop signature button in div
|
||||
if (item === "onclick") {
|
||||
@@ -1515,7 +1512,6 @@ function PdfRequestFiles(
|
||||
dragTypeValue,
|
||||
owner,
|
||||
filterSignerPos?.placeHolder,
|
||||
null,
|
||||
widgetValue
|
||||
),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
@@ -1533,16 +1529,12 @@ function PdfRequestFiles(
|
||||
//`containerRect.top` The distance from the top of the viewport to the top of the element.
|
||||
const x = offset.x - containerRect.left;
|
||||
const y = offset.y - containerRect.top;
|
||||
let getXPosition = signBtnPosition[0] ? x - signBtnPosition[0].xPos : x;
|
||||
let getYPosition = signBtnPosition[0] ? y - signBtnPosition[0].yPos : y;
|
||||
// to avoid negative position values (half portion of widget should not be out of pdf container)
|
||||
if (getXPosition < 0) {
|
||||
getXPosition = 0;
|
||||
}
|
||||
if (getYPosition < 0) {
|
||||
getYPosition = 0;
|
||||
}
|
||||
|
||||
const getXPosition = signBtnPosition[0]
|
||||
? x - signBtnPosition[0].xPos
|
||||
: x;
|
||||
const getYPosition = signBtnPosition[0]
|
||||
? y - signBtnPosition[0].yPos
|
||||
: y;
|
||||
dropObj = {
|
||||
xPosition: getXPosition / (containerScale * scale),
|
||||
yPosition: getYPosition / (containerScale * scale),
|
||||
@@ -1556,7 +1548,6 @@ function PdfRequestFiles(
|
||||
dragTypeValue,
|
||||
owner,
|
||||
filterSignerPos?.placeHolder,
|
||||
null,
|
||||
widgetValue
|
||||
),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
@@ -1579,7 +1570,6 @@ function PdfRequestFiles(
|
||||
);
|
||||
const getPos = currentPagePosition?.pos;
|
||||
const newSignPos = getPos.concat(dropData);
|
||||
newSignPos.sort((a, b) => a.yPosition - b.yPosition);
|
||||
let xyPos = { pageNumber: pageNumber, pos: newSignPos };
|
||||
updatePlace.push(xyPos);
|
||||
const updatesignerPos = signerPos.map((x) =>
|
||||
@@ -1590,17 +1580,9 @@ function PdfRequestFiles(
|
||||
//else condition to add placeholder widgets on multiple page first time
|
||||
const updatesignerPos = signerPos.map((x) =>
|
||||
x.Id === uniqueId && x?.placeHolder
|
||||
? {
|
||||
...x,
|
||||
placeHolder: [...x.placeHolder, placeHolder].sort(
|
||||
(a, b) => a.pageNumber - b.pageNumber
|
||||
)
|
||||
}
|
||||
? { ...x, placeHolder: [...x.placeHolder, placeHolder] }
|
||||
: x.Id === uniqueId
|
||||
? {
|
||||
...x,
|
||||
placeHolder: [placeHolder]
|
||||
}
|
||||
? { ...x, placeHolder: [placeHolder] }
|
||||
: x
|
||||
);
|
||||
setSignerPos(updatesignerPos);
|
||||
@@ -2049,6 +2031,7 @@ function PdfRequestFiles(
|
||||
{containerWH && (
|
||||
<RenderPdf
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
drop={drop}
|
||||
pageNumber={pageNumber}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pdfNewWidth={pdfNewWidth}
|
||||
@@ -2086,8 +2069,6 @@ function PdfRequestFiles(
|
||||
setIsReqSignTourDisabled={handleCloseTour}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
isShowModal={isShowModal}
|
||||
signBtnPosition={signBtnPosition}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import axios from "axios";
|
||||
import Parse from "parse";
|
||||
import "../styles/signature.css";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { useDrop } from "react-dnd";
|
||||
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import WidgetComponent from "../components/pdf/WidgetComponent";
|
||||
import Tour from "../primitives/Tour";
|
||||
@@ -163,6 +164,11 @@ function PlaceHolderSign() {
|
||||
const [docTitle, setDocTitle] = useState("");
|
||||
const [isEditDoc, setIsEditDoc] = useState(false);
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
drop: (item, monitor) => addPositionOfSignature(item, monitor),
|
||||
collect: (monitor) => ({ isOver: !!monitor.isOver() })
|
||||
});
|
||||
const currentUser = localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
@@ -547,8 +553,7 @@ function PlaceHolderSign() {
|
||||
options: addWidgetOptions(
|
||||
dragTypeValue,
|
||||
owner,
|
||||
filterSignerPos?.placeHolder,
|
||||
roleName
|
||||
filterSignerPos?.placeHolder
|
||||
),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
Height: widgetHeight / (containerScale * scale)
|
||||
@@ -565,33 +570,12 @@ function PlaceHolderSign() {
|
||||
//`containerRect.top` The distance from the top of the viewport to the top of the element.
|
||||
const x = offset.x - containerRect.left;
|
||||
const y = offset.y - containerRect.top;
|
||||
let getXPosition = signBtnPosition[0]
|
||||
const getXPosition = signBtnPosition[0]
|
||||
? x - signBtnPosition[0].xPos
|
||||
: x;
|
||||
let getYPosition = signBtnPosition[0]
|
||||
const getYPosition = signBtnPosition[0]
|
||||
? y - signBtnPosition[0].yPos
|
||||
: y;
|
||||
|
||||
// to avoid negative position values (half portion of widget should not be out of pdf container)
|
||||
const calculateWidth =
|
||||
getXPosition + widgetWidth - containerRect.width;
|
||||
const calculateHeight =
|
||||
getYPosition + widgetHeight - containerRect.height;
|
||||
|
||||
if (getXPosition < 0) {
|
||||
getXPosition = 0;
|
||||
}
|
||||
if (getYPosition < 0) {
|
||||
getYPosition = 0;
|
||||
}
|
||||
|
||||
if (calculateWidth > 0) {
|
||||
getXPosition = getXPosition - calculateWidth;
|
||||
}
|
||||
|
||||
if (calculateHeight > 0) {
|
||||
getYPosition = getYPosition - calculateHeight;
|
||||
}
|
||||
dropObj = {
|
||||
xPosition: getXPosition / (containerScale * scale),
|
||||
yPosition: getYPosition / (containerScale * scale),
|
||||
@@ -604,8 +588,7 @@ function PlaceHolderSign() {
|
||||
options: addWidgetOptions(
|
||||
dragTypeValue,
|
||||
owner,
|
||||
filterSignerPos?.placeHolder,
|
||||
roleName
|
||||
filterSignerPos?.placeHolder
|
||||
),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
Height: widgetHeight / (containerScale * scale)
|
||||
@@ -737,7 +720,9 @@ function PlaceHolderSign() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((url) => {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((url) => {
|
||||
if (url.key === keyValue) {
|
||||
return {
|
||||
...url,
|
||||
@@ -1468,9 +1453,9 @@ function PlaceHolderSign() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (currWidgetsDetails?.type === radioButtonWidget) {
|
||||
if (addOption) {
|
||||
@@ -1499,8 +1484,12 @@ function PlaceHolderSign() {
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
defaultValue: defaultValue,
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1534,8 +1523,12 @@ function PlaceHolderSign() {
|
||||
layout: layout,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1548,8 +1541,12 @@ function PlaceHolderSign() {
|
||||
status: status,
|
||||
values: dropdownOptions,
|
||||
defaultValue: defaultValue,
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
...(isReadOnly ? { isReadOnly: isReadOnly || false } : {})
|
||||
}
|
||||
};
|
||||
@@ -1564,11 +1561,9 @@ function PlaceHolderSign() {
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
const recalculatedPlaceholders =
|
||||
utils.applyNumberFormulasToPages(newUpdateSignPos);
|
||||
const newUpdateSigner = signerPos.map((obj) => {
|
||||
if (obj.Id === uniqueId) {
|
||||
return { ...obj, placeHolder: recalculatedPlaceholders };
|
||||
return { ...obj, placeHolder: newUpdateSignPos };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
@@ -1600,9 +1595,9 @@ function PlaceHolderSign() {
|
||||
);
|
||||
|
||||
if (getPageNumer.length > 0) {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === textInputWidget) {
|
||||
return {
|
||||
@@ -1615,8 +1610,12 @@ function PlaceHolderSign() {
|
||||
defaultValue: defaultdata?.defaultValue || "",
|
||||
validation:
|
||||
{},
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
isReadOnly: defaultdata?.isReadOnly || false
|
||||
}
|
||||
};
|
||||
@@ -1635,13 +1634,16 @@ function PlaceHolderSign() {
|
||||
),
|
||||
validation:
|
||||
{},
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
isReadOnly: defaultdata?.isReadOnly || false
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (["signature"].includes(position.type)) {
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
@@ -1659,8 +1661,12 @@ function PlaceHolderSign() {
|
||||
status: defaultdata.status,
|
||||
defaultValue: defaultdata.defaultValue,
|
||||
hint: defaultdata?.hint || "",
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1946,7 +1952,6 @@ function PlaceHolderSign() {
|
||||
return { ...x, ...data };
|
||||
});
|
||||
setPdfDetails(updateDocument);
|
||||
setDocTitle(updateDocument?.[0]?.Name);
|
||||
try {
|
||||
const Bcc = updateDocument?.[0]?.Bcc?.length
|
||||
? {
|
||||
@@ -2040,12 +2045,12 @@ function PlaceHolderSign() {
|
||||
setSignBtnPosition={setSignBtnPosition}
|
||||
pageNumber={pageNumber}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
signedUrl={pdfDetails?.[0]?.SignedUrl || ""}
|
||||
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
setIsUploadPdf={setIsUploadPdf}
|
||||
pdfArrayBuffer={pdfArrayBuffer}
|
||||
isMergePdfBtn={true}
|
||||
pdfDetails={pdfDetails}
|
||||
/>
|
||||
{/* pdf render view */}
|
||||
<div className="w-full md:w-[57%] flex mr-4">
|
||||
@@ -2065,7 +2070,6 @@ function PlaceHolderSign() {
|
||||
setAllPages={setAllPages}
|
||||
setPageNumber={setPageNumber}
|
||||
setIsTour={setPlaceholderTour}
|
||||
pdfDetails={pdfDetails}
|
||||
/>
|
||||
<div className=" w-full md:w-[95%] ">
|
||||
{/* this modal is used show alert set placeholder for all signers before send mail */}
|
||||
@@ -2348,6 +2352,7 @@ function PlaceHolderSign() {
|
||||
numPages={numPages}
|
||||
pageDetails={pageDetails}
|
||||
placeholder={true}
|
||||
drop={drop}
|
||||
handleDeleteWidget={handleDeleteWidget}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
@@ -2383,8 +2388,6 @@ function PlaceHolderSign() {
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
setRoleName={setRoleName}
|
||||
isShowModal={isShowModal}
|
||||
signBtnPosition={signBtnPosition}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -2529,8 +2532,6 @@ function PlaceHolderSign() {
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
roleName={roleName}
|
||||
widgetsSource={signerPos}
|
||||
activeSignerId={uniqueId}
|
||||
/>
|
||||
<RotateAlert
|
||||
showRotateAlert={showRotateAlert.status}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import Alert from "../primitives/Alert";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Loader from "../primitives/Loader";
|
||||
@@ -6,24 +6,37 @@ import Tooltip from "../primitives/Tooltip";
|
||||
import {
|
||||
getTenantDetails,
|
||||
handleSignatureType,
|
||||
openInNewTab,
|
||||
signatureTypes,
|
||||
usertimezone
|
||||
} from "../constant/Utils";
|
||||
import Parse from "parse";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import ReactQuill from "react-quill-new";
|
||||
import "../styles/quill.css";
|
||||
import EditorToolbar, {
|
||||
module1,
|
||||
module2,
|
||||
formats
|
||||
} from "../components/pdf/EditorToolbar";
|
||||
import TimezoneSelector from "../components/preferences/TimezoneSelector";
|
||||
import DateFormatSelector from "../components/preferences/DateFormatSelector";
|
||||
import FilenameFormatSelector from "../components/preferences/FilenameFormatSelector";
|
||||
import axios from "axios";
|
||||
import MailTemplateEditor from "../components/preferences/MailTemplateEditor";
|
||||
|
||||
const Preferences = () => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const editorRef = useRef();
|
||||
const editorRefCom = useRef();
|
||||
const Extand_Class = localStorage.getItem("Extand_Class");
|
||||
const extClass = Extand_Class && JSON.parse(Extand_Class);
|
||||
const [requestSubject, setRequestSubject] = useState("");
|
||||
const [completionBody, SetCompletionBody] = useState("");
|
||||
const [completionsubject, setCompletionSubject] = useState("");
|
||||
const [requestBody, setRequestBody] = useState("");
|
||||
const [defaultCompHtml, setDefaultCompHtml] = useState({});
|
||||
const [tenantId, setTenantId] = useState("");
|
||||
const [defaultReqHtml, setDefaultReqHtml] = useState({});
|
||||
const [isalert, setIsAlert] = useState({ type: "success", msg: "" });
|
||||
const [isTopLoader, setIsTopLoader] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
@@ -43,9 +56,11 @@ const Preferences = () => {
|
||||
const [dateFormat, setDateFormat] = useState("MM/DD/YYYY");
|
||||
const [is12HourTime, setIs12HourTime] = useState(false);
|
||||
const [isLTVEnabled, setIsLTVEnabled] = useState(false);
|
||||
const [isDefaultMail, setIsDefaultMail] = useState({
|
||||
requestMail: false,
|
||||
completionMail: false
|
||||
});
|
||||
const [fileNameFormat, setFileNameFormat] = useState("DOCNAME");
|
||||
const [userInfo, setUserInfo] = useState({});
|
||||
const [tenantInfo, setTenantInfo] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
fetchSignType();
|
||||
@@ -54,10 +69,10 @@ const Preferences = () => {
|
||||
|
||||
const fetchSignType = async () => {
|
||||
setIsTopLoader(true);
|
||||
const EmailTab = [
|
||||
{ name: "email", title: t("email"), icon: "fa-light fa-envelope" }
|
||||
];
|
||||
|
||||
const EmailTab =
|
||||
extClass?.[0]?.UserRole === "contracts_Admin"
|
||||
? [{ name: "email", title: t("email"), icon: "fa-light fa-envelope" }]
|
||||
: [];
|
||||
const arr = [
|
||||
generaltab,
|
||||
...EmailTab,
|
||||
@@ -70,29 +85,17 @@ const Preferences = () => {
|
||||
)
|
||||
);
|
||||
const tenantDetails = await getTenantDetails(user?.objectId);
|
||||
setTenantInfo(tenantDetails);
|
||||
await tenantEmailTemplate(tenantDetails);
|
||||
const signatureType = tenantDetails?.SignatureType || [];
|
||||
const tenantSignTypes = signatureType?.filter((x) => x.enabled === true);
|
||||
const extUser = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}functions/getUserDetails`,
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
const getUser = extUser.data?.result;
|
||||
const getUser = await Parse.Cloud.run("getUserDetails");
|
||||
if (getUser) {
|
||||
const _getUser = JSON.parse(JSON.stringify(getUser));
|
||||
setUserInfo(_getUser);
|
||||
setIsNotifyOnSignatures(
|
||||
const notifyOnSignatures =
|
||||
_getUser?.NotifyOnSignatures !== undefined
|
||||
? _getUser?.NotifyOnSignatures
|
||||
: true
|
||||
);
|
||||
: true;
|
||||
setIsNotifyOnSignatures(notifyOnSignatures);
|
||||
setTimezone(_getUser?.Timezone || usertimezone);
|
||||
if (tenantSignTypes?.length > 0) {
|
||||
const signatureType = _getUser?.SignatureType || signatureTypes;
|
||||
@@ -105,29 +108,31 @@ const Preferences = () => {
|
||||
const SignatureType = _getUser?.SignatureType || signatureTypes;
|
||||
setSignatureType(SignatureType);
|
||||
}
|
||||
setSendinOrder(
|
||||
_getUser?.SendinOrder !== undefined ? _getUser?.SendinOrder : true
|
||||
);
|
||||
setIsTourEnabled(
|
||||
_getUser?.IsTourEnabled !== undefined ? _getUser?.IsTourEnabled : true
|
||||
);
|
||||
setDateFormat(
|
||||
const sendinorder =
|
||||
_getUser?.SendinOrder !== undefined ? _getUser?.SendinOrder : true;
|
||||
setSendinOrder(sendinorder);
|
||||
const istourenabled =
|
||||
_getUser?.IsTourEnabled !== undefined
|
||||
? _getUser?.IsTourEnabled
|
||||
: true;
|
||||
setIsTourEnabled(istourenabled);
|
||||
const DateFormat =
|
||||
_getUser?.DateFormat !== undefined
|
||||
? _getUser?.DateFormat
|
||||
: "MM/DD/YYYY"
|
||||
);
|
||||
setIs12HourTime(
|
||||
_getUser?.Is12HourTime !== undefined ? _getUser?.Is12HourTime : false
|
||||
);
|
||||
setIsLTVEnabled(
|
||||
_getUser?.IsLTVEnabled !== undefined ? _getUser?.IsLTVEnabled : false
|
||||
);
|
||||
: "MM/DD/YYYY";
|
||||
setDateFormat(DateFormat);
|
||||
const is12Hr =
|
||||
_getUser?.Is12HourTime !== undefined ? _getUser?.Is12HourTime : false;
|
||||
setIs12HourTime(is12Hr);
|
||||
const isLTVEnabled =
|
||||
_getUser?.IsLTVEnabled !== undefined ? _getUser?.IsLTVEnabled : false;
|
||||
setIsLTVEnabled(isLTVEnabled);
|
||||
const downloadFilenameFormat =
|
||||
_getUser?.DownloadFilenameFormat || "DOCNAME";
|
||||
setFileNameFormat(downloadFilenameFormat);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error while getting user details: ", err);
|
||||
console.log("err while getting user details", err);
|
||||
setErrMsg(t("something-went-wrong-mssg"));
|
||||
} finally {
|
||||
setIsTopLoader(false);
|
||||
@@ -210,7 +215,7 @@ const Preferences = () => {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error updating signature type: ", err);
|
||||
console.log("Error updating signature type", err);
|
||||
setIsAlert({ type: "danger", msg: err.message });
|
||||
}
|
||||
|
||||
@@ -223,12 +228,178 @@ const Preferences = () => {
|
||||
const handleNotifySignChange = (value) => {
|
||||
setIsNotifyOnSignatures(value);
|
||||
};
|
||||
const tenantEmailTemplate = async (tenantRes) => {
|
||||
if (tenantRes === "user does not exist!") {
|
||||
alert(t("user-not-exist"));
|
||||
} else if (tenantRes) {
|
||||
setIsLoader(true);
|
||||
const updateRes = tenantRes;
|
||||
setTenantId(updateRes?.objectId);
|
||||
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}} has requested you to review and sign {{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p><a href='{{signing_url}}' rel='noopener noreferrer' target='_blank'>Sign here</a></p><br><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
|
||||
if (updateRes?.RequestBody) {
|
||||
setRequestBody(updateRes?.RequestBody);
|
||||
setRequestSubject(updateRes?.RequestSubject);
|
||||
} else {
|
||||
setRequestBody(defaultRequestBody);
|
||||
setRequestSubject(
|
||||
`{{sender_name}} has requested you to sign {{document_title}}`
|
||||
);
|
||||
setIsDefaultMail((prev) => ({ ...prev, requestMail: true }));
|
||||
}
|
||||
setDefaultReqHtml({
|
||||
body: defaultRequestBody,
|
||||
subject: `{{sender_name}} has requested you to sign {{document_title}}`
|
||||
});
|
||||
const defaultCompletionBody = `<p>Hi {{sender_name}},</p><br><p>All parties have successfully signed the document {{document_title}}. Kindly download the document from the attachment.</p><br><p>Thanks</p><p> Team ${appName}</p><br>`;
|
||||
if (updateRes?.CompletionBody) {
|
||||
SetCompletionBody(updateRes?.CompletionBody);
|
||||
setCompletionSubject(updateRes?.CompletionSubject);
|
||||
} else {
|
||||
SetCompletionBody(defaultCompletionBody);
|
||||
setCompletionSubject(
|
||||
`Document {{document_title}} has been signed by all parties`
|
||||
);
|
||||
setIsDefaultMail((prev) => ({ ...prev, completionMail: true }));
|
||||
}
|
||||
setDefaultCompHtml({
|
||||
body: defaultCompletionBody,
|
||||
subject: `Document {{document_title}} has been signed by all parties`
|
||||
});
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
//function to save completion email template
|
||||
const handleSaveCompletionEmail = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
setIsLoader(true);
|
||||
const replacedHtmlBody = completionBody.replace(/"/g, "'");
|
||||
const htmlBody = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>${replacedHtmlBody}</body></html>`;
|
||||
const updateTenant = await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: {
|
||||
CompletionBody: htmlBody,
|
||||
CompletionSubject: completionsubject
|
||||
}
|
||||
});
|
||||
if (updateTenant) {
|
||||
const updateRes = JSON.parse(JSON.stringify(updateTenant));
|
||||
SetCompletionBody(updateRes?.CompletionBody);
|
||||
setCompletionSubject(updateRes?.CompletionSubject);
|
||||
setIsAlert({ type: "success", msg: t("saved-successfully") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err", err);
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
//function to save request email template
|
||||
const handleSaveRequestEmail = async (e) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
setIsLoader(true);
|
||||
const replacedHtmlBody = requestBody.replace(/"/g, "'");
|
||||
const htmlBody = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>${replacedHtmlBody}</body></html>`;
|
||||
const updateTenant = await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: {
|
||||
RequestBody: htmlBody,
|
||||
RequestSubject: requestSubject
|
||||
}
|
||||
});
|
||||
if (updateTenant) {
|
||||
const updateRes = JSON.parse(JSON.stringify(updateTenant));
|
||||
setRequestBody(updateRes?.RequestBody);
|
||||
setRequestSubject(updateRes?.RequestSubject);
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.RequestBody = updateRes?.RequestBody;
|
||||
extUser.TenantId.RequestSubject = updateRes?.RequestSubject;
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
setIsAlert({ type: "success", msg: t("saved-successfully") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err", err);
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
|
||||
//function to use reset form
|
||||
const handleReset = async (request, completion) => {
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
handleModifyMail(request);
|
||||
if (request && !isDefaultMail?.requestMail) {
|
||||
setRequestBody(defaultReqHtml?.body);
|
||||
setRequestSubject(defaultReqHtml?.subject);
|
||||
try {
|
||||
await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: { RequestBody: "", RequestSubject: "" }
|
||||
});
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.RequestBody = "";
|
||||
extUser.TenantId.RequestSubject = "";
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in reseting request mail", err);
|
||||
}
|
||||
} else if (completion && !isDefaultMail?.completionMail) {
|
||||
setCompletionSubject(defaultCompHtml?.subject);
|
||||
SetCompletionBody(defaultCompHtml?.body);
|
||||
try {
|
||||
await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: { CompletionBody: "", CompletionSubject: "" }
|
||||
});
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.CompletionBody = "";
|
||||
extUser.TenantId.CompletionSubject = "";
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in reseting completion mail", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
//function for handle ontext change and save again text in delta
|
||||
const handleOnchangeRequest = () => {
|
||||
if (editorRef.current) {
|
||||
const html = editorRef.current.editor.root.innerHTML;
|
||||
setRequestBody(html);
|
||||
}
|
||||
};
|
||||
const handleOnchangeCompletion = () => {
|
||||
if (editorRefCom.current) {
|
||||
const html = editorRefCom.current.editor.root.innerHTML;
|
||||
SetCompletionBody(html);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTourInput = () => setIsTourEnabled(!isTourEnabled);
|
||||
const handleSendinOrderInput = () => setSendinOrder(!sendinOrder);
|
||||
const tabName = (ind) => tab.find((t, i) => i === ind)?.name;
|
||||
|
||||
const handleModifyMail = (mode) => {
|
||||
mode === "request"
|
||||
? setIsDefaultMail((p) => ({ ...p, requestMail: !p?.requestMail }))
|
||||
: setIsDefaultMail((p) => ({ ...p, completionMail: !p?.completionMail }));
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
{isalert.msg && <Alert type={isalert.type}>{isalert.msg}</Alert>}
|
||||
@@ -622,12 +793,160 @@ const Preferences = () => {
|
||||
)}
|
||||
{tabName(activeTab) === "email" && (
|
||||
<div className="flex flex-col mb-4">
|
||||
<MailTemplateEditor
|
||||
info={
|
||||
tenantInfo
|
||||
<div className="flex flex-col">
|
||||
<h1 className="text-[14px] mb-[0.7rem] font-medium">
|
||||
{t("request-email")}
|
||||
</h1>
|
||||
<div className="relative mt-2 mb-4">
|
||||
{
|
||||
isDefaultMail?.requestMail && (
|
||||
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
|
||||
<button
|
||||
onClick={() => handleModifyMail("request")}
|
||||
className="op-btn op-btn-primary shadow-lg"
|
||||
>
|
||||
{t("modify")}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
tenantId={tenantInfo?.objectId}
|
||||
/>
|
||||
<form
|
||||
onSubmit={handleSaveRequestEmail}
|
||||
className="p-3 border-[1px] border-base-content rounded-box"
|
||||
>
|
||||
<div className="text-lg font-normal">
|
||||
<label className="text-sm">
|
||||
{t("subject")}{" "}
|
||||
<Tooltip
|
||||
id={"request-sub-tooltip"}
|
||||
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
value={requestSubject}
|
||||
onChange={(e) =>
|
||||
setRequestSubject(e.target.value)
|
||||
}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-lg font-normal py-2">
|
||||
<label className="text-sm mt-3">
|
||||
{t("body")}{" "}
|
||||
<Tooltip
|
||||
id={"request-body-tooltip"}
|
||||
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
|
||||
/>
|
||||
</label>
|
||||
<EditorToolbar containerId="toolbar1" />
|
||||
<ReactQuill
|
||||
theme="snow"
|
||||
value={requestBody}
|
||||
placeholder="add body of email"
|
||||
// onChangeSelection={handleTextSelection} // Listen for text selection
|
||||
ref={editorRef}
|
||||
modules={module1}
|
||||
formats={formats}
|
||||
onChange={handleOnchangeRequest}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2">
|
||||
<button
|
||||
disabled={!requestBody || !requestSubject}
|
||||
className="op-btn op-btn-primary"
|
||||
type="submit"
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => handleReset("request")}
|
||||
>
|
||||
{t("reset")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<h1 className="text-[14px] mb-[0.7rem] font-medium">
|
||||
{t("completion-email")}
|
||||
</h1>
|
||||
<div className="relative my-2">
|
||||
{
|
||||
isDefaultMail?.completionMail && (
|
||||
<div className="absolute backdrop-blur-[2px] flex w-full h-full justify-center items-center bg-black/10 rounded-box select-none z-20">
|
||||
<button
|
||||
onClick={() => handleModifyMail("completion")}
|
||||
className="op-btn op-btn-primary shadow-lg"
|
||||
>
|
||||
{t("modify")}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<form
|
||||
onSubmit={handleSaveCompletionEmail}
|
||||
className="p-3 border-[1px] border-base-content rounded-box"
|
||||
>
|
||||
<div className="text-lg font-normal">
|
||||
<label className="text-sm">
|
||||
{t("subject")}{" "}
|
||||
<Tooltip
|
||||
id={"complete-sub-tooltip"}
|
||||
message={`${t("variables-use")}:{{sender_name}} {{document_title}}`}
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
required
|
||||
value={completionsubject}
|
||||
onChange={(e) =>
|
||||
setCompletionSubject(e.target.value)
|
||||
}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-lg font-normal py-2">
|
||||
<label className="text-sm mt-3">
|
||||
{t("body")}{" "}
|
||||
<Tooltip
|
||||
id={"complete-body-tooltip"}
|
||||
message={`${t("variables-use")}:{{sender_name}} {{document_title}} {{signing_url}}`}
|
||||
/>
|
||||
</label>
|
||||
<EditorToolbar containerId="toolbar2" />
|
||||
<ReactQuill
|
||||
theme="snow"
|
||||
value={completionBody}
|
||||
placeholder="add body of email "
|
||||
// onChangeSelection={handleTextSelection} // Listen for text selection
|
||||
ref={editorRefCom}
|
||||
modules={module2}
|
||||
formats={formats}
|
||||
onChange={handleOnchangeCompletion}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center mt-3 gap-2">
|
||||
<button
|
||||
disabled={!completionBody || !completionsubject}
|
||||
className="op-btn op-btn-primary"
|
||||
type="submit"
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => handleReset(null, "completion")}
|
||||
>
|
||||
{t("reset")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{tabName(activeTab) === "security" && (
|
||||
|
||||
@@ -37,7 +37,6 @@ const Report = () => {
|
||||
// Number of documents to fetch per API call
|
||||
const docLimit = 20;
|
||||
|
||||
|
||||
// below useEffect is call when id param change
|
||||
useEffect(() => {
|
||||
setReportName("");
|
||||
|
||||
@@ -5,6 +5,7 @@ import Parse from "parse";
|
||||
import Confetti from "react-confetti";
|
||||
import axios from "axios";
|
||||
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import { useDrop } from "react-dnd";
|
||||
import EmailComponent from "../components/pdf/EmailComponent";
|
||||
import WidgetComponent from "../components/pdf/WidgetComponent";
|
||||
import {
|
||||
@@ -68,9 +69,6 @@ import {
|
||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
|
||||
import WidgetNameModal from "../components/pdf/WidgetNameModal";
|
||||
import CellsSettingModal from "../components/pdf/CellsSettingModal";
|
||||
import {
|
||||
applyNumberFormulasToPages,
|
||||
} from "../utils";
|
||||
//For signYourself inProgress section signer can add sign and complete doc sign.
|
||||
function SignYourSelf() {
|
||||
const { t } = useTranslation();
|
||||
@@ -142,10 +140,13 @@ function SignYourSelf() {
|
||||
const [isDownloadModal, setIsDownloadModal] = useState(false);
|
||||
const [isResize, setIsResize] = useState(false);
|
||||
const [isUploadPdf, setIsUploadPdf] = useState(false);
|
||||
const [unSignedWidgetId, setUnSignedWidgetId] = useState("");
|
||||
const [widgetsTour, setWidgetsTour] = useState(false);
|
||||
|
||||
const [owner, setOwner] = useState({});
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
drop: (item, monitor) => addPositionOfSignature(item, monitor),
|
||||
collect: (monitor) => ({ isOver: !!monitor.isOver() })
|
||||
});
|
||||
const index = xyPosition?.findIndex((object) => {
|
||||
return object.pageNumber === pageNumber;
|
||||
});
|
||||
@@ -393,34 +394,15 @@ function SignYourSelf() {
|
||||
.getBoundingClientRect();
|
||||
//`containerRect.left`, The distance from the left of the viewport to the left side of the element.
|
||||
//`containerRect.top` The distance from the top of the viewport to the top of the element.
|
||||
|
||||
const x = offset.x - containerRect.left;
|
||||
const y = offset.y - containerRect.top;
|
||||
let getXPosition = signBtnPosition[0] ? x - signBtnPosition[0].xPos : x;
|
||||
let getYPosition = signBtnPosition[0] ? y - signBtnPosition[0].yPos : y;
|
||||
const getXPosition = signBtnPosition[0] ? x - signBtnPosition[0].xPos : x;
|
||||
const getYPosition = signBtnPosition[0] ? y - signBtnPosition[0].yPos : y;
|
||||
const getWidth = widgetTypeExist
|
||||
? calculateInitialWidthHeight(widgetValue).getWidth
|
||||
: defaultWidthHeight(dragTypeValue).width;
|
||||
const getHeight = defaultWidthHeight(dragTypeValue).height;
|
||||
|
||||
// to avoid negative position values (half portion of widget should not be out of pdf container)
|
||||
const calculateWidth = getXPosition + getWidth - containerRect.width;
|
||||
const calculateHeight = getYPosition + getWidth - containerRect.height;
|
||||
|
||||
// to avoid negative position values (half portion of widget should not be out of pdf container)
|
||||
if (getXPosition < 0) {
|
||||
getXPosition = 0;
|
||||
}
|
||||
if (getYPosition < 0) {
|
||||
getYPosition = 0;
|
||||
}
|
||||
|
||||
if (calculateWidth > 0) {
|
||||
getXPosition = getXPosition - calculateWidth;
|
||||
}
|
||||
|
||||
if (calculateHeight > 0) {
|
||||
getYPosition = getYPosition - calculateHeight;
|
||||
}
|
||||
dropObj = {
|
||||
xPosition: getXPosition / (containerScale * scale),
|
||||
yPosition: getYPosition / (containerScale * scale),
|
||||
@@ -469,7 +451,9 @@ function SignYourSelf() {
|
||||
setFontColor("black");
|
||||
}
|
||||
dispatch(setIsShowModal({ [key]: true }));
|
||||
// if (dragTypeValue !== "checkbox") {
|
||||
setCurrWidgetsDetails(dropObj);
|
||||
// }
|
||||
};
|
||||
|
||||
//`handleResend` function is used to resend otp for email verification
|
||||
@@ -602,23 +586,9 @@ function SignYourSelf() {
|
||||
}
|
||||
if (!isEnableOTP || isEmailVerified) {
|
||||
let showAlert = false,
|
||||
widgetKey,
|
||||
tourPageNumber;
|
||||
isSignatureExist = false;
|
||||
try {
|
||||
const isSignatureExist = xyPosition?.some((p) =>
|
||||
p?.pos?.some((x) => x?.type === "signature")
|
||||
);
|
||||
if (!isSignatureExist) {
|
||||
setIsAlert({
|
||||
header: t("fields-required"),
|
||||
isShow: true,
|
||||
alertMessage: t("signature-widget-alert-1")
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < xyPosition?.length; i++) {
|
||||
tourPageNumber = xyPosition[i]?.pageNumber;
|
||||
const requiredWidgets = xyPosition[i].pos.filter(
|
||||
(position) => position.type !== "checkbox"
|
||||
);
|
||||
@@ -627,24 +597,34 @@ function SignYourSelf() {
|
||||
for (let i = 0; i < requiredWidgets?.length; i++) {
|
||||
checkSigned = requiredWidgets[i]?.options?.response;
|
||||
if (!checkSigned) {
|
||||
const checkSignUrl = requiredWidgets[i]?.SignUrl;
|
||||
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
|
||||
let checkDefaultSigned =
|
||||
requiredWidgets[i]?.options?.defaultValue;
|
||||
if (!checkSignUrl && !checkDefaultSigned && !showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredWidgets[i]?.key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showAlert) {
|
||||
break;
|
||||
//condition to check exist signature widget or not
|
||||
if (!isSignatureExist) {
|
||||
isSignatureExist = xyPosition[i].pos.some(
|
||||
(data) => data?.type === "signature"
|
||||
);
|
||||
}
|
||||
}
|
||||
if (showAlert) {
|
||||
setUnSignedWidgetId(widgetKey);
|
||||
setPageNumber(tourPageNumber);
|
||||
setWidgetsTour(true);
|
||||
if (xyPosition.length === 0 || !isSignatureExist) {
|
||||
setIsAlert({
|
||||
header: t("fields-required"),
|
||||
isShow: true,
|
||||
alertMessage: t("signature-widget-alert-1")
|
||||
});
|
||||
return;
|
||||
} else if (showAlert) {
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("signature-widget-alert-2")
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
setIsUiLoading(true);
|
||||
@@ -791,7 +771,9 @@ function SignYourSelf() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (filterDropPos?.length > 0) {
|
||||
const addSign = xyPosition[index]?.pos?.map((url) => {
|
||||
const getXYdata = xyPosition[index].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSign = getPosData.map((url) => {
|
||||
if (url.key === dragKey) {
|
||||
return {
|
||||
...url,
|
||||
@@ -976,9 +958,11 @@ function SignYourSelf() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const widgetLayout =
|
||||
currWidgetsDetails?.type === "checkbox" ? { layout: layout } : {};
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (addOption) {
|
||||
return {
|
||||
@@ -1040,7 +1024,9 @@ function SignYourSelf() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
return {
|
||||
...position,
|
||||
@@ -1097,8 +1083,6 @@ function SignYourSelf() {
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const updatePos = getPageNumer[0].pos.map((position) => {
|
||||
const textSize = newFontSize || position?.options?.fontSize;
|
||||
const textColor = newFontColor || position?.options?.fontColor;
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === cellsWidget) {
|
||||
const count = parseInt(
|
||||
@@ -1112,19 +1096,20 @@ function SignYourSelf() {
|
||||
name: defaultdata?.name || position.options?.name || "Cells",
|
||||
cellCount: count,
|
||||
defaultValue: (defaultdata?.defaultValue || "").slice(0, count),
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor:
|
||||
newFontColor || position.options?.fontColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || position.options?.name,
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor:
|
||||
newFontColor || position.options?.fontColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1134,7 +1119,7 @@ function SignYourSelf() {
|
||||
const updateXYposition = xyPosition.map((obj, ind) =>
|
||||
ind === index ? { ...obj, pos: updatePos } : obj
|
||||
);
|
||||
setXyPosition(applyNumberFormulasToPages(updateXYposition));
|
||||
setXyPosition(updateXYposition);
|
||||
}
|
||||
setFontSize();
|
||||
setFontColor();
|
||||
@@ -1211,14 +1196,6 @@ function SignYourSelf() {
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
setShowRotateAlert({ status: false, degree: 0 });
|
||||
};
|
||||
const WidgetTourConfig = [
|
||||
{
|
||||
selector: '[data-tut="IsSigned"]',
|
||||
content: t("signature-validate-alert-2"),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{isLoading.isLoad ? (
|
||||
@@ -1264,14 +1241,6 @@ function SignYourSelf() {
|
||||
isOpen={signTour}
|
||||
/>
|
||||
)}
|
||||
<Tour
|
||||
showNumber={false}
|
||||
showNavigation={false}
|
||||
showNavigationNumber={false}
|
||||
onRequestClose={() => setWidgetsTour(false)}
|
||||
steps={WidgetTourConfig}
|
||||
isOpen={widgetsTour}
|
||||
/>
|
||||
{/* this component used to render all pdf pages in left side */}
|
||||
<RenderAllPdfPage
|
||||
allPages={allPages}
|
||||
@@ -1281,12 +1250,12 @@ function SignYourSelf() {
|
||||
pageNumber={pageNumber}
|
||||
containerWH={containerWH}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
signedUrl={pdfDetails?.[0]?.SignedUrl || ""}
|
||||
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
setIsUploadPdf={setIsUploadPdf}
|
||||
pdfArrayBuffer={pdfArrayBuffer}
|
||||
isMergePdfBtn={!pdfDetails?.[0]?.IsCompleted}
|
||||
pdfDetails={pdfDetails}
|
||||
/>
|
||||
<div className="w-full md:w-[57%] flex mr-4">
|
||||
<PdfTools
|
||||
@@ -1304,7 +1273,6 @@ function SignYourSelf() {
|
||||
setAllPages={setAllPages}
|
||||
setPageNumber={setPageNumber}
|
||||
isDisableEditTools={isCompleted}
|
||||
pdfDetails={pdfDetails}
|
||||
/>
|
||||
<div className="w-full md:w-[95%]">
|
||||
<ModalUi
|
||||
@@ -1401,6 +1369,7 @@ function SignYourSelf() {
|
||||
pageNumber={pageNumber}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pdfNewWidth={pdfNewWidth}
|
||||
drop={drop}
|
||||
successEmail={successEmail}
|
||||
nodeRef={nodeRef}
|
||||
handleTabDrag={handleTabDrag}
|
||||
@@ -1438,9 +1407,6 @@ function SignYourSelf() {
|
||||
divRef={divRef}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
isShowModal={isShowModal}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
signBtnPosition={signBtnPosition}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1492,8 +1458,6 @@ function SignYourSelf() {
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
widgetsSource={xyPosition}
|
||||
isSelfSign={true}
|
||||
/>
|
||||
<CellsSettingModal
|
||||
isOpen={isCellsSetting}
|
||||
|
||||
@@ -3,6 +3,7 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import { useParams, useNavigate } from "react-router";
|
||||
import axios from "axios";
|
||||
import "../styles/signature.css";
|
||||
import { useDrop } from "react-dnd";
|
||||
import WidgetComponent from "../components/pdf/WidgetComponent";
|
||||
import Tour from "../primitives/Tour";
|
||||
import SignerListPlace from "../components/pdf/SignerListPlace";
|
||||
@@ -123,6 +124,11 @@ const TemplatePlaceholder = () => {
|
||||
const [pdfBase64Url, setPdfBase64Url] = useState("");
|
||||
const [isUploadPdf, setIsUploadPdf] = useState(false);
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
drop: (item, monitor) => addPositionOfSignature(item, monitor),
|
||||
collect: (monitor) => ({ isOver: !!monitor.isOver() })
|
||||
});
|
||||
const [uniqueId, setUniqueId] = useState("");
|
||||
const [isModalRole, setIsModalRole] = useState(false);
|
||||
const [roleName, setRoleName] = useState("");
|
||||
@@ -209,21 +215,28 @@ const TemplatePlaceholder = () => {
|
||||
const filterSignTypes = signatureType?.filter(
|
||||
(x) => x.enabled === true
|
||||
);
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
const subject = tenantDetails?.RequestSubject ?? "";
|
||||
const body = tenantDetails?.RequestBody ?? "";
|
||||
//customize mail state is handle to when user want to customize already set tenant email format then use that format
|
||||
const userSubject =
|
||||
subject;
|
||||
const userBody =
|
||||
body;
|
||||
setCustomizeMail({
|
||||
subject: userSubject ?? defaultMailSubject,
|
||||
body: userBody ?? defaultMailBody
|
||||
});
|
||||
setDefaultMail({ subject: userSubject, body: userBody });
|
||||
if (tenantDetails?.RequestBody) {
|
||||
//customize mail state is handle to when user want to customize already set tenant email format then use that format
|
||||
setCustomizeMail({
|
||||
subject: tenantDetails?.RequestSubject,
|
||||
body: tenantDetails?.RequestBody
|
||||
});
|
||||
setDefaultMail({
|
||||
subject: tenantDetails?.RequestSubject,
|
||||
body: tenantDetails?.RequestBody
|
||||
});
|
||||
} else {
|
||||
const defaultRequestBody = defaultMailBody;
|
||||
const defaultSubject = defaultMailSubject;
|
||||
setCustomizeMail({
|
||||
subject: defaultSubject,
|
||||
body: defaultRequestBody
|
||||
});
|
||||
setDefaultMail({
|
||||
subject: defaultSubject,
|
||||
body: defaultRequestBody
|
||||
});
|
||||
}
|
||||
return filterSignTypes;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -335,8 +348,8 @@ const TemplatePlaceholder = () => {
|
||||
setPrefillSigner([utils?.prefillObj()]);
|
||||
}
|
||||
setSignersData(updatedSigners);
|
||||
setUniqueId(updatedSigners[0]?.Id);
|
||||
setBlockColor(updatedSigners[0]?.blockColor);
|
||||
setUniqueId(updatedSigners[0].Id);
|
||||
setBlockColor(updatedSigners[0].blockColor);
|
||||
setIsSelectId(0);
|
||||
} else {
|
||||
const updatedSigners = documentData[0].Signers.map((x, index) => ({
|
||||
@@ -345,8 +358,8 @@ const TemplatePlaceholder = () => {
|
||||
Role: "Role " + (index + 1)
|
||||
}));
|
||||
setSignersData(updatedSigners);
|
||||
setUniqueId(updatedSigners[0]?.Id);
|
||||
setBlockColor(updatedSigners[0]?.blockColor);
|
||||
setUniqueId(updatedSigners[0].Id);
|
||||
setBlockColor(updatedSigners[0].blockColor);
|
||||
setPrefillSigner([utils?.prefillObj()]);
|
||||
}
|
||||
} else {
|
||||
@@ -424,6 +437,7 @@ const TemplatePlaceholder = () => {
|
||||
const addPositionOfSignature = (item, monitor) => {
|
||||
getSignerPos(item, monitor);
|
||||
};
|
||||
|
||||
// `getSignerPos` is used to get placeholder position when user place it and save it in array
|
||||
const getSignerPos = (item, monitor) => {
|
||||
if (uniqueId) {
|
||||
@@ -469,8 +483,7 @@ const TemplatePlaceholder = () => {
|
||||
options: addWidgetOptions(
|
||||
dragTypeValue,
|
||||
owner,
|
||||
filterSignerPos?.placeHolder,
|
||||
roleName
|
||||
filterSignerPos?.placeHolder
|
||||
),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
Height: widgetHeight / (containerScale * scale)
|
||||
@@ -485,33 +498,12 @@ const TemplatePlaceholder = () => {
|
||||
.getBoundingClientRect();
|
||||
const x = offset.x - containerRect.left;
|
||||
const y = offset.y - containerRect.top;
|
||||
let getXPosition = signBtnPosition[0]
|
||||
const getXPosition = signBtnPosition[0]
|
||||
? x - signBtnPosition[0].xPos
|
||||
: x;
|
||||
let getYPosition = signBtnPosition[0]
|
||||
const getYPosition = signBtnPosition[0]
|
||||
? y - signBtnPosition[0].yPos
|
||||
: y;
|
||||
// to avoid negative position values (half portion of widget should not be out of pdf container)
|
||||
const calculateWidth =
|
||||
getXPosition + widgetWidth - containerRect.width;
|
||||
const calculateHeight =
|
||||
getYPosition + widgetHeight - containerRect.height;
|
||||
|
||||
if (getXPosition < 0) {
|
||||
getXPosition = 0;
|
||||
}
|
||||
if (getYPosition < 0) {
|
||||
getYPosition = 0;
|
||||
}
|
||||
|
||||
if (calculateWidth > 0) {
|
||||
getXPosition = getXPosition - calculateWidth;
|
||||
}
|
||||
|
||||
if (calculateHeight > 0) {
|
||||
getYPosition = getYPosition - calculateHeight;
|
||||
}
|
||||
|
||||
dropObj = {
|
||||
xPosition: getXPosition / (containerScale * scale),
|
||||
yPosition: getYPosition / (containerScale * scale),
|
||||
@@ -525,8 +517,7 @@ const TemplatePlaceholder = () => {
|
||||
options: addWidgetOptions(
|
||||
dragTypeValue,
|
||||
owner,
|
||||
filterSignerPos?.placeHolder,
|
||||
roleName
|
||||
filterSignerPos?.placeHolder
|
||||
),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
Height: widgetHeight / (containerScale * scale)
|
||||
@@ -623,6 +614,7 @@ const TemplatePlaceholder = () => {
|
||||
setIsAddRole(true);
|
||||
}
|
||||
};
|
||||
|
||||
const tourAddRole = [
|
||||
{
|
||||
selector: '[data-tut="reactourAddbtn"]',
|
||||
@@ -670,7 +662,9 @@ const TemplatePlaceholder = () => {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((url) => {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((url) => {
|
||||
if (url.key === keyValue) {
|
||||
return {
|
||||
...url,
|
||||
@@ -762,11 +756,7 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (
|
||||
pdfDetails?.[0]?.CreatedBy?.objectId !== user?.objectId ||
|
||||
isPrefillModal
|
||||
)
|
||||
return;
|
||||
if (pdfDetails?.[0]?.CreatedBy?.objectId !== user?.objectId) return;
|
||||
const timer = setTimeout(() => {
|
||||
autosavedetails();
|
||||
}, 2000);
|
||||
@@ -1329,9 +1319,10 @@ const TemplatePlaceholder = () => {
|
||||
);
|
||||
|
||||
if (getPageNumer.length > 0) {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (currWidgetsDetails?.type === radioButtonWidget) {
|
||||
if (addOption) {
|
||||
@@ -1360,8 +1351,12 @@ const TemplatePlaceholder = () => {
|
||||
defaultValue: defaultValue,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1395,8 +1390,12 @@ const TemplatePlaceholder = () => {
|
||||
isReadOnly: isReadOnly || false,
|
||||
defaultValue: defaultValue,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1409,8 +1408,12 @@ const TemplatePlaceholder = () => {
|
||||
status: status,
|
||||
values: dropdownOptions,
|
||||
defaultValue: defaultValue,
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
...(isReadOnly ? { isReadOnly: isReadOnly || false } : {})
|
||||
}
|
||||
};
|
||||
@@ -1425,11 +1428,9 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
const recalculatedPlaceholders =
|
||||
utils.applyNumberFormulasToPages(newUpdateSignPos);
|
||||
const newUpdateSigner = signerPos.map((obj) => {
|
||||
if (obj.Id === uniqueId) {
|
||||
return { ...obj, placeHolder: recalculatedPlaceholders };
|
||||
return { ...obj, placeHolder: newUpdateSignPos };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
@@ -1462,9 +1463,9 @@ const TemplatePlaceholder = () => {
|
||||
);
|
||||
|
||||
if (getPageNumer.length > 0) {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === textInputWidget) {
|
||||
return {
|
||||
@@ -1478,8 +1479,12 @@ const TemplatePlaceholder = () => {
|
||||
isReadOnly: defaultdata?.isReadOnly || false,
|
||||
validation:
|
||||
{},
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
} else if (position.type === cellsWidget) {
|
||||
@@ -1498,12 +1503,15 @@ const TemplatePlaceholder = () => {
|
||||
validation:
|
||||
{},
|
||||
isReadOnly: defaultdata?.isReadOnly || false,
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
else if (["signature"].includes(position.type)) {
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
@@ -1521,8 +1529,12 @@ const TemplatePlaceholder = () => {
|
||||
status: defaultdata.status,
|
||||
defaultValue: defaultdata.defaultValue,
|
||||
hint: defaultdata?.hint || "",
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1762,12 +1774,12 @@ const TemplatePlaceholder = () => {
|
||||
setSignBtnPosition={setSignBtnPosition}
|
||||
pageNumber={pageNumber}
|
||||
pdfBase64Url={pdfBase64Url}
|
||||
signedUrl={pdfDetails?.[0]?.SignedUrl || ""}
|
||||
setPdfArrayBuffer={setPdfArrayBuffer}
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
setIsUploadPdf={setIsUploadPdf}
|
||||
pdfArrayBuffer={pdfArrayBuffer}
|
||||
isMergePdfBtn={true}
|
||||
pdfDetails={pdfDetails}
|
||||
/>
|
||||
|
||||
{/* pdf render view */}
|
||||
@@ -1787,7 +1799,6 @@ const TemplatePlaceholder = () => {
|
||||
allPages={allPages}
|
||||
setAllPages={setAllPages}
|
||||
setPageNumber={setPageNumber}
|
||||
pdfDetails={pdfDetails}
|
||||
/>
|
||||
<div className="w-full md:w-[95%]">
|
||||
<ModalUi
|
||||
@@ -1938,6 +1949,7 @@ const TemplatePlaceholder = () => {
|
||||
numPages={numPages}
|
||||
pageDetails={pageDetails}
|
||||
placeholder={true}
|
||||
drop={drop}
|
||||
handleDeleteWidget={handleDeleteWidgetObj}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
@@ -1974,8 +1986,6 @@ const TemplatePlaceholder = () => {
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
setRoleName={setRoleName}
|
||||
isShowModal={isShowModal}
|
||||
signBtnPosition={signBtnPosition}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -2052,6 +2062,9 @@ const TemplatePlaceholder = () => {
|
||||
initial={true}
|
||||
isTemplateFlow={true}
|
||||
roleName={roleName}
|
||||
Add
|
||||
commentMore
|
||||
actions
|
||||
isPrefillDropdown={true}
|
||||
/>
|
||||
</div>
|
||||
@@ -2111,8 +2124,6 @@ const TemplatePlaceholder = () => {
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
roleName={roleName}
|
||||
widgetsSource={signerPos}
|
||||
activeSignerId={uniqueId}
|
||||
/>
|
||||
{isPrefillModal && (
|
||||
<PrefillWidgetModal
|
||||
@@ -2128,6 +2139,7 @@ const TemplatePlaceholder = () => {
|
||||
navigatePageToDoc={navigatePageToDoc}
|
||||
setIsNewContact={setIsNewContact}
|
||||
isNewContact={isNewContact}
|
||||
documentFlow={"template"}
|
||||
docId={pdfDetails[0]?.objectId}
|
||||
isSubmit={isUseTemplate}
|
||||
/>
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
} from "react-i18next";
|
||||
import DeleteUserModal from "../primitives/DeleteUserModal";
|
||||
import axios from "axios";
|
||||
import PasswordResetModal from "../primitives/PasswordResetModal";
|
||||
|
||||
const actions = [
|
||||
{
|
||||
@@ -24,14 +23,6 @@ const actions = [
|
||||
action: "delete",
|
||||
restrictAdmin: true
|
||||
},
|
||||
{
|
||||
btnId: "1910",
|
||||
hoverLabel: "Reset password",
|
||||
btnIcon: "fa-light fa-key",
|
||||
redirectUrl: "",
|
||||
action: "resetpassword",
|
||||
restrictAdmin: true
|
||||
},
|
||||
];
|
||||
const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"];
|
||||
const UserList = () => {
|
||||
@@ -217,9 +208,9 @@ const UserList = () => {
|
||||
};
|
||||
|
||||
// `showAlert` handle show/hide alert
|
||||
const showAlert = (type, msg, timer = 1500) => {
|
||||
const showAlert = (type, msg) => {
|
||||
setIsAlert({ type, msg });
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), timer);
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
};
|
||||
|
||||
const handleDeleteAccount = async (item) => {
|
||||
@@ -256,15 +247,13 @@ const UserList = () => {
|
||||
};
|
||||
|
||||
const handleActionBtn = async (act, item) => {
|
||||
setIsActModal({ [`${act.action}_${item.objectId}`]: true });
|
||||
if (act.action === "delete") {
|
||||
setIsActModal({ [`delete_${item.objectId}`]: true });
|
||||
}
|
||||
};
|
||||
const handleBtnVisibility = (act, item) => {
|
||||
if (act.restrictAdmin) {
|
||||
if (item?.UserRole === "contracts_Admin") {
|
||||
return false;
|
||||
} else {
|
||||
return item?.objectId !== extClass?.[0]?.objectId;
|
||||
}
|
||||
return item?.objectId !== extClass?.[0]?.objectId;
|
||||
} else if (
|
||||
act.restrictBtn === true &&
|
||||
item?.objectId === extClass?.[0]?.objectId
|
||||
@@ -274,28 +263,6 @@ const UserList = () => {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const handleActiveToggleVisibility = (item) => {
|
||||
if (item?.UserRole === "contracts_Admin") {
|
||||
return false;
|
||||
} else {
|
||||
return item?.objectId !== extClass?.[0]?.objectId;
|
||||
}
|
||||
};
|
||||
|
||||
async function submitPassword(userId, password) {
|
||||
setIsLoader(true);
|
||||
setIsActModal({});
|
||||
try {
|
||||
const params = { userId, password };
|
||||
await Parse.Cloud.run("resetpassword", params);
|
||||
showAlert("success", t("password-has-been-reset"));
|
||||
} catch (err) {
|
||||
console.log("err while reset password", err);
|
||||
showAlert("danger", t(err.message), 2000);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="relative">
|
||||
{isLoader && (
|
||||
@@ -373,7 +340,7 @@ const UserList = () => {
|
||||
<td className="px-4 py-2">
|
||||
{formatRow(item.TeamIds)}
|
||||
</td>
|
||||
{handleActiveToggleVisibility(item) ? (
|
||||
{item.UserRole !== "contracts_Admin" ? (
|
||||
<td className="px-4 py-2 font-semibold">
|
||||
<label className="cursor-pointer relative block items-center mb-0">
|
||||
<input
|
||||
@@ -469,17 +436,6 @@ const UserList = () => {
|
||||
deleteRes={deleteUserRes}
|
||||
handleClose={handleCloseModal}
|
||||
/>
|
||||
<PasswordResetModal
|
||||
isOpen={
|
||||
isActModal[
|
||||
"resetpassword_" + item.objectId
|
||||
]
|
||||
}
|
||||
userId={item?.UserId?.objectId}
|
||||
onClose={handleCloseModal}
|
||||
onSubmit={submitPassword}
|
||||
showAlert={showAlert}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import { Navigate, useNavigate } from "react-router";
|
||||
import Parse from "parse";
|
||||
import { SaveFileSize } from "../constant/saveFileSize";
|
||||
@@ -6,7 +9,10 @@ import dp from "../assets/images/dp.png";
|
||||
import { sanitizeFileName } from "../utils";
|
||||
import axios from "axios";
|
||||
import Tooltip from "../primitives/Tooltip";
|
||||
import { getSecureUrl, handleSendOTP } from "../constant/Utils";
|
||||
import {
|
||||
getSecureUrl,
|
||||
handleSendOTP,
|
||||
} from "../constant/Utils";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -127,7 +133,7 @@ function UserProfile() {
|
||||
Name: obj.Name,
|
||||
JobTitle: jobTitle,
|
||||
Company: company,
|
||||
Language: obj?.language || ""
|
||||
Language: obj?.language || "",
|
||||
};
|
||||
|
||||
await axios.put(
|
||||
@@ -166,8 +172,8 @@ function UserProfile() {
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total) => {
|
||||
if (progressValue !== null) {
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
// console.log("percentCompleted ", percentCompleted);
|
||||
setpercentage(percentCompleted);
|
||||
@@ -443,7 +449,7 @@ function UserProfile() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
editmode ? handleSubmit(e) : setEditMode(true);
|
||||
editmode ? handleSubmit(e) : setEditMode(true);
|
||||
}}
|
||||
className="op-btn op-btn-primary md:w-[100px]"
|
||||
>
|
||||
@@ -454,7 +460,9 @@ function UserProfile() {
|
||||
onClick={() =>
|
||||
editmode ? handleCancel() : navigate("/changepassword")
|
||||
}
|
||||
className={`op-btn ${editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"}`}
|
||||
className={
|
||||
`op-btn ${editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"}`
|
||||
}
|
||||
>
|
||||
{editmode ? t("cancel") : t("change-password")}
|
||||
</button>
|
||||
|
||||
@@ -9,12 +9,10 @@ const ModalUi = ({
|
||||
showHeader = true,
|
||||
showClose = true,
|
||||
reduceWidth,
|
||||
position,
|
||||
crossColor
|
||||
position
|
||||
}) => {
|
||||
const width = reduceWidth;
|
||||
const isBottom = position === "bottom" ? "items-end pb-2 !bg-black/10" : "";
|
||||
const crossBtnColor = crossColor ?? "text-base-content";
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
@@ -40,7 +38,7 @@ const ModalUi = ({
|
||||
)}
|
||||
{showClose && (
|
||||
<button
|
||||
className={`${crossBtnColor} op-btn op-btn-sm op-btn-circle op-btn-ghost 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"
|
||||
onClick={() => handleClose && handleClose()}
|
||||
>
|
||||
✕
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import ModalUi from "./ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
/**
|
||||
* PasswordResetModal
|
||||
* - Validates password rules (length >= 8, upper, lower, digit, special)
|
||||
* - Buttons: Submit, Autogenerate (12 chars), Copy (Font Awesome icon only)
|
||||
* - `Autogenerate` fills a valid 12-char password but remains editable
|
||||
* - `Copy` copies the current password to clipboard
|
||||
*
|
||||
* Props:
|
||||
* - isOpen: boolean
|
||||
* - onClose: () => void
|
||||
* - onSubmit: (password: string) => Promise<void> | void
|
||||
*/
|
||||
export default function PasswordResetModal({
|
||||
userId,
|
||||
isOpen,
|
||||
onClose,
|
||||
onSubmit,
|
||||
showAlert
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [password, setPassword] = useState("");
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
// Validation helpers
|
||||
const rules = useMemo(
|
||||
() => ({
|
||||
hasUpper: /[A-Z]/,
|
||||
hasLower: /[a-z]/,
|
||||
hasDigit: /\d/,
|
||||
// Richer special set including common keyboard symbols
|
||||
hasSpecial: /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]/,
|
||||
minLen: 8
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const checks = useMemo(() => {
|
||||
return {
|
||||
lenOK: password.length >= rules.minLen,
|
||||
upperLowerDigitOK:
|
||||
rules.hasUpper.test(password) &&
|
||||
rules.hasLower.test(password) &&
|
||||
rules.hasDigit.test(password),
|
||||
specialOK: rules.hasSpecial.test(password)
|
||||
};
|
||||
}, [password, rules]);
|
||||
|
||||
// Always-present condition list with per-condition status
|
||||
const conditions = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: "len",
|
||||
ok: checks.lenOK,
|
||||
text: "password-length"
|
||||
},
|
||||
{
|
||||
key: "uld",
|
||||
ok: checks.upperLowerDigitOK,
|
||||
text: "password-case"
|
||||
},
|
||||
{
|
||||
key: "spec",
|
||||
ok: checks.specialOK,
|
||||
text: "password-special-case"
|
||||
}
|
||||
],
|
||||
[checks]
|
||||
);
|
||||
|
||||
const allValid = conditions.every((c) => c.ok) && password.length > 0;
|
||||
|
||||
// Autogenerate a valid random password (12 chars)
|
||||
const handleAutogen = () => {
|
||||
const length = 12;
|
||||
const upper = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // omit ambiguous I/O
|
||||
const lower = "abcdefghijkmnopqrstuvwxyz"; // omit ambiguous l
|
||||
const digits = "23456789"; // omit 0/1
|
||||
const special = "!@#$%^&*()-_=+[]{};:,.?";
|
||||
const all = upper + lower + digits + special;
|
||||
|
||||
function pick(str) {
|
||||
return str[Math.floor(Math.random() * str.length)];
|
||||
}
|
||||
function shuffle(arr) {
|
||||
return arr.sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
let pwd = [pick(upper), pick(lower), pick(digits), pick(special)];
|
||||
while (pwd.length < length) pwd.push(pick(all));
|
||||
pwd = shuffle(pwd).join("");
|
||||
|
||||
setPassword(pwd);
|
||||
};
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(password);
|
||||
showAlert("success", t("copied"), 1200);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false, 1200));
|
||||
} catch (e) {
|
||||
console.error("Clipboard error", e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e?.preventDefault?.();
|
||||
if (!allValid) return; // guard
|
||||
try {
|
||||
setSubmitting(true);
|
||||
await onSubmit?.(userId, password);
|
||||
setSubmitting(false);
|
||||
onClose?.();
|
||||
setPassword("");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ModalUi
|
||||
isOpen={isOpen}
|
||||
title={t("reset-password")}
|
||||
handleClose={() => {
|
||||
setPassword("");
|
||||
onClose?.();
|
||||
}}
|
||||
>
|
||||
<form onSubmit={handleSubmit} className="space-y-3 pt-[15px] p-[20px]">
|
||||
<p className="text-sm text-base-content/60">
|
||||
{t("enter-strong-password")}
|
||||
</p>
|
||||
<label className="op-form-control w-full">
|
||||
<div className="op-label">
|
||||
<span className="op-label-text">{t("new-password")}</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
placeholder="Enter password or click Autogenerate"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-square op-btn-sm"
|
||||
onClick={handleCopy}
|
||||
title={copied ? "Copied" : "Copy"}
|
||||
aria-label="Copy password"
|
||||
disabled={!password}
|
||||
>
|
||||
<i
|
||||
className={`fa-regular fa-copy ${copied ? "opacity-70" : ""}`}
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
{/* When everything is valid, show just a right tick */}
|
||||
<ul className="mt-1 ml-2">
|
||||
{conditions.map((c) => (
|
||||
<li
|
||||
key={c.key}
|
||||
className={`${c.ok ? "text-success" : "text-error"} text-[12px] leading-snug`}
|
||||
>
|
||||
{c.ok ? "✓" : "✗"} {t(c.text)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="pt-2 flex flex-row gap-x-2">
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-primary"
|
||||
disabled={!allValid || submitting}
|
||||
>
|
||||
{t("submit")}
|
||||
</button>
|
||||
<button type="button" className="op-btn" onClick={handleAutogen}>
|
||||
{t("autogenerate")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
onClick={() => {
|
||||
setPassword("");
|
||||
onClose?.();
|
||||
}}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalUi>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ function CustomModal(props) {
|
||||
className="op-btn op-btn-primary px-6 ml-[20px] mb-3 mt-1"
|
||||
onClick={() => handleExtendBtn()}
|
||||
>
|
||||
{t("extend")}
|
||||
Extend
|
||||
</button>
|
||||
)}
|
||||
{props.isDownloadBtn && (
|
||||
|
||||
@@ -32,8 +32,7 @@ export const RenderReportCell = ({
|
||||
startIndex,
|
||||
handleDownload,
|
||||
handleRemovePrefill,
|
||||
reportName,
|
||||
handleItemClick
|
||||
reportName
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const appName =
|
||||
@@ -60,11 +59,7 @@ export const RenderReportCell = ({
|
||||
);
|
||||
case "Reason":
|
||||
return (
|
||||
<td
|
||||
key={col}
|
||||
className="p-2 text-center cursor-pointer"
|
||||
onClick={() => handleItemClick(col, rowData?.DeclineReason)}
|
||||
>
|
||||
<td key={col} className="p-2 text-center">
|
||||
{rowData?.DeclineReason?.length > 25
|
||||
? rowData?.DeclineReason?.slice(0, 25) + "..."
|
||||
: rowData?.DeclineReason || "-"}
|
||||
@@ -73,12 +68,7 @@ export const RenderReportCell = ({
|
||||
case "Note":
|
||||
return (
|
||||
<td key={col} className="p-2 text-center">
|
||||
<p
|
||||
className="truncate w-[100px] cursor-pointer"
|
||||
onClick={() => handleItemClick(col, rowData?.Note)}
|
||||
>
|
||||
{rowData?.Note || "-"}
|
||||
</p>
|
||||
<p className="truncate w-[100px]">{rowData?.Note || "-"}</p>
|
||||
</td>
|
||||
);
|
||||
case "Folder":
|
||||
|
||||
@@ -14,7 +14,7 @@ const Tooltip = ({
|
||||
onClick={() =>
|
||||
handleOnlickHelp ? handleOnlickHelp() : openInNewTab(url)
|
||||
}
|
||||
className="text-center cursor-pointer focus:outline-none"
|
||||
className={"text-center cursor-pointer"}
|
||||
>
|
||||
<sup>
|
||||
<i
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Parse from "parse";
|
||||
import { Outlet } from "react-router";
|
||||
import SessionExpiredModal from "./SessionExpiredModal";
|
||||
|
||||
import { Outlet, useNavigate, useLocation } from "react-router";
|
||||
import ModalUi from "./ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const Validate = () => {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const [isUserValid, setIsUserValid] = useState(true);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -34,7 +37,30 @@ const Validate = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return isUserValid ? <Outlet /> : <SessionExpiredModal />;
|
||||
const handleLoginBtn = async () => {
|
||||
try {
|
||||
await Parse.User.logOut();
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
} finally {
|
||||
localStorage.removeItem("accesstoken");
|
||||
navigate("/", { replace: true, state: { from: location } });
|
||||
}
|
||||
};
|
||||
return isUserValid ? (
|
||||
<div>
|
||||
<Outlet />
|
||||
</div>
|
||||
) : (
|
||||
<ModalUi showHeader={false} isOpen={true} showClose={false}>
|
||||
<div className="flex flex-col justify-center items-center py-4 md:py-5 gap-5">
|
||||
<p className="text-xl font-medium">{t("session-expired")}</p>
|
||||
<button onClick={handleLoginBtn} className="op-btn op-btn-neutral">
|
||||
{t("login")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
);
|
||||
};
|
||||
|
||||
export default Validate;
|
||||
|
||||
+10
-13
@@ -1,29 +1,26 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocation, useNavigate } from "react-router";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { sessionStatus } from "../redux/reducers/userReducer";
|
||||
import Parse from "parse";
|
||||
import ModalUi from "./ModalUi";
|
||||
import { useNavigate } from "react-router";
|
||||
import Parse from "parse";
|
||||
|
||||
const SessionExpiredModal = () => {
|
||||
const ValidateSession = ({ children }) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleLoginBtn = async () => {
|
||||
try {
|
||||
await Parse?.User?.logOut();
|
||||
} catch (err) {
|
||||
console.log(`err: ${err}`);
|
||||
console.log("err ", err);
|
||||
} finally {
|
||||
localStorage.removeItem("accesstoken");
|
||||
dispatch(sessionStatus(true));
|
||||
navigate("/", { replace: true, state: { from: location } });
|
||||
navigate("/", { replace: true });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
return localStorage.getItem("accesstoken") ? (
|
||||
children
|
||||
) : (
|
||||
<ModalUi showHeader={false} isOpen={true} showClose={false}>
|
||||
<div className="flex flex-col justify-center items-center py-4 md:py-5 gap-5">
|
||||
<p className="text-xl font-medium">{t("session-expired")}</p>
|
||||
@@ -35,4 +32,4 @@ const SessionExpiredModal = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SessionExpiredModal;
|
||||
export default ValidateSession;
|
||||
@@ -1,15 +0,0 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
const initialState = { isValidSession: true };
|
||||
const userReducer = createSlice({
|
||||
name: "user",
|
||||
initialState: initialState,
|
||||
reducers: {
|
||||
sessionStatus: (state, action) => {
|
||||
state.isValidSession = action.payload;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const { sessionStatus } = userReducer.actions;
|
||||
|
||||
export default userReducer.reducer;
|
||||
@@ -4,7 +4,6 @@ import ShowTenant from "./reducers/ShowTenant";
|
||||
import TourStepsReducer from "./reducers/TourStepsReducer";
|
||||
import widgetReducer from "./reducers/widgetSlice";
|
||||
import sidebarReducer from "./reducers/sidebarReducer";
|
||||
import userReducer from "./reducers/userReducer";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
@@ -12,7 +11,6 @@ export const store = configureStore({
|
||||
TourSteps: TourStepsReducer,
|
||||
ShowTenant,
|
||||
widget: widgetReducer,
|
||||
sidebar: sidebarReducer,
|
||||
user: userReducer
|
||||
sidebar: sidebarReducer
|
||||
}
|
||||
});
|
||||
|
||||
@@ -97,7 +97,6 @@ const DocumentsReport = (props) => {
|
||||
const [defaultMail, setDefaultMail] = useState({ body: "", subject: "" });
|
||||
const [currUserId, setCurrUserId] = useState(false);
|
||||
const [documentDetails, setDocumentDetails] = useState();
|
||||
const [objInfoModal, setObjInfoModal] = useState({ title: "", info: "" });
|
||||
const startIndex = (currentPage - 1) * props.docPerPage;
|
||||
const { isMoreDocs, setIsNextRecord } = props;
|
||||
|
||||
@@ -107,15 +106,6 @@ const DocumentsReport = (props) => {
|
||||
}
|
||||
}, [props.isSearchResult]);
|
||||
|
||||
// Close dropdown when clicking outside or on button again
|
||||
useEffect(() => {
|
||||
const onDocClick = (e) => {
|
||||
if (!e.target.closest('[data-dropdown-root="1"]')) setIsOption({});
|
||||
};
|
||||
document.addEventListener("click", onDocClick);
|
||||
return () => document.removeEventListener("click", onDocClick);
|
||||
}, []);
|
||||
|
||||
const getPaginationRange = () => {
|
||||
const totalPageNumbers = 7; // Adjust this value to show more/less page numbers
|
||||
const pages = [];
|
||||
@@ -203,21 +193,29 @@ const DocumentsReport = (props) => {
|
||||
const filterSignTypes = signatureType?.filter(
|
||||
(x) => x.enabled === true
|
||||
);
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
const subject = tenantDetails?.RequestSubject ?? "";
|
||||
const body = tenantDetails?.RequestBody ?? "";
|
||||
//customize mail state is handle to when user want to customize already set tenant email format then use that format
|
||||
const userSubject =
|
||||
subject;
|
||||
const userBody =
|
||||
body;
|
||||
setCustomizeMail({
|
||||
subject: userSubject ?? defaultMailSubject,
|
||||
body: userBody ?? defaultMailBody
|
||||
});
|
||||
setDefaultMail({ subject: userSubject, body: userBody });
|
||||
//condition to check tenant have some already set any email template
|
||||
if (tenantDetails?.RequestBody) {
|
||||
//customize mail state is handle to when user want to customize already set tenant email format then use that format
|
||||
setCustomizeMail({
|
||||
subject: tenantDetails?.RequestSubject,
|
||||
body: tenantDetails?.RequestBody
|
||||
});
|
||||
setDefaultMail({
|
||||
subject: tenantDetails?.RequestSubject,
|
||||
body: tenantDetails?.RequestBody
|
||||
});
|
||||
} else {
|
||||
const defaultRequestBody = defaultMailBody;
|
||||
const defaultSubject = defaultMailSubject;
|
||||
setCustomizeMail({
|
||||
subject: defaultSubject,
|
||||
body: defaultRequestBody
|
||||
});
|
||||
setDefaultMail({
|
||||
subject: defaultSubject,
|
||||
body: defaultRequestBody
|
||||
});
|
||||
}
|
||||
return filterSignTypes;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -332,8 +330,6 @@ const DocumentsReport = (props) => {
|
||||
setIsModal({ [`extendexpiry_${item.objectId}`]: true });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Get current list
|
||||
const indexOfLastDoc = currentPage * props.docPerPage;
|
||||
const indexOfFirstDoc = indexOfLastDoc - props.docPerPage;
|
||||
@@ -796,7 +792,6 @@ const DocumentsReport = (props) => {
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setError("");
|
||||
setIsModal({});
|
||||
};
|
||||
const handleSaveAsTemplate = async (doc) => {
|
||||
@@ -1042,10 +1037,6 @@ const DocumentsReport = (props) => {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleItemClick = (title, info) => {
|
||||
setObjInfoModal({ title, info });
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{Object.keys(actLoader)?.length > 0 && (
|
||||
@@ -1170,7 +1161,6 @@ const DocumentsReport = (props) => {
|
||||
handleDownload={handleDownload}
|
||||
handleRemovePrefill={handleRemovePrefill}
|
||||
reportName={props.ReportName}
|
||||
handleItemClick={handleItemClick}
|
||||
/>
|
||||
))}
|
||||
{/* actions */}
|
||||
@@ -1181,7 +1171,6 @@ const DocumentsReport = (props) => {
|
||||
<React.Fragment key={index}>
|
||||
{handleBtnVisibility(act, item) && (
|
||||
<div
|
||||
data-dropdown-root="1"
|
||||
role="button"
|
||||
data-tut={act?.selector}
|
||||
onClick={() => handleActionBtn(act, item)}
|
||||
@@ -1850,13 +1839,6 @@ const DocumentsReport = (props) => {
|
||||
)}
|
||||
</div>
|
||||
</ModalUi>
|
||||
<ModalUi
|
||||
title={t(`report-heading.${objInfoModal.title}`)}
|
||||
isOpen={objInfoModal.title}
|
||||
handleClose={() => setObjInfoModal({ title: "", info: "" })}
|
||||
>
|
||||
<div className="p-[20px]">{objInfoModal.info || "-"}</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ import "../../styles/quill.css";
|
||||
import BulkSendUi from "../../components/BulkSendUi";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import { serverUrl_fn } from "../../constant/appinfo";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useElSize } from "../../hook/useElSize";
|
||||
import LottieWithLoader from "../../primitives/DotLottieReact";
|
||||
import PrefillWidgetModal from "../../components/pdf/PrefillWidgetsModal";
|
||||
@@ -67,7 +67,7 @@ const TemplatesReport = (props) => {
|
||||
const [userDetails, setUserDetails] = useState({});
|
||||
const [isNextStep, setIsNextStep] = useState({});
|
||||
const [isBulkSend, setIsBulkSend] = useState({});
|
||||
const [templateDetails, setTemplateDetails] = useState({});
|
||||
const [templateDeatils, setTemplateDetails] = useState({});
|
||||
const [placeholders, setPlaceholders] = useState([]);
|
||||
const [isLoader, setIsLoader] = useState({});
|
||||
const [isShareWith, setIsShareWith] = useState({});
|
||||
@@ -86,7 +86,6 @@ const TemplatesReport = (props) => {
|
||||
const [isNewContact, setIsNewContact] = useState({ status: false, id: "" });
|
||||
const [isPrefillModal, setIsPrefillModal] = useState({});
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [objInfoModal, setObjInfoModal] = useState({ title: "", info: "" });
|
||||
const startIndex = (currentPage - 1) * props.docPerPage;
|
||||
const { isMoreDocs, setIsNextRecord } = props;
|
||||
const [isMailModal, setIsMailModal] = useState(false);
|
||||
@@ -166,15 +165,6 @@ const TemplatesReport = (props) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Close dropdown when clicking outside or on button again
|
||||
useEffect(() => {
|
||||
const onDocClick = (e) => {
|
||||
if (!e.target.closest('[data-dropdown-root="1"]')) setIsOption({});
|
||||
};
|
||||
document.addEventListener("click", onDocClick);
|
||||
return () => document.removeEventListener("click", onDocClick);
|
||||
}, []);
|
||||
|
||||
// `fetchTeamList` is used to fetch team list for share with functionality
|
||||
const fetchTeamList = async () => {
|
||||
try {
|
||||
@@ -249,8 +239,9 @@ const TemplatesReport = (props) => {
|
||||
);
|
||||
if (checkIsSignatureExist) {
|
||||
setActLoader({ [`${item.objectId}_${act.btnId}`]: true });
|
||||
const template = await fetchTemplate(item.objectId);
|
||||
const templateData = template.data && template.data.result;
|
||||
const templateDeatils = await fetchTemplate(item.objectId);
|
||||
const templateData =
|
||||
templateDeatils.data && templateDeatils.data.result;
|
||||
if (!templateData.error) {
|
||||
setXyPosition(templateData?.Placeholders);
|
||||
const signer = utils.handleSignersList(templateData);
|
||||
@@ -293,7 +284,7 @@ const TemplatesReport = (props) => {
|
||||
}
|
||||
}
|
||||
);
|
||||
if (axiosRes?.data) {
|
||||
if (axiosRes) {
|
||||
return axiosRes;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -306,10 +297,10 @@ const TemplatesReport = (props) => {
|
||||
const navigatePageToDoc = async (templateRes, placeholder, signer) => {
|
||||
setIsPrefillModal({});
|
||||
const res = await createDocument(
|
||||
[templateRes || templateDetails],
|
||||
[templateRes || templateDeatils],
|
||||
placeholder || xyPosition,
|
||||
signer || signerList,
|
||||
templateRes?.URL || templateDetails?.URL
|
||||
templateRes?.URL || templateDeatils?.URL
|
||||
);
|
||||
if (res.status === "success") {
|
||||
navigate(`/placeHolderSign/${res.id}`, {
|
||||
@@ -888,8 +879,8 @@ const TemplatesReport = (props) => {
|
||||
signerList,
|
||||
setIsPrefillModal,
|
||||
scale,
|
||||
templateDetails?.URL,
|
||||
[templateDetails],
|
||||
templateDeatils?.URL,
|
||||
[templateDeatils],
|
||||
prefillImg,
|
||||
extClass?.[0]?.UserId?.objectId,
|
||||
);
|
||||
@@ -920,21 +911,29 @@ const TemplatesReport = (props) => {
|
||||
if (tenantDetails && tenantDetails === "user does not exist!") {
|
||||
alert(t("user-not-exist"));
|
||||
} else if (tenantDetails) {
|
||||
const extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
const subject = tenantDetails?.RequestSubject ?? "";
|
||||
const body = tenantDetails?.RequestBody ?? "";
|
||||
//customize mail state is handle to when user want to customize already set tenant email format then use that format
|
||||
const userSubject =
|
||||
subject;
|
||||
const userBody =
|
||||
body;
|
||||
setCustomizeMail({
|
||||
subject: userSubject ?? defaultMailSubject,
|
||||
body: userBody ?? defaultMailBody
|
||||
});
|
||||
setDefaultMail({ subject: userSubject, body: userBody });
|
||||
//condition to check tenant have some already set any email template
|
||||
if (tenantDetails?.RequestBody) {
|
||||
//customize mail state is handle to when user want to customize already set tenant email format then use that format
|
||||
setCustomizeMail({
|
||||
subject: tenantDetails?.RequestSubject,
|
||||
body: tenantDetails?.RequestBody
|
||||
});
|
||||
setDefaultMail({
|
||||
subject: tenantDetails?.RequestSubject,
|
||||
body: tenantDetails?.RequestBody
|
||||
});
|
||||
} else {
|
||||
const defaultRequestBody = defaultMailBody;
|
||||
const defaultSubject = defaultMailSubject;
|
||||
setCustomizeMail({
|
||||
subject: defaultSubject,
|
||||
body: defaultRequestBody
|
||||
});
|
||||
setDefaultMail({
|
||||
subject: defaultSubject,
|
||||
body: defaultRequestBody
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
alert(t("user-not-exist"));
|
||||
@@ -1004,10 +1003,6 @@ const TemplatesReport = (props) => {
|
||||
navigate(`/recipientSignPdf/${docId}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleItemClick = (title, info) => {
|
||||
setObjInfoModal({ title, info });
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
{Object.keys(actLoader)?.length > 0 && (
|
||||
@@ -1144,7 +1139,6 @@ const TemplatesReport = (props) => {
|
||||
handleDownload={handleDownload}
|
||||
handleRemovePrefill={handleRemovePrefill}
|
||||
reportName={props.ReportName}
|
||||
handleItemClick={handleItemClick}
|
||||
/>
|
||||
))}
|
||||
<td className="px-2 py-2">
|
||||
@@ -1159,7 +1153,6 @@ const TemplatesReport = (props) => {
|
||||
role="button"
|
||||
data-tut={act?.selector}
|
||||
key={index}
|
||||
data-dropdown-root="1"
|
||||
onClick={() => handleActionBtn(act, item)}
|
||||
title={t(`btnLabel.${act.hoverLabel}`)}
|
||||
className={
|
||||
@@ -1339,7 +1332,7 @@ const TemplatesReport = (props) => {
|
||||
) : (
|
||||
<BulkSendUi
|
||||
Placeholders={placeholders}
|
||||
item={templateDetails}
|
||||
item={templateDeatils}
|
||||
handleClose={handleQuickSendClose}
|
||||
signatureType={signatureType}
|
||||
/>
|
||||
@@ -1680,13 +1673,6 @@ const TemplatesReport = (props) => {
|
||||
)}
|
||||
</div>
|
||||
</ModalUi>
|
||||
<ModalUi
|
||||
title={t(`report-heading.${objInfoModal.title}`)}
|
||||
isOpen={objInfoModal.title}
|
||||
handleClose={() => setObjInfoModal({ title: "", info: "" })}
|
||||
>
|
||||
<div className="p-[20px]">{objInfoModal.info || "-"}</div>
|
||||
</ModalUi>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { generateTitleFromFilename } from "../constant/Utils";
|
||||
import { generateTitleFromFilename, getBase64FromUrl } from "../constant/Utils";
|
||||
import { base64StringtoFile, uploadFile } from "./fileUtils";
|
||||
import { sanitizeFileName } from "./sanitizeFileName";
|
||||
import Parse from "parse";
|
||||
@@ -17,20 +17,21 @@ export const saveToMySign = async (widget) => {
|
||||
const file = base64StringtoFile(base64, fileName);
|
||||
const fileUrl = await uploadFile(file, User?.id);
|
||||
// below code is used to save or update default signature, initials, stamp
|
||||
const params = {
|
||||
id: widget?.defaultSignId, // pass id to update existing signature/initials
|
||||
userId: User.id
|
||||
};
|
||||
if (widget?.type === "initials") {
|
||||
params.initials = fileUrl; // save initials image url
|
||||
} else if (widget?.type === "signature") {
|
||||
params.signature = fileUrl; // save signature image url
|
||||
const signCls = new Parse.Object("contracts_Signature");
|
||||
if (widget?.defaultSignId) {
|
||||
signCls.id = widget.defaultSignId;
|
||||
}
|
||||
const signRes = await Parse.Cloud.run("savesignature", params);
|
||||
if (widget?.type === "initials") {
|
||||
signCls.set("Initials", fileUrl);
|
||||
} else if (widget?.type === "signature") {
|
||||
signCls.set("ImageURL", fileUrl);
|
||||
}
|
||||
signCls.set("UserId", Parse.User.createWithoutData(User.id));
|
||||
const signRes = await signCls.save();
|
||||
return { base64File: base64, id: signRes?.id };
|
||||
} catch (err) {
|
||||
console.log("Err while saving signature", err);
|
||||
return err;
|
||||
return url;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -40,40 +41,3 @@ export const hasSignatureWidget = (s) =>
|
||||
(s.placeHolder ?? []).some((ph) =>
|
||||
(ph?.pos ?? []).some((p) => p?.type === "signature")
|
||||
);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Array<object>} pages
|
||||
* Array of page objects. Each page is expected to have:
|
||||
* - pos: Array<Widget> where Widget has shape:
|
||||
* {
|
||||
* key: string,
|
||||
* type: string,
|
||||
* options?: {
|
||||
* response?: number|string,
|
||||
* defaultValue?: number|string,
|
||||
* formula?: string
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @returns {Array<object>}
|
||||
*/
|
||||
export const applyNumberFormulasToPages = (pages = []) => {
|
||||
// Guard: if not an array or empty, just return as-is
|
||||
if (!Array.isArray(pages) || pages.length === 0) {
|
||||
return pages;
|
||||
}
|
||||
|
||||
// Clone pages and each widget.options so we do not mutate caller's data.
|
||||
// (Shallow clone is sufficient for our current usage since we only touch `options`.)
|
||||
const clonedPages = pages.map((page) => ({
|
||||
...page,
|
||||
pos: (page?.pos || []).map((widget) => ({
|
||||
...widget,
|
||||
options: { ...widget.options }
|
||||
}))
|
||||
}));
|
||||
|
||||
// Return the cloned, updated pages
|
||||
return clonedPages;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:22.14.0
|
||||
|
||||
|
||||
# Install LibreOffice for DOCX to PDF conversions
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y libreoffice \
|
||||
|
||||
@@ -3,7 +3,6 @@ import { format, toZonedTime } from 'date-fns-tz';
|
||||
import getPresignedUrl, { getSignedLocalUrl } from './cloud/parsefunction/getSignedUrl.js';
|
||||
import crypto from 'node:crypto';
|
||||
import { PDFDocument, rgb } from 'pdf-lib';
|
||||
import { parseUploadFile } from './utils/fileUtils.js';
|
||||
|
||||
dotenv.config({ quiet: true });
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ export const app = express();
|
||||
|
||||
dotenv.config({ quiet: true });
|
||||
app.use(cors());
|
||||
app.use(express.json({ limit: '100mb' }));
|
||||
app.use(express.urlencoded({ limit: '100mb', extended: true }));
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
|
||||
app.post('/file_upload', uploadFile);
|
||||
app.post('/docxtopdf', docxUpload.single('file'), docxtopdf);
|
||||
|
||||
@@ -32,7 +32,7 @@ const resetPasswordAndDeleteSession = async userId => {
|
||||
await Parse.Object.destroyAll(sessions, { useMasterKey: true });
|
||||
}
|
||||
};
|
||||
export async function deleteUser(userId, adminId, adminTenantId, isOrgAdmin, orgPtr) {
|
||||
export async function deleteUser(userId, adminId) {
|
||||
const userPointer = { __type: 'Pointer', className: '_User', objectId: userId };
|
||||
let userDetails = {
|
||||
UserRole: 'not found',
|
||||
@@ -47,18 +47,13 @@ export async function deleteUser(userId, adminId, adminTenantId, isOrgAdmin, org
|
||||
const Users = Parse.Object.extend('contracts_Users');
|
||||
const userQuery = new Parse.Query(Users);
|
||||
userQuery.equalTo('UserId', userPointer);
|
||||
if (adminTenantId) {
|
||||
userQuery.equalTo('TenantId', adminTenantId);
|
||||
if (isOrgAdmin && orgPtr) {
|
||||
userQuery.equalTo('OrganizationId', orgPtr);
|
||||
}
|
||||
} else if (adminId) {
|
||||
if (adminId) {
|
||||
userQuery.equalTo('CreatedBy', { __type: 'Pointer', className: '_User', objectId: adminId });
|
||||
}
|
||||
const userResult = await userQuery.first({ useMasterKey: true });
|
||||
userDetails = { ...userDetails, UserId: userId };
|
||||
if (!userResult) {
|
||||
const errorMessage = isOrgAdmin ? 'Unauthorized.' : 'User not found.';
|
||||
const errorMessage = 'User not found.';
|
||||
return { code: 400, message: errorMessage };
|
||||
}
|
||||
const contractsUserId = userResult.id;
|
||||
@@ -66,10 +61,6 @@ export async function deleteUser(userId, adminId, adminTenantId, isOrgAdmin, org
|
||||
const teamIds = userResult.get('TeamIds') || [];
|
||||
const organizationId = userResult.get('OrganizationId')?.id;
|
||||
const isAdmin = userResult?.get('UserRole') === 'contracts_Admin' ? true : false;
|
||||
if (isOrgAdmin && isAdmin) {
|
||||
const errorMessage = 'Unauthorized.';
|
||||
return { code: 400, message: errorMessage };
|
||||
}
|
||||
userDetails = {
|
||||
...userDetails,
|
||||
UserRole: userResult?.get('UserRole'),
|
||||
@@ -276,21 +267,6 @@ export const deleteUserPost = async (req, res) => {
|
||||
return res.send(errorMessage);
|
||||
}
|
||||
|
||||
if (extUser?.get('UserRole') !== 'contracts_Admin') {
|
||||
const errorMessage =
|
||||
'This action is not permitted. Kindly contact your administrator to request account deletion.';
|
||||
return res.send(errorMessage);
|
||||
}
|
||||
|
||||
const extUsers = new Parse.Query('contracts_Users');
|
||||
extUsers.equalTo('TenantId', extUser?.get('TenantId'));
|
||||
extUsers.notEqualTo('UserRole', 'contracts_Admin');
|
||||
const isTeamUsers = await extUsers.first({ useMasterKey: true });
|
||||
if (isTeamUsers) {
|
||||
const errorMessage = `To delete this account, start by removing all team users associated with it. Once all users are removed, you'll be able to permanently delete the account.`;
|
||||
return res.send(errorMessage);
|
||||
}
|
||||
|
||||
// Get stored OTP info
|
||||
const savedOtp = extUser.get('DeleteOTP') || '';
|
||||
const expiry = extUser.get('DeleteOTPExpiry');
|
||||
@@ -376,30 +352,7 @@ export const deleteUserByAdmin = async (req, res) => {
|
||||
const errorMessage = 'User not found.';
|
||||
return res.status(400).json({ message: errorMessage });
|
||||
}
|
||||
|
||||
if (adminId === userId) {
|
||||
return res.status(400).json({ message: 'You cannot delete your own account.' });
|
||||
}
|
||||
// 2. ext user details
|
||||
const extUserQuery = new Parse.Query('contracts_Users');
|
||||
extUserQuery.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: adminId });
|
||||
const extUser = await extUserQuery.first({ useMasterKey: true });
|
||||
if (!extUser) {
|
||||
const errorMessage = 'User not found.';
|
||||
return res.status(400).json({ message: errorMessage });
|
||||
}
|
||||
const isAdmin =
|
||||
extUser?.get('UserRole') === 'contracts_Admin' ||
|
||||
extUser?.get('UserRole') === 'contracts_OrgAdmin'
|
||||
? true
|
||||
: false;
|
||||
const isOrgAdmin = extUser?.get('UserRole') === 'contracts_OrgAdmin';
|
||||
const tenantId = extUser?.get('TenantId');
|
||||
const orgPtr = isOrgAdmin && extUser?.get('OrganizationId');
|
||||
if (!isAdmin) {
|
||||
return res.status(400).json({ message: 'Unauthorized.' });
|
||||
}
|
||||
const response = await deleteUser(userId, adminId, tenantId, isOrgAdmin, orgPtr);
|
||||
const response = await deleteUser(userId, adminId);
|
||||
const code = response?.code || 400;
|
||||
const message = response?.message || 'An error occurred while deleting your account.';
|
||||
return res.status(code).json({ message: message });
|
||||
|
||||
@@ -11,7 +11,6 @@ export const deleteUserOtp = async (req, res) => {
|
||||
|
||||
const extUserQuery = new Parse.Query('contracts_Users');
|
||||
extUserQuery.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
|
||||
extUserQuery.include('TenantId');
|
||||
const extUser = await extUserQuery.first({ useMasterKey: true });
|
||||
if (!extUser) return res.status(404).json({ error: 'User not found' });
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ export function generateOtp(len = OTP_LENGTH) {
|
||||
}
|
||||
|
||||
export async function sendDeleteOtpEmail(extUser, otp) {
|
||||
const _extUser = extUser && JSON.parse(JSON.stringify(extUser));
|
||||
const params = {
|
||||
extUserId: extUser.id,
|
||||
from: appName,
|
||||
|
||||
@@ -1,149 +1,169 @@
|
||||
import fs from 'node:fs';
|
||||
import axios from 'axios';
|
||||
import multer from 'multer';
|
||||
import libre from 'libreoffice-convert';
|
||||
import util from 'node:util';
|
||||
import { cloudServerUrl, getSecureUrl, serverAppId } from '../../Utils.js';
|
||||
|
||||
/** @returns {Promise<Buffer>} */
|
||||
async function convertLibre(input, ext, opts) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
try {
|
||||
libre.convert(input, ext, opts, (err, out) => (err ? reject(err) : resolve(out)));
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
// -------------------- Concurrency limiter (no dependency) --------------------
|
||||
const MAX_CONCURRENCY = Number(process.env.DOCX2PDF_CONCURRENCY || 2); // 1 for tiny droplets
|
||||
let active = 0;
|
||||
const queue = [];
|
||||
function runWithLimit(task) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const run = () => {
|
||||
active++;
|
||||
Promise.resolve()
|
||||
.then(task)
|
||||
.then(resolve, reject)
|
||||
.finally(() => {
|
||||
active--;
|
||||
if (queue.length) queue.shift()();
|
||||
});
|
||||
};
|
||||
if (active < MAX_CONCURRENCY) run();
|
||||
else queue.push(run);
|
||||
});
|
||||
}
|
||||
libre.convertAsync = util.promisify(libre.convert);
|
||||
|
||||
// -------------------- Timeout helper (async version = no TS hint) --------------------
|
||||
/**
|
||||
* @template T
|
||||
* @param {Promise<T>} promise
|
||||
* @param {number} ms
|
||||
* @param {string} [label='operation']
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
export async function withTimeout(promise, ms, label = 'operation') {
|
||||
let timer;
|
||||
try {
|
||||
const timeout = new Promise((_, reject) => {
|
||||
timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
||||
});
|
||||
return await Promise.race([promise, timeout]);
|
||||
} finally {
|
||||
if (timer) clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Multer: use memory storage --------------------
|
||||
const storage = multer.memoryStorage();
|
||||
export const upload = multer({
|
||||
storage,
|
||||
fileFilter: (req, file, cb) => {
|
||||
const okExt = /\.docx$/i.test(file.originalname || '');
|
||||
const okMime =
|
||||
file.mimetype === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
||||
file.mimetype === 'application/octet-stream';
|
||||
if (okExt && okMime) return cb(null, true);
|
||||
cb(new Error('Only .docx files are supported'));
|
||||
const storage = multer.diskStorage({
|
||||
destination(req, file, cb) {
|
||||
cb(null, 'exports');
|
||||
},
|
||||
filename(req, file, cb) {
|
||||
cb(null, file.originalname);
|
||||
},
|
||||
});
|
||||
|
||||
export const upload = multer({ storage });
|
||||
|
||||
function generatePdfName(length) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export default async function docxtopdf(req, res) {
|
||||
const serverUrl = cloudServerUrl;
|
||||
const parseAppKey = { 'X-Parse-Application-Id': serverAppId };
|
||||
const appId = serverAppId;
|
||||
const masterKey = process.env.MASTER_KEY;
|
||||
|
||||
const masterHeader = { ...parseAppKey, 'X-Parse-Master-Key': masterKey };
|
||||
const sessionHeader = { ...parseAppKey, 'X-Parse-Session-Token': req.headers['sessiontoken'] };
|
||||
|
||||
if (!req.file || !req.file.buffer) {
|
||||
return res.status(400).json({ error: 'No file uploaded.' });
|
||||
}
|
||||
const inputPath = req.file.path;
|
||||
const name = generatePdfName(16);
|
||||
const fileName = `${name}.pdf`;
|
||||
const outputPath = './exports/output.pdf';
|
||||
|
||||
try {
|
||||
// ---- Auth: current user ----
|
||||
const userRes = await axios.get(`${serverUrl}/users/me`, { headers: sessionHeader });
|
||||
|
||||
// ---- contracts_Users ----
|
||||
const whereUser = JSON.stringify({
|
||||
UserId: { __type: 'Pointer', className: '_User', objectId: userRes.data.objectId },
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': req.headers['sessiontoken'],
|
||||
},
|
||||
});
|
||||
const userId = JSON.stringify({
|
||||
UserId: {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userRes.data.objectId,
|
||||
},
|
||||
});
|
||||
const resUser = await axios.get(
|
||||
`${serverUrl}/classes/contracts_Users?where=${whereUser}&limit=1&include=TenantId`,
|
||||
{ headers: masterHeader }
|
||||
serverUrl + `/classes/contracts_Users?where=${userId}&limit=1&include=TenantId`,
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!resUser?.data?.results?.length) {
|
||||
return res.status(403).json({ error: 'User not linked to tenant.' });
|
||||
}
|
||||
|
||||
const tenantId = resUser.data.results[0]?.TenantId?.objectId;
|
||||
if (!tenantId) {
|
||||
return res.status(403).json({ error: 'Tenant not found for user.' });
|
||||
}
|
||||
|
||||
// ---- DOCX -> PDF (buffer -> buffer), with concurrency + timeout ----
|
||||
const fileName = `${generatePdfName(16)}.pdf`;
|
||||
|
||||
const pdfBuffer = await runWithLimit(() =>
|
||||
withTimeout(convertLibre(req.file.buffer, '.pdf', undefined), 60_000, 'DOCX->PDF')
|
||||
);
|
||||
|
||||
// ---- Upload PDF (no disk IO) ----
|
||||
const activeFileAdapter = resUser.data.results[0]?.TenantId?.ActiveFileAdapter;
|
||||
let fileUrl;
|
||||
if (activeFileAdapter) {
|
||||
const params = {
|
||||
fileBase64: Buffer.from(pdfBuffer).toString('base64'),
|
||||
fileName,
|
||||
id: activeFileAdapter,
|
||||
};
|
||||
const url = serverUrl + '/functions/savetofileadapter';
|
||||
const headers = { 'Content-Type': 'application/json', ...sessionHeader };
|
||||
const savetos3 = await axios.post(url, params, { headers });
|
||||
fileUrl = savetos3?.data?.result?.url;
|
||||
if (!fileUrl) throw new Error('No URL returned from file adapter');
|
||||
} else {
|
||||
const parsefile = await axios.post(`${serverUrl}/files/${fileName}`, pdfBuffer, {
|
||||
headers: { ...masterHeader, 'Content-Type': 'application/pdf' },
|
||||
if (resUser?.data?.results?.length > 0) {
|
||||
const tenantId = resUser.data.results[0].TenantId?.objectId;
|
||||
const ext = '.pdf';
|
||||
const outPath = `./exports/output${ext}`;
|
||||
const docxBuf = fs.readFileSync(inputPath);
|
||||
const pdfBuffer = await libre.convertAsync(docxBuf, ext, undefined);
|
||||
fs.writeFileSync(outPath, pdfBuffer);
|
||||
const file = fs.readFileSync(outPath);
|
||||
const size = fs.statSync(outPath).size;
|
||||
const PartnersTenant = JSON.stringify({
|
||||
PartnersTenant: {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: tenantId,
|
||||
},
|
||||
});
|
||||
const fileRes = getSecureUrl(parsefile.data.url);
|
||||
fileUrl = fileRes.url;
|
||||
const resTenantCredit = await axios.get(
|
||||
serverUrl + `/classes/partners_TenantCredits?where=${PartnersTenant}&limit=1`,
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
if (resTenantCredit.data?.results?.length > 0) {
|
||||
const tenantCreditsId = resTenantCredit.data.results[0].objectId;
|
||||
const activeFileAdapter = resUser.data.results[0].TenantId?.ActiveFileAdapter;
|
||||
let fileUrl;
|
||||
if (activeFileAdapter) {
|
||||
const params = {
|
||||
fileBase64: file.toString('base64'),
|
||||
fileName,
|
||||
id: activeFileAdapter,
|
||||
};
|
||||
const url = serverUrl + '/functions/savetofileadapter';
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': req.headers['sessiontoken'],
|
||||
};
|
||||
try {
|
||||
const savetos3 = await axios.post(url, params, { headers });
|
||||
fileUrl = savetos3?.data?.result?.url;
|
||||
} catch (err) {
|
||||
console.log('err in save to customfile', err);
|
||||
}
|
||||
} else {
|
||||
const parsefile = await axios.post(serverUrl + `/files/${fileName}`, file, {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
'Content-Type': 'application/pdf',
|
||||
},
|
||||
});
|
||||
const fileRes = getSecureUrl(parsefile.data.url);
|
||||
fileUrl = fileRes.url;
|
||||
}
|
||||
const usedStorage = resTenantCredit.data.results[0].usedStorage
|
||||
? resTenantCredit.data.results[0].usedStorage + size
|
||||
: size;
|
||||
await axios.put(
|
||||
serverUrl + `/classes/partners_TenantCredits/${tenantCreditsId}`,
|
||||
{ usedStorage },
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
await axios.post(
|
||||
serverUrl + '/classes/partners_DataFiles',
|
||||
{
|
||||
FileSize: size,
|
||||
FileUrl: fileUrl,
|
||||
TenantPtr: {
|
||||
__type: 'Pointer',
|
||||
className: 'partners_Tenant',
|
||||
objectId: tenantId,
|
||||
},
|
||||
UserId: {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userRes.data.objectId,
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Master-Key': masterKey,
|
||||
},
|
||||
}
|
||||
);
|
||||
[inputPath, outPath].forEach(p => fs.existsSync(p) && fs.unlinkSync(p));
|
||||
return res.status(200).json({ message: 'success.', url: fileUrl });
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(200).json({ message: 'success.', url: fileUrl });
|
||||
} catch (err) {
|
||||
[inputPath, outputPath].forEach(p => fs.existsSync(p) && fs.unlinkSync(p));
|
||||
const msg =
|
||||
err?.response?.data?.error || err?.response?.data || err?.message || 'Something went wrong.';
|
||||
console.log(`Error in docxtopdf: ${msg}`);
|
||||
// Friendly message to the client
|
||||
console.log(`Error converting file: ${msg}`);
|
||||
|
||||
const message =
|
||||
'We are currently experiencing some issues with processing DOCX files. Please upload the PDF version or contact us on support@opensignlabs.com';
|
||||
return res.status(400).json({ error: message });
|
||||
|
||||
@@ -56,11 +56,6 @@ import loginUser from './parsefunction/loginUser.js';
|
||||
import addUser from './parsefunction/addUser.js';
|
||||
import filterDocs from './parsefunction/filterDocs.js';
|
||||
import sendDeleteUserMail from './parsefunction/sendDeleteUserMail.js';
|
||||
import resetPassword from './parsefunction/resetPassword.js';
|
||||
import saveSignature from './parsefunction/saveSignature.js';
|
||||
import manageSign from './parsefunction/manageSign.js';
|
||||
import getSignature from './parsefunction/getSignature.js';
|
||||
import updateEmailTemplates from './parsefunction/updateEmailTemplates.js';
|
||||
|
||||
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
|
||||
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
|
||||
@@ -127,8 +122,3 @@ Parse.Cloud.define('loginuser', loginUser);
|
||||
Parse.Cloud.define('adduser', addUser);
|
||||
Parse.Cloud.define('filterdocs', filterDocs);
|
||||
Parse.Cloud.define('senddeleterequest', sendDeleteUserMail);
|
||||
Parse.Cloud.define('resetpassword', resetPassword);
|
||||
Parse.Cloud.define('savesignature', saveSignature);
|
||||
Parse.Cloud.define('managesign', manageSign);
|
||||
Parse.Cloud.define('getdefaultsignature', getSignature);
|
||||
Parse.Cloud.define('updateemailtemplates', updateEmailTemplates);
|
||||
|
||||
@@ -49,8 +49,8 @@ async function sendMail(document, publicUrl) {
|
||||
let signPdf = `${hostUrl}/login/${encodeBase64}`;
|
||||
const orgName = document.ExtUserPtr.Company ? document.ExtUserPtr.Company : '';
|
||||
const senderObj = document?.ExtUserPtr;
|
||||
let mailBody = senderObj?.TenantId?.RequestBody || '';
|
||||
let mailSubject = senderObj?.TenantId?.RequestSubject || '';
|
||||
const mailBody = document?.ExtUserPtr?.TenantId?.RequestBody || '';
|
||||
const mailSubject = document?.ExtUserPtr?.TenantId?.RequestSubject || '';
|
||||
let replaceVar;
|
||||
if (mailBody && mailSubject) {
|
||||
const replacedRequestBody = mailBody.replace(/"/g, "'");
|
||||
@@ -126,8 +126,8 @@ async function batchQuery(userId, Documents, Ip, parseConfig, type, publicUrl) {
|
||||
}
|
||||
});
|
||||
}
|
||||
let mailBody = x?.ExtUserPtr?.TenantId?.RequestBody || '';
|
||||
let mailSubject = x?.ExtUserPtr?.TenantId?.RequestSubject || '';
|
||||
const mailBody = x?.ExtUserPtr?.TenantId?.RequestBody || '';
|
||||
const mailSubject = x?.ExtUserPtr?.TenantId?.RequestSubject || '';
|
||||
return {
|
||||
method: 'POST',
|
||||
path: '/app/classes/contracts_Document',
|
||||
|
||||
@@ -1,78 +1,26 @@
|
||||
import axios from 'axios';
|
||||
import { appName, cloudServerUrl, serverAppId } from '../../Utils.js';
|
||||
const serverUrl = cloudServerUrl;
|
||||
const APPID = serverAppId;
|
||||
const masterKEY = process.env.MASTER_KEY;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
};
|
||||
|
||||
async function sendDeclineMail(doc, publicUrl, userId, reason) {
|
||||
try {
|
||||
const TenantAppName = appName;
|
||||
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 removePrefill =
|
||||
doc?.Placeholders?.length > 0 && doc?.Placeholders?.filter(x => x?.Role !== 'prefill');
|
||||
const signUser =
|
||||
removePrefill?.length > 0 &&
|
||||
removePrefill?.find(x => x?.signerPtr?.UserId?.objectId === userId);
|
||||
|
||||
const sender = doc.ExtUserPtr;
|
||||
const pdfName = doc.Name;
|
||||
const creatorName = doc.ExtUserPtr.Name;
|
||||
const creatorEmail = doc.ExtUserPtr.Email;
|
||||
const signerName = signUser?.signerPtr?.Name || '';
|
||||
const signerEmail = signUser?.signerPtr?.Email || signUser?.email || '';
|
||||
const viewDocUrl = `${publicUrl}/recipientSignPdf/${doc.objectId}`;
|
||||
const subject = `Document "${pdfName}" has been declined by ${signerName}`;
|
||||
const body =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
|
||||
`<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 declined by ${signerName}</p>` +
|
||||
`</div><div style='padding:20px;font-family:system-ui;font-size:14px'><p>Dear ${creatorName},</p>` +
|
||||
`<p>${pdfName} has been declined by ${signerName} "${signerEmail}" on ${new Date().toLocaleDateString()}.</p>` +
|
||||
`<p>Decline Reason: ${reason || 'Not specified'}</p>` +
|
||||
`<p><a href=${viewDocUrl} target=_blank>View Document</a></p></div></div><div><p>This is an automated email from ${TenantAppName}. 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 ${TenantAppName}${opurl}.</p></div></div></body></html>`;
|
||||
|
||||
const params = {
|
||||
extUserId: sender.objectId,
|
||||
from: TenantAppName,
|
||||
recipient: creatorEmail,
|
||||
subject: subject,
|
||||
pdfName: pdfName,
|
||||
html: body,
|
||||
};
|
||||
await axios.post(serverUrl + '/functions/sendmailv3', params, { headers });
|
||||
} catch (err) {
|
||||
console.log('err in sendnotifymail', err);
|
||||
}
|
||||
}
|
||||
export default async function declinedocument(request) {
|
||||
const docId = request.params.docId;
|
||||
const reason = request.params?.reason || '';
|
||||
const userId = request.params.userId;
|
||||
const declineBy = { __type: 'Pointer', className: '_User', objectId: userId };
|
||||
const publicUrl = request.headers.public_url;
|
||||
const declineBy = {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: request.params?.userId,
|
||||
};
|
||||
|
||||
if (!docId) {
|
||||
throw new Parse.Error(Parse.Error.SCRIPT_FAILED, 'missing parameter docId.');
|
||||
}
|
||||
try {
|
||||
const docCls = new Parse.Query('contracts_Document');
|
||||
docCls.include('ExtUserPtr.TenantId,Placeholders.signerPtr,Signers');
|
||||
docCls.include('ExtUserPtr.TenantId');
|
||||
const updateDoc = await docCls.get(docId, { useMasterKey: true });
|
||||
if (updateDoc) {
|
||||
const _doc = JSON.parse(JSON.stringify(updateDoc));
|
||||
const isEnableOTP = updateDoc?.get('IsEnableOTP') || false;
|
||||
if (!isEnableOTP) {
|
||||
updateDoc.set('IsDeclined', true);
|
||||
updateDoc.set('DeclineReason', reason);
|
||||
updateDoc.set('DeclineBy', declineBy);
|
||||
await updateDoc.save(null, { useMasterKey: true });
|
||||
sendDeclineMail(_doc, publicUrl, userId, reason);
|
||||
return 'document declined';
|
||||
} else {
|
||||
if (!request?.user) {
|
||||
@@ -82,7 +30,6 @@ export default async function declinedocument(request) {
|
||||
updateDoc.set('DeclineReason', reason);
|
||||
updateDoc.set('DeclineBy', declineBy);
|
||||
await updateDoc.save(null, { useMasterKey: true });
|
||||
sendDeclineMail(_doc, publicUrl, userId, reason);
|
||||
return 'document declined';
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -7,7 +7,6 @@ import fs from 'node:fs';
|
||||
import dotenv from 'dotenv';
|
||||
import GenerateCertificate from './pdf/GenerateCertificate.js';
|
||||
import { getSecureUrl } from '../../Utils.js';
|
||||
import { parseUploadFile } from '../../utils/fileUtils.js';
|
||||
dotenv.config({ quiet: true });
|
||||
const eSignName = 'OpenSign';
|
||||
const eSigncontact = 'hello@opensignlabs.com';
|
||||
@@ -17,14 +16,10 @@ async function uploadFile(pdfName, filepath) {
|
||||
try {
|
||||
const filedata = fs.readFileSync(filepath);
|
||||
let fileUrl;
|
||||
|
||||
// const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
|
||||
// await file.save({ useMasterKey: true });
|
||||
// const fileRes = getSecureUrl(file.url());
|
||||
// fileUrl = fileRes.url;
|
||||
|
||||
const fileRes = await parseUploadFile(pdfName, filedata, 'application/pdf');
|
||||
fileUrl = getSecureUrl(fileRes?.url)?.url;
|
||||
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
const fileRes = getSecureUrl(file.url());
|
||||
fileUrl = fileRes.url;
|
||||
return { imageUrl: fileUrl };
|
||||
} catch (err) {
|
||||
console.log('Err ', err);
|
||||
@@ -57,9 +52,7 @@ export default async function generateCertificatebydocId(req) {
|
||||
const certificatePath = `./exports/certificate_${docId}.pdf`;
|
||||
try {
|
||||
const getDocument = new Parse.Query('contracts_Document');
|
||||
getDocument.include(
|
||||
'ExtUserPtr,Signers,AuditTrail.UserPtr,Placeholders,ExtUserPtr.TenantId,ExtUserPtr.UserId'
|
||||
);
|
||||
getDocument.include('ExtUserPtr,Signers,AuditTrail.UserPtr,Placeholders,ExtUserPtr.TenantId');
|
||||
const docRes = await getDocument.get(docId, { useMasterKey: true });
|
||||
|
||||
if (docRes && docRes?.get('IsCompleted') && !docRes?.get('CertificateUrl')) {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
export default async function getSignature(request) {
|
||||
const { userId } = request.params;
|
||||
if (!userId) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Missing userId parameter.');
|
||||
}
|
||||
if (userId !== request.user?.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot save signature for the current user.');
|
||||
}
|
||||
try {
|
||||
const query = new Parse.Query('contracts_Signature');
|
||||
query.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
|
||||
const result = await query.first({ useMasterKey: true });
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error('Error fetching signature:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -20,23 +20,22 @@ async function getTenantByUserId(userId, contactId) {
|
||||
}
|
||||
} else {
|
||||
const query = new Parse.Query('contracts_Users');
|
||||
query.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
|
||||
query.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userId,
|
||||
});
|
||||
const extuser = await query.first({ useMasterKey: true });
|
||||
if (extuser) {
|
||||
const tenantId = extuser?.get('TenantId')?.id || '';
|
||||
const user = extuser?.get('CreatedBy')?.id || userId;
|
||||
const tenantQuery = new Parse.Query('partners_Tenant');
|
||||
if (tenantId) {
|
||||
tenantQuery.equalTo('objectId', tenantId);
|
||||
} else {
|
||||
tenantQuery.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: user,
|
||||
});
|
||||
}
|
||||
tenantQuery.exclude('FileAdapters,PfxFile');
|
||||
const res = await tenantQuery.first({ useMasterKey: true });
|
||||
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
|
||||
tenantCreditsQuery.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: user,
|
||||
});
|
||||
tenantCreditsQuery.exclude('FileAdapters,PfxFile');
|
||||
const res = await tenantCreditsQuery.first({ useMasterKey: true });
|
||||
return res;
|
||||
} else {
|
||||
return {};
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
export default async function manageSign(request) {
|
||||
const { signature, userId, initials, id, title } = request.params;
|
||||
|
||||
if (!userId) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Missing userId parameter.');
|
||||
}
|
||||
if (userId !== request.user?.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot save signature for the current user.');
|
||||
}
|
||||
const userPtr = { __type: 'Pointer', className: '_User', objectId: userId };
|
||||
try {
|
||||
const signatureCls = new Parse.Object('contracts_Signature');
|
||||
if (id) {
|
||||
signatureCls.id = id;
|
||||
}
|
||||
signatureCls.set('Initials', initials ? initials : '');
|
||||
signatureCls.set('ImageURL', signature ? signature : '');
|
||||
signatureCls.set('SignatureName', title ? title : '');
|
||||
if (userPtr) {
|
||||
signatureCls.set('UserId', userPtr);
|
||||
}
|
||||
const signRes = await signatureCls.save(null, { useMasterKey: true });
|
||||
return signRes;
|
||||
} catch (err) {
|
||||
console.error('Error saving signature:', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,6 @@ export default async function GenerateCertificate(docDetails) {
|
||||
const maxX = width - margin - textWidth; // Ensures text stays inside the border with 30px margin
|
||||
const OriginIp = docDetails?.OriginIp || '';
|
||||
const company = docDetails?.ExtUserPtr?.Company || '';
|
||||
const documentHash = docDetails?.DocumentHash || '';
|
||||
const createdAt = docDetails?.DocSentAt?.iso || docDetails.createdAt;
|
||||
const createdAtperTimezone = formatDateTime(createdAt, DateFormat, timezone, Is12Hr);
|
||||
const IsEnableOTP = docDetails?.IsEnableOTP || false;
|
||||
@@ -151,36 +150,9 @@ export default async function GenerateCertificate(docDetails) {
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
if (documentHash) {
|
||||
page.drawText('Document hash (sha256) :', {
|
||||
x: 30,
|
||||
y: 670,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(documentHash, {
|
||||
x: 170,
|
||||
y: 670,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
}
|
||||
|
||||
const organizationY = documentHash ? 650 : 670;
|
||||
const createdOnY = organizationY - 20;
|
||||
const completedOnY = createdOnY - 20;
|
||||
const signersY = completedOnY - 20;
|
||||
const originatorHeaderY = signersY - 20;
|
||||
const nameY = originatorHeaderY - 17;
|
||||
const emailY = nameY - 20;
|
||||
const ipY = emailY - 20;
|
||||
|
||||
page.drawText('Organization :', {
|
||||
x: 30,
|
||||
y: organizationY,
|
||||
y: 670,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
@@ -188,14 +160,14 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText(company, {
|
||||
x: 110,
|
||||
y: organizationY,
|
||||
y: 670,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Created on :', {
|
||||
x: 30,
|
||||
y: createdOnY,
|
||||
y: 650,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
@@ -203,14 +175,14 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText(`${createdAtperTimezone}`, {
|
||||
x: 97,
|
||||
y: createdOnY,
|
||||
y: 650,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Completed on :', {
|
||||
x: 30,
|
||||
y: completedOnY,
|
||||
y: 630,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
@@ -218,14 +190,14 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText(`${completedUTCtime}`, {
|
||||
x: 115,
|
||||
y: completedOnY,
|
||||
y: 630,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Signers :', {
|
||||
x: 30,
|
||||
y: signersY,
|
||||
y: 610,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
@@ -233,75 +205,75 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText(`${signersCount}`, {
|
||||
x: 80,
|
||||
y: signersY,
|
||||
y: 610,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Document originator', {
|
||||
x: 30,
|
||||
y: originatorHeaderY,
|
||||
y: 590,
|
||||
size: 17,
|
||||
font: timesRomanFont,
|
||||
color: titleColor,
|
||||
});
|
||||
page.drawText('Name :', {
|
||||
x: 60,
|
||||
y: nameY,
|
||||
y: 573,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText(ownerName, {
|
||||
x: 105,
|
||||
y: nameY,
|
||||
y: 573,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Email :', {
|
||||
x: 60,
|
||||
y: emailY,
|
||||
y: 553,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText(ownerEmail, {
|
||||
x: 105,
|
||||
y: emailY,
|
||||
y: 553,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('IP address :', {
|
||||
x: 60,
|
||||
y: ipY,
|
||||
y: 533,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText(`${OriginIp}`, {
|
||||
x: 125,
|
||||
y: ipY,
|
||||
y: 533,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawLine({
|
||||
start: { x: 30, y: ipY - 6 },
|
||||
end: { x: width - 30, y: ipY - 6 },
|
||||
start: { x: 30, y: 527 },
|
||||
end: { x: width - 30, y: 527 },
|
||||
color: rgb(0.12, 0.12, 0.12),
|
||||
thickness: 0.5,
|
||||
});
|
||||
let yPosition1 = ipY - 21;
|
||||
let yPosition2 = yPosition1 - 14;
|
||||
let yPosition3 = yPosition2 - 20;
|
||||
let yPosition4 = yPosition3 - 20;
|
||||
let yPosition5 = yPosition4 - 20;
|
||||
let yPosition6 = yPosition5 - 20;
|
||||
let yPosition7 = yPosition6 - 20;
|
||||
let yPosition8 = yPosition7 - 35;
|
||||
let yPosition1 = 512;
|
||||
let yPosition2 = 498;
|
||||
let yPosition3 = 478;
|
||||
let yPosition4 = 458;
|
||||
let yPosition5 = 438;
|
||||
let yPosition6 = 418;
|
||||
let yPosition7 = 398;
|
||||
let yPosition8 = 363;
|
||||
|
||||
auditTrail.slice(0, 3).forEach(async (x, i) => {
|
||||
const embedPng = x.Signature ? await pdfDoc.embedPng(x.Signature) : '';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import fs from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import axios from 'axios';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import {
|
||||
@@ -15,7 +14,7 @@ import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
|
||||
import { Placeholder } from './Placeholder.js';
|
||||
import { SignPdf } from '@signpdf/signpdf';
|
||||
import { P12Signer } from '@signpdf/signer-p12';
|
||||
import { buildDownloadFilename, parseUploadFile } from '../../../utils/fileUtils.js';
|
||||
import { buildDownloadFilename } from '../../../utils/fileUtils.js';
|
||||
|
||||
const serverUrl = cloudServerUrl; // process.env.SERVER_URL;
|
||||
const APPID = serverAppId;
|
||||
@@ -29,10 +28,6 @@ const headers = {
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
};
|
||||
|
||||
function generateDocumentHash(buffer) {
|
||||
return createHash('sha256').update(buffer).digest('hex');
|
||||
}
|
||||
|
||||
async function unlinkFile(path) {
|
||||
if (fs.existsSync(path)) {
|
||||
try {
|
||||
@@ -48,9 +43,10 @@ async function uploadFile(pdfName, filepath) {
|
||||
try {
|
||||
const filedata = fs.readFileSync(filepath);
|
||||
let fileUrl;
|
||||
|
||||
const fileRes = await parseUploadFile(pdfName, filedata, 'application/pdf');
|
||||
fileUrl = getSecureUrl(fileRes?.url)?.url;
|
||||
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
const fileRes = getSecureUrl(file.url());
|
||||
fileUrl = fileRes.url;
|
||||
|
||||
return { imageUrl: fileUrl };
|
||||
} catch (err) {
|
||||
@@ -61,7 +57,7 @@ async function uploadFile(pdfName, filepath) {
|
||||
}
|
||||
|
||||
// `updateDoc` is used to update signedUrl, AuditTrail, Iscompleted in document
|
||||
async function updateDoc(docId, url, userId, ipAddress, data, className, sign, documentHash) {
|
||||
async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
|
||||
try {
|
||||
const UserPtr = { __type: 'Pointer', className: className, objectId: userId };
|
||||
const obj = {
|
||||
@@ -100,16 +96,8 @@ async function updateDoc(docId, url, userId, ipAddress, data, className, sign, d
|
||||
isCompleted = true;
|
||||
}
|
||||
const body = { SignedUrl: url, AuditTrail: updateAuditTrail, IsCompleted: isCompleted };
|
||||
if (documentHash && isCompleted) {
|
||||
body.DocumentHash = documentHash;
|
||||
}
|
||||
const signedRes = await axios.put(`${docUrl}/${docId}`, body, { headers });
|
||||
return {
|
||||
isCompleted: isCompleted,
|
||||
message: 'success',
|
||||
AuditTrail: updateAuditTrail,
|
||||
DocumentHash: documentHash && isCompleted ? documentHash : undefined,
|
||||
};
|
||||
return { isCompleted: isCompleted, message: 'success', AuditTrail: updateAuditTrail };
|
||||
} catch (err) {
|
||||
console.log('update doc err ', err);
|
||||
return 'err';
|
||||
@@ -191,8 +179,8 @@ async function sendCompletedMail(obj) {
|
||||
if (obj?.isCustomMail) {
|
||||
const tenant = sender?.TenantId;
|
||||
if (tenant) {
|
||||
subject = tenant?.CompletionSubject ? tenant?.CompletionSubject : subject;
|
||||
body = tenant?.CompletionBody ? tenant?.CompletionBody : body;
|
||||
subject = tenant?.CompletionSubject || '';
|
||||
body = tenant?.CompletionBody || '';
|
||||
} else {
|
||||
const userId = sender?.CreatedBy?.objectId || sender?.UserId?.objectId;
|
||||
if (userId) {
|
||||
@@ -206,8 +194,8 @@ async function sendCompletedMail(obj) {
|
||||
const tenantRes = await tenantQuery.first({ useMasterKey: true });
|
||||
if (tenantRes) {
|
||||
const _tenantRes = JSON.parse(JSON.stringify(tenantRes));
|
||||
subject = _tenantRes?.CompletionSubject ? tenant?.CompletionSubject : subject;
|
||||
body = _tenantRes?.CompletionBody ? tenant?.CompletionBody : body;
|
||||
subject = _tenantRes?.CompletionSubject || '';
|
||||
body = _tenantRes?.CompletionBody || '';
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('error in fetch tenant in signpdf', err.message);
|
||||
@@ -362,7 +350,7 @@ async function PDF(req) {
|
||||
const publicUrl = req.headers.public_url;
|
||||
// below bode is used to get info of docId
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
docQuery.include('ExtUserPtr,Signers,ExtUserPtr.TenantId,Bcc,CreatedBy');
|
||||
docQuery.include('ExtUserPtr,Signers,ExtUserPtr.TenantId,Bcc');
|
||||
docQuery.equalTo('objectId', docId);
|
||||
const resDoc = await docQuery.first({ useMasterKey: true });
|
||||
if (!resDoc) {
|
||||
@@ -435,7 +423,6 @@ async function PDF(req) {
|
||||
let filePath = `./exports/${name}`;
|
||||
let signedFilePath = `./exports/signed_${name}`;
|
||||
let pdfSize = PdfBuffer.length;
|
||||
let documentHash;
|
||||
if (isCompleted) {
|
||||
const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>');
|
||||
const reason =
|
||||
@@ -453,7 +440,6 @@ async function PDF(req) {
|
||||
//`saveUrl` is used to save signed pdf in exports folder
|
||||
fs.writeFileSync(signedFilePath, signedDocs);
|
||||
pdfSize = signedDocs.length;
|
||||
documentHash = generateDocumentHash(signedDocs);
|
||||
console.log(`✅ PDF digitally signed created: ${signedFilePath} \n`);
|
||||
} else {
|
||||
//`saveUrl` is used to save signed pdf in exports folder
|
||||
@@ -474,17 +460,12 @@ async function PDF(req) {
|
||||
userIP, // client ipAddress,
|
||||
_resDoc, // auditTrail, signers, etc data
|
||||
className, // className based on flow
|
||||
sign, // sign base64
|
||||
isCompleted ? documentHash : undefined
|
||||
sign // sign base64
|
||||
);
|
||||
sendNotifyMail(_resDoc, signUser, mailProvider, publicUrl);
|
||||
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
|
||||
if (updatedDoc && updatedDoc.isCompleted) {
|
||||
const hashForDoc = documentHash || updatedDoc?.DocumentHash;
|
||||
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
|
||||
if (hashForDoc) {
|
||||
doc.DocumentHash = hashForDoc;
|
||||
}
|
||||
sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, `signed_${name}`);
|
||||
} else {
|
||||
unlinkFile(pfxname);
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
export default async function resetPassword(request) {
|
||||
const userId = request.params.userId;
|
||||
const newPassword = request.params.password;
|
||||
|
||||
if (!userId || !newPassword) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide required parameters.');
|
||||
}
|
||||
|
||||
if (!request.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
}
|
||||
|
||||
if (request.user.id === userId) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Unauthorized to reset your own password.');
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Get Admin User TenantId
|
||||
const adminUserQuery = new Parse.Query('contracts_Users');
|
||||
adminUserQuery.equalTo('UserId', request.user);
|
||||
const adminUser = await adminUserQuery.first({ useMasterKey: true });
|
||||
|
||||
const tenantId = adminUser?.get('TenantId');
|
||||
if (!tenantId) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Admin user tenant not found.');
|
||||
}
|
||||
const isAdmin =
|
||||
adminUser.get('UserRole') === 'contracts_Admin' ||
|
||||
adminUser.get('UserRole') === 'contracts_OrgAdmin';
|
||||
if (!isAdmin) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Unauthorized.');
|
||||
}
|
||||
|
||||
// 2. Verify the user belongs to the same tenant and is not an admin
|
||||
const targetUserQuery = new Parse.Query('contracts_Users');
|
||||
targetUserQuery.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
|
||||
targetUserQuery.equalTo('TenantId', tenantId);
|
||||
targetUserQuery.notEqualTo('UserRole', 'contracts_Admin');
|
||||
const targetUser = await targetUserQuery.first({ useMasterKey: true });
|
||||
|
||||
if (!targetUser) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found or not allowed.');
|
||||
}
|
||||
|
||||
// 3. Update user's password
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
userQuery.equalTo('objectId', userId);
|
||||
const user = await userQuery.first({ useMasterKey: true });
|
||||
|
||||
if (!user) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
|
||||
}
|
||||
|
||||
user.set('password', newPassword);
|
||||
await user.save(null, { useMasterKey: true });
|
||||
|
||||
return { status: 'success', message: 'Password has been reset.' };
|
||||
} catch (error) {
|
||||
console.error('Error while resetting password:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,6 @@ export default async function saveAsTemplate(request) {
|
||||
options: {
|
||||
...p.options,
|
||||
status: 'required',
|
||||
response: '',
|
||||
...(p?.options?.defaultValue ? { defaultValue: '' } : {}),
|
||||
},
|
||||
})),
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { flattenPdf, getSecureUrl } from '../../Utils.js';
|
||||
import { parseUploadFile } from '../../utils/fileUtils.js';
|
||||
|
||||
export default async function saveFile(request) {
|
||||
if (!request.params.fileBase64) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide file.');
|
||||
@@ -12,7 +10,6 @@ export default async function saveFile(request) {
|
||||
const extCls = new Parse.Query('contracts_Users');
|
||||
extCls.equalTo('UserId', request.user);
|
||||
extCls.include('TenantId');
|
||||
extCls.include('UserId');
|
||||
const resExt = await extCls.first({ useMasterKey: true });
|
||||
if (resExt) {
|
||||
const _resExt = JSON.parse(JSON.stringify(resExt));
|
||||
@@ -23,26 +20,17 @@ export default async function saveFile(request) {
|
||||
if (ext === 'pdf') {
|
||||
mimeType = 'application/pdf';
|
||||
const flatPdf = await flattenPdf(fileBase64);
|
||||
// file = [...flatPdf];
|
||||
file = flatPdf;
|
||||
file = [...flatPdf];
|
||||
} else if (ext === 'png' || ext === 'jpeg' || ext === 'jpg') {
|
||||
mimeType = `image/${ext}`;
|
||||
// file = { base64: fileBase64 };
|
||||
file = Buffer.from(fileBase64, 'base64');
|
||||
}
|
||||
// const pdfFile = new Parse.File(fileName, file, mimeType);
|
||||
// // Save the Parse File if needed
|
||||
// const pdfData = await pdfFile.save({ useMasterKey: true });
|
||||
// const presignedUrl = pdfData.url();
|
||||
// const fileRes = getSecureUrl(presignedUrl);
|
||||
// return { url: fileRes.url };
|
||||
try {
|
||||
const fileRes = await parseUploadFile(fileName, file, mimeType);
|
||||
fileUrl = getSecureUrl(fileRes?.url)?.url;
|
||||
return { url: fileUrl };
|
||||
} catch (err) {
|
||||
throw new Parse.Error(400, err?.message);
|
||||
file = { base64: fileBase64 };
|
||||
}
|
||||
const pdfFile = new Parse.File(fileName, file, mimeType);
|
||||
// Save the Parse File if needed
|
||||
const pdfData = await pdfFile.save({ useMasterKey: true });
|
||||
const presignedUrl = pdfData.url();
|
||||
const fileRes = getSecureUrl(presignedUrl);
|
||||
return { url: fileRes.url };
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
export default async function saveSignature(request) {
|
||||
const { signature, userId, initials, id, title } = request.params;
|
||||
|
||||
if (!userId) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Missing userId parameter.');
|
||||
}
|
||||
if (userId !== request.user?.id) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Cannot save signature for the current user.');
|
||||
}
|
||||
const userPtr = { __type: 'Pointer', className: '_User', objectId: userId };
|
||||
try {
|
||||
const signatureCls = new Parse.Object('contracts_Signature');
|
||||
if (id) {
|
||||
signatureCls.id = id;
|
||||
}
|
||||
if (initials) {
|
||||
signatureCls.set('Initials', initials);
|
||||
}
|
||||
if (signature) {
|
||||
signatureCls.set('ImageURL', signature);
|
||||
}
|
||||
if (title) {
|
||||
signatureCls.set('SignatureName', title);
|
||||
}
|
||||
if (userPtr) {
|
||||
signatureCls.set('UserId', userPtr);
|
||||
}
|
||||
const signRes = await signatureCls.save(null, { useMasterKey: true });
|
||||
return signRes;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -159,8 +159,7 @@ export default async function sendMailGmailProvider(_extRes, template) {
|
||||
template;
|
||||
|
||||
if (_extRes) {
|
||||
let refresh_token = '';
|
||||
refresh_token = _extRes?.TenantId?.google_refresh_token;
|
||||
const refresh_token = _extRes.google_refresh_token;
|
||||
// generate access token
|
||||
const access_token = await refreshAccessToken(refresh_token);
|
||||
|
||||
|
||||
@@ -12,23 +12,15 @@ async function sendMailProvider(req, plan, monthchange) {
|
||||
let mailgunClient;
|
||||
let mailgunDomain;
|
||||
if (smtpenable) {
|
||||
let transporterConfig = {
|
||||
transporterSMTP = createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: process.env.SMTP_PORT || 465,
|
||||
secure: smtpsecure,
|
||||
};
|
||||
|
||||
// ✅ Add auth only if BOTH username & password exist
|
||||
const smtpUser = process.env.SMTP_USERNAME;
|
||||
const smtpPass = process.env.SMTP_PASS;
|
||||
|
||||
if (smtpUser && smtpPass) {
|
||||
transporterConfig.auth = {
|
||||
auth: {
|
||||
user: process.env.SMTP_USERNAME ? process.env.SMTP_USERNAME : process.env.SMTP_USER_EMAIL,
|
||||
pass: smtpPass,
|
||||
};
|
||||
}
|
||||
transporterSMTP = createTransport(transporterConfig);
|
||||
pass: process.env.SMTP_PASS,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (mailgunApiKey) {
|
||||
const mailgun = new Mailgun(formData);
|
||||
@@ -193,7 +185,7 @@ async function sendMailProvider(req, plan, monthchange) {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`Error in sendmailv3: ${err}`);
|
||||
console.log('err in sendmailv3', err);
|
||||
if (fs.existsSync(testPdf)) {
|
||||
try {
|
||||
fs.unlinkSync(testPdf);
|
||||
@@ -244,7 +236,7 @@ async function sendMailProvider(req, plan, monthchange) {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`Error in sendmailv3: ${err}`);
|
||||
console.log('err in sendmailv3', err);
|
||||
if (err) {
|
||||
return { status: 'error' };
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
export default async function updateEmailTemplates(request) {
|
||||
const { tenantId, details } = request.params;
|
||||
|
||||
if (!tenantId || !details) {
|
||||
throw new Parse.Error(400, 'Missing tenantId or details.');
|
||||
}
|
||||
|
||||
if (!request.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'unauthorized');
|
||||
}
|
||||
|
||||
try {
|
||||
const extUser = new Parse.Query('contracts_Users');
|
||||
extUser.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: request.user.id });
|
||||
extUser.include('TenantId');
|
||||
const extUserRes = await extUser.first({ useMasterKey: true });
|
||||
const mailKeys = ['CompletionBody', 'CompletionSubject', 'RequestBody', 'RequestSubject'];
|
||||
Object.keys(details).forEach(key => {
|
||||
if (mailKeys.includes(key)) {
|
||||
if (details?.[key]) {
|
||||
extUserRes.set(key, details?.[key]);
|
||||
} else {
|
||||
extUserRes.unset(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
const updateExtRes = await extUserRes.save(null, { useMasterKey: true });
|
||||
if (updateExtRes) {
|
||||
const res = JSON.parse(JSON.stringify(updateExtRes));
|
||||
return res;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('error while updating email templates:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,7 @@ export default async function updateTenant(request) {
|
||||
// Update tenant details
|
||||
Object.keys(details).forEach(key => {
|
||||
if (validKeys.includes(key)) {
|
||||
if (details?.[key] !== undefined) {
|
||||
tenant.set(key, details?.[key]);
|
||||
} else {
|
||||
tenant.unset(key);
|
||||
}
|
||||
tenant.set(key, details?.[key]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -61,28 +61,20 @@ let mailgunDomain;
|
||||
let isMailAdapter = false;
|
||||
if (smtpenable) {
|
||||
try {
|
||||
let transporterConfig = {
|
||||
transporterMail = createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: process.env.SMTP_PORT || 465,
|
||||
secure: smtpsecure,
|
||||
};
|
||||
|
||||
// ✅ Add auth only if BOTH username & password exist
|
||||
const smtpUser = process.env.SMTP_USERNAME;
|
||||
const smtpPass = process.env.SMTP_PASS;
|
||||
|
||||
if (smtpUser && smtpPass) {
|
||||
transporterConfig.auth = {
|
||||
auth: {
|
||||
user: process.env.SMTP_USERNAME ? process.env.SMTP_USERNAME : process.env.SMTP_USER_EMAIL,
|
||||
pass: smtpPass,
|
||||
};
|
||||
}
|
||||
transporterMail = createTransport(transporterConfig);
|
||||
pass: process.env.SMTP_PASS,
|
||||
},
|
||||
});
|
||||
await transporterMail.verify();
|
||||
isMailAdapter = true;
|
||||
} catch (err) {
|
||||
isMailAdapter = false;
|
||||
console.log(`Please provide valid SMTP credentials: ${err}`);
|
||||
console.log('Please provide valid SMTP credentials');
|
||||
}
|
||||
} else if (process.env.MAILGUN_API_KEY) {
|
||||
try {
|
||||
@@ -108,7 +100,7 @@ export const config = {
|
||||
appId: serverAppId,
|
||||
logLevel: ['error'],
|
||||
maxLimit: 500,
|
||||
maxUploadSize: '100mb',
|
||||
maxUploadSize: '30mb',
|
||||
masterKey: process.env.MASTER_KEY, //Add your master key here. Keep it secret!
|
||||
masterKeyIps: ['0.0.0.0/0', '::/0'], // '::1'
|
||||
serverURL: cloudServerUrl, // Don't forget to change to https if needed
|
||||
@@ -118,7 +110,6 @@ export const config = {
|
||||
appName: appName,
|
||||
allowClientClassCreation: false,
|
||||
allowExpiredAuthDataToken: false,
|
||||
enableInsecureAuthAdapters: false,
|
||||
encodeParseObjectInCloudFunction: true,
|
||||
...(isMailAdapter === true
|
||||
? {
|
||||
@@ -165,8 +156,8 @@ export const config = {
|
||||
|
||||
export const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json({ limit: '100mb' }));
|
||||
app.use(express.urlencoded({ limit: '100mb', extended: true }));
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
app.use(function (req, res, next) {
|
||||
req.headers['x-real-ip'] = getUserIP(req);
|
||||
const publicUrl = 'https://' + req?.get('host');
|
||||
|
||||
Generated
+2899
-3089
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "open_sign_server",
|
||||
"version": "2.30.0",
|
||||
"description": "opensign server using the parse-server module",
|
||||
"version": "2.26.0",
|
||||
"description": "An example Parse API server using the parse-server module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/OpenSignLabs/OpenSign"
|
||||
"url": "https://github.com/ParsePlatform/parse-server-example"
|
||||
},
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
@@ -18,8 +18,8 @@
|
||||
"watch": "nodemon index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.936.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.936.0",
|
||||
"@aws-sdk/client-s3": "^3.888.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.888.0",
|
||||
"@parse/fs-files-adapter": "^3.0.0",
|
||||
"@parse/s3-files-adapter": "^4.2.0",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
@@ -27,42 +27,42 @@
|
||||
"@signpdf/signer-p12": "^3.2.4",
|
||||
"@signpdf/signpdf": "^3.2.5",
|
||||
"aws-sdk": "^2.1692.0",
|
||||
"axios": "^1.13.2",
|
||||
"axios": "^1.12.2",
|
||||
"coherentpdf": "^2.5.5",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"dotenv": "^17.2.3",
|
||||
"dotenv": "^17.2.2",
|
||||
"express": "^5.1.0",
|
||||
"form-data": "^4.0.5",
|
||||
"form-data": "^4.0.4",
|
||||
"generate-api-key": "^1.0.2",
|
||||
"googleapis": "^166.0.0",
|
||||
"googleapis": "^159.0.0",
|
||||
"libreoffice-convert": "^1.7.0",
|
||||
"mailgun.js": "^12.1.1",
|
||||
"mongodb": "^5.9.2",
|
||||
"mailgun.js": "^12.0.3",
|
||||
"mongodb": "^6.19.0",
|
||||
"multer": "^2.0.2",
|
||||
"multer-s3": "^3.0.1",
|
||||
"node-forge": "^1.3.1",
|
||||
"nodemailer": "^7.0.10",
|
||||
"p-limit": "^7.2.0",
|
||||
"parse": "^7.0.2",
|
||||
"nodemailer": "^7.0.6",
|
||||
"p-limit": "^7.1.1",
|
||||
"parse": "^6.1.1",
|
||||
"parse-dbtool": "^1.2.0",
|
||||
"parse-server": "^8.4.0",
|
||||
"parse-server": "^8.2.4",
|
||||
"parse-server-api-mail-adapter": "^4.1.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"posthog-node": "^5.13.2",
|
||||
"posthog-node": "^5.8.4",
|
||||
"qrcode": "^1.5.4",
|
||||
"rate-limiter-flexible": "^8.2.1",
|
||||
"sharp": "^0.34.5",
|
||||
"rate-limiter-flexible": "^7.3.1",
|
||||
"sharp": "^0.34.3",
|
||||
"speakeasy": "^2.0.0",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.28.5",
|
||||
"eslint": "^9.39.1",
|
||||
"jasmine": "^5.12.0",
|
||||
"mongodb-runner": "^5.11.1",
|
||||
"nodemon": "^3.1.11",
|
||||
"@babel/eslint-parser": "^7.28.4",
|
||||
"eslint": "^9.35.0",
|
||||
"jasmine": "^5.10.0",
|
||||
"mongodb-runner": "^5.9.3",
|
||||
"nodemon": "^3.1.10",
|
||||
"nyc": "^17.1.0",
|
||||
"prettier": "^3.6.2"
|
||||
},
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl, serverAppId } from '../Utils.js';
|
||||
|
||||
function formatFixedDate(date = new Date()) {
|
||||
const dd = String(date.getDate()).padStart(2, '0');
|
||||
const months = [
|
||||
@@ -78,23 +75,3 @@ export function buildDownloadFilename(formatId, ctx) {
|
||||
const safeExt = ext.replace(/\.+/g, '').toLowerCase() || 'pdf';
|
||||
return `${stem}.${safeExt}`;
|
||||
}
|
||||
|
||||
export async function parseUploadFile(fileName, fileData, mimeType) {
|
||||
try {
|
||||
const res = await axios.post(`${cloudServerUrl}/files/${fileName}`, fileData, {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': serverAppId,
|
||||
'X-Parse-Master-Key': process.env.MASTER_KEY,
|
||||
'Content-Type': mimeType,
|
||||
},
|
||||
});
|
||||
|
||||
// console.log('File uploaded:', res.data);
|
||||
return res.data;
|
||||
} catch (err) {
|
||||
const errorMessage = err?.response?.data?.error || 'Unknown error';
|
||||
const statusCode = err?.response?.status || 500;
|
||||
console.log('Err in parseUploadFile', errorMessage);
|
||||
throw { message: errorMessage, code: statusCode };
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+356
-142
@@ -9,17 +9,16 @@
|
||||
"version": "1.0.6",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.2.7",
|
||||
"prettier": "^3.6.2"
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^15.1.0",
|
||||
"prettier": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
|
||||
"integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
|
||||
"integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"environment": "^1.0.0"
|
||||
},
|
||||
@@ -31,11 +30,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -44,11 +42,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -68,12 +65,23 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
|
||||
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
||||
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"restore-cursor": "^5.0.0"
|
||||
},
|
||||
@@ -85,17 +93,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
|
||||
"integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
|
||||
"integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"slice-ansi": "^7.1.0",
|
||||
"string-width": "^8.0.0"
|
||||
"slice-ansi": "^5.0.0",
|
||||
"string-width": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -105,32 +112,59 @@
|
||||
"version": "2.0.20",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
|
||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "14.0.2",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
|
||||
"integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==",
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"version": "10.4.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
|
||||
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/environment": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
|
||||
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -142,8 +176,30 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
||||
"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^8.0.1",
|
||||
"human-signals": "^5.0.0",
|
||||
"is-stream": "^3.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^5.1.0",
|
||||
"onetime": "^6.0.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
"strip-final-newline": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
@@ -158,11 +214,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
|
||||
"integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz",
|
||||
"integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -170,33 +225,49 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/husky": {
|
||||
"version": "9.1.7",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
|
||||
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
|
||||
"node_modules/get-stream": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
|
||||
"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
|
||||
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/husky": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
|
||||
"integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"husky": "bin.js"
|
||||
"husky": "lib/bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
|
||||
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
|
||||
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -211,39 +282,70 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "16.2.7",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz",
|
||||
"integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==",
|
||||
"node_modules/is-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
|
||||
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
|
||||
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antonk52"
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "15.2.9",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.9.tgz",
|
||||
"integrity": "sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^14.0.2",
|
||||
"listr2": "^9.0.5",
|
||||
"micromatch": "^4.0.8",
|
||||
"nano-spawn": "^2.0.0",
|
||||
"pidtree": "^0.6.0",
|
||||
"string-argv": "^0.3.2",
|
||||
"yaml": "^2.8.1"
|
||||
"chalk": "~5.3.0",
|
||||
"commander": "~12.1.0",
|
||||
"debug": "~4.3.6",
|
||||
"execa": "~8.0.1",
|
||||
"lilconfig": "~3.1.2",
|
||||
"listr2": "~8.2.4",
|
||||
"micromatch": "~4.0.7",
|
||||
"pidtree": "~0.6.0",
|
||||
"string-argv": "~0.3.2",
|
||||
"yaml": "~2.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"lint-staged": "bin/lint-staged.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.17"
|
||||
"node": ">=18.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/lint-staged"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz",
|
||||
"integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==",
|
||||
"version": "8.2.4",
|
||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz",
|
||||
"integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cli-truncate": "^5.0.0",
|
||||
"cli-truncate": "^4.0.0",
|
||||
"colorette": "^2.0.20",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"log-update": "^6.1.0",
|
||||
@@ -251,7 +353,7 @@
|
||||
"wrap-ansi": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update": {
|
||||
@@ -259,7 +361,6 @@
|
||||
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
|
||||
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-escapes": "^7.0.0",
|
||||
"cli-cursor": "^5.0.0",
|
||||
@@ -274,6 +375,43 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/is-fullwidth-code-point": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz",
|
||||
"integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/slice-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"is-fullwidth-code-point": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
@@ -287,12 +425,23 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-function": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
|
||||
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
@@ -300,35 +449,63 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/nano-spawn": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz",
|
||||
"integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==",
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
|
||||
"integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.17"
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/nano-spawn?sponsor=1"
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path/node_modules/path-key": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
|
||||
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
|
||||
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
|
||||
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-function": "^5.0.0"
|
||||
"mimic-fn": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
@@ -354,11 +531,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
|
||||
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
@@ -374,7 +550,6 @@
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
|
||||
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"onetime": "^7.0.0",
|
||||
"signal-exit": "^4.1.0"
|
||||
@@ -386,19 +561,53 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor/node_modules/onetime": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
|
||||
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-function": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
@@ -407,17 +616,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
|
||||
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"is-fullwidth-code-point": "^5.0.0"
|
||||
"ansi-styles": "^6.0.0",
|
||||
"is-fullwidth-code-point": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
@@ -433,28 +641,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
|
||||
"integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==",
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.0",
|
||||
"emoji-regex": "^10.3.0",
|
||||
"get-east-asian-width": "^1.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
@@ -465,6 +672,18 @@
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-final-newline": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
|
||||
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -477,12 +696,26 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
|
||||
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz",
|
||||
"integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"string-width": "^7.0.0",
|
||||
@@ -495,35 +728,16 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/string-width": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^10.3.0",
|
||||
"get-east-asian-width": "^1.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
||||
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz",
|
||||
"integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
"node": ">= 14"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -23,8 +23,8 @@
|
||||
"author": "OpenSignLabs",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.2.7",
|
||||
"prettier": "^3.6.2"
|
||||
"husky": "^8.0.0",
|
||||
"lint-staged": "^15.1.0",
|
||||
"prettier": "3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user