mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-03 08:18:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e2e535b70 | ||
|
|
336cfb3482 | ||
|
|
f1278d32a8 | ||
|
|
79d184cf88 | ||
|
|
cd2904b9f0 | ||
|
|
fce20a165b | ||
|
|
3a737ea1b7 | ||
|
|
e035592e94 | ||
|
|
0891cf55fc | ||
|
|
5d328b51c3 | ||
|
|
336114f6ec | ||
|
|
eb8e7b089b | ||
|
|
bce0e4971e | ||
|
|
4fb8aa9a16 | ||
|
|
8a2329dd60 | ||
|
|
75a40fbf43 | ||
|
|
334edb3c0c | ||
|
|
6465aa7c2c | ||
|
|
4593ce7fec | ||
|
|
b4ffdf0dc9 | ||
|
|
3c8b8835c7 | ||
|
|
20752161d3 | ||
|
|
002b0d5277 | ||
|
|
47348b990e | ||
|
|
ac86b6ef61 | ||
|
|
7894a6edb8 | ||
|
|
dac8957904 | ||
|
|
ef686aec6a | ||
|
|
f31155e0b4 | ||
|
|
78e4857d1a | ||
|
|
c3144366d9 | ||
|
|
f00a7c9e50 | ||
|
|
803d25e9d1 | ||
|
|
ca1b87309f | ||
|
|
0b4548130d | ||
|
|
e8b3c7fed8 | ||
|
|
2ce43ec2c8 | ||
|
|
a878ca1a96 |
@@ -241,6 +241,15 @@
|
||||
"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,34 +21,46 @@ jobs:
|
||||
dockerfile: apps/OpenSign/Dockerhubfile
|
||||
- image: opensign/opensignserver
|
||||
dockerfile: apps/OpenSignServer/Dockerhubfile
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Login to Docker Hub
|
||||
|
||||
# 👇 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
|
||||
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
|
||||
|
||||
- name: Build and push (multi-arch)
|
||||
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 }}
|
||||
|
||||
@@ -60,7 +60,7 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
|
||||
- **PDF Template Creation:** OpenSign™ allows you to create and store PDF document templates for repeated use thereby saving you a lot of time & collect e-signatures seamlessly.
|
||||
- **OpenSign™ Drive:** It is a centralised secure vault for your digital documents that makes storing, signing, organizing, sharing & archieving your docs a breeze.
|
||||
- **Audit Trails & completion certificate:** Being a security focused solution, OpenSign™ makes it a top priority to save detailed logs for tracking document activities along with time-stamps, IP addresses, email IDs & phone numbers. A completion certificate is generated as soon as document is completed which contains all the document related logs for added safety.
|
||||
- **API Support:** OpenSign™ API allows seamless integration into existing systems and software. You can generate an API key from the app and refer the [official API docs](https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1) to start integrating it in your existing applications.
|
||||
- **API Support:** OpenSign™ API allows seamless integration into existing systems and software. You can generate an API key from the app and refer the [official API docs](https://docs.opensignlabs.com/docs/API-docs/v1.1) to start integrating it in your existing applications.
|
||||
- **Integrations:** The open source document signing experience becomes even more seamless because of integrations with various Cloud storage systems, CRMs & enterprise platforms. We also have a Zapier integration that allows you to integrate it with virtually any application.
|
||||
<img alt="Login page" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/8016edb6-7d6e-4185-ab72-82d2c4a16032" height='200' >
|
||||
<img alt="Dash_board" src="https://github.com/OpenSignLabs/OpenSign/assets/171803164/6e7b96f7-3154-4c25-bc1e-caf33925cc47" height='200'>
|
||||
@@ -165,6 +165,7 @@ 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>
|
||||
|
||||
@@ -21,9 +21,7 @@ const DisabledButtonExamples = () => {
|
||||
|
||||
{/* Option C: Conditional styling */}
|
||||
<button
|
||||
className={`op-btn op-btn-primary ${
|
||||
isDisabled ? "op-btn-vscode-disabled" : ""
|
||||
}`}
|
||||
className={`op-btn op-btn-primary ${isDisabled ? "op-btn-vscode-disabled" : ""}`}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
Conditional Button
|
||||
|
||||
+44
-41
@@ -1,58 +1,57 @@
|
||||
{
|
||||
"name": "open_sign",
|
||||
"version": "2.26.0",
|
||||
"version": "2.35.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.9.0",
|
||||
"@imgly/background-removal": "^1.7.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.16.2",
|
||||
"@lottiefiles/dotlottie-react": "^0.17.13",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.2.1",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"axios": "^1.12.2",
|
||||
"@radix-ui/themes": "^3.3.0",
|
||||
"@reactour/tour": "^3.8.0",
|
||||
"@reduxjs/toolkit": "^2.11.2",
|
||||
"axios": "^1.13.4",
|
||||
"body-scroll-lock": "^4.0.0-beta.0",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^25.4.0",
|
||||
"i18next": "^25.8.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": "^6.1.1",
|
||||
"parse": "^8.0.3",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pkijs": "^3.0.8",
|
||||
"pkijs": "^3.3.3",
|
||||
"print-js": "^1.6.0",
|
||||
"prismjs": "^1.30.0",
|
||||
"quill-html-edit-button": "^3.0.0",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^18.3.1",
|
||||
"react": "^19.2.4",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-datepicker": "^8.7.0",
|
||||
"react-datepicker": "^9.1.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-dom": "^19.2.4",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.7.4",
|
||||
"react-konva": "^18.2.12",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-quill-new": "^3.6.0",
|
||||
"react-i18next": "^16.5.4",
|
||||
"react-konva": "^19.2.1",
|
||||
"react-pdf": "^10.2.0",
|
||||
"react-quill-new": "^3.8.3",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.9.3",
|
||||
"react-router": "^7.13.0",
|
||||
"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.29.1",
|
||||
"reactour": "^1.19.4",
|
||||
"react-to-print": "^3.2.0",
|
||||
"react-tooltip": "^5.30.0",
|
||||
"redux": "^5.0.1",
|
||||
"regex-parser": "^2.3.1",
|
||||
"serve": "^14.2.5",
|
||||
"styled-components": "^5.3.11",
|
||||
"web-vitals": "^5.1.0",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||
},
|
||||
@@ -94,36 +93,40 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.28.3",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@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",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.0",
|
||||
"@babel/preset-react": "^7.28.5",
|
||||
"@babel/runtime-corejs2": "^7.28.6",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/react": "^19.1.13",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"@types/react": "^19.2.10",
|
||||
"@vitejs/plugin-react": "^5.1.3",
|
||||
"@vitest/ui": "^4.0.18",
|
||||
"autoprefixer": "^10.4.24",
|
||||
"babel-loader": "^10.0.0",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.2.1",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-loader": "^7.1.3",
|
||||
"daisyui": "^4.12.24",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-plugin-prettier": "^5.5.5",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"jsdom": "^27.0.0",
|
||||
"lint-staged": "^16.1.6",
|
||||
"jsdom": "^28.0.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier": "^3.8.1",
|
||||
"pretty-quick": "^4.2.2",
|
||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"vite": "^7.1.4",
|
||||
"tailwindcss": "^3.4.19",
|
||||
"tmp": "^0.2.5",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-svgr": "^4.5.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.4"
|
||||
"vite-tsconfig-paths": "^6.0.5",
|
||||
"vitest": "^4.0.18"
|
||||
},
|
||||
"overrides": {
|
||||
"tmp": "$tmp"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || 22"
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"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",
|
||||
@@ -120,7 +121,8 @@
|
||||
"Teams": "Teams",
|
||||
"Team-update-successfully": "Team erfolgreich aktualisiert.",
|
||||
"Users": "Benutzer"
|
||||
}
|
||||
},
|
||||
"Global SMTP": "Globales SMTP"
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "Benötigt Ihre Unterschrift",
|
||||
@@ -166,7 +168,10 @@
|
||||
"Save as template": "Als Vorlage speichern",
|
||||
"Fix & resend": "Korrigieren und erneut senden",
|
||||
"Kiosk Mode": "Kiosk Modus",
|
||||
"Reset password": "Passwort zurücksetzen"
|
||||
"Reset password": "Passwort zurücksetzen",
|
||||
"View formdata": "Formulardaten ansehen",
|
||||
"Download all formdata": "Alle Formulardaten herunterladen",
|
||||
"API credits": "API-Credits"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nr.",
|
||||
@@ -201,7 +206,8 @@
|
||||
"Redirect url": "Weiterleitungs-URL",
|
||||
"Created Date": "Erstellungsdatum",
|
||||
"Updated Date": "Aktualisierungsdatum",
|
||||
"Expiry Date": "Ablaufdatum"
|
||||
"Expiry Date": "Ablaufdatum",
|
||||
"Sent Date": "Sendedatum"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Dies sind Dokumente, die Sie begonnen, aber noch nicht zum Versenden fertiggestellt haben.",
|
||||
@@ -218,7 +224,7 @@
|
||||
"New Template": "Neue Vorlage",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "Datei ausgewählt",
|
||||
"files-selected": "Datei(en) ausgewählt",
|
||||
"template-title": "Vorlagentitel",
|
||||
"document-title": "Dokumenttitel",
|
||||
"title": "Titel",
|
||||
@@ -264,7 +270,7 @@
|
||||
"signature": "Unterschrift",
|
||||
"upload-image": "Bild hochladen",
|
||||
"clear": "Löschen",
|
||||
"upload-signature/Image": "Bitte Unterschrift/Bild hochladen",
|
||||
"upload-signature/Image": "Bitte Unterschrift/Bild hochladen/Stempel",
|
||||
"initials": "Initialen",
|
||||
"API": "API",
|
||||
"api-token": "API-Token",
|
||||
@@ -331,7 +337,7 @@
|
||||
"quick-send-alert-1": "Alle Rollen in diesem Dokument sind derzeit mit Kontakten verknüpft. Um Kopien dieser Vorlage schnell an mehrere Unterzeichner zu senden, stellen Sie sicher, dass mindestens eine Rolle keinem Kontakt zugeordnet ist.",
|
||||
"quick-send-alert-2": "Bitte stellen Sie sicher, dass für alle Empfänger mindestens ein Signatur-Widget hinzugefügt wurde.",
|
||||
"quick-send-alert-3": "Bitte fügen Sie diesem template mindestens eine Rolle hinzu.",
|
||||
"quick-send-alert-4": "Das Limit für Schnellsendungen wurde erreicht.",
|
||||
"quick-send-alert-4": "Limit für {{flow}} erreicht.",
|
||||
"copy-link": "Link kopieren",
|
||||
"copy": "Kopieren",
|
||||
"revoke-document": "Dokument widerrufen",
|
||||
@@ -386,7 +392,9 @@
|
||||
"dropdown": "Dropdown",
|
||||
"radio button": "Radiobutton",
|
||||
"image": "Bild",
|
||||
"email": "E-Mail"
|
||||
"email": "E-Mail",
|
||||
"number": "Zahl",
|
||||
"draw": "Zeichnen"
|
||||
},
|
||||
"fields": "Felder",
|
||||
"recipients": "Empfänger",
|
||||
@@ -439,10 +447,23 @@
|
||||
"Next to current widget": "Neben dem aktuellen Widget"
|
||||
},
|
||||
"options": "Optionen",
|
||||
"minimun-check": "Minimale Anzahl",
|
||||
"minimum-check": "Minimale Anzahl",
|
||||
"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",
|
||||
@@ -496,14 +517,14 @@
|
||||
"placeholder-alert-4": "Sie haben erfolgreich E-Mails an alle Empfänger gesendet!",
|
||||
"placeholder-mail-alert": "Sie haben erfolgreich eine E-Mail an {{name}} gesendet. Die nachfolgenden Unterzeichner erhalten eine E-Mail, sobald {{name}} das Dokument unterzeichnet.",
|
||||
"placeholder-mail-alert-you": "Nachfolgende Unterzeichner erhalten E-Mails, sobald Sie das Dokument unterschreiben.",
|
||||
"placeholder-alert-5": "Möchten Sie die Dokumente jetzt unterschreiben?",
|
||||
"placeholder-alert-5": "Sie können jetzt unterschreiben oder es an Ihre E-Mail senden, um es später abzuschließen.",
|
||||
"placeholder-alert-6": "Bitte richten Sie den E-Mail-Adapter ein, um E-Mails zu senden!",
|
||||
"placeholder-alert-7": "Bitte wählen Sie einen Unterzeichner aus, um Platzhalter hinzuzufügen!",
|
||||
"email-subject": "E-Mail-Betreff",
|
||||
"email-body": "E-Mail-Inhalt",
|
||||
"email-placeholder": "E-Mail-Text eingeben",
|
||||
"reset-to-default": "Auf Standard zurücksetzen",
|
||||
"cutomize-email": "E-Mail anpassen",
|
||||
"customize-email": "E-Mail anpassen",
|
||||
"upgrade-to-customize-email": "Upgrade, um E-Mails anzupassen",
|
||||
"sign-url": "Signatur-URL",
|
||||
"mails-sent": "E-Mails gesendet",
|
||||
@@ -568,7 +589,7 @@
|
||||
"provide-your-details": "Bitte geben Sie Ihre Daten an",
|
||||
"provide-email": "Bitte E-Mail angeben.",
|
||||
"additional-info": "Zusätzliche Informationen",
|
||||
"signature-saved-alert": "Unterschrift erfolgreich gespeichert.",
|
||||
"signature-saved-alert": "erfolgreich gespeichert.",
|
||||
"saved-successfully": "Erfolgreich gespeichert.",
|
||||
"fill-field": "Bitte dieses Feld ausfüllen",
|
||||
"error-template": "Fehler: Vorlage nicht gefunden!",
|
||||
@@ -684,7 +705,7 @@
|
||||
"select-date-format": "Wählen Sie ein Datumsformat",
|
||||
"quantity-of-credits": "Anzahl der Premium-Credits",
|
||||
"remaining-credits": "Verfügbare Premium-Credits:",
|
||||
"remaining-credits-help": "Verwenden Sie Premium-Credits für API-Dokumentensignaturen, Massenversand oder die Einbettung der OpenSign-Integration auf Ihrer Website. Sie haben {{allowedcredits}} enthaltene Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig.",
|
||||
"remaining-credits-help": "Verwenden Sie Premium-Credits für die API-Dokumentensignatur, den Massenversand oder die Einbettung der OpenSign-Integration auf Ihrer Website. Sie haben noch {{allowedcredits}} inkludierte Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig. Wenn Sie einen Teams- oder Enterprise-Plan nutzen, können diese Credits von allen Benutzern in Ihrem Unternehmen gemeinsam verwendet werden, sofern der Kontoadministrator für Ihr Konto kein Limit festgelegt hat.",
|
||||
"additional-credits": "Bitte kaufen Sie Premium-Credits hinzu",
|
||||
"quota-err-quicksend": "Kontingent erreicht, Sie haben nicht genügend Credits.",
|
||||
"buy-credits": "Premium-Credits kaufen",
|
||||
@@ -1223,6 +1244,7 @@
|
||||
},
|
||||
"read-only-date-error": "Das schreibgeschützte Datums-Widget muss ein Standarddatum haben.",
|
||||
"set-date": "Datum festlegen",
|
||||
"default-date": "Standarddatum",
|
||||
"set-today": "Unterzeichnungsdatum",
|
||||
"enter-name": "Name eingeben",
|
||||
"enter-email": "E-Mail eingeben",
|
||||
@@ -1242,5 +1264,101 @@
|
||||
"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"
|
||||
"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.",
|
||||
"hide-text-with-asterisks": "Text mit Sternchen ausblenden",
|
||||
"hide-text-with-asterisks-help": "Der Text im unterzeichneten Dokument wird ausgeblendet, bleibt jedoch für den Dokumentabsender (Eigentümer) über sein {{appName}}-Konto zugänglich.",
|
||||
"document-id": "Dokument-ID",
|
||||
"sent-date": "Sendedatum",
|
||||
"status": "Status",
|
||||
"signer-form-data-for": "Formulardaten für Unterzeichner: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Widget-Name",
|
||||
"widget-responses": "Widget-Antworten",
|
||||
"formdata-download-note": "Hinweis: Signaturen, Stempel, Initialen und Bilder werden nicht exportiert.",
|
||||
"export-all-formdata-error": "Sie können die Formulardaten nicht exportieren, da die Vorlage doppelte Widget-Namen enthält. Bitte erstellen Sie eine neue Vorlage, um Formulardaten exportieren zu können.",
|
||||
"export-formdata-error": "Sie können die Formulardaten nicht exportieren, da das Dokument doppelte Widget-Namen enthält. Bitte erstellen Sie ein neues Dokument ohne doppelte Widget-Namen, um Formulardaten exportieren zu können.",
|
||||
"duplicate-widget-name-error": "Dieser Widget-Name ist bereits einem anderen Unterzeichner zugewiesen. Bitte wählen Sie einen anderen Namen.",
|
||||
"duplicate-template-widget-error": "Sie können diese Vorlage nicht duplizieren, da sie doppelte Widget-Namen enthält.",
|
||||
"credit-alert": "Kreditwarnung",
|
||||
"document-form-data": "Dokumenten-Formulardaten",
|
||||
"view-formdata-paid-title-error": "Die Funktion 'Formulardaten anzeigen' ermöglicht es Ihnen, die von Unterzeichnern ausgefüllten Widget-Daten auszudrucken und herunterzuladen.",
|
||||
"view-formdata-paid-error": "Hinweis: Die Funktion 'Formulardaten anzeigen' ist nur für Dokumente verfügbar, die ab Version v2.32.0 erstellt wurden und keine doppelten Widget-Namen enthalten.",
|
||||
"download-all-formdata-paid-title-error": "Die Funktion 'Alle Formulardaten herunterladen' ermöglicht es Ihnen, die von Unterzeichnern ausgefüllten Widget-Daten für alle aus der Vorlage erstellten Dokumente herunterzuladen.",
|
||||
"download-all-formdata-paid-error": "Hinweis: Die Funktion 'Alle Formulardaten herunterladen' ist nur für Vorlagen verfügbar, die ab Version v2.32.0 erstellt wurden und keine doppelten Widget-Namen enthalten.",
|
||||
"api-token-not-found": "API-Token nicht gefunden.",
|
||||
"sub-head-api-credits": "Legen Sie die maximale Anzahl von API-Credits fest, die dieser Benutzer verbrauchen darf.",
|
||||
"consumed-credits": "Verbrauchte Credits",
|
||||
"allowed-api-credits": "Zulässige API-Credits",
|
||||
"allowed-api-credits-help": "Die zulässigen Credits sollten größer oder gleich den verbrauchten Credits sein.",
|
||||
"Enter-allowed-api-credits": "Zulässige API-Credits eingeben",
|
||||
"login-to-sandbox": "Im Sandbox-Modus anmelden",
|
||||
"your-credits-usage": "Ihr Kreditverbrauch",
|
||||
"remaining": "verbleibend",
|
||||
"used": "verwendet",
|
||||
"your-credits-usage-help": "Dies ist die Anzahl der Credits, die mit Ihrem API-Schlüssel verbraucht wurden.",
|
||||
"api-credits-updated": "API-Credits wurden erfolgreich zugewiesen.",
|
||||
"min-date": "Mindestdatum",
|
||||
"max-date": "Höchstdatum",
|
||||
"invalid-min-date": "Ungültiges Mindestdatum.",
|
||||
"min-less-max": "Das Mindestdatum muss kleiner oder gleich dem Höchstdatum sein.",
|
||||
"invalid-max-date": "Ungültiges Höchstdatum.",
|
||||
"max-less-min": "Das Höchstdatum muss größer oder gleich dem Mindestdatum sein.",
|
||||
"date-range": "Datumsbereich",
|
||||
"date-range-help": "Legen Sie ein Mindest- und/oder Höchstdatum fest, um die Auswahl einzuschränken. Das gewählte Datum muss innerhalb dieses Bereichs liegen.",
|
||||
"option-disabled-by-owner": "Der Dokumentersteller hat diese Option deaktiviert",
|
||||
"pen-colors": "Stiftfarben",
|
||||
"pen-colors-help": "Wählen Sie aus, welche Stiftfarben für Signatur- und Initialen-Widgets verfügbar sind. Aktivieren Sie ein Kontrollkästchen, um eine Farbe anzuzeigen, und deaktivieren Sie es, um sie auszublenden.",
|
||||
"security-key": "Sicherheitsschlüssel",
|
||||
"remove-limit": "Limit entfernen",
|
||||
"removed-api-credits": "Das API-Credit-Limit wurde entfernt.",
|
||||
"enable-authentication": "Authentifizierung aktivieren",
|
||||
"working": "In Bearbeitung",
|
||||
"test-security-key": "Sicherheitsschlüssel testen",
|
||||
"generate": "Generieren",
|
||||
"regenerate": "Neu generieren",
|
||||
"signing-date": "Signierdatum",
|
||||
"date-widget": "Datums-Widget",
|
||||
"mail-failed": "Beim Senden der E-Mail ist etwas schiefgelaufen. Bitte versuchen Sie es erneut.",
|
||||
"mail-status-head": "Nächster Schritt: Ihr Dokument unterschreiben?",
|
||||
"send-to-email": "Per E-Mail senden",
|
||||
"date-pref-help-sub-title": "Mit den Datums-Widget-Einstellungen können Sie die Standardkonfiguration für das Datums-Widget festlegen. Diese Standards werden automatisch angewendet, wenn Sie ein neues Datums-Widget zu einem Dokument oder einer Vorlage hinzufügen. Sie können jedes Datums-Widget jederzeit individuell anpassen, indem Sie auf das Zahnradsymbol klicken.",
|
||||
"date-pref-help-format": "Wählen Sie das standardmäßige Datumsformat für Datums-Widgets.",
|
||||
"date-pref-help-default-date": "Legen Sie einen Standard-Datumswert für Datums-Widgets fest.",
|
||||
"date-pref-help-signing-date": "Wenn aktiviert, verwendet das Datums-Widget automatisch das Datum, an dem der Unterzeichner das Dokument signiert.",
|
||||
"date-pref-help-read-only": "Wenn aktiviert, ist das Datums-Widget für den Unterzeichner schreibgeschützt.",
|
||||
"upload-csv": "CSV hochladen",
|
||||
"file-selected": "Datei ausgewählt",
|
||||
"bulk-send-csv-helper": "Verwenden Sie Header für Unterzeichnerrollen (Rollen-E-Mail/Name/Telefon) und Vorabfüllwerte (Vorabfüllung: Feldname). Erforderliche Vorabfüllfelder müssen ausgefüllt sein.",
|
||||
"bulk-send-csv-helper-note": "Hinweis: Für Vorabfüllfelder vom Typ Datum in der CSV muss der Wert in folgendem Format angegeben werden: {{value}} (Beispiel: {{example}}).",
|
||||
"bulk-send-csv-summary": "{{total}} Zeilen importiert. {{invalid}} ungültige Zeilen wurden übersprungen.",
|
||||
"bulk-send-csv-missing-headers": "Erforderliche CSV-Header fehlen. Rollen: {{roles}}. Vorabfüllung: {{prefill}}.",
|
||||
"bulk-send-no-records": "Bitte fügen Sie vor dem Senden mindestens einen Empfänger hinzu.",
|
||||
"csv-only-support": "Es werden nur CSV-Dateien unterstützt.",
|
||||
"csv-no-records": "Keine Datensätze in der CSV gefunden.",
|
||||
"select-at-least-option": "Bitte wählen Sie mindestens eine Option aus.",
|
||||
"draw-required": "Bitte zeichnen Sie in das Feld.",
|
||||
"enter-smtp-username": "SMTP-Benutzernamen eingeben",
|
||||
"enter-webhook-url": "Webhook-URL eingeben",
|
||||
"enter-user-name": "Benutzernamen eingeben",
|
||||
"enter-tagline": "Slogan eingeben",
|
||||
"enter-password-or-click-autogenerate": "Passwort eingeben oder auf „Automatisch generieren“ klicken",
|
||||
"enter-daily-email-quota": "Tägliches E-Mail-Kontingent eingeben",
|
||||
"enter-custom-regular-expression": "Benutzerdefinierten regulären Ausdruck eingeben",
|
||||
"enter-value": "{{value}} eingeben",
|
||||
"are-you-sure-want-to-send-document": "Sind Sie sicher, dass Sie das Dokument senden möchten?"
|
||||
}
|
||||
@@ -71,6 +71,7 @@
|
||||
"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",
|
||||
@@ -120,7 +121,8 @@
|
||||
"Teams": "Teams",
|
||||
"Team-update-successfully": "Team Updated successfully.",
|
||||
"Users": "Users"
|
||||
}
|
||||
},
|
||||
"Global SMTP": "Global SMTP"
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "Need your signature",
|
||||
@@ -166,7 +168,10 @@
|
||||
"Save as template": "Save as template",
|
||||
"Fix & resend": "Fix & Resend",
|
||||
"Kiosk Mode": "Kiosk Mode",
|
||||
"Reset password": "Reset password"
|
||||
"Reset password": "Reset password",
|
||||
"View formdata": "View formdata",
|
||||
"Download all formdata": "Download all formdata",
|
||||
"API credits": "API credits"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Sr.No",
|
||||
@@ -201,7 +206,8 @@
|
||||
"Redirect url": "Redirect url",
|
||||
"Created Date": "Created Date",
|
||||
"Updated Date": "Updated Date",
|
||||
"Expiry Date": "Expiry Date"
|
||||
"Expiry Date": "Expiry Date",
|
||||
"Sent Date": "Sent Date"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "These are documents you have started but have not finalized for sending.",
|
||||
@@ -218,7 +224,7 @@
|
||||
"New Template": "New template",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "file(s) selected",
|
||||
"files-selected": "file(s) selected",
|
||||
"template-title": "Template title",
|
||||
"document-title": "Document title",
|
||||
"title": "Title",
|
||||
@@ -264,7 +270,7 @@
|
||||
"signature": "Signature",
|
||||
"upload-image": " Upload image",
|
||||
"clear": "Clear",
|
||||
"upload-signature/Image": "Please upload signature/Image",
|
||||
"upload-signature/Image": "Please upload signature/Image/stamp",
|
||||
"initials": "Initials",
|
||||
"API": "API",
|
||||
"api-token": "API token",
|
||||
@@ -331,7 +337,7 @@
|
||||
"quick-send-alert-1": "All roles in this document are currently linked to contacts. To quick send copies of this template to multiple signers, please ensure that at least one role is not linked to any contact.",
|
||||
"quick-send-alert-2": "Please ensure there's at least one signature widget added for all recipients.",
|
||||
"quick-send-alert-3": "Please add at least one role to this template.",
|
||||
"quick-send-alert-4": "Quick send reached limit.",
|
||||
"quick-send-alert-4": "Limit reached for {{flow}}.",
|
||||
"copy-link": "Copy link",
|
||||
"copy": "Copy",
|
||||
"revoke-document": "Revoke document",
|
||||
@@ -386,7 +392,9 @@
|
||||
"dropdown": "dropdown",
|
||||
"radio button": "radio button",
|
||||
"image": "image",
|
||||
"email": "email"
|
||||
"email": "email",
|
||||
"number": "number",
|
||||
"draw": "draw"
|
||||
},
|
||||
"fields": "Fields",
|
||||
"recipients": "Recipients",
|
||||
@@ -440,10 +448,23 @@
|
||||
"Next to current widget": "Next to current widget"
|
||||
},
|
||||
"options": "Options",
|
||||
"minimun-check": "Minimun check",
|
||||
"minimum-check": "Minimum check",
|
||||
"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",
|
||||
@@ -497,14 +518,14 @@
|
||||
"placeholder-alert-4": "You have successfully sent mails to all recipients!",
|
||||
"placeholder-mail-alert": "You have successfully sent email to {{name}}. Subsequent signers will get email(s) once {{name}} signs the document",
|
||||
"placeholder-mail-alert-you": "Subsequent signers will get email(s) once you sign the document.",
|
||||
"placeholder-alert-5": "Do you want to sign the document right now?",
|
||||
"placeholder-alert-5": "You can sign now or send it to your email to complete later.",
|
||||
"placeholder-alert-6": "Please setup mail adapter to send mail!",
|
||||
"placeholder-alert-7": "Please select signer for add placeholder!",
|
||||
"email-subject": "email subject",
|
||||
"email-body": "email body",
|
||||
"email-placeholder": "add body of email",
|
||||
"reset-to-default": "Reset to default",
|
||||
"cutomize-email": "Cutomize email",
|
||||
"customize-email": "Customize email",
|
||||
"upgrade-to-customize-email": "Upgrade to customize Email",
|
||||
"sign-url": "Sign url",
|
||||
"mails-sent": "Mails Sent",
|
||||
@@ -569,7 +590,7 @@
|
||||
"provide-your-details": "Provide your details",
|
||||
"provide-email": "Please provide email.",
|
||||
"additional-info": "Additional info",
|
||||
"signature-saved-alert": "Signature saved successfully.",
|
||||
"signature-saved-alert": "Saved successfully.",
|
||||
"saved-successfully": "Saved successfully.",
|
||||
"fill-field": "Please fill out this field",
|
||||
"error-template": "Error: Template not found!",
|
||||
@@ -685,7 +706,7 @@
|
||||
"select-date-format": "Select a date format",
|
||||
"quantity-of-credits": "Quantity of premium credits",
|
||||
"remaining-credits": "Premium credits available:",
|
||||
"remaining-credits-help": "Use premium credits for API document signing, bulk sending, or embedding OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
|
||||
"remaining-credits-help": "Use premium credits for API document signing, bulk sending, or embedding OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining. If you are on Teams or enterprise plan these are the credits that could be shared across all users in your company unless there is a limit set by the account admin for your account.",
|
||||
"additional-credits": "Please purchase premium credits",
|
||||
"quota-err-quicksend": "Quota reached, you don't have enough credits.",
|
||||
"buy-credits": "Buy premium credits",
|
||||
@@ -1224,6 +1245,7 @@
|
||||
},
|
||||
"read-only-date-error": "Read-only date widget must have a default date.",
|
||||
"set-date": "Set Date",
|
||||
"default-date": "Default Date",
|
||||
"set-today": "Signing Date",
|
||||
"enter-name": "Enter name",
|
||||
"enter-email": "Enter email",
|
||||
@@ -1243,5 +1265,101 @@
|
||||
"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"
|
||||
"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.",
|
||||
"hide-text-with-asterisks": "Hide text with asterisks",
|
||||
"hide-text-with-asterisks-help": "The text on the signed document will be concealed from view but remain accessible to the document sender (owner) from their {{appName}} account.",
|
||||
"document-id": "Document ID",
|
||||
"sent-date": "Sent Date",
|
||||
"status": "Status",
|
||||
"signer-form-data-for": "Signer Form data for: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Widget Name",
|
||||
"widget-responses": "Widget Responses",
|
||||
"formdata-download-note": "Note: Signatures, stamps, initials and images wont be exported.",
|
||||
"export-all-formdata-error": "You can/t export form data because the template contains duplicate widget names. Please create a fresh template to be able to export form data.",
|
||||
"export-formdata-error": "You can't export form data because the document contains duplicate widget names. Please create a fresh document without duplicate widget names to be able to export form data.",
|
||||
"duplicate-widget-name-error": "This widget name is already assigned to another signer. Please choose a different name.",
|
||||
"duplicate-template-widget-error": "You can't duplicate this template because it contains duplicate widget names.",
|
||||
"credit-alert": "Credit Alert",
|
||||
"document-form-data": "Document Form Data",
|
||||
"view-formdata-paid-title-error": "The View Form Data feature lets you print and download the widget data filled in by signers while signing.",
|
||||
"view-formdata-paid-error": "Note: 'View Formdata' feature is only available for documents created on or after v2.32.0 that do not contain duplicate widget names.",
|
||||
"download-all-formdata-paid-title-error": "The Download All Form Data feature lets you download widget data filled in by signers for all documents created from the template.",
|
||||
"download-all-formdata-paid-error": "Note: 'Download All Formdata' feature is only available for templates created on or after v2.32.0 that do not contain duplicate widget names.",
|
||||
"api-token-not-found": "API token not found.",
|
||||
"sub-head-api-credits": " Define the maximum number of API credits this user is allowed to consume.",
|
||||
"consumed-credits": "Consumed credits",
|
||||
"allowed-api-credits": "Allowed API credits",
|
||||
"allowed-api-credits-help": "The allowed credits should be greater than or equal to the consumed credits.",
|
||||
"Enter-allowed-api-credits": "Enter allowed API credits",
|
||||
"login-to-sandbox": "Login to sandbox",
|
||||
"your-credits-usage": "Your credits usage",
|
||||
"remaining": "remaining",
|
||||
"used": "used",
|
||||
"your-credits-usage-help": "This is the number of credits consumed with your API key.",
|
||||
"api-credits-updated": "API credits have been allocated successfully.",
|
||||
"min-date": "Min Date",
|
||||
"max-date": "Max Date",
|
||||
"invalid-min-date": "Invalid min date.",
|
||||
"min-less-max": "Min date must be less than or equal to max date.",
|
||||
"invalid-max-date": "Invalid max date.",
|
||||
"max-less-min": "Max date must be greater than or equal to min date.",
|
||||
"date-range": "Date range",
|
||||
"date-range-help": "Set Min and/or Max to restrict selection. Chosen date must fall within the range.",
|
||||
"option-disabled-by-owner": "Document creator has disabled this option",
|
||||
"pen-colors": "Pen colors",
|
||||
"pen-colors-help": "Choose which pen colors are available for Signature and Initials widgets. Check to show a color, uncheck to hide it.",
|
||||
"security-key": "Security key",
|
||||
"remove-limit": "Remove limit",
|
||||
"removed-api-credits": "The API credit limit has been removed.",
|
||||
"enable-authentication": "Enable authentication",
|
||||
"working": "Working",
|
||||
"test-security-key": "Test security key",
|
||||
"generate": "Generate",
|
||||
"regenerate": "Regenerate",
|
||||
"signing-date": "Signing Date",
|
||||
"date-widget": "Date Widget",
|
||||
"mail-failed": "Something went wrong while sending the email. Please try again.",
|
||||
"mail-status-head": "Next step: Sign your document ?",
|
||||
"send-to-email": "Send to Email",
|
||||
"date-pref-help-sub-title": "Date Widget Preferences let you define the default settings for the Date widget. These defaults are automatically applied whenever you add a new Date widget to a document or template. You can still customize any individual Date widget at any time by clicking the gear icon.",
|
||||
"date-pref-help-format": "Choose the default date format used for Date widgets.",
|
||||
"date-pref-help-default-date": "Set a default date value for Date widgets.",
|
||||
"date-pref-help-signing-date": "When enabled, the Date widget automatically uses the date the signer signs the document.",
|
||||
"date-pref-help-read-only": "When enabled, the Date widget is read-only for the signer.",
|
||||
"upload-csv": "Upload CSV",
|
||||
"file-selected": "file selected",
|
||||
"bulk-send-csv-helper": "Use headers for signer roles (Role Email/Name/Phone) and prefill values (Prefill: Field Name). Required prefill fields must be completed.",
|
||||
"bulk-send-csv-helper-note": "Note: For Date-type prefill fields in the CSV, the value must be provided in this format: {{value}} (example: {{example}}).",
|
||||
"bulk-send-csv-summary": "Imported {{total}} rows. {{invalid}} invalid rows were skipped.",
|
||||
"bulk-send-csv-missing-headers": "Missing required CSV headers. Roles: {{roles}}. Prefill: {{prefill}}.",
|
||||
"bulk-send-no-records": "Please add at least one recipient before sending.",
|
||||
"csv-only-support": "Only CSV files are supported.",
|
||||
"csv-no-records": "No records found in the CSV.",
|
||||
"select-at-least-option": "Please select at least one option.",
|
||||
"draw-required": "Please draw in the box.",
|
||||
"enter-smtp-username": "Enter your SMTP username",
|
||||
"enter-webhook-url": "Enter webhook url",
|
||||
"enter-user-name": "Enter user name",
|
||||
"enter-tagline": "Enter tagline",
|
||||
"enter-password-or-click-autogenerate": "Enter password or click Autogenerate",
|
||||
"enter-daily-email-quota": "Enter daily email quota",
|
||||
"enter-custom-regular-expression": "Enter custom regular expression",
|
||||
"enter-value": "Enter {{value}}",
|
||||
"are-you-sure-want-to-send-document": "Are you sure want to send document?"
|
||||
}
|
||||
@@ -71,6 +71,7 @@
|
||||
"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",
|
||||
@@ -120,7 +121,8 @@
|
||||
"Teams": "Equipos",
|
||||
"Team-update-successfully": "Equipo actualizado correctamente.",
|
||||
"Users": "Usuarios"
|
||||
}
|
||||
},
|
||||
"Global SMTP": "SMTP Global"
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "Necesitan tu firma",
|
||||
@@ -166,7 +168,10 @@
|
||||
"Save as template": "Guardar como plantilla",
|
||||
"Fix & resend": "Corregir y reenviar",
|
||||
"Kiosk Mode": "Modo Kiosco",
|
||||
"Reset password": "Restablecer contraseña"
|
||||
"Reset password": "Restablecer contraseña",
|
||||
"View formdata": "Ver datos del formulario",
|
||||
"Download all formdata": "Descargar todos los datos del formulario",
|
||||
"API credits": "Créditos de API"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nº",
|
||||
@@ -201,7 +206,8 @@
|
||||
"Redirect url": "URL de redirección",
|
||||
"Created Date": "Fecha de creación",
|
||||
"Updated Date": "Fecha de actualización",
|
||||
"Expiry Date": "Fecha de vencimiento"
|
||||
"Expiry Date": "Fecha de vencimiento",
|
||||
"Sent Date": "Fecha de envío"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Estos son documentos que has iniciado pero no has finalizado para su envío.",
|
||||
@@ -218,7 +224,7 @@
|
||||
"New Template": "Nueva plantilla",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "archivo seleccionado",
|
||||
"files-selected": "archivo(s) seleccionado(s)",
|
||||
"template-title": "Título de la plantilla",
|
||||
"document-title": "Título del documento",
|
||||
"title": "Título",
|
||||
@@ -264,7 +270,7 @@
|
||||
"signature": "Firma",
|
||||
"upload-image": " Subir imagen",
|
||||
"clear": "Limpiar",
|
||||
"upload-signature/Image": "Por favor, sube la firma/imagen",
|
||||
"upload-signature/Image": "Por favor, sube la firma/imagen/estampilla",
|
||||
"initials": "Iniciales",
|
||||
"API": "API",
|
||||
"api-token": "Token API",
|
||||
@@ -331,7 +337,7 @@
|
||||
"quick-send-alert-1": "Todos los roles de este documento están actualmente vinculados a contactos. Para enviar rápidamente copias de esta plantilla a varios firmantes, por favor, asegúrate de que al menos un rol no esté vinculado a ningún contacto.",
|
||||
"quick-send-alert-2": "Por favor, asegúrate de que hay al menos un widget de firma añadido para cada destinatario.",
|
||||
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
|
||||
"quick-send-alert-4": "El envío rápido ha alcanzado el límite.",
|
||||
"quick-send-alert-4": "Se alcanzó el límite para {{flow}}.",
|
||||
"copy-link": "Copiar enlace",
|
||||
"copy": "Copiar",
|
||||
"revoke-document": "Revocar documento",
|
||||
@@ -386,7 +392,9 @@
|
||||
"dropdown": "desplegable",
|
||||
"radio button": "botón de radio",
|
||||
"image": "imagen",
|
||||
"email": "correo"
|
||||
"email": "correo",
|
||||
"number": "número",
|
||||
"draw": "Dibujar"
|
||||
},
|
||||
"fields": "Campos",
|
||||
"recipients": "Destinatarios",
|
||||
@@ -440,10 +448,23 @@
|
||||
"Next to current widget": "Junto al widget actual"
|
||||
},
|
||||
"options": "Opciones",
|
||||
"minimun-check": "Chequeo mínimo",
|
||||
"minimum-check": "Chequeo mínimo",
|
||||
"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",
|
||||
@@ -497,14 +518,14 @@
|
||||
"placeholder-alert-4": "¡Has enviado exitosamente correos a todos los destinatarios!",
|
||||
"placeholder-mail-alert": "Has enviado un correo electrónico con éxito a {{name}}. Los siguientes firmantes recibirán un correo electrónico una vez que {{name}} firme el documento.",
|
||||
"placeholder-mail-alert-you": "Los firmantes siguientes recibirán un correo electrónico una vez que usted firme el documento.",
|
||||
"placeholder-alert-5": "¿Quieres firmar documentos ahora mismo?",
|
||||
"placeholder-alert-5": "Puede firmar ahora o enviarlo a su correo electrónico para completarlo más tarde.",
|
||||
"placeholder-alert-6": "¡Por favor, configura el adaptador de correo para enviar correos!",
|
||||
"placeholder-alert-7": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
|
||||
"email-subject": "asunto del correo",
|
||||
"email-body": "cuerpo del correo",
|
||||
"email-placeholder": "agregar cuerpo del correo",
|
||||
"reset-to-default": "Restablecer a por defecto",
|
||||
"cutomize-email": "Personalizar correo",
|
||||
"customize-email": "Personalizar correo",
|
||||
"upgrade-to-customize-email": "Mejora para personalizar el correo",
|
||||
"sign-url": "URL de firma",
|
||||
"mails-sent": "Correos enviados",
|
||||
@@ -569,7 +590,7 @@
|
||||
"provide-your-details": "Proporciona tus detalles",
|
||||
"provide-email": "Por favor, proporciona un correo.",
|
||||
"additional-info": "Información adicional",
|
||||
"signature-saved-alert": "Firma guardada exitosamente.",
|
||||
"signature-saved-alert": "guardada exitosamente.",
|
||||
"saved-successfully": "Guardado con éxito.",
|
||||
"fill-field": "Por favor, rellena este campo",
|
||||
"error-template": "Error: ¡Plantilla no encontrada!",
|
||||
@@ -685,7 +706,7 @@
|
||||
"select-date-format": "Selecciona un formato de fecha",
|
||||
"quantity-of-credits": "Cantidad de créditos premium",
|
||||
"remaining-credits": "Créditos premium disponibles:",
|
||||
"remaining-credits-help": "Usa créditos premium para la API para la firma de documentos, envío en masa, o para incorporar la integración de OpenSign en tu página web. Te quedan {{allowedcredits}} créditos incluidos y {{addoncredits}} créditos comprados adicionalmente.",
|
||||
"remaining-credits-help": "Utilice créditos premium para la firma de documentos mediante API, el envío masivo o para integrar OpenSign en su sitio web. Le quedan {{allowedcredits}} créditos incluidos y {{addoncredits}} créditos adicionales comprados. Si está en un plan Teams o Enterprise, estos créditos pueden compartirse entre todos los usuarios de su empresa, a menos que el administrador de la cuenta haya establecido un límite para su cuenta.",
|
||||
"additional-credits": "Por favor, compra créditos premium",
|
||||
"quota-err-quicksend": "Cuota alcanzada, no tienes créditos suficientes.",
|
||||
"buy-credits": "Comprar créditos premium",
|
||||
@@ -1224,6 +1245,7 @@
|
||||
},
|
||||
"read-only-date-error": "El widget de fecha de solo lectura debe tener una fecha predeterminada.",
|
||||
"set-date": "Establecer fecha",
|
||||
"default-date": "Fecha predeterminada",
|
||||
"set-today": "Fecha de firma",
|
||||
"enter-name": "Ingrese nombre",
|
||||
"enter-email": "Ingrese correo electrónico",
|
||||
@@ -1243,5 +1265,101 @@
|
||||
"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"
|
||||
"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.",
|
||||
"hide-text-with-asterisks": "Ocultar texto con asteriscos",
|
||||
"hide-text-with-asterisks-help": "El texto en el documento firmado se ocultará, pero seguirá siendo accesible para el remitente (propietario) desde su cuenta de {{appName}}.",
|
||||
"document-id": "ID del documento",
|
||||
"sent-date": "Fecha de envío",
|
||||
"status": "Estado",
|
||||
"signer-form-data-for": "Datos del formulario para el firmante: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Nombre del widget",
|
||||
"widget-responses": "Respuestas del widget",
|
||||
"formdata-download-note": "Nota: Las firmas, sellos, iniciales e imágenes no se exportarán.",
|
||||
"export-all-formdata-error": "No puede exportar los datos del formulario porque la plantilla contiene nombres de widgets duplicados. Cree una nueva plantilla para poder exportar los datos.",
|
||||
"export-formdata-error": "No puede exportar los datos del formulario porque el documento contiene nombres de widgets duplicados. Cree un documento nuevo sin nombres duplicados para poder exportar los datos.",
|
||||
"duplicate-widget-name-error": "Este nombre de widget ya está asignado a otro firmante. Por favor elija un nombre diferente.",
|
||||
"duplicate-template-widget-error": "No puede duplicar esta plantilla porque contiene nombres de widgets duplicados.",
|
||||
"credit-alert": "Alerta de crédito",
|
||||
"document-form-data": "Datos del formulario del documento",
|
||||
"view-formdata-paid-title-error": "La función 'Ver datos del formulario' le permite imprimir y descargar los datos de los widgets completados por los firmantes durante la firma.",
|
||||
"view-formdata-paid-error": "Nota: La función 'Ver datos del formulario' solo está disponible para documentos creados en o después de la versión v2.32.0 que no contengan nombres de widgets duplicados.",
|
||||
"download-all-formdata-paid-title-error": "La función 'Descargar todos los datos del formulario' le permite descargar los datos de los widgets completados por los firmantes para todos los documentos creados a partir de la plantilla.",
|
||||
"download-all-formdata-paid-error": "Nota: La función 'Descargar todos los datos del formulario' solo está disponible para plantillas creadas en o después de la versión v2.32.0 que no contengan nombres de widgets duplicados.",
|
||||
"api-token-not-found": "Token de API no encontrado.",
|
||||
"sub-head-api-credits": "Defina la cantidad máxima de créditos API que este usuario puede consumir.",
|
||||
"consumed-credits": "Créditos consumidos",
|
||||
"allowed-api-credits": "Créditos API permitidos",
|
||||
"allowed-api-credits-help": "Los créditos permitidos deben ser mayores o iguales a los créditos consumidos.",
|
||||
"Enter-allowed-api-credits": "Ingrese los créditos API permitidos",
|
||||
"login-to-sandbox": "Iniciar sesión en sandbox",
|
||||
"your-credits-usage": "Uso de sus créditos",
|
||||
"remaining": "restantes",
|
||||
"used": "usados",
|
||||
"your-credits-usage-help": "Esta es la cantidad de créditos consumidos con su clave API.",
|
||||
"api-credits-updated": "Los créditos de API se han asignado correctamente.",
|
||||
"min-date": "Fecha mínima",
|
||||
"max-date": "Fecha máxima",
|
||||
"invalid-min-date": "Fecha mínima no válida.",
|
||||
"min-less-max": "La fecha mínima debe ser menor o igual a la fecha máxima.",
|
||||
"invalid-max-date": "Fecha máxima no válida.",
|
||||
"max-less-min": "La fecha máxima debe ser mayor o igual a la fecha mínima.",
|
||||
"date-range": "Rango de fechas",
|
||||
"date-range-help": "Establezca una fecha mínima y/o máxima para restringir la selección. La fecha elegida debe estar dentro del rango.",
|
||||
"option-disabled-by-owner": "El creador del documento ha deshabilitado esta opción",
|
||||
"pen-colors": "Colores del bolígrafo",
|
||||
"pen-colors-help": "Elija qué colores de bolígrafo están disponibles para los widgets de Firma e Iniciales. Marque una casilla para mostrar un color y desmárquela para ocultarlo.",
|
||||
"security-key": "Clave de seguridad",
|
||||
"remove-limit": "Eliminar límite",
|
||||
"removed-api-credits": "El límite de créditos API ha sido eliminado.",
|
||||
"enable-authentication": "Habilitar autenticación",
|
||||
"working": "En funcionamiento",
|
||||
"test-security-key": "Probar clave de seguridad",
|
||||
"generate": "Generar",
|
||||
"regenerate": "Regenerar",
|
||||
"signing-date": "Fecha de firma",
|
||||
"date-widget": "Widget de fecha",
|
||||
"mail-failed": "Algo salió mal al enviar el correo electrónico. Por favor, inténtelo de nuevo.",
|
||||
"mail-status-head": "Siguiente paso: ¿Firmar su documento?",
|
||||
"send-to-email": "Enviar por correo electrónico",
|
||||
"date-pref-help-sub-title": "Las preferencias del widget de fecha le permiten definir la configuración predeterminada del widget de fecha. Estos valores se aplican automáticamente cada vez que añade un nuevo widget de fecha a un documento o plantilla. Aún puede personalizar cualquier widget de fecha individual en cualquier momento haciendo clic en el icono de engranaje.",
|
||||
"date-pref-help-format": "Elija el formato de fecha predeterminado utilizado para los widgets de fecha.",
|
||||
"date-pref-help-default-date": "Establezca un valor de fecha predeterminado para los widgets de fecha.",
|
||||
"date-pref-help-signing-date": "Cuando está habilitado, el widget de fecha utiliza automáticamente la fecha en que el firmante firma el documento.",
|
||||
"date-pref-help-read-only": "Cuando está habilitado, el widget de fecha es de solo lectura para el firmante.",
|
||||
"upload-csv": "Subir CSV",
|
||||
"file-selected": "archivo seleccionado",
|
||||
"bulk-send-csv-helper": "Use encabezados para los roles de firmantes (Correo/Nombre/Teléfono del rol) y valores de prellenado (Prellenado: Nombre del campo). Los campos de prellenado obligatorios deben completarse.",
|
||||
"bulk-send-csv-helper-note": "Nota: Para los campos de prellenado de tipo fecha en el CSV, el valor debe proporcionarse en el siguiente formato: {{value}} (ejemplo: {{example}}).",
|
||||
"bulk-send-csv-summary": "{{total}} filas importadas. {{invalid}} filas no válidas fueron omitidas.",
|
||||
"bulk-send-csv-missing-headers": "Faltan encabezados obligatorios del CSV. Roles: {{roles}}. Prellenado: {{prefill}}.",
|
||||
"bulk-send-no-records": "Agregue al menos un destinatario antes de enviar.",
|
||||
"csv-only-support": "Solo se admiten archivos CSV.",
|
||||
"csv-no-records": "No se encontraron registros en el CSV.",
|
||||
"select-at-least-option": "Por favor, seleccione al menos una opción.",
|
||||
"draw-required": "Por favor, dibuje en el campo.",
|
||||
"enter-smtp-username": "Ingrese su nombre de usuario SMTP",
|
||||
"enter-webhook-url": "Ingrese la URL del webhook",
|
||||
"enter-user-name": "Ingrese el nombre de usuario",
|
||||
"enter-tagline": "Ingrese el eslogan",
|
||||
"enter-password-or-click-autogenerate": "Ingrese la contraseña o haga clic en Autogenerar",
|
||||
"enter-daily-email-quota": "Ingrese la cuota diaria de correo electrónico",
|
||||
"enter-custom-regular-expression": "Ingrese una expresión regular personalizada",
|
||||
"enter-value": "Ingrese {{value}}",
|
||||
"are-you-sure-want-to-send-document": "¿Está seguro de que desea enviar el documento?"
|
||||
}
|
||||
@@ -71,6 +71,7 @@
|
||||
"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",
|
||||
@@ -120,7 +121,8 @@
|
||||
"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",
|
||||
@@ -174,7 +176,8 @@
|
||||
"Redirect url": "URL de redirection",
|
||||
"Created Date": "Date de création",
|
||||
"Updated Date": "Date de mise à jour",
|
||||
"Expiry Date": "Date d'expiration"
|
||||
"Expiry Date": "Date d'expiration",
|
||||
"Sent Date": "Date d'envoi"
|
||||
},
|
||||
"btnLabel": {
|
||||
"sign": "Signer",
|
||||
@@ -201,7 +204,10 @@
|
||||
"Save as template": "Enregistrer comme modèle",
|
||||
"Fix & resend": "Corriger et renvoyer",
|
||||
"Kiosk Mode": "Mode Kiosque",
|
||||
"Reset password": "Réinitialiser le mot de passe"
|
||||
"Reset password": "Réinitialiser le mot de passe",
|
||||
"View formdata": "Voir les données du formulaire",
|
||||
"Download all formdata": "Télécharger toutes les données du formulaire",
|
||||
"API credits": "Crédits API"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
@@ -218,7 +224,7 @@
|
||||
"New Template": "Nouveau modèle",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "fichier sélectionné",
|
||||
"files-selected": "fichier(s) sélectionné(s)",
|
||||
"template-title": "Titre du modèle",
|
||||
"document-title": "Titre du document",
|
||||
"title": "Titre",
|
||||
@@ -264,7 +270,7 @@
|
||||
"signature": "Signature",
|
||||
"upload-image": "Télécharger une image",
|
||||
"clear": "Vider",
|
||||
"upload-signature/Image": "télécharger-signature/Image",
|
||||
"upload-signature/Image": "télécharger-signature/Image/timbre",
|
||||
"initials": "Initiales",
|
||||
"API": "API",
|
||||
"api-token": "Jeton API",
|
||||
@@ -331,7 +337,7 @@
|
||||
"quick-send-alert-1": "Tous les rôles dans ce document sont actuellement liés à des contacts. Pour envoyer rapidement des copies de ce modèle à plusieurs signataires, veuillez vous assurer qu'au moins un rôle n'est lié à aucun contact.",
|
||||
"quick-send-alert-2": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
|
||||
"quick-send-alert-3": "Veuillez ajouter au moins un rôle à ce template.",
|
||||
"quick-send-alert-4": "L'envoi rapide a atteint la limite.",
|
||||
"quick-send-alert-4": "Limite atteint pour {{flow}}.",
|
||||
"copy-link": "Copier le lien",
|
||||
"copy": "Copier",
|
||||
"revoke-document": "Révoquer le document",
|
||||
@@ -386,7 +392,9 @@
|
||||
"dropdown": "dérouler",
|
||||
"radio button": "bouton radio",
|
||||
"image": "image",
|
||||
"email": "e-mail"
|
||||
"email": "e-mail",
|
||||
"number": "nombre",
|
||||
"draw": "Dessiner"
|
||||
},
|
||||
"fields": "Des champs",
|
||||
"recipients": "Destinataires",
|
||||
@@ -439,10 +447,23 @@
|
||||
"Next to current widget": "À côté du widget actuel"
|
||||
},
|
||||
"options": "Possibilités",
|
||||
"minimun-check": "Vérification minimale",
|
||||
"minimum-check": "Vérification minimale",
|
||||
"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",
|
||||
@@ -496,14 +517,14 @@
|
||||
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
|
||||
"placeholder-mail-alert": "Vous avez envoyé un e-mail avec succès à {{name}}. Les signataires suivants recevront un e-mail une fois que {{name}} aura signé le document.",
|
||||
"placeholder-mail-alert-you": "Les signataires suivants recevront un e-mail une fois que vous aurez signé le document.",
|
||||
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
|
||||
"placeholder-alert-5": "Vous pouvez signer maintenant ou l’envoyer à votre e-mail pour le terminer plus tard.",
|
||||
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
|
||||
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
|
||||
"email-subject": "Sujet du courriel",
|
||||
"email-body": "corps de l'e-mail",
|
||||
"email-placeholder": "Ajouter le corps de l'e-mail",
|
||||
"reset-to-default": "Réinitialiser aux valeurs par défaut",
|
||||
"cutomize-email": "Personnaliser l'e-mail",
|
||||
"customize-email": "Personnaliser l'e-mail",
|
||||
"upgrade-to-customize-email": "Mise à niveau pour personnaliser l'e-mail",
|
||||
"sign-url": "Signer l'URL",
|
||||
"mails-sent": "Mails envoyés avec succès !",
|
||||
@@ -568,7 +589,7 @@
|
||||
"provide-your-details": "Fournissez vos coordonnées",
|
||||
"provide-email": "Veuillez fournir un e-mail.",
|
||||
"additional-info": "Information additionnelle",
|
||||
"signature-saved-alert": "Signature enregistrée avec succès.",
|
||||
"signature-saved-alert": "enregistrée avec succès.",
|
||||
"saved-successfully": "Enregistré avec succès.",
|
||||
"fill-field": "Veuillez remplir ce champ",
|
||||
"error-template": "Erreur : modèle introuvable ! ",
|
||||
@@ -685,7 +706,7 @@
|
||||
"quantity-of-credits": "Quantité de crédits de prime",
|
||||
"remaining-credits": "Crédits premium disponibles :",
|
||||
"additional-credits": "Veuillez acheter des crédits premium",
|
||||
"remaining-credits-help": "Utilisez des crédits premium pour la signature de documents API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits achetés supplémentaires.",
|
||||
"remaining-credits-help": "Utilisez des crédits premium pour la signature de documents via l'API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits supplémentaires achetés. Si vous êtes sur un plan Teams ou Enterprise, ces crédits peuvent être partagés entre tous les utilisateurs de votre entreprise, sauf si l'administrateur du compte a défini une limite pour votre compte.",
|
||||
"quota-err-quicksend": "Quota atteint, vous n'avez pas assez de crédits.",
|
||||
"buy-credits": "Acheter des crédits premium",
|
||||
"rotate-right": "Faire pivoter à droite",
|
||||
@@ -1223,6 +1244,7 @@
|
||||
},
|
||||
"read-only-date-error": "Le widget de date en lecture seule doit avoir une date par défaut.",
|
||||
"set-date": "Définir la date",
|
||||
"default-date": "Date par défaut",
|
||||
"set-today": "Date de signature",
|
||||
"enter-name": "Saisir le nom",
|
||||
"enter-email": "Saisir l'e-mail",
|
||||
@@ -1242,5 +1264,101 @@
|
||||
"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"
|
||||
"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.",
|
||||
"hide-text-with-asterisks": "Masquer le texte avec des astérisques",
|
||||
"hide-text-with-asterisks-help": "Le texte sur le document signé sera masqué, mais restera accessible à l'expéditeur du document (propriétaire) depuis son compte {{appName}}.",
|
||||
"document-id": "ID du document",
|
||||
"sent-date": "Date d'envoi",
|
||||
"status": "Statut",
|
||||
"signer-form-data-for": "Données du formulaire pour le signataire : {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Nom du widget",
|
||||
"widget-responses": "Réponses du widget",
|
||||
"formdata-download-note": "Remarque : Les signatures, tampons, initiales et images ne seront pas exportés.",
|
||||
"export-all-formdata-error": "Vous ne pouvez pas exporter les données du formulaire car le modèle contient des noms de widgets en double. Veuillez créer un nouveau modèle pour pouvoir exporter les données.",
|
||||
"export-formdata-error": "Vous ne pouvez pas exporter les données du formulaire car le document contient des noms de widgets en double. Veuillez créer un nouveau document sans doublons pour pouvoir exporter les données.",
|
||||
"duplicate-widget-name-error": "Ce nom de widget est déjà attribué à un autre signataire. Veuillez choisir un autre nom.",
|
||||
"duplicate-template-widget-error": "Vous ne pouvez pas dupliquer ce modèle car il contient des noms de widgets en double.",
|
||||
"credit-alert": "Alerte de crédit",
|
||||
"document-form-data": "Données du formulaire du document",
|
||||
"view-formdata-paid-title-error": "La fonction 'Afficher les données du formulaire' vous permet d'imprimer et de télécharger les données des widgets remplies par les signataires lors de la signature.",
|
||||
"view-formdata-paid-error": "Remarque : La fonction 'Afficher les données du formulaire' est uniquement disponible pour les documents créés à partir de la version v2.32.0 et ne contenant pas de noms de widgets en double.",
|
||||
"download-all-formdata-paid-title-error": "La fonction 'Télécharger toutes les données du formulaire' vous permet de télécharger les données des widgets remplies par les signataires pour tous les documents créés à partir du modèle.",
|
||||
"download-all-formdata-paid-error": "Remarque : La fonction 'Télécharger toutes les données du formulaire' est uniquement disponible pour les modèles créés à partir de la version v2.32.0 et ne contenant pas de noms de widgets en double.",
|
||||
"api-token-not-found": "Jeton API introuvable.",
|
||||
"sub-head-api-credits": "Définissez le nombre maximal de crédits API que cet utilisateur est autorisé à consommer.",
|
||||
"consumed-credits": "Crédits consommés",
|
||||
"allowed-api-credits": "Crédits API autorisés",
|
||||
"allowed-api-credits-help": "Les crédits autorisés doivent être supérieurs ou égaux aux crédits consommés.",
|
||||
"Enter-allowed-api-credits": "Saisissez les crédits API autorisés",
|
||||
"login-to-sandbox": "Se connecter au sandbox",
|
||||
"your-credits-usage": "Votre utilisation des crédits",
|
||||
"remaining": "restants",
|
||||
"used": "utilisés",
|
||||
"your-credits-usage-help": "Ceci est le nombre de crédits consommés avec votre clé API.",
|
||||
"api-credits-updated": "Les crédits API ont été attribués avec succès.",
|
||||
"min-date": "Date minimale",
|
||||
"max-date": "Date maximale",
|
||||
"invalid-min-date": "Date minimale invalide.",
|
||||
"min-less-max": "La date minimale doit être inférieure ou égale à la date maximale.",
|
||||
"invalid-max-date": "Date maximale invalide.",
|
||||
"max-less-min": "La date maximale doit être supérieure ou égale à la date minimale.",
|
||||
"date-range": "Plage de dates",
|
||||
"date-range-help": "Définissez une date minimale et/ou maximale pour restreindre la sélection. La date choisie doit se situer dans cette plage.",
|
||||
"option-disabled-by-owner": "Le créateur du document a désactivé cette option",
|
||||
"pen-colors": "Couleurs du stylo",
|
||||
"pen-colors-help": "Choisissez quelles couleurs de stylo sont disponibles pour les widgets Signature et Initiales. Cochez pour afficher une couleur, décochez pour la masquer.",
|
||||
"security-key": "Clé de sécurité",
|
||||
"remove-limit": "Supprimer la limite",
|
||||
"removed-api-credits": "La limite de crédits API a été supprimée.",
|
||||
"enable-authentication": "Activer l'authentification",
|
||||
"working": "En cours",
|
||||
"test-security-key": "Tester la clé de sécurité",
|
||||
"generate": "Générer",
|
||||
"regenerate": "Régénérer",
|
||||
"signing-date": "Date de signature",
|
||||
"date-widget": "Widget de date",
|
||||
"mail-failed": "Une erreur s'est produite lors de l'envoi de l'e-mail. Veuillez réessayer.",
|
||||
"mail-status-head": "Étape suivante : signer votre document ?",
|
||||
"send-to-email": "Envoyer par e-mail",
|
||||
"date-pref-help-sub-title": "Les préférences du widget de date vous permettent de définir les paramètres par défaut du widget de date. Ces paramètres sont automatiquement appliqués lorsque vous ajoutez un nouveau widget de date à un document ou à un modèle. Vous pouvez toujours personnaliser chaque widget de date individuellement en cliquant sur l'icône d'engrenage.",
|
||||
"date-pref-help-format": "Choisissez le format de date par défaut utilisé pour les widgets de date.",
|
||||
"date-pref-help-default-date": "Définissez une valeur de date par défaut pour les widgets de date.",
|
||||
"date-pref-help-signing-date": "Lorsqu'elle est activée, le widget de date utilise automatiquement la date à laquelle le signataire signe le document.",
|
||||
"date-pref-help-read-only": "Lorsqu'elle est activée, le widget de date est en lecture seule pour le signataire.",
|
||||
"upload-csv": "Téléverser un CSV",
|
||||
"file-selected": "fichier sélectionné",
|
||||
"bulk-send-csv-helper": "Utilisez des en-têtes pour les rôles des signataires (E-mail/Nom/Téléphone du rôle) et les valeurs de préremplissage (Préremplissage : Nom du champ). Les champs de préremplissage requis doivent être complétés.",
|
||||
"bulk-send-csv-helper-note": "Remarque : Pour les champs de préremplissage de type date dans le CSV, la valeur doit être fournie dans le format suivant : {{value}} (exemple : {{example}}).",
|
||||
"bulk-send-csv-summary": "{{total}} lignes importées. {{invalid}} lignes non valides ont été ignorées.",
|
||||
"bulk-send-csv-missing-headers": "En-têtes CSV requis manquants. Rôles : {{roles}}. Préremplissage : {{prefill}}.",
|
||||
"bulk-send-no-records": "Veuillez ajouter au moins un destinataire avant l'envoi.",
|
||||
"csv-only-support": "Seuls les fichiers CSV sont pris en charge.",
|
||||
"csv-no-records": "Aucun enregistrement trouvé dans le CSV.",
|
||||
"select-at-least-option": "Veuillez sélectionner au moins une option.",
|
||||
"draw-required": "Veuillez dessiner dans le champ.",
|
||||
"enter-smtp-username": "Saisissez votre nom d'utilisateur SMTP",
|
||||
"enter-webhook-url": "Saisissez l'URL du webhook",
|
||||
"enter-user-name": "Saisissez le nom d'utilisateur",
|
||||
"enter-tagline": "Saisissez le slogan",
|
||||
"enter-password-or-click-autogenerate": "Saisissez le mot de passe ou cliquez sur Génération automatique",
|
||||
"enter-daily-email-quota": "Saisissez le quota quotidien d'e-mails",
|
||||
"enter-custom-regular-expression": "Saisissez une expression régulière personnalisée",
|
||||
"enter-value": "Saisissez {{value}}",
|
||||
"are-you-sure-want-to-send-document": "Êtes-vous sûr de vouloir envoyer le document ?"
|
||||
}
|
||||
@@ -71,6 +71,7 @@
|
||||
"login-page": "लॉगिन पृष्ठ",
|
||||
"or": "या",
|
||||
"signup-page": "साइनअप पृष्ठ",
|
||||
"password-match-length": "नया पासवर्ड और पुष्टि पासवर्ड मेल खाना चाहिए",
|
||||
"password-length": "पासवर्ड 8 वर्ण लंबा होना चाहिए",
|
||||
"password-case": "पासवर्ड में अपरकेस अक्षर, लोअरकेस अक्षर, अंक होना चाहिए",
|
||||
"password-special-char": " पासवर्ड में विशेष वर्ण होना चाहिए",
|
||||
@@ -120,7 +121,8 @@
|
||||
"Teams": "टीमें",
|
||||
"Team-update-successfully": "टीम सफलतापूर्वक अपडेट की गई।",
|
||||
"Users": "उपयोगकर्ता"
|
||||
}
|
||||
},
|
||||
"Global SMTP": "ग्लोबल SMTP"
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "आपके हस्ताक्षर की आवश्यकता है",
|
||||
@@ -166,7 +168,10 @@
|
||||
"Save as template": "टेम्पलेट के रूप में सहेजें",
|
||||
"Fix & resend": "ठीक करें और पुनः भेजें",
|
||||
"Kiosk Mode": "कियोस्क मोड",
|
||||
"Reset password": "पासवर्ड रीसेट करें"
|
||||
"Reset password": "पासवर्ड रीसेट करें",
|
||||
"View formdata": "फॉर्म डेटा देखें",
|
||||
"Download all formdata": "सभी फॉर्म डेटा डाउनलोड करें",
|
||||
"API credits": "API क्रेडिट्स"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "क्रमांक",
|
||||
@@ -201,7 +206,8 @@
|
||||
"Redirect url": "रीडायरेक्ट URL",
|
||||
"Created Date": "निर्माण तिथि",
|
||||
"Updated Date": "अद्यतन तिथि",
|
||||
"Expiry Date": "समाप्ति तिथि"
|
||||
"Expiry Date": "समाप्ति तिथि",
|
||||
"Sent Date": "भेजे जाने की तिथि"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "ये वे दस्तावेज़ हैं जिन्हें आपने शुरू तो किया है लेकिन भेजने के लिए अंतिम रूप नहीं दिया है।",
|
||||
@@ -218,7 +224,7 @@
|
||||
"New Template": "नया टेम्पलेट",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "फ़ाइल चयनित",
|
||||
"files-selected": "फ़ाइलें चयनित",
|
||||
"template-title": "टेम्पलेट शीर्षक",
|
||||
"document-title": "दस्तावेज़ शीर्षक",
|
||||
"title": "शीर्षक",
|
||||
@@ -264,7 +270,7 @@
|
||||
"signature": "हस्ताक्षर",
|
||||
"upload-image": " छवि अपलोड करें",
|
||||
"clear": "स्पष्ट",
|
||||
"upload-signature/Image": "कृपया हस्ताक्षर/छवि अपलोड करें",
|
||||
"upload-signature/Image": "कृपया हस्ताक्षर/छवि अपलोड करें/मोहर",
|
||||
"initials": "हस्ताक्षर",
|
||||
"API": "एपीआई",
|
||||
"api-token": "एपीआई टोकन",
|
||||
@@ -331,7 +337,7 @@
|
||||
"quick-send-alert-1": "इस दस्तावेज़ में सभी भूमिकाएँ वर्तमान में संपर्कों से जुड़ी हुई हैं। इस टेम्पलेट की प्रतियां कई हस्ताक्षरकर्ताओं को त्वरित रूप से भेजने के लिए, कृपया सुनिश्चित करें कि कम से कम एक भूमिका किसी भी संपर्क से जुड़ी नहीं है।",
|
||||
"quick-send-alert-2": "कृपया सुनिश्चित करें कि सभी प्राप्तकर्ताओं के लिए कम से कम एक हस्ताक्षर विजेट जोड़ा गया है।",
|
||||
"quick-send-alert-3": "कृपया इस टेम्पलेट में कम से कम एक भूमिका जोड़ें।",
|
||||
"quick-send-alert-4": "त्वरित भेजें सीमा तक पहुँच गया।",
|
||||
"quick-send-alert-4": "{{flow}} के लिए सीमा पूरी हो गई है।",
|
||||
"copy-link": "लिंक कॉपी करें",
|
||||
"copy": "कॉपी करें",
|
||||
"revoke-document": "दस्तावेज़ रद्द करें",
|
||||
@@ -386,7 +392,9 @@
|
||||
"dropdown": "ड्रॉपडाउन",
|
||||
"radio button": "रेडियो बटन",
|
||||
"image": "छवि",
|
||||
"email": "ईमेल"
|
||||
"email": "ईमेल",
|
||||
"number": "संख्या",
|
||||
"draw": "आरेखित करें"
|
||||
},
|
||||
"fields": "क्षेत्र",
|
||||
"recipients": "प्राप्तकर्ता",
|
||||
@@ -439,10 +447,23 @@
|
||||
"Next to current widget": "वर्तमान विजेट के आगे"
|
||||
},
|
||||
"options": "विकल्प",
|
||||
"minimun-check": "न्यूनतम जाँच",
|
||||
"minimum-check": "न्यूनतम जाँच",
|
||||
"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": "लेबल छिपाएँ",
|
||||
@@ -496,14 +517,14 @@
|
||||
"placeholder-alert-4": "आपने सभी प्राप्तकर्ताओं को सफलतापूर्वक मेल भेज दिए हैं!",
|
||||
"placeholder-mail-alert": "आपने {{name}} को सफलतापूर्वक ईमेल भेज दिया है। {{name}} द्वारा दस्तावेज़ पर हस्ताक्षर करने के बाद बाद के हस्ताक्षरकर्ताओं को ईमेल प्राप्त होंगे",
|
||||
"placeholder-mail-alert-you": "आपके दस्तावेज़ पर हस्ताक्षर करने के बाद अगले हस्ताक्षरकर्ता को ईमेल प्राप्त होगा।",
|
||||
"placeholder-alert-5": "क्या आप अभी दस्तावेज़ पर हस्ताक्षर करना चाहते हैं?",
|
||||
"placeholder-alert-5": "आप अभी हस्ताक्षर कर सकते हैं या इसे बाद में पूरा करने के लिए अपने ईमेल पर भेज सकते हैं।",
|
||||
"placeholder-alert-6": "मेल भेजने के लिए कृपया मेल एडॉप्टर सेटअप करें!",
|
||||
"placeholder-alert-7": "प्लेसहोल्डर जोड़ने के लिए कृपया हस्ताक्षरकर्ता चुनें!",
|
||||
"email-subject": "ईमेल विषय",
|
||||
"email-body": "ईमेल मुख्य भाग",
|
||||
"email-placeholder": "ईमेल का मुख्य भाग जोड़ें",
|
||||
"reset-to-default": "डिफ़ॉल्ट पर रीसेट करें",
|
||||
"cutomize-email": "ईमेल अनुकूलित करें",
|
||||
"customize-email": "ईमेल अनुकूलित करें",
|
||||
"upgrade-to-customize-email": "ईमेल अनुकूलित करने के लिए अपग्रेड करें",
|
||||
"sign-url": "यूआरएल पर हस्ताक्षर करें",
|
||||
"mails-sent": "मेल भेजे गए",
|
||||
@@ -568,7 +589,7 @@
|
||||
"provide-your-details": "अपना विवरण प्रदान करें",
|
||||
"provide-email": "कृपया ईमेल प्रदान करें।",
|
||||
"additional-info": "अतिरिक्त जानकारी",
|
||||
"signature-saved-alert": "हस्ताक्षर सफलतापूर्वक सहेजा गया।",
|
||||
"signature-saved-alert": "सफलतापूर्वक सहेजा गया।",
|
||||
"saved-successfully": "सफलतापूर्वक सहेजा गया।",
|
||||
"fill-field": "कृपया इस फ़ील्ड को भरें",
|
||||
"error-template": "त्रुटि: टेम्पलेट नहीं मिला!",
|
||||
@@ -684,7 +705,7 @@
|
||||
"select-date-format": "एक दिनांक प्रारूप चुनें",
|
||||
"quantity-of-credits": "प्रीमियम क्रेडिट की मात्रा",
|
||||
"remaining-credits": "प्रीमियम क्रेडिट उपलब्ध:",
|
||||
"remaining-credits-help": "अपनी वेबसाइट पर एपीआई दस्तावेज़ हस्ताक्षर, थोक भेजने या OpenSign एकीकरण को एम्बेड करने के लिए प्रीमियम क्रेडिट का उपयोग करें। आपके पास {{allowedcredits}} शामिल क्रेडिट और {{addoncredits}} अतिरिक्त खरीदे गए क्रेडिट शेष हैं।",
|
||||
"remaining-credits-help": "API दस्तावेज़ साइनिंग, बल्क सेंडिंग या अपनी वेबसाइट पर OpenSign इंटीग्रेशन एम्बेड करने के लिए प्रीमियम क्रेडिट का उपयोग करें। आपके पास {{allowedcredits}} शामिल क्रेडिट और {{addoncredits}} अतिरिक्त खरीदे हुए क्रेडिट शेष हैं। यदि आप Teams या Enterprise प्लान पर हैं, तो ये क्रेडिट आपकी कंपनी के सभी उपयोगकर्ताओं के बीच साझा किए जा सकते हैं, जब तक कि आपके खाते के लिए अकाउंट एडमिन ने कोई सीमा निर्धारित न की हो।",
|
||||
"additional-credits": "कृपया प्रीमियम क्रेडिट खरीदें",
|
||||
"quota-err-quicksend": "कोटा पहुँच गया, आपके पास पर्याप्त क्रेडिट नहीं हैं।",
|
||||
"buy-credits": "प्रीमियम क्रेडिट खरीदें",
|
||||
@@ -1223,6 +1244,7 @@
|
||||
},
|
||||
"read-only-date-error": "रीड-ओनली दिनांक विजेट में एक डिफ़ॉल्ट दिनांक होना आवश्यक है।",
|
||||
"set-date": "दिनांक सेट करें",
|
||||
"default-date": "डिफ़ॉल्ट तिथि",
|
||||
"set-today": "हस्ताक्षर की तिथि",
|
||||
"enter-name": "नाम दर्ज करें",
|
||||
"enter-email": "ईमेल दर्ज करें",
|
||||
@@ -1242,5 +1264,101 @@
|
||||
"Please provide required parameters.": "कृपया आवश्यक पैरामीटर प्रदान करें।",
|
||||
"reset-password": "उपयोगकर्ता पासवर्ड रीसेट करें",
|
||||
"enter-strong-password": "एक मज़बूत पासवर्ड दर्ज करें या स्वचालित रूप से उत्पन्न करें।",
|
||||
"autogenerate": "स्वतः उत्पन्न करें"
|
||||
"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": "यदि कोई व्यक्तिगत उपयोगकर्ता-स्तरीय सेटिंग्स कॉन्फ़िगर नहीं की गई हैं, तो ये वैश्विक सेटिंग्स स्वचालित रूप से लागू होंगी।",
|
||||
"hide-text-with-asterisks": "टेक्स्ट को तारांकन (*) के साथ छुपाएँ",
|
||||
"hide-text-with-asterisks-help": "हस्ताक्षरित दस्तावेज़ पर टेक्स्ट दृश्य से छिपा रहेगा, लेकिन दस्तावेज़ भेजने वाले (मालिक) के लिए उनके {{appName}} खाते से सुलभ रहेगा।",
|
||||
"document-id": "दस्तावेज़ ID",
|
||||
"sent-date": "भेजे जाने की तिथि",
|
||||
"status": "स्थिति",
|
||||
"signer-form-data-for": "हस्ताक्षरकर्ता के लिए फ़ॉर्म डेटा: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "विजेट नाम",
|
||||
"widget-responses": "विजेट प्रतिक्रियाएँ",
|
||||
"formdata-download-note": "नोट: हस्ताक्षर, स्टैम्प, प्रारंभिक अक्षर और छवियाँ निर्यात नहीं की जाएँगी।",
|
||||
"export-all-formdata-error": "आप फ़ॉर्म डेटा निर्यात नहीं कर सकते क्योंकि टेम्पलेट में डुप्लिकेट विजेट नाम हैं। कृपया नया टेम्पलेट बनाएं ताकि आप फ़ॉर्म डेटा निर्यात कर सकें।",
|
||||
"export-formdata-error": "आप फ़ॉर्म डेटा निर्यात नहीं कर सकते क्योंकि दस्तावेज़ में डुप्लिकेट विजेट नाम हैं। कृपया नया दस्तावेज़ बनाएं जिसमें डुप्लिकेट विजेट नाम न हों।",
|
||||
"duplicate-widget-name-error": "यह विजेट नाम पहले से ही किसी अन्य साइनर को सौंपा गया है। कृपया कोई दूसरा नाम चुनें।",
|
||||
"duplicate-template-widget-error": "आप इस टेम्पलेट को डुप्लिकेट नहीं कर सकते क्योंकि इसमें डुप्लिकेट विजेट नाम हैं।",
|
||||
"credit-alert": "क्रेडिट अलर्ट",
|
||||
"document-form-data": "दस्तावेज़ फ़ॉर्म डेटा",
|
||||
"view-formdata-paid-title-error": "'फ़ॉर्म डेटा देखें' सुविधा आपको हस्ताक्षर करते समय साइनरों द्वारा भरे गए विजेट डेटा को प्रिंट और डाउनलोड करने की सुविधा देती है.",
|
||||
"view-formdata-paid-error": "नोट: 'फ़ॉर्म डेटा देखें' सुविधा केवल उन दस्तावेज़ों के लिए उपलब्ध है जो v2.32.0 या उसके बाद बनाए गए हों और जिनमें डुप्लिकेट विजेट नाम न हों.",
|
||||
"download-all-formdata-paid-title-error": "'सभी फ़ॉर्म डेटा डाउनलोड करें' सुविधा आपको टेम्पलेट से बनाए गए सभी दस्तावेज़ों के लिए साइनरों द्वारा भरे गए विजेट डेटा को डाउनलोड करने देती है.",
|
||||
"download-all-formdata-paid-error": "नोट: 'सभी फ़ॉर्म डेटा डाउनलोड करें' सुविधा केवल उन टेम्पलेट्स के लिए उपलब्ध है जो v2.32.0 या उसके बाद बनाए गए हों और जिनमें डुप्लिकेट विजेट नाम न हों.",
|
||||
"api-token-not-found": "API टोकन नहीं मिला।",
|
||||
"sub-head-api-credits": "इस उपयोगकर्ता द्वारा उपभोग किए जाने वाले API क्रेडिट की अधिकतम संख्या निर्धारित करें।",
|
||||
"consumed-credits": "उपयोग किए गए क्रेडिट",
|
||||
"allowed-api-credits": "अनुमत API क्रेडिट",
|
||||
"allowed-api-credits-help": "अनुमत क्रेडिट उपयोग किए गए क्रेडिट के बराबर या उससे अधिक होने चाहिए।",
|
||||
"Enter-allowed-api-credits": "अनुमत API क्रेडिट दर्ज करें",
|
||||
"login-to-sandbox": "सैंडबॉक्स में लॉगिन करें",
|
||||
"your-credits-usage": "आपके क्रेडिट उपयोग",
|
||||
"remaining": "शेष",
|
||||
"used": "उपयोग किए गए",
|
||||
"your-credits-usage-help": "यह आपके API कुंजी द्वारा उपभोग किए गए क्रेडिट की संख्या है।",
|
||||
"api-credits-updated": "API क्रेडिट सफलतापूर्वक आवंटित कर दिए गए हैं।",
|
||||
"min-date": "न्यूनतम तिथि",
|
||||
"max-date": "अधिकतम तिथि",
|
||||
"invalid-min-date": "अमान्य न्यूनतम तिथि।",
|
||||
"min-less-max": "न्यूनतम तिथि अधिकतम तिथि से कम या उसके बराबर होनी चाहिए।",
|
||||
"invalid-max-date": "अमान्य अधिकतम तिथि।",
|
||||
"max-less-min": "अधिकतम तिथि न्यूनतम तिथि से अधिक या उसके बराबर होनी चाहिए।",
|
||||
"date-range": "तिथि सीमा",
|
||||
"date-range-help": "चयन को सीमित करने के लिए न्यूनतम और/या अधिकतम तिथि सेट करें। चयनित तिथि इस सीमा के भीतर होनी चाहिए।",
|
||||
"option-disabled-by-owner": "दस्तावेज़ निर्माता ने इस विकल्प को अक्षम कर दिया है",
|
||||
"pen-colors": "पेन के रंग",
|
||||
"pen-colors-help": "हस्ताक्षर और प्रारंभिक विजेट्स के लिए उपलब्ध पेन रंग चुनें। किसी रंग को दिखाने के लिए चेक करें और छिपाने के लिए अनचेक करें।",
|
||||
"security-key": "सुरक्षा कुंजी",
|
||||
"remove-limit": "सीमा हटाएँ",
|
||||
"removed-api-credits": "API क्रेडिट सीमा हटा दी गई है।",
|
||||
"enable-authentication": "प्रमाणीकरण सक्षम करें",
|
||||
"working": "कार्यरत",
|
||||
"test-security-key": "सुरक्षा कुंजी का परीक्षण करें",
|
||||
"generate": "जनरेट करें",
|
||||
"regenerate": "पुनः जनरेट करें",
|
||||
"signing-date": "हस्ताक्षर तिथि",
|
||||
"date-widget": "तिथि विजेट",
|
||||
"mail-failed": "ईमेल भेजते समय कुछ गलत हो गया। कृपया पुनः प्रयास करें।",
|
||||
"mail-status-head": "अगला कदम: अपने दस्तावेज़ पर हस्ताक्षर करें?",
|
||||
"send-to-email": "ईमेल पर भेजें",
|
||||
"date-pref-help-sub-title": "डेट विजेट प्राथमिकताएँ आपको डेट विजेट के लिए डिफ़ॉल्ट सेटिंग्स निर्धारित करने की अनुमति देती हैं। जब भी आप किसी दस्तावेज़ या टेम्पलेट में नया डेट विजेट जोड़ते हैं, ये डिफ़ॉल्ट सेटिंग्स स्वतः लागू हो जाती हैं। आप गियर आइकन पर क्लिक करके किसी भी डेट विजेट को कभी भी अनुकूलित कर सकते हैं।",
|
||||
"date-pref-help-format": "डेट विजेट के लिए उपयोग किए जाने वाले डिफ़ॉल्ट तिथि प्रारूप का चयन करें।",
|
||||
"date-pref-help-default-date": "डेट विजेट के लिए एक डिफ़ॉल्ट तिथि मान सेट करें।",
|
||||
"date-pref-help-signing-date": "सक्षम होने पर, डेट विजेट स्वतः उस तिथि का उपयोग करता है जब साइनर दस्तावेज़ पर हस्ताक्षर करता है।",
|
||||
"date-pref-help-read-only": "सक्षम होने पर, डेट विजेट साइनर के लिए केवल-पढ़ने योग्य होता है।",
|
||||
"upload-csv": "CSV अपलोड करें",
|
||||
"file-selected": "फ़ाइल चयनित",
|
||||
"bulk-send-csv-helper": "साइनर भूमिकाओं (भूमिका ईमेल/नाम/फ़ोन) और प्रीफ़िल मानों (प्रीफ़िल: फ़ील्ड नाम) के लिए हेडर का उपयोग करें। आवश्यक प्रीफ़िल फ़ील्ड भरना अनिवार्य है।",
|
||||
"bulk-send-csv-helper-note": "नोट: CSV में तिथि-प्रकार के प्रीफ़िल फ़ील्ड्स के लिए मान इस प्रारूप में प्रदान किया जाना चाहिए: {{value}} (उदाहरण: {{example}})।",
|
||||
"bulk-send-csv-summary": "{{total}} पंक्तियाँ आयात की गईं। {{invalid}} अमान्य पंक्तियाँ छोड़ दी गईं।",
|
||||
"bulk-send-csv-missing-headers": "आवश्यक CSV हेडर अनुपलब्ध हैं। भूमिकाएँ: {{roles}}। प्रीफ़िल: {{prefill}}।",
|
||||
"bulk-send-no-records": "भेजने से पहले कृपया कम से कम एक प्राप्तकर्ता जोड़ें।",
|
||||
"csv-only-support": "केवल CSV फ़ाइलें समर्थित हैं।",
|
||||
"csv-no-records": "CSV में कोई रिकॉर्ड नहीं मिला।",
|
||||
"select-at-least-option": "कृपया कम से कम एक विकल्प चुनें।",
|
||||
"draw-required": "कृपया बॉक्स में ड्रॉ करें।",
|
||||
"enter-smtp-username": "अपना SMTP उपयोगकर्ता नाम दर्ज करें",
|
||||
"enter-webhook-url": "Webhook URL दर्ज करें",
|
||||
"enter-user-name": "उपयोगकर्ता नाम दर्ज करें",
|
||||
"enter-tagline": "टैगलाइन दर्ज करें",
|
||||
"enter-password-or-click-autogenerate": "पासवर्ड दर्ज करें या ऑटो-जनरेट पर क्लिक करें",
|
||||
"enter-daily-email-quota": "दैनिक ईमेल कोटा दर्ज करें",
|
||||
"enter-custom-regular-expression": "कस्टम रेगुलर एक्सप्रेशन दर्ज करें",
|
||||
"enter-value": "{{value}} दर्ज करें",
|
||||
"are-you-sure-want-to-send-document": "क्या आप वाकई दस्तावेज़ भेजना चाहते हैं?"
|
||||
}
|
||||
@@ -71,6 +71,7 @@
|
||||
"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",
|
||||
@@ -120,7 +121,8 @@
|
||||
"Teams": "Team",
|
||||
"Team-update-successfully": "Team aggiornato con successo.",
|
||||
"Users": "Utenti"
|
||||
}
|
||||
},
|
||||
"Global SMTP": "SMTP Globale"
|
||||
},
|
||||
"dashboard-card": {
|
||||
"Need your Signature": "Necessita della tua firma",
|
||||
@@ -166,7 +168,10 @@
|
||||
"Save as template": "Salva come modello",
|
||||
"Fix & resend": "Correggi e reinvia",
|
||||
"Kiosk Mode": "Modalità Kiosk",
|
||||
"Reset password": "Reimposta password"
|
||||
"Reset password": "Reimposta password",
|
||||
"View formdata": "Visualizza i dati del modulo",
|
||||
"Download all formdata": "Scarica tutti i dati del modulo",
|
||||
"API credits": "Crediti API"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nr.",
|
||||
@@ -201,7 +206,8 @@
|
||||
"Redirect url": "URL di reindirizzamento",
|
||||
"Created Date": "Data di creazione",
|
||||
"Updated Date": "Data di aggiornamento",
|
||||
"Expiry Date": "Data di scadenza"
|
||||
"Expiry Date": "Data di scadenza",
|
||||
"Sent Date": "Data di invio"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Questi sono documenti che hai iniziato ma non hai ancora finalizzato per l'invio.",
|
||||
@@ -218,7 +224,7 @@
|
||||
"New Template": "Nuovo modello",
|
||||
"file-type": "pdf, png, jpg, jpeg",
|
||||
"docx": "docx",
|
||||
"file-selected": "file selezionato",
|
||||
"files-selected": "file selezionati",
|
||||
"template-title": "Titolo del modello",
|
||||
"document-title": "Titolo del documento",
|
||||
"title": "Titolo",
|
||||
@@ -263,8 +269,8 @@
|
||||
"my-signature": "La mia firma",
|
||||
"signature": "Firma",
|
||||
"upload-image": "Carica immagine",
|
||||
"clear": "Chiara",
|
||||
"upload-signature/Image": "Carica firma/immagine",
|
||||
"clear": "Cancella",
|
||||
"upload-signature/Image": "Carica firma/immagine/timbro",
|
||||
"initials": "Iniziali",
|
||||
"API": "API",
|
||||
"api-token": "Token API",
|
||||
@@ -331,7 +337,7 @@
|
||||
"quick-send-alert-1": "Tutti i ruoli in questo documento sono attualmente collegati a contatti. Per inviare rapidamente copie di questo modello a più firmatari, assicurati che almeno un ruolo non sia collegato a nessun contatto.",
|
||||
"quick-send-alert-2": "Assicurati che ci sia almeno un widget firma aggiunto per tutti i destinatari.",
|
||||
"quick-send-alert-3": "Si prega di aggiungere almeno un ruolo a questo template.",
|
||||
"quick-send-alert-4": "Limite di invio rapido raggiunto.",
|
||||
"quick-send-alert-4": "Limite raggiunto per {{flow}}.",
|
||||
"copy-link": "Copia link",
|
||||
"copy": "Copia",
|
||||
"revoke-document": "Revoca documento",
|
||||
@@ -386,7 +392,9 @@
|
||||
"dropdown": "menu a tendina",
|
||||
"radio button": "pulsante di opzione",
|
||||
"image": "immagine",
|
||||
"email": "email"
|
||||
"email": "email",
|
||||
"number": "numero",
|
||||
"draw": "Disegno"
|
||||
},
|
||||
"fields": "Campi",
|
||||
"recipients": "Destinatari",
|
||||
@@ -439,10 +447,23 @@
|
||||
"Next to current widget": "Accanto al widget corrente"
|
||||
},
|
||||
"options": "Opzioni",
|
||||
"minimun-check": "Controllo minimo",
|
||||
"minimum-check": "Controllo minimo",
|
||||
"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",
|
||||
@@ -496,14 +517,14 @@
|
||||
"placeholder-alert-4": "Hai inviato con successo le mail a tutti i destinatari!",
|
||||
"placeholder-mail-alert": "Hai inviato con successo un'e-mail a {{name}}. I firmatari successivi riceveranno un'e-mail una volta che {{name}} avrà firmato il documento.",
|
||||
"placeholder-mail-alert-you": "I firmatari successivi riceveranno un'email dopo che avrai firmato il documento.",
|
||||
"placeholder-alert-5": "Vuoi firmare i documenti ora?",
|
||||
"placeholder-alert-5": "Puoi firmare ora oppure inviarlo alla tua email per completarlo in seguito.",
|
||||
"placeholder-alert-6": "Configura l'adattatore email per inviare mail!",
|
||||
"placeholder-alert-7": "Seleziona il firmatario per aggiungere un segnaposto!",
|
||||
"email-subject": "Oggetto email",
|
||||
"email-body": "Corpo email",
|
||||
"email-placeholder": "aggiungi corpo dell'email",
|
||||
"reset-to-default": "Reimposta predefinita",
|
||||
"cutomize-email": "Personalizza Email",
|
||||
"customize-email": "Personalizza Email",
|
||||
"upgrade-to-customize-email": "Aggiorna per personalizzare l'Email",
|
||||
"sign-url": "URL Firma",
|
||||
"mails-sent": "Mail inviate",
|
||||
@@ -568,7 +589,7 @@
|
||||
"provide-your-details": "Fornisci i tuoi dettagli",
|
||||
"provide-email": "Fornisci l'email.",
|
||||
"additional-info": "Informazioni aggiuntive",
|
||||
"signature-saved-alert": "Firma salvata con successo.",
|
||||
"signature-saved-alert": "salvata con successo.",
|
||||
"saved-successfully": "Salvato con successo.",
|
||||
"fill-field": "Compila questo campo",
|
||||
"error-template": "Errore: Modello non trovato!",
|
||||
@@ -684,7 +705,7 @@
|
||||
"select-date-format": "Seleziona un formato data",
|
||||
"quantity-of-credits": "Quantità di crediti premium",
|
||||
"remaining-credits": "Crediti premium disponibili:",
|
||||
"remaining-credits-help": "Usa i crediti premium per firmare documenti API, invii in massa o per integrare OpenSign nel tuo sito web. Hai a disposizione {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti acquistati aggiuntivi rimanenti.",
|
||||
"remaining-credits-help": "Usa i crediti premium per la firma di documenti tramite API, l'invio massivo o per integrare OpenSign nel tuo sito web. Hai ancora {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti aggiuntivi acquistati. Se sei in un piano Teams o Enterprise, questi crediti possono essere condivisi tra tutti gli utenti della tua azienda, a meno che l'amministratore dell'account non abbia impostato un limite specifico per il tuo account.",
|
||||
"additional-credits": "Acquista crediti premium",
|
||||
"quota-err-quicksend": "Quota raggiunta, non hai abbastanza crediti.",
|
||||
"buy-credits": "Acquista Crediti Premium",
|
||||
@@ -1223,6 +1244,7 @@
|
||||
},
|
||||
"read-only-date-error": "Il widget data di sola lettura deve avere una data predefinita.",
|
||||
"set-date": "Imposta data",
|
||||
"default-date": "Data predefinita",
|
||||
"set-today": "Data di firma",
|
||||
"enter-name": "Inserisci nome",
|
||||
"enter-email": "Inserisci e-mail",
|
||||
@@ -1242,5 +1264,101 @@
|
||||
"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"
|
||||
"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.",
|
||||
"hide-text-with-asterisks": "Nascondi il testo con asterischi",
|
||||
"hide-text-with-asterisks-help": "Il testo nel documento firmato sarà nascosto dalla visualizzazione ma rimarrà accessibile al mittente (proprietario) tramite il suo account {{appName}}.",
|
||||
"document-id": "ID documento",
|
||||
"sent-date": "Data di invio",
|
||||
"status": "Stato",
|
||||
"signer-form-data-for": "Dati del modulo per il firmatario: {{signerName}} <{{signerEmail}}>",
|
||||
"widget-name": "Nome del widget",
|
||||
"widget-responses": "Risposte del widget",
|
||||
"formdata-download-note": "Nota: Firme, timbri, iniziali e immagini non verranno esportati.",
|
||||
"export-all-formdata-error": "Non è possibile esportare i dati del modulo perché il modello contiene nomi di widget duplicati. Creare un nuovo modello per poter esportare i dati.",
|
||||
"export-formdata-error": "Non è possibile esportare i dati del modulo perché il documento contiene nomi di widget duplicati. Creare un nuovo documento senza nomi duplicati per poter esportare i dati.",
|
||||
"duplicate-widget-name-error": "Questo nome di widget è già assegnato a un altro firmatario. Scegli un nome diverso.",
|
||||
"duplicate-template-widget-error": "Non è possibile duplicare questo modello perché contiene nomi di widget duplicati.",
|
||||
"credit-alert": "Avviso di credito",
|
||||
"document-form-data": "Dati del modulo del documento",
|
||||
"view-formdata-paid-title-error": "La funzione 'Visualizza dati del modulo' consente di stampare e scaricare i dati dei widget compilati dai firmatari durante la firma.",
|
||||
"view-formdata-paid-error": "Nota: La funzione 'Visualizza dati del modulo' è disponibile solo per documenti creati dalla versione v2.32.0 in poi che non contengono nomi di widget duplicati.",
|
||||
"download-all-formdata-paid-title-error": "La funzione 'Scarica tutti i dati del modulo' consente di scaricare i dati dei widget compilati dai firmatari per tutti i documenti creati dal modello.",
|
||||
"download-all-formdata-paid-error": "Nota: La funzione 'Scarica tutti i dati del modulo' è disponibile solo per modelli creati dalla versione v2.32.0 in poi che non contengano nomi di widget duplicati.",
|
||||
"api-token-not-found": "Token API non trovato.",
|
||||
"sub-head-api-credits": "Definisci il numero massimo di crediti API che questo utente può consumare.",
|
||||
"consumed-credits": "Crediti consumati",
|
||||
"allowed-api-credits": "Crediti API consentiti",
|
||||
"allowed-api-credits-help": "I crediti consentiti devono essere maggiori o uguali ai crediti consumati.",
|
||||
"Enter-allowed-api-credits": "Inserisci i crediti API consentiti",
|
||||
"login-to-sandbox": "Accedi alla sandbox",
|
||||
"your-credits-usage": "Utilizzo dei tuoi crediti",
|
||||
"remaining": "rimanenti",
|
||||
"used": "usati",
|
||||
"your-credits-usage-help": "Questo è il numero di crediti consumati con la tua chiave API.",
|
||||
"api-credits-updated": "I crediti API sono stati assegnati con successo.",
|
||||
"min-date": "Data minima",
|
||||
"max-date": "Data massima",
|
||||
"invalid-min-date": "Data minima non valida.",
|
||||
"min-less-max": "La data minima deve essere minore o uguale alla data massima.",
|
||||
"invalid-max-date": "Data massima non valida.",
|
||||
"max-less-min": "La data massima deve essere maggiore o uguale alla data minima.",
|
||||
"date-range": "Intervallo di date",
|
||||
"date-range-help": "Imposta una data minima e/o massima per limitare la selezione. La data scelta deve rientrare nell'intervallo.",
|
||||
"option-disabled-by-owner": "Il creatore del documento ha disabilitato questa opzione.",
|
||||
"pen-colors": "Colori della penna",
|
||||
"pen-colors-help": "Scegli quali colori della penna sono disponibili per i widget Firma e Iniziali. Seleziona una casella per mostrare un colore e deselezionala per nasconderlo.",
|
||||
"security-key": "Chiave di sicurezza",
|
||||
"remove-limit": "Rimuovi limite",
|
||||
"removed-api-credits": "Il limite dei crediti API è stato rimosso.",
|
||||
"enable-authentication": "Abilita autenticazione",
|
||||
"working": "In funzione",
|
||||
"test-security-key": "Testa la chiave di sicurezza",
|
||||
"generate": "Genera",
|
||||
"regenerate": "Rigenera",
|
||||
"signing-date": "Data di firma",
|
||||
"date-widget": "Widget data",
|
||||
"mail-failed": "Si è verificato un errore durante l'invio dell'e-mail. Per favore riprova.",
|
||||
"mail-status-head": "Passaggio successivo: firmare il tuo documento?",
|
||||
"send-to-email": "Invia tramite email",
|
||||
"date-pref-help-sub-title": "Le preferenze del widget Data consentono di definire le impostazioni predefinite per il widget Data. Queste impostazioni vengono applicate automaticamente ogni volta che si aggiunge un nuovo widget Data a un documento o a un modello. È comunque possibile personalizzare qualsiasi widget Data in qualsiasi momento facendo clic sull'icona a forma di ingranaggio.",
|
||||
"date-pref-help-format": "Scegli il formato di data predefinito utilizzato per i widget Data.",
|
||||
"date-pref-help-default-date": "Imposta un valore di data predefinito per i widget Data.",
|
||||
"date-pref-help-signing-date": "Quando abilitato, il widget Data utilizza automaticamente la data in cui il firmatario firma il documento.",
|
||||
"date-pref-help-read-only": "Quando abilitato, il widget Data è in sola lettura per il firmatario.",
|
||||
"upload-csv": "Carica CSV",
|
||||
"file-selected": "file selezionato",
|
||||
"bulk-send-csv-helper": "Utilizza le intestazioni per i ruoli dei firmatari (Email/Nome/Telefono del ruolo) e i valori di precompilazione (Precompilazione: Nome campo). I campi di precompilazione obbligatori devono essere compilati.",
|
||||
"bulk-send-csv-helper-note": "Nota: Per i campi di precompilazione di tipo data nel CSV, il valore deve essere fornito in questo formato: {{value}} (esempio: {{example}}).",
|
||||
"bulk-send-csv-summary": "{{total}} righe importate. {{invalid}} righe non valide sono state ignorate.",
|
||||
"bulk-send-csv-missing-headers": "Intestazioni CSV obbligatorie mancanti. Ruoli: {{roles}}. Precompilazione: {{prefill}}.",
|
||||
"bulk-send-no-records": "Aggiungi almeno un destinatario prima dell'invio.",
|
||||
"csv-only-support": "Sono supportati solo file CSV.",
|
||||
"csv-no-records": "Nessun record trovato nel CSV.",
|
||||
"select-at-least-option": "Seleziona almeno un'opzione.",
|
||||
"draw-required": "Per favore, disegna nel riquadro.",
|
||||
"enter-smtp-username": "Inserisci il nome utente SMTP",
|
||||
"enter-webhook-url": "Inserisci l'URL del webhook",
|
||||
"enter-user-name": "Inserisci il nome utente",
|
||||
"enter-tagline": "Inserisci lo slogan",
|
||||
"enter-password-or-click-autogenerate": "Inserisci la password o fai clic su Genera automaticamente",
|
||||
"enter-daily-email-quota": "Inserisci la quota giornaliera di e-mail",
|
||||
"enter-custom-regular-expression": "Inserisci un'espressione regolare personalizzata",
|
||||
"enter-value": "Inserisci {{value}}",
|
||||
"are-you-sure-want-to-send-document": "Sei sicuro di voler inviare il documento?"
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,302 +0,0 @@
|
||||
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;
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import {
|
||||
useTranslation
|
||||
} from "react-i18next";
|
||||
import { withSessionValidation } from "../utils";
|
||||
|
||||
function generatePassword(length) {
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
@@ -67,7 +69,7 @@ const AddUser = (props) => {
|
||||
}
|
||||
};
|
||||
// Define a function to handle form submission
|
||||
const handleSubmit = async (e) => {
|
||||
const handleSubmit = withSessionValidation(async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!emailRegex.test(formdata.email)) {
|
||||
@@ -130,7 +132,7 @@ const AddUser = (props) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Define a function to handle the "add yourself" checkbox
|
||||
const handleReset = () => {
|
||||
@@ -225,7 +227,6 @@ const AddUser = (props) => {
|
||||
className="block text-xs font-semibold"
|
||||
>
|
||||
{t("phone")}
|
||||
{/* <span className="text-[red] text-[13px]"> *</span> */}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -1,316 +0,0 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import axios from "axios";
|
||||
import SuggestionInput from "./shared/fields/SuggestionInput";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
emailRegex,
|
||||
} from "../constant/const";
|
||||
const BulkSendUi = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [forms, setForms] = useState([]);
|
||||
const formRef = useRef(null);
|
||||
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
|
||||
const [isSubmit, setIsSubmit] = useState(false);
|
||||
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||
const [isDisableBulkSend, setIsDisableBulkSend] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [signers, setSigners] = useState([]);
|
||||
const [emails, setEmails] = useState([]);
|
||||
const [isPrefillExist, setIsPrefillExist] = useState(false);
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
//function to check at least one signature field exist
|
||||
const signatureExist = async () => {
|
||||
const isPrefill = props?.Placeholders.some((x) => x?.Role === "prefill");
|
||||
if (isPrefill) {
|
||||
setIsPrefillExist(isPrefill);
|
||||
}
|
||||
setIsDisableBulkSend(false);
|
||||
const getPlaceholder = props?.Placeholders;
|
||||
const removePrefill = getPlaceholder.filter((x) => x?.Role !== "prefill");
|
||||
const checkIsSignatureExistt = removePrefill?.every((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((holder) =>
|
||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||
)
|
||||
);
|
||||
setIsSignatureExist(checkIsSignatureExistt);
|
||||
setIsLoader(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (scrollOnNextUpdate && formRef.current) {
|
||||
formRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "end",
|
||||
inline: "nearest"
|
||||
});
|
||||
setScrollOnNextUpdate(false);
|
||||
}
|
||||
}, [forms, scrollOnNextUpdate]);
|
||||
|
||||
useEffect(() => {
|
||||
(() => {
|
||||
if (props?.Placeholders?.length > 0) {
|
||||
let users = [];
|
||||
let emails = [];
|
||||
props?.Placeholders?.forEach((element) => {
|
||||
const signerEmail = element?.email || element?.signerPtr?.Email;
|
||||
|
||||
// only add when there's a non-empty signerEmail
|
||||
if (signerEmail) {
|
||||
emails = [...emails, signerEmail];
|
||||
}
|
||||
if (!element.signerObjId) {
|
||||
users = [
|
||||
...users,
|
||||
{
|
||||
fieldId: element.Id,
|
||||
email: "",
|
||||
label: element.Role,
|
||||
signer: {}
|
||||
}
|
||||
];
|
||||
}
|
||||
});
|
||||
setEmails(emails);
|
||||
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
|
||||
const signer = props.item?.Signers?.filter((x) => x?.objectId);
|
||||
setSigners(signer);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
const handleInputChange = (index, signer, fieldIndex) => {
|
||||
const newForms = [...forms];
|
||||
newForms[index].fields[fieldIndex].email = signer?.Email
|
||||
? signer?.Email
|
||||
: signer || "";
|
||||
newForms[index].fields[fieldIndex].signer = signer?.objectId ? signer : "";
|
||||
setForms(newForms);
|
||||
};
|
||||
|
||||
|
||||
const handleRemoveForm = (index) => {
|
||||
const updatedForms = forms.filter((_, i) => i !== index);
|
||||
setForms(updatedForms);
|
||||
};
|
||||
|
||||
function validateEmails(data) {
|
||||
for (const item of data) {
|
||||
let email = "";
|
||||
for (const field of item.fields) {
|
||||
if (!emailRegex.test(field.email)) {
|
||||
alert(t("invalid-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else if (email === field.email || emails?.includes(field.email)) {
|
||||
alert(t("duplicate-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else {
|
||||
email = field.email;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSubmit(true);
|
||||
if (validateEmails(forms)) {
|
||||
// Create a copy of Placeholders array from props.item
|
||||
let Placeholders = [...props.Placeholders];
|
||||
// Initialize an empty array to store updated documents
|
||||
let Documents = [];
|
||||
// Loop through each form
|
||||
forms.forEach((form) => {
|
||||
//checking if user enter email which already exist as a signer then add user in a signers array
|
||||
let existSigner = [];
|
||||
form.fields.map((data) => {
|
||||
if (data.signer) {
|
||||
existSigner.push(data.signer);
|
||||
}
|
||||
});
|
||||
// Map through the copied Placeholders array to update email values
|
||||
const updatedPlaceholders = Placeholders.map((placeholder) => {
|
||||
// Find the field in the current form that matches the placeholder Id
|
||||
const field = form.fields.find(
|
||||
(element) => parseInt(element.fieldId) === placeholder.Id
|
||||
);
|
||||
// If a matching field is found, update the email value in the placeholder
|
||||
const signer = field?.signer?.objectId ? field.signer : "";
|
||||
if (field) {
|
||||
if (signer) {
|
||||
return {
|
||||
...placeholder,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...placeholder,
|
||||
email: field.email,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
}
|
||||
}
|
||||
// If no matching field is found, keep the placeholder as is
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Push a new document object with updated Placeholders into the Documents array
|
||||
if (existSigner?.length > 0) {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
Signers: signers ? [...signers, ...existSigner] : [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
SignatureType: props.signatureType,
|
||||
Signers: signers
|
||||
});
|
||||
}
|
||||
});
|
||||
await batchQuery(Documents);
|
||||
} else {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
|
||||
const batchQuery = async (Documents) => {
|
||||
const token =
|
||||
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
|
||||
const functionsUrl = `${localStorage.getItem(
|
||||
"baseUrl"
|
||||
)}functions/batchdocuments`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
...token,
|
||||
};
|
||||
const params = { Documents: JSON.stringify(Documents) };
|
||||
try {
|
||||
const res = await axios.post(functionsUrl, params, { headers: headers });
|
||||
// console.log("res ", res);
|
||||
if (res.data && res.data.result) {
|
||||
props.handleClose("success", Documents?.length);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
props.handleClose("error", 0);
|
||||
} finally {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{isLoader ? (
|
||||
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
|
||||
<Loader />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{isSubmit && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{isPrefillExist ? (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("prefill-bulk-error")}
|
||||
</div>
|
||||
) : !isDisableBulkSend ? (
|
||||
<>
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
<>
|
||||
{props.Placeholders?.some((x) => !x.signerObjId) ? (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-y-auto">
|
||||
{forms?.map((form, index) => (
|
||||
<div
|
||||
key={form.Id}
|
||||
className="p-3 op-card border-[1px] border-gray-400 mt-3 mx-4 mb-4 bg-base-200 text-base-content grid grid-cols-1 md:grid-cols-2 gap-2 relative"
|
||||
>
|
||||
{form?.fields?.map((field, fieldIndex) => (
|
||||
<div
|
||||
className="flex flex-col"
|
||||
key={field.fieldId}
|
||||
>
|
||||
<label className="block text-xs font-semibold">
|
||||
{field.label}
|
||||
</label>
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.value}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(
|
||||
index,
|
||||
signer,
|
||||
fieldIndex
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<button
|
||||
onClick={() => handleRemoveForm(index)}
|
||||
className="absolute right-3 top-1 text-[red] border-[1px] border-[red] rounded-lg w-[1.7rem] h-[1.7rem]"
|
||||
>
|
||||
<i className="fa-light fa-trash"></i>
|
||||
</button>
|
||||
)}
|
||||
<div ref={formRef}></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col mx-4 mb-4 gap-3">
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-accent focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>{" "}
|
||||
<span>{t("send")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-1")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-2")}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-3")}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BulkSendUi;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -7,7 +7,6 @@ const ColumnSelector = ({
|
||||
allColumns = [],
|
||||
visibleColumns = [],
|
||||
columnLabels = {},
|
||||
defaultColumns = [],
|
||||
onApply,
|
||||
onClose
|
||||
}) => {
|
||||
@@ -42,7 +41,7 @@ const ColumnSelector = ({
|
||||
type="checkbox"
|
||||
checked={selected.includes(col)}
|
||||
onChange={() => handleChange(col)}
|
||||
className="mb-0"
|
||||
className="mb-0 cursor-pointer"
|
||||
/>
|
||||
<span className="whitespace-nowrap ml-1">
|
||||
{t(`report-heading.${col}`, { defaultValue: col })}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DateFormat = ({ selectDate, dateFormatList, handleChangeFormat }) => {
|
||||
const selectedFormatIndex = dateFormatList?.findIndex(
|
||||
(item) => item.format === selectDate?.format
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex flex-col md:items-center md:flex-row gap-y-3 my-2">
|
||||
<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-2"
|
||||
value={selectedFormatIndex >= 0 ? selectedFormatIndex : ""}
|
||||
onChange={(e) => handleChangeFormat(e)}
|
||||
>
|
||||
<option value="" disabled>
|
||||
{t("select-date-format")}
|
||||
</option>
|
||||
{dateFormatList.map((data, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={ind} key={ind}>
|
||||
{data?.date ? data?.date : "nodata"}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<span className="text-xs text-gray-400 ml-1 uppercase">
|
||||
{selectDate.format}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateFormat;
|
||||
@@ -0,0 +1,88 @@
|
||||
import { forwardRef } from "react";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import {
|
||||
getDefaultDate,
|
||||
getMonth,
|
||||
getYear,
|
||||
months,
|
||||
years
|
||||
} from "../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DatePicker = ({
|
||||
selectDate,
|
||||
format,
|
||||
minDate,
|
||||
maxDate,
|
||||
onChange,
|
||||
handleClear
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const CustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
className="border-gray-400 rounded-[50px] border-[1px] px-3 text-xs py-2 focus:outline-none hover:border-base-content "
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
{value}
|
||||
<i
|
||||
className={`${value ? "ml-[15px]" : "w-20"} fa-light fa-calendar `}
|
||||
></i>
|
||||
</div>
|
||||
));
|
||||
CustomInput.displayName = "CustomInput";
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span>{t("default-date")}: </span>
|
||||
<ReactDatePicker
|
||||
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={<CustomInput />}
|
||||
onChange={(date) => onChange(date)}
|
||||
dateFormat={
|
||||
selectDate ? selectDate?.format : format ? format : "MM/dd/yyyy"
|
||||
}
|
||||
portalId="root-portal"
|
||||
/>
|
||||
<span
|
||||
onClick={() => handleClear()}
|
||||
className="underline text-blue-500 cursor-pointer ml-2"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatePicker;
|
||||
@@ -1,12 +1,8 @@
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { TouchBackend } from "react-dnd-touch-backend";
|
||||
import {
|
||||
DndProvider,
|
||||
TouchTransition,
|
||||
MouseTransition,
|
||||
Preview
|
||||
MouseTransition
|
||||
} from "react-dnd-multi-backend";
|
||||
import DragElement from "./pdf/DragElement";
|
||||
import LazyPage from "../primitives/LazyPage";
|
||||
import { GuidelinesProvider } from "../context/GuidelinesContext";
|
||||
|
||||
@@ -16,33 +12,13 @@ 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,8 @@ import {
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { toggleSidebar } from "../redux/reducers/sidebarReducer.js";
|
||||
import { sessionStatus } from "../redux/reducers/userReducer.js";
|
||||
import { toggleSidebar } from "../redux/reducers/sidebarReducer";
|
||||
import { sessionStatus } from "../redux/reducers/userReducer";
|
||||
|
||||
const Header = ({ isConsole, setIsLoggingOut }) => {
|
||||
const { t, i18n } = useTranslation();
|
||||
@@ -60,7 +60,6 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
|
||||
setAppLogo(logo);
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
setIsOpen(false);
|
||||
setIsLoggingOut(true);
|
||||
@@ -141,7 +140,10 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 ml-2">
|
||||
<div className="h-[25px] md:h-[40px] w-auto overflow-hidden">
|
||||
<div
|
||||
onClick={() => navigate("/dashboard/35KBoSgoAK")}
|
||||
className="h-[25px] md:h-[40px] w-auto overflow-hidden cursor-pointer"
|
||||
>
|
||||
{applogo && (
|
||||
<img
|
||||
className="object-contain h-full w-auto"
|
||||
@@ -195,7 +197,7 @@ const Header = ({ isConsole, setIsLoggingOut }) => {
|
||||
</div>
|
||||
<ul
|
||||
tabIndex={0}
|
||||
className={`mt-3 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-56 ${
|
||||
className={`mt-4 z-[1] p-2 shadow op-dropdown-open op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-56 ${
|
||||
isOpen ? "" : "hidden"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useLocation, matchPath } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMemo } from "react";
|
||||
@@ -70,13 +69,13 @@ export default function Title() {
|
||||
[t, prefix, appName]
|
||||
);
|
||||
const manifestUrl = useManifestUrl(appName, logo);
|
||||
|
||||
|
||||
return (
|
||||
<Helmet>
|
||||
<>
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={title} />
|
||||
{logo && <link rel="icon" type="image/png" href={logo} />}
|
||||
<link rel="manifest" href={manifestUrl} />
|
||||
</Helmet>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,605 @@
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo
|
||||
} from "react";
|
||||
import axios from "axios";
|
||||
import SuggestionInput from "../shared/fields/SuggestionInput";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
emailRegex,
|
||||
} from "../../constant/const";
|
||||
import {
|
||||
convertPdfArrayBuffer,
|
||||
getSignedUrl,
|
||||
isBase64,
|
||||
getBase64FromUrl,
|
||||
} from "../../constant/Utils";
|
||||
import {
|
||||
formatCSVDate,
|
||||
handleEmbedPrefillToDoc,
|
||||
normalizeKey
|
||||
} from "../../utils";
|
||||
import RenderWidgets from "./components/RenderWidgets";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setBulkLoader } from "../../redux/reducers/widgetSlice";
|
||||
|
||||
const EXCLUDED_PREFILL_TYPES = new Set(["image", "draw"]);
|
||||
const EXCLUDED_WIDGET_TYPES = new Set([
|
||||
"image",
|
||||
"signature",
|
||||
"stamp",
|
||||
"initials"
|
||||
]);
|
||||
const ALL_EXCLUDED_TYPES = new Set([
|
||||
...EXCLUDED_PREFILL_TYPES,
|
||||
...EXCLUDED_WIDGET_TYPES
|
||||
]);
|
||||
|
||||
const requiredAsteriskCls = (isRequired = false) => {
|
||||
return isRequired ? "after:content-['_*'] after:text-red-500" : "";
|
||||
};
|
||||
const BulkSendUi = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const dispatch = useDispatch();
|
||||
const { isBulkLoader } = useSelector((state) => state.widget);
|
||||
const [forms, setForms] = useState([]);
|
||||
const [isSignatureExist, setIsSignatureExist] = useState();
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const [signers, setSigners] = useState([]);
|
||||
const [emails, setEmails] = useState([]);
|
||||
const [isVacantRoles, setIsVacantRoles] = useState(false);
|
||||
const [fields, setFields] = useState([]);
|
||||
const [headers, setHeaders] = useState([]);
|
||||
|
||||
const tokenHeader = useMemo(() => {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
|
||||
};
|
||||
return {
|
||||
...headers,
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
}, [props?.jwttoken]);
|
||||
|
||||
// Initialize form state (fields, widgets/responses, emails, headers, forms, signers, allowedForm) from template placeholders with existing values.
|
||||
const initializeWidgetsWithValues = async () => {
|
||||
if (props?.Placeholders?.length > 0) {
|
||||
const roles = props?.Placeholders?.filter((p) => !p.signerObjId) || [];
|
||||
const emails = [];
|
||||
const users = await Promise.all(
|
||||
roles.map(async (role) => {
|
||||
const isPrefill = role?.Role?.toLowerCase() === "prefill";
|
||||
const uniqueNames = new Set();
|
||||
const pages = role?.placeHolder ?? [];
|
||||
const widgets = await Promise.all(
|
||||
pages.flatMap((page) =>
|
||||
page.pos
|
||||
.filter((widget) => {
|
||||
const widgetName = widget?.options?.name;
|
||||
if (!widgetName) return false; //|| widget?.options?.isReadOnly
|
||||
// exclude certain widget types for non-prefill
|
||||
if (EXCLUDED_WIDGET_TYPES.has(widget.type) && !isPrefill)
|
||||
return false;
|
||||
// unique by widget name
|
||||
if (uniqueNames.has(widgetName)) return false;
|
||||
uniqueNames.add(widgetName);
|
||||
return true;
|
||||
})
|
||||
.map(async (widget) => {
|
||||
let response = "";
|
||||
if (ALL_EXCLUDED_TYPES.has(widget.type)) {
|
||||
const url =
|
||||
widget.options?.response ||
|
||||
widget.options?.defaultValue ||
|
||||
"";
|
||||
if (url && !isBase64(url)) {
|
||||
const signedUrl = await getSignedUrl(
|
||||
url,
|
||||
"", // docId
|
||||
props.item.objectId, // templateId
|
||||
);
|
||||
|
||||
response = await getBase64FromUrl(signedUrl, true);
|
||||
} else {
|
||||
response = "";
|
||||
}
|
||||
} else {
|
||||
if (widget.type === "date") {
|
||||
const value =
|
||||
widget.options?.response ||
|
||||
widget.options?.defaultValue ||
|
||||
"";
|
||||
response = value ? formatCSVDate(widget, value) : "";
|
||||
} else {
|
||||
response =
|
||||
widget.options?.response ||
|
||||
widget.options?.defaultValue ||
|
||||
"";
|
||||
}
|
||||
}
|
||||
const label = `${role.Role}::${widget.options?.name}`;
|
||||
|
||||
return {
|
||||
...widget,
|
||||
label: label,
|
||||
response,
|
||||
pageNumber: page.pageNumber
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
const signerEmail =
|
||||
normalizeKey(role?.email) || role?.signerPtr?.Email;
|
||||
if (signerEmail) emails.push(signerEmail);
|
||||
|
||||
return {
|
||||
fieldId: role.Id,
|
||||
email: "",
|
||||
label: role.Role,
|
||||
signer: {},
|
||||
widgets: widgets
|
||||
};
|
||||
})
|
||||
);
|
||||
setFields(users);
|
||||
setEmails(emails);
|
||||
|
||||
// Build headers with required ordering:
|
||||
// 1) all role emails first
|
||||
// 2) then all widgets
|
||||
const emailHeaders = [];
|
||||
const widgetHeaders = [];
|
||||
for (const role of users) {
|
||||
const isPrefill = role?.label?.toLowerCase() === "prefill";
|
||||
|
||||
if (!isPrefill) {
|
||||
emailHeaders.push({
|
||||
label: `${role.label}::Email`,
|
||||
role: role.label,
|
||||
type: "role",
|
||||
isRequired: true
|
||||
});
|
||||
}
|
||||
|
||||
for (const widget of role?.widgets ?? []) {
|
||||
const isRequired =
|
||||
widget?.options?.status === "required" && isPrefill;
|
||||
|
||||
widgetHeaders.push({
|
||||
label: widget?.label,
|
||||
role: role?.label,
|
||||
type: widget?.type,
|
||||
isRequired
|
||||
});
|
||||
}
|
||||
}
|
||||
setHeaders([...emailHeaders, ...widgetHeaders]);
|
||||
setForms((prev) => [...prev, { Id: 1, fields: users }]);
|
||||
const signer = props.item?.Signers?.filter((x) => x?.objectId);
|
||||
setSigners(signer);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
signatureExist();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const checkSignatureAndRoles = (placeholders = []) => {
|
||||
const filtered = placeholders.filter((x) => x?.Role !== "prefill");
|
||||
|
||||
const isSignExist = filtered.every((p) =>
|
||||
p?.placeHolder?.some((h) => h?.pos?.some((p) => p?.type === "signature"))
|
||||
);
|
||||
|
||||
setIsSignatureExist(isSignExist);
|
||||
setIsVacantRoles(placeholders.some((x) => !x.signerObjId));
|
||||
};
|
||||
|
||||
//function to check at least one signature field exist
|
||||
const signatureExist = async () => {
|
||||
setIsLoader(true);
|
||||
await initializeWidgetsWithValues();
|
||||
checkSignatureAndRoles(props?.Placeholders);
|
||||
setIsLoader(false);
|
||||
};
|
||||
|
||||
const handleInputChange = (formIndex, signer, role) => {
|
||||
const newForms = [...forms];
|
||||
const email = signer?.Email ? signer?.Email : signer || "";
|
||||
const fieldId = newForms[formIndex].fields.findIndex(
|
||||
(f) => f.label === role
|
||||
);
|
||||
newForms[formIndex].fields[fieldId].email = normalizeKey(email);
|
||||
newForms[formIndex].fields[fieldId].signer = signer?.objectId ? signer : "";
|
||||
setForms(newForms);
|
||||
};
|
||||
|
||||
const handleWidgetDetails = (
|
||||
value = "",
|
||||
formIndex,
|
||||
RoleIndex = 0,
|
||||
widgetIndex
|
||||
) => {
|
||||
const newForms = [...forms];
|
||||
newForms[formIndex].fields[RoleIndex].widgets[widgetIndex].response = value;
|
||||
setForms(newForms);
|
||||
};
|
||||
|
||||
|
||||
function validateEmails(data) {
|
||||
for (const item of data) {
|
||||
let email = "";
|
||||
const filterFields = item.fields.filter((p) => p.label !== "prefill");
|
||||
for (const field of filterFields) {
|
||||
if (!emailRegex.test(field.email)) {
|
||||
alert(t("invalid-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else if (email === field.email || emails?.includes(field.email)) {
|
||||
alert(t("duplicate-email-found", { email: field.email }));
|
||||
return false;
|
||||
} else {
|
||||
email = field.email;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// `updateWidgetValues` will update responses for all widget types
|
||||
const updateWidgetValues = (widgetValues = [], placeholders = []) => {
|
||||
if (!widgetValues?.length) return placeholders;
|
||||
const responseByName = new Map(
|
||||
widgetValues
|
||||
.map((w) => [w?.options?.name, w?.response])
|
||||
.filter(([name]) => !!name)
|
||||
);
|
||||
|
||||
return placeholders.map((ph) => {
|
||||
const updatedPages = (ph?.placeHolder ?? []).map((page) => {
|
||||
const updatedPos = (page?.pos ?? []).map((widgetDetails) => {
|
||||
const name = widgetDetails?.options?.name;
|
||||
if (!name || !responseByName.has(name)) return widgetDetails;
|
||||
|
||||
const newResponse = responseByName.get(name);
|
||||
|
||||
return {
|
||||
...widgetDetails,
|
||||
options: {
|
||||
...widgetDetails.options,
|
||||
// update response; if you DON'T want undefined to overwrite, keep the ?? fallback
|
||||
response:
|
||||
newResponse ??
|
||||
widgetDetails.options?.response ??
|
||||
widgetDetails.options?.defaultValue
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return { ...page, pos: updatedPos };
|
||||
});
|
||||
|
||||
return { ...ph, placeHolder: updatedPages };
|
||||
});
|
||||
};
|
||||
|
||||
// `removeWidgetValues` will clear out responses for draw/image/signature/stamp/initials widgets
|
||||
const removeWidgetValues = (widgetValues = [], placeholders = []) => {
|
||||
if (!widgetValues?.length) return placeholders;
|
||||
return placeholders.map((ph) => {
|
||||
const updatedPages = (ph?.placeHolder ?? []).map((page) => {
|
||||
const updatedPos = (page?.pos ?? []).map((widgetDetails) => {
|
||||
const isImageType = ALL_EXCLUDED_TYPES.has(widgetDetails?.type);
|
||||
return {
|
||||
...widgetDetails,
|
||||
options: {
|
||||
...widgetDetails.options,
|
||||
// update response; if you DON'T want undefined to overwrite, keep the ?? fallback
|
||||
response: isImageType ? "" : widgetDetails.options?.response,
|
||||
defaultValue: isImageType
|
||||
? ""
|
||||
: widgetDetails.options?.defaultValue
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
return { ...page, pos: updatedPos };
|
||||
});
|
||||
|
||||
return { ...ph, placeHolder: updatedPages };
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
dispatch(setBulkLoader(true));
|
||||
if (!forms.length) {
|
||||
alert(t("bulk-send-no-records"));
|
||||
dispatch(setBulkLoader(false));
|
||||
return;
|
||||
}
|
||||
if (validateEmails(forms)) {
|
||||
// Create a copy of Placeholders array from props.item
|
||||
let Placeholders = [...props.Placeholders];
|
||||
// Initialize an empty array to store updated documents
|
||||
let Documents = [];
|
||||
let error = "";
|
||||
// Loop through each form
|
||||
for (const form of forms) {
|
||||
//checking if user enter email which already exist as a signer then add user in a signers array
|
||||
let existSigner = [];
|
||||
form.fields.map((data) => {
|
||||
if (data.signer) {
|
||||
existSigner.push(data.signer);
|
||||
}
|
||||
});
|
||||
// Map through the copied Placeholders array to update email values
|
||||
const updatedPlaceholders = Placeholders.map((placeholder) => {
|
||||
// Find the field in the current form that matches the placeholder Id
|
||||
const field = form.fields.find(
|
||||
(element) => parseInt(element.fieldId) === placeholder.Id
|
||||
);
|
||||
// If a matching field is found, update the email value in the placeholder
|
||||
const signer = field?.signer?.objectId ? field.signer : "";
|
||||
if (field) {
|
||||
if (signer) {
|
||||
return {
|
||||
...placeholder,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...placeholder,
|
||||
email: normalizeKey(field.email),
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
}
|
||||
}
|
||||
// If no matching field is found, keep the placeholder as is
|
||||
return placeholder;
|
||||
});
|
||||
const widgetValues = form?.fields?.flatMap((f) => f.widgets);
|
||||
const updateWidgetPlaceholder = updateWidgetValues(
|
||||
widgetValues,
|
||||
updatedPlaceholders
|
||||
);
|
||||
const url = props.item?.URL || props.item?.SignedUrl;
|
||||
let signedUrl = await getSignedUrl(
|
||||
url,
|
||||
"", //docId
|
||||
props.item.objectId, // templateId
|
||||
);
|
||||
const prefillField = form?.fields?.find((f) => f.label === "prefill");
|
||||
const prefills = prefillField?.widgets ?? [];
|
||||
if (prefills?.length > 0) {
|
||||
const prefillDetails = updateWidgetPlaceholder?.find(
|
||||
(x) => x.Role === "prefill"
|
||||
);
|
||||
const pdfArrayBuffer = await convertPdfArrayBuffer(signedUrl);
|
||||
if (pdfArrayBuffer === "Error") {
|
||||
error = t("something-went-wrong-mssg");
|
||||
dispatch(setBulkLoader(false));
|
||||
break;
|
||||
}
|
||||
signedUrl = await handleEmbedPrefillToDoc(
|
||||
prefillDetails,
|
||||
1, // scale
|
||||
pdfArrayBuffer,
|
||||
[], // prefillImg,
|
||||
props.item?.ExtUserPtr?.UserId?.objectId // userId
|
||||
);
|
||||
if (signedUrl?.error) {
|
||||
error = signedUrl.error.includes("not compatible")
|
||||
? t("pdf-uncompatible", { appName: appName })
|
||||
: signedUrl?.error;
|
||||
dispatch(setBulkLoader(false));
|
||||
break;
|
||||
}
|
||||
}
|
||||
const placeholders = removeWidgetValues(
|
||||
widgetValues,
|
||||
updateWidgetPlaceholder
|
||||
);
|
||||
if (!signedUrl) {
|
||||
error = t("something-went-wrong-mssg");
|
||||
dispatch(setBulkLoader(false));
|
||||
break;
|
||||
}
|
||||
// Push a new document object with updated Placeholders into the Documents array
|
||||
if (existSigner?.length > 0) {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
URL: signedUrl,
|
||||
Placeholders: placeholders,
|
||||
Signers: signers ? [...signers, ...existSigner] : [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
URL: signedUrl,
|
||||
Placeholders: placeholders,
|
||||
SignatureType: props.signatureType,
|
||||
Signers: signers
|
||||
});
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
alert(error);
|
||||
} else {
|
||||
await batchQuery(Documents);
|
||||
}
|
||||
} else {
|
||||
dispatch(setBulkLoader(false));
|
||||
}
|
||||
};
|
||||
|
||||
const batchQuery = async (Documents) => {
|
||||
const functionsUrl = `${localStorage.getItem("baseUrl")}functions/batchdocuments`;
|
||||
const headers = {
|
||||
...tokenHeader,
|
||||
};
|
||||
const params = { Documents: JSON.stringify(Documents) };
|
||||
try {
|
||||
const res = await axios.post(functionsUrl, params, { headers: headers });
|
||||
if (res.data && res.data.result) {
|
||||
props.handleClose("success", Documents?.length);
|
||||
}
|
||||
} catch (err) {
|
||||
const message =
|
||||
err?.response?.data?.error || err?.message || "something went wrong.";
|
||||
console.error("Error sending documents:", message);
|
||||
props.handleClose("error", 0, message);
|
||||
} finally {
|
||||
dispatch(setBulkLoader(false));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (isLoader) {
|
||||
return (
|
||||
<div className="w-full h-[100px] flex justify-center items-center z-[999]">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* {isBulkLoader && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)} */}
|
||||
{props.Placeholders?.length > 0 ? (
|
||||
isSignatureExist ? (
|
||||
isVacantRoles ? (
|
||||
<>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="min-h-max max-h-[250px] overflow-auto">
|
||||
{
|
||||
forms?.length > 0 && (
|
||||
<div className="mx-4">
|
||||
<table className="op-table border-collapse w-full">
|
||||
<thead className="text-[13px] text-center">
|
||||
<tr className="border-y-[1px]">
|
||||
{/* Roles + widgets */}
|
||||
{headers?.map((header) => (
|
||||
<th
|
||||
key={header.label}
|
||||
className={`${requiredAsteriskCls(header?.isRequired)} p-2`}
|
||||
>
|
||||
{header.label}
|
||||
</th>
|
||||
))}
|
||||
{forms?.length > 1 && (
|
||||
<th className="p-2">{t("action")}</th>
|
||||
)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-[12px] text-base-content">
|
||||
{forms.map((form, formIndex) => {
|
||||
const fields = form?.fields ?? [];
|
||||
const emailFields = fields.filter(
|
||||
(f) => f.label !== "prefill"
|
||||
);
|
||||
const widgets = fields.flatMap(
|
||||
(f, fieldIndex) =>
|
||||
(f.widgets ?? []).map(
|
||||
(widget, widgetIndex) => ({
|
||||
widget,
|
||||
fieldIndex, // keep the fieldIndex that produced this widget
|
||||
widgetIndex
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<tr key={form.Id}>
|
||||
{/* Email cell (label + SuggestionInput) */}
|
||||
{emailFields?.map((field, fieldIndex) => (
|
||||
<td key={field.fieldId} className="p-2">
|
||||
<div className="flex flex-col min-w-max">
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
value={field.email ?? ""}
|
||||
index={fieldIndex}
|
||||
onChange={(signer) =>
|
||||
handleInputChange(
|
||||
formIndex,
|
||||
signer,
|
||||
field.label
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
))}
|
||||
|
||||
{widgets.map(
|
||||
({ widget, fieldIndex, widgetIndex }) => (
|
||||
<td key={widget.key} className="p-2">
|
||||
<RenderWidgets
|
||||
widget={widget}
|
||||
formIndex={formIndex}
|
||||
prefillIndex={widgetIndex}
|
||||
handleWidgetDetails={(value) =>
|
||||
handleWidgetDetails(
|
||||
value,
|
||||
formIndex,
|
||||
fieldIndex,
|
||||
widgetIndex
|
||||
)
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="flex flex-row flex-wrap pb-3 pt-2 px-3 gap-3 justify-center">
|
||||
<button
|
||||
type="submit"
|
||||
className="op-btn op-btn-accent w-[150px] focus:outline-none"
|
||||
>
|
||||
<i className="fa-light fa-paper-plane"></i>
|
||||
<span>{t("send")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-1")}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-2")}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
|
||||
{t("quick-send-alert-3")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BulkSendUi;
|
||||
@@ -0,0 +1,582 @@
|
||||
import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
||||
import DatePicker from "react-datepicker";
|
||||
import SignatureCanvas from "react-signature-canvas";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import moment from "moment";
|
||||
import {
|
||||
changeDateToMomentFormat,
|
||||
compressedFileSize,
|
||||
getMonth,
|
||||
getYear,
|
||||
months,
|
||||
radioButtonWidget,
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
years
|
||||
} from "../../../constant/Utils";
|
||||
import PenColorComponent from "../../pdf/tab/PenColorComponent";
|
||||
import { getDatePickerDate, toHtmlPattern } from "../../../utils";
|
||||
import CellsInput from "../../shared/fields/CellsInput";
|
||||
import { emailRegex } from "../../../constant/const";
|
||||
|
||||
const inputOpt = new Set(["text", "email", "number"]);
|
||||
const PREFILL_PREFIX = "prefill::";
|
||||
|
||||
const inputTypes = {
|
||||
email: emailRegex,
|
||||
number: /^[0-9\s]*$/,
|
||||
ssn: /^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}$/
|
||||
};
|
||||
const inputValidation = (pattern, type) => {
|
||||
if (pattern) return pattern;
|
||||
return inputTypes[type] || "";
|
||||
};
|
||||
const canavasTheme = `opensigncss:bg-white opensigndark:bg-[#121212] opensigndark:border-[#f6f3f4]/20 opensigncss:border-gray-300 opensigndark:hover:border-white opensigncss:hover:border-black border-[1px]`;
|
||||
const DateWidget = ({ widget, isRequired, onChange }) => {
|
||||
const format = widget?.options?.validation?.format || "MM/dd/yyyy";
|
||||
|
||||
const PrefillDateInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
style={{ fontFamily: "Arial, sans-serif" }}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full"
|
||||
onClick={onClick}
|
||||
ref={ref}
|
||||
>
|
||||
{value ? value : format}
|
||||
<i className="fa-light fa-calendar ml-[5px]"></i>
|
||||
</div>
|
||||
));
|
||||
PrefillDateInput.displayName = "PrefillDateInput";
|
||||
|
||||
const handleDate = (widget) => {
|
||||
// The getDatePickerDate function retrieves the date in the correct format supported by the DatePicker.
|
||||
try {
|
||||
return getDatePickerDate(widget?.response, format);
|
||||
} catch (err) {
|
||||
console.error("handleDate error ", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
//function to set date with required date format onchange date
|
||||
const handleOnDateChange = (date) => {
|
||||
let updateDate = date;
|
||||
let newDate;
|
||||
const isSpecialDateFormat =
|
||||
format && ["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(format);
|
||||
if (isSpecialDateFormat) {
|
||||
newDate = moment(updateDate).format(changeDateToMomentFormat(format));
|
||||
} 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
|
||||
newDate = new Date(updateDate);
|
||||
newDate = moment(newDate.getTime()).format(
|
||||
changeDateToMomentFormat(format)
|
||||
);
|
||||
}
|
||||
onChange(newDate);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-max">
|
||||
<DatePicker
|
||||
portalId="datepicker-portal-root"
|
||||
id={`widget-${widget?.options?.name}-${widget.key}`}
|
||||
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={handleDate(widget)}
|
||||
onChange={(date) => handleOnDateChange(date, widget)}
|
||||
customInput={<PrefillDateInput />}
|
||||
dateFormat={widget?.options?.validation?.format || "MM/dd/yyyy"}
|
||||
required={isRequired}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const TextWidget = ({ widget, isRequired, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState([]);
|
||||
const inputType = widget?.options?.validation?.type || "";
|
||||
const serverRegex = widget?.options?.validation?.pattern;
|
||||
const isPredfineType = inputType ? inputOpt?.has(inputType) : "";
|
||||
const regExpression = inputValidation(serverRegex, inputType);
|
||||
const pattern = useMemo(() => toHtmlPattern(regExpression), [regExpression]);
|
||||
|
||||
useEffect(() => {
|
||||
const response =
|
||||
widget?.options?.response ?? widget?.options?.defaultValue ?? "";
|
||||
const isNumber = inputType === "number" && response;
|
||||
setValue(isNumber ? Number(response) : response);
|
||||
}, []);
|
||||
const handleInputChange = (e) => {
|
||||
const text = e.target.value || "";
|
||||
setValue(text);
|
||||
onChange(text);
|
||||
};
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-max">
|
||||
<input
|
||||
type={isPredfineType ? inputType : "text"}
|
||||
id={`widget-${widget?.options?.name}-${widget.key}`}
|
||||
value={value}
|
||||
placeholder={
|
||||
widget?.options?.hint ||
|
||||
t("enter-value", { value: widget?.options?.name })
|
||||
}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onChange={(e) => handleInputChange(e)}
|
||||
pattern={pattern || undefined} // if no pattern, browser won't do pattern validation
|
||||
onInvalid={(e) => {
|
||||
const el = e.currentTarget;
|
||||
// ✅ Only override message if pattern exists AND the error is patternMismatch
|
||||
if (pattern && el.validity.patternMismatch) {
|
||||
el.setCustomValidity(t("validation-alert-1"));
|
||||
} else {
|
||||
el.setCustomValidity("");
|
||||
}
|
||||
}}
|
||||
onInput={(e) => {
|
||||
e.currentTarget.setCustomValidity("");
|
||||
}}
|
||||
required={isRequired}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const CheckboxWidget = ({ widget, isRequired, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedCheckbox(
|
||||
widget?.options?.response ?? widget?.options?.defaultValue ?? []
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleCheckboxValue = (isChecked, ind) => {
|
||||
const prevArr = Array.isArray(selectedCheckbox) ? selectedCheckbox : [];
|
||||
|
||||
const updateResponse = isChecked
|
||||
? prevArr.includes(ind)
|
||||
? prevArr
|
||||
: [...prevArr, ind] // add once
|
||||
: prevArr.filter((v) => v !== ind); // remove
|
||||
|
||||
setSelectedCheckbox(updateResponse);
|
||||
onChange(updateResponse);
|
||||
};
|
||||
const noneSelected = selectedCheckbox?.length === 0;
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col gap-y-1 min-w-max">
|
||||
{widget.options?.values?.map((value, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
className="select-none-cls flex items-center text-center gap-0.5"
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${widget.key + ind}`}
|
||||
className="mt-[2px] op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
checked={selectedCheckbox.includes(ind)}
|
||||
required={isRequired && noneSelected && ind === 0} // ✅ key line
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("select-at-least-option"))
|
||||
}
|
||||
onChange={(e) => {
|
||||
e.target.setCustomValidity(""); // ✅ clear message as soon as user changes
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`checkbox-${widget.key + ind}`}
|
||||
className="text-xs mb-0 text-center ml-[3px] cursor-pointer"
|
||||
>
|
||||
{value}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const DropdownWidget = ({ widget, isRequired, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const [selected, setSelected] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(
|
||||
widget?.options?.response ?? widget?.options?.defaultValue ?? ""
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleDropdownChange = (value) => {
|
||||
setSelected(value);
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-max">
|
||||
<select
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-base-content w-full"
|
||||
id={`widget-${widget?.options?.name}-${widget?.key}`}
|
||||
value={selected}
|
||||
onChange={(e) => handleDropdownChange(e.target.value)}
|
||||
required={isRequired}
|
||||
>
|
||||
{/* Default/Title option */}
|
||||
<option value="" disabled hidden>
|
||||
{t("choose-one")}
|
||||
</option>
|
||||
{widget?.options?.values?.map((option, ind) => (
|
||||
<option key={ind} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const RadioButtonWidget = ({ widget, isRequired, onChange, formIndex }) => {
|
||||
const [selected, setSelected] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setSelected(
|
||||
widget?.options?.response || widget?.options?.defaultValue || ""
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleRadioChange = (value) => {
|
||||
setSelected(value);
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col gap-y-1 min-w-max">
|
||||
{widget.options?.values?.map((data, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
className="select-none-cls flex items-center text-center gap-0.5"
|
||||
>
|
||||
<input
|
||||
id={`radio-${widget.key + ind}`}
|
||||
className="mt-[2px] op-radio op-radio-xs"
|
||||
name={`radio-group-${widget.key}-${formIndex}`} // ✅ same name => browser treats all radios as ONE group
|
||||
type="radio"
|
||||
required={isRequired && ind === 0} // ✅ set required ONCE (on first radio) to make the whole group mandatory
|
||||
checked={selected === data}
|
||||
onChange={() => handleRadioChange(data)}
|
||||
/>
|
||||
<label
|
||||
htmlFor={`radio-${widget.key + ind}`}
|
||||
className="text-xs mb-0 ml-[2px] cursor-pointer"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const ImageWidget = ({ widget, isRequired, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const imageRef = useRef(null);
|
||||
const [img, setImg] = useState({ src: "", imgType: "" });
|
||||
const [defaultImg, setDefaultImg] = useState({ src: "", imgType: "" });
|
||||
|
||||
useEffect(() => {
|
||||
if (widget?.response) {
|
||||
setDefaultImg({ src: widget?.response, imgType: "" });
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onImageChange = (e) => {
|
||||
if (e.target.files && e.target.files?.[0]) {
|
||||
const file = e.target.files?.[0];
|
||||
compressedFileSize(file, ({ src, imgType }) => {
|
||||
onChange(src);
|
||||
setImg({ src, imgType });
|
||||
});
|
||||
}
|
||||
};
|
||||
const handleClearImage = () => {
|
||||
setDefaultImg({ src: "", imgType: "" });
|
||||
setImg({ src: "", imgType: "" });
|
||||
onChange("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col prefillCanvas">
|
||||
{defaultImg?.src || img?.src ? (
|
||||
<>
|
||||
<div
|
||||
className={`cursor-pointer rounded-box ${canavasTheme} flex flex-col w-full h-full justify-center items-center`}
|
||||
>
|
||||
<img
|
||||
alt={`image_${widget?.options?.name}`}
|
||||
src={img.src || defaultImg?.src}
|
||||
draggable="false"
|
||||
className="object-contain h-full w-full aspect-[5/2]"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
onClick={handleClearImage}
|
||||
className="flex justify-start text-blue-500 underline cursor-pointer ml-1"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<div
|
||||
className={`cursor-pointer rounded-box ${canavasTheme} overflow-hidden w-full h-full aspect-[5/2] flex flex-col justify-center items-center`}
|
||||
onClick={() => imageRef.current?.click()}
|
||||
>
|
||||
<i className="fa-light text-base-content fa-cloud-upload-alt text-[25px]"></i>
|
||||
<div className="text-[10px] text-base-content">{t("upload")}</div>
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => onImageChange?.(e, widget)}
|
||||
className="filetype w-[1px] h-[1px] opacity-0"
|
||||
accept="image/png,image/jpeg"
|
||||
ref={imageRef} // Assign ref dynamically
|
||||
required={isRequired}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const DrawWidget = ({ widget, isRequired, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const canvasRef = useRef(null);
|
||||
const sigRequiredRef = useRef(null);
|
||||
const [hasDraw, setHasDraw] = useState("");
|
||||
const [penColor, setPenColor] = useState("blue");
|
||||
const [image, setImage] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (widget?.response) {
|
||||
setImage(widget?.response);
|
||||
setHasDraw("signed");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleSignatureChange = () => {
|
||||
const draw = canvasRef.current?.isEmpty?.() ? "" : "signed";
|
||||
setHasDraw(draw);
|
||||
onChange(canvasRef.current.toDataURL());
|
||||
|
||||
// clear custom error once user signs
|
||||
sigRequiredRef.current?.setCustomValidity("");
|
||||
};
|
||||
|
||||
const handleClear = () => {
|
||||
setImage("");
|
||||
if (canvasRef?.current) {
|
||||
canvasRef.current.clear();
|
||||
onChange("");
|
||||
setHasDraw("");
|
||||
// clear custom error once user signs
|
||||
sigRequiredRef.current?.setCustomValidity("");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="relative">
|
||||
{image ? (
|
||||
<div className={`prefillCanvas ${canavasTheme} rounded-[10px]`}>
|
||||
<img
|
||||
alt={`draw_${widget?.options?.name}`}
|
||||
src={image}
|
||||
className="prefillCanvas object-contain"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<SignatureCanvas
|
||||
ref={canvasRef}
|
||||
penColor={penColor}
|
||||
canvasProps={{
|
||||
className: `prefillCanvas ${canavasTheme} rounded-[10px]`
|
||||
}}
|
||||
onEnd={() => handleSignatureChange()}
|
||||
dotSize={1}
|
||||
/>
|
||||
)}
|
||||
{/* ✅ Native form validation hook */}
|
||||
<input
|
||||
ref={sigRequiredRef}
|
||||
type="text"
|
||||
value={hasDraw}
|
||||
required={isRequired}
|
||||
tabIndex={-1}
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("draw-required"))}
|
||||
onChange={() => {}}
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 w-[1px] h-[1px] pointer-events-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between mt-[8px] w-[200px]">
|
||||
<PenColorComponent
|
||||
penColor={penColor}
|
||||
setPenColor={setPenColor}
|
||||
hideLabel
|
||||
penSize="sm"
|
||||
/>
|
||||
<span
|
||||
onClick={handleClear}
|
||||
className="flex justify-start text-blue-500 underline cursor-pointer"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CellsWidget = ({ widget, isRequired, onChange }) => {
|
||||
const { t } = useTranslation();
|
||||
const count = widget?.options?.cellCount || 1;
|
||||
const [word, setWord] = useState("");
|
||||
const inputType = widget?.options?.validation?.type || "";
|
||||
const serverRegex = widget?.options?.validation?.pattern;
|
||||
const regExpression = inputValidation(serverRegex, inputType);
|
||||
const pattern = useMemo(() => toHtmlPattern(regExpression), [regExpression]);
|
||||
|
||||
useEffect(() => {
|
||||
const response =
|
||||
widget?.options?.response ?? widget?.options?.defaultValue ?? "";
|
||||
const isNumber = inputType === "number" && response;
|
||||
setWord(isNumber ? Number(response) : response);
|
||||
}, []);
|
||||
|
||||
const handleChange = (words) => {
|
||||
const value = words;
|
||||
setWord(value);
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="relative">
|
||||
<CellsInput
|
||||
hint={
|
||||
widget?.options?.hint ||
|
||||
t("enter-value", { value: widget?.options?.name })
|
||||
}
|
||||
count={count}
|
||||
value={word}
|
||||
onChange={handleChange}
|
||||
only="all"
|
||||
autoFocus
|
||||
/>
|
||||
<input
|
||||
id={`widget-${widget?.options?.name}-${widget.key}`}
|
||||
value={word}
|
||||
required={isRequired}
|
||||
tabIndex={-1}
|
||||
onChange={() => {}}
|
||||
pattern={pattern || undefined} // if no pattern, browser won't do pattern validation
|
||||
onInvalid={(e) => {
|
||||
const el = e.currentTarget;
|
||||
// ✅ Only override message if pattern exists AND the error is patternMismatch
|
||||
if (pattern && el.validity.patternMismatch) {
|
||||
el.setCustomValidity(t("validation-alert-1"));
|
||||
} else {
|
||||
el.setCustomValidity("");
|
||||
}
|
||||
}}
|
||||
onInput={(e) => {
|
||||
e.currentTarget.setCustomValidity("");
|
||||
}}
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 w-[1px] h-[1px] pointer-events-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderWidgets = ({
|
||||
widget,
|
||||
formIndex,
|
||||
prefillIndex,
|
||||
handleWidgetDetails
|
||||
}) => {
|
||||
const label = widget?.label?.toLowerCase() ?? "";
|
||||
const isPrefill = label.includes(PREFILL_PREFIX);
|
||||
const commonProps = {
|
||||
widget,
|
||||
isRequired: widget.options?.status === "required" && isPrefill,
|
||||
formIndex
|
||||
};
|
||||
|
||||
const onChange = (value) => {
|
||||
handleWidgetDetails(value, formIndex, prefillIndex);
|
||||
};
|
||||
|
||||
switch (widget.type) {
|
||||
case "date":
|
||||
return <DateWidget {...commonProps} onChange={onChange} />;
|
||||
case textWidget:
|
||||
case textInputWidget:
|
||||
case "name":
|
||||
case "email":
|
||||
case "company":
|
||||
case "job title":
|
||||
case "number":
|
||||
return <TextWidget {...commonProps} onChange={onChange} />;
|
||||
case "checkbox":
|
||||
return <CheckboxWidget {...commonProps} onChange={onChange} />;
|
||||
case radioButtonWidget:
|
||||
return <RadioButtonWidget {...commonProps} onChange={onChange} />;
|
||||
case "dropdown":
|
||||
return <DropdownWidget {...commonProps} onChange={onChange} />;
|
||||
case "image":
|
||||
case "stamp":
|
||||
return <ImageWidget {...commonProps} onChange={onChange} />;
|
||||
case "initials":
|
||||
case "draw":
|
||||
return <DrawWidget {...commonProps} onChange={onChange} />;
|
||||
case "cells":
|
||||
return <CellsWidget {...commonProps} onChange={onChange} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default RenderWidgets;
|
||||
@@ -5,6 +5,7 @@ import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
|
||||
import { useNavigate } from "react-router";
|
||||
import Tooltip from "../../primitives/Tooltip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { withSessionValidation } from "../../utils";
|
||||
|
||||
const DashboardCard = (props) => {
|
||||
const navigate = useNavigate();
|
||||
@@ -14,7 +15,7 @@ const DashboardCard = (props) => {
|
||||
const [response, setresponse] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const renderData = async () => {
|
||||
const renderData = withSessionValidation(async () => {
|
||||
if (props.Data.queryType === "function") {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -186,9 +187,9 @@ const DashboardCard = (props) => {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const filterRender = async () => {
|
||||
const filterRender = withSessionValidation(async () => {
|
||||
if (props.FilterData && props.FilterData.queryType === "function") {
|
||||
setLoading(true);
|
||||
try {
|
||||
@@ -266,7 +267,7 @@ const DashboardCard = (props) => {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const setFormat = (val) => {
|
||||
switch (props.Format) {
|
||||
|
||||
@@ -5,9 +5,9 @@ import reportJson from "../../json/ReportJson";
|
||||
import axios from "axios";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { withSessionValidation } from "../../utils";
|
||||
function DashboardReport(props) {
|
||||
const { t } = useTranslation();
|
||||
// console.log("props ", props)
|
||||
const [List, setList] = useState([]);
|
||||
const [isLoader, setIsLoader] = useState(true);
|
||||
const [reportName, setReportName] = useState("");
|
||||
@@ -47,7 +47,7 @@ function DashboardReport(props) {
|
||||
// eslint-disable-next-line
|
||||
}, [isNextRecord]);
|
||||
|
||||
const handleSearchChange = async (e) => {
|
||||
const handleSearchChange = withSessionValidation(async (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
setSearchTerm(term);
|
||||
if (debounceTimer.current) {
|
||||
@@ -83,7 +83,7 @@ function DashboardReport(props) {
|
||||
}
|
||||
}, 300);
|
||||
setIsSearchResult(false);
|
||||
};
|
||||
});
|
||||
|
||||
const handleSearchPaste = (e) => {
|
||||
setTimeout(() => {
|
||||
@@ -99,96 +99,95 @@ function DashboardReport(props) {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const getReportData = async (
|
||||
id,
|
||||
skipUserRecord = 0,
|
||||
limit = 20,
|
||||
term = searchTerm
|
||||
) => {
|
||||
setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
if (json) {
|
||||
setActions(json.actions);
|
||||
setReportName(json.reportName);
|
||||
setHeading(json.heading);
|
||||
const currentUser = Parse.User.current().id;
|
||||
const getReportData = withSessionValidation(
|
||||
async (id, skipUserRecord = 0, limit = 20, term = searchTerm) => {
|
||||
setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
if (json) {
|
||||
setActions(json.actions);
|
||||
setReportName(json.reportName);
|
||||
setHeading(json.heading);
|
||||
const currentUser = Parse.User.current().id;
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
try {
|
||||
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
|
||||
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
|
||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||
if (term) {
|
||||
params.searchTerm = term;
|
||||
}
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(url, params, {
|
||||
headers: headers,
|
||||
signal: abortController.signal // is used to cancel fetch query
|
||||
});
|
||||
if (id === "5Go51Q7T8r") {
|
||||
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
try {
|
||||
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
|
||||
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
|
||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||
if (term) {
|
||||
params.searchTerm = term;
|
||||
}
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(url, params, {
|
||||
headers: headers,
|
||||
signal: abortController.signal // is used to cancel fetch query
|
||||
});
|
||||
if (id === "5Go51Q7T8r") {
|
||||
const listData = res.data?.result.filter(
|
||||
(x) => x.Signers.length > 0
|
||||
);
|
||||
if (isSigner) {
|
||||
let isRecord;
|
||||
if (obj?.AuditTrail && obj?.AuditTrail.length > 0) {
|
||||
isRecord = obj?.AuditTrail.some(
|
||||
(item) =>
|
||||
item?.UserPtr?.UserId?.objectId === currentUser &&
|
||||
item.Activity === "Signed"
|
||||
);
|
||||
} else {
|
||||
isRecord = false;
|
||||
}
|
||||
if (isRecord === false) {
|
||||
arr.push(obj);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser
|
||||
);
|
||||
if (isSigner) {
|
||||
let isRecord;
|
||||
if (obj?.AuditTrail && obj?.AuditTrail.length > 0) {
|
||||
isRecord = obj?.AuditTrail.some(
|
||||
(item) =>
|
||||
item?.UserPtr?.UserId?.objectId === currentUser &&
|
||||
item.Activity === "Signed"
|
||||
);
|
||||
} else {
|
||||
isRecord = false;
|
||||
}
|
||||
if (isRecord === false) {
|
||||
arr.push(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (arr.length === docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
||||
);
|
||||
} else {
|
||||
if (res.data.result.length >= docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
setIsNextRecord(false);
|
||||
if (!res.data.result.error) {
|
||||
if (arr.length === docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0
|
||||
? [...prevRecord, ...res.data.result]
|
||||
: res.data.result
|
||||
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
||||
);
|
||||
} else {
|
||||
if (res.data.result.length >= docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
setIsNextRecord(false);
|
||||
if (!res.data.result.error) {
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0
|
||||
? [...prevRecord, ...res.data.result]
|
||||
: res.data.result
|
||||
);
|
||||
}
|
||||
}
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
const isCancel = axios.isCancel(err);
|
||||
if (!isCancel) {
|
||||
console.log("err ", err);
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
const isCancel = axios.isCancel(err);
|
||||
if (!isCancel) {
|
||||
console.log("err ", err);
|
||||
setIsLoader(false);
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{isLoader ? (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { Suspense } from "react";
|
||||
import { useState, useEffect, Suspense } from "react";
|
||||
import { lazyWithRetry } from "../../utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const DashboardButton = lazyWithRetry(() => import("./DashboardButton"));
|
||||
@@ -20,6 +20,7 @@ const buttonList = [
|
||||
];
|
||||
const GetDashboard = (props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const Button = ({ label, redirectId, redirectType, icon }) => (
|
||||
<DashboardButton
|
||||
Icon={icon}
|
||||
@@ -59,7 +60,9 @@ 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>
|
||||
@@ -93,7 +96,9 @@ 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>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import FolderModal from "../shared/fields/FolderModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { handleDownloadPdf, isMobile } from "../../constant/Utils";
|
||||
import Parse from "parse";
|
||||
import { withSessionValidation } from "../../utils";
|
||||
|
||||
function DriveBody(props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -52,13 +53,11 @@ function DriveBody(props) {
|
||||
props.setSkip(0);
|
||||
};
|
||||
//function for change doc name and update doc name in _document class
|
||||
const handledRenameDoc = async (data) => {
|
||||
const handledRenameDoc = withSessionValidation(async (data) => {
|
||||
setRename("");
|
||||
const trimmedValue = renameValue.trim();
|
||||
if (trimmedValue.length > 0) {
|
||||
const updateName = {
|
||||
Name: renameValue
|
||||
};
|
||||
const updateName = { Name: renameValue };
|
||||
const docId = data.objectId;
|
||||
const docData = props.pdfData;
|
||||
const updatedData = docData.map((item) => {
|
||||
@@ -71,11 +70,9 @@ function DriveBody(props) {
|
||||
});
|
||||
props.setPdfData(updatedData);
|
||||
props.sortingData(null, null, updatedData);
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem(
|
||||
"baseUrl"
|
||||
)}classes/contracts_Document/${docId}`,
|
||||
try {
|
||||
await axios.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${docId}`,
|
||||
updateName,
|
||||
{
|
||||
headers: {
|
||||
@@ -84,20 +81,16 @@ function DriveBody(props) {
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
)
|
||||
.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")
|
||||
});
|
||||
);
|
||||
} catch (err) {
|
||||
console.error("Error in rename doc", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
//function for navigate user to microapp-signature component
|
||||
const checkPdfStatus = async (data) => {
|
||||
@@ -163,12 +156,10 @@ function DriveBody(props) {
|
||||
}
|
||||
};
|
||||
//function for delete document
|
||||
const handleDeleteDocument = async (docData) => {
|
||||
const handleDeleteDocument = withSessionValidation(async (docData) => {
|
||||
setIsDeleteDoc({});
|
||||
const docId = docData.objectId;
|
||||
const data = {
|
||||
IsArchive: true
|
||||
};
|
||||
const data = { IsArchive: true };
|
||||
|
||||
await axios
|
||||
.put(
|
||||
@@ -190,19 +181,19 @@ function DriveBody(props) {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("Err ", err);
|
||||
console.error("Err in delete doc", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
const handleMoveDocument = async (docData) => {
|
||||
setIsOpenMoveModal(true);
|
||||
setSelectDoc(docData);
|
||||
};
|
||||
//function for move document from one folder to another folder
|
||||
const handleMoveFolder = async (selectFolderData) => {
|
||||
const handleMoveFolder = withSessionValidation(async (selectFolderData) => {
|
||||
const selecFolderId = selectDoc?.Folder?.objectId;
|
||||
const moveFolderId = selectFolderData?.ObjectId;
|
||||
let updateDocId = selectDoc?.objectId;
|
||||
@@ -224,16 +215,12 @@ function DriveBody(props) {
|
||||
}
|
||||
};
|
||||
} else {
|
||||
updateData = {
|
||||
Folder: { __op: "Delete" }
|
||||
};
|
||||
updateData = { Folder: { __op: "Delete" } };
|
||||
}
|
||||
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem(
|
||||
"baseUrl"
|
||||
)}classes/contracts_Document/${updateDocId}`,
|
||||
`${localStorage.getItem("baseUrl")}classes/contracts_Document/${updateDocId}`,
|
||||
updateData,
|
||||
{
|
||||
headers: {
|
||||
@@ -243,9 +230,7 @@ function DriveBody(props) {
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
.then((Listdata) => {
|
||||
// console.log("Listdata ", Listdata);
|
||||
const res = Listdata.data;
|
||||
if (res) {
|
||||
const updatedData = props.pdfData.filter(
|
||||
@@ -255,7 +240,7 @@ function DriveBody(props) {
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("err", err);
|
||||
console.error("err in move folder", err);
|
||||
});
|
||||
|
||||
setIsOpenMoveModal(false);
|
||||
@@ -263,7 +248,7 @@ function DriveBody(props) {
|
||||
alert(t("folder-already-exist!"));
|
||||
setIsOpenMoveModal(false);
|
||||
}
|
||||
};
|
||||
});
|
||||
const handleEnterPress = (e, data) => {
|
||||
if (e.key === "Enter") {
|
||||
handledRenameDoc(data);
|
||||
@@ -303,7 +288,7 @@ function DriveBody(props) {
|
||||
props.setPdfData(updatedData);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
console.error("Err in delete folder", err);
|
||||
props.setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { fontsizeArr, fontColorArr } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -7,7 +7,7 @@ export default function CellsSettingModal({
|
||||
isOpen,
|
||||
handleClose,
|
||||
defaultData,
|
||||
handleSave
|
||||
handleSave,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useState("");
|
||||
@@ -31,13 +31,16 @@ export default function CellsSettingModal({
|
||||
name,
|
||||
cellCount: parseInt(cellCount, 10),
|
||||
fontSize,
|
||||
fontColor
|
||||
fontColor,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("widget-info")}>
|
||||
<form onSubmit={onSubmit} className="p-[20px] text-base-content flex flex-col gap-3">
|
||||
<ModalUi isOpen={isOpen} handleClose={handleClose} title={t("widget-info")}>
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
className="p-[20px] text-base-content flex flex-col gap-3"
|
||||
>
|
||||
<div>
|
||||
<label htmlFor="name" className="text-[13px]">
|
||||
{t("name")} <span className="text-[red]">*</span>
|
||||
@@ -91,7 +94,10 @@ export default function CellsSettingModal({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<span className="w-5 h-[19px] ml-1" style={{ background: fontColor }} />
|
||||
<span
|
||||
className="w-5 h-[19px] ml-1"
|
||||
style={{ background: fontColor }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[1px] w-full bg-[#b7b3b3] my-[16px]"></div>
|
||||
|
||||
@@ -3,6 +3,8 @@ import ModalUi from "../../primitives/ModalUi";
|
||||
import { EmailBody } from "./EmailBody";
|
||||
import {
|
||||
contractDocument,
|
||||
defaultMailBody,
|
||||
defaultMailSubject,
|
||||
sendEmailToSigners
|
||||
} from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -11,7 +13,6 @@ import { useNavigate } from "react-router";
|
||||
|
||||
function CustomizeMail(props) {
|
||||
const { t } = useTranslation();
|
||||
const editorRef = useRef();
|
||||
const navigate = useNavigate();
|
||||
const copyUrlRef = useRef(null);
|
||||
const [isCustomize, setIsCustomize] = useState(false);
|
||||
@@ -25,20 +26,17 @@ function CustomizeMail(props) {
|
||||
props?.setIsMailModal(false);
|
||||
navigate("/report/1MwEuxLEkF");
|
||||
};
|
||||
const handleOnchangeRequest = () => {
|
||||
if (editorRef.current) {
|
||||
const html = editorRef.current.editor.root.innerHTML;
|
||||
props?.setCustomizeMail((prev) => ({
|
||||
...prev,
|
||||
body: html
|
||||
}));
|
||||
}
|
||||
const handleOnchangeRequest = (value) => {
|
||||
props?.setCustomizeMail((prev) => ({
|
||||
...prev,
|
||||
body: value
|
||||
}));
|
||||
};
|
||||
const handleEmailSendToSigners = async () => {
|
||||
setIsLoader(true);
|
||||
const documentData = await contractDocument(props?.documentId);
|
||||
if (documentData && documentData?.length > 0) {
|
||||
props?.setDocumentDetails(documentData[0]);
|
||||
props?.setDocumentDetails && props?.setDocumentDetails(documentData[0]);
|
||||
if (
|
||||
documentData?.[0]?.SendinOrder &&
|
||||
documentData?.[0]?.SendinOrder === true
|
||||
@@ -47,7 +45,7 @@ function CustomizeMail(props) {
|
||||
const ownerDetails = documentData[0].Signers.find(
|
||||
(x) => x.Email === ownerEmail
|
||||
);
|
||||
props?.setCurrUserId(ownerDetails?.objectId);
|
||||
props?.setCurrUserId && props?.setCurrUserId(ownerDetails?.objectId);
|
||||
}
|
||||
//function is used to send email to signers for sign the document
|
||||
const mailRes = await sendEmailToSigners(
|
||||
@@ -92,16 +90,18 @@ function CustomizeMail(props) {
|
||||
isCustomize && (
|
||||
<>
|
||||
<EmailBody
|
||||
editorRef={editorRef}
|
||||
requestBody={props?.customizeMail.body}
|
||||
requestSubject={props?.customizeMail.subject}
|
||||
requestBody={props?.customizeMail?.body}
|
||||
requestSubject={props?.customizeMail?.subject}
|
||||
handleOnchangeRequest={handleOnchangeRequest}
|
||||
setCustomizeMail={props?.setCustomizeMail}
|
||||
/>
|
||||
<div
|
||||
className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary"
|
||||
onClick={() => {
|
||||
props?.setCustomizeMail(props?.defaultMail);
|
||||
props?.setCustomizeMail({
|
||||
subject: defaultMailSubject,
|
||||
body: defaultMailBody
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span>{t("reset-to-default")}</span>
|
||||
@@ -130,9 +130,9 @@ function CustomizeMail(props) {
|
||||
!isCustomize && (
|
||||
<span
|
||||
className="op-link op-link-accent text-sm"
|
||||
onClick={() => setIsCustomize(!isCustomize)}
|
||||
onClick={() => setIsCustomize(true)}
|
||||
>
|
||||
{t("cutomize-email")}
|
||||
{t("customize-email")}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
function DefaultSignature(props) {
|
||||
@@ -105,9 +105,7 @@ function DefaultSignature(props) {
|
||||
</button>
|
||||
{!props.isDefault && (
|
||||
<div className="absolute bg-black/70 text-white w-full h-full flex items-center justify-center text-[11px] cursor-default">
|
||||
<span className="-rotate-45">
|
||||
Document creator has disabled this option
|
||||
</span>
|
||||
<span className="-rotate-45">{t("option-disabled-by-owner")}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
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 { isDraggingWidget, offset, itemType } = useDragLayer((monitor) => ({
|
||||
isDraggingWidget: monitor.isDragging(),
|
||||
offset: monitor.getClientOffset(),
|
||||
itemType: monitor.getItem()
|
||||
}));
|
||||
useEffect(() => {
|
||||
// Hide guidelines when dragging stops
|
||||
if (!isDraggingWidget) {
|
||||
showGuidelines(false);
|
||||
return;
|
||||
}
|
||||
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 !== isDraggingWidget || // 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: isDraggingWidget, x, y };
|
||||
if (isDraggingWidget) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isDraggingWidget, offset, itemType, showGuidelines]);
|
||||
}
|
||||
|
||||
export default DragGuideLinesLayer;
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { radioButtonWidget } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -111,16 +111,15 @@ function DropdownWidgetOption(props) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const defaultData =
|
||||
defaultCheckbox && defaultCheckbox.length > 0
|
||||
? defaultCheckbox
|
||||
: defaultValue;
|
||||
|
||||
const isDropdownOrRadio =
|
||||
props?.type === "dropdown" || props?.type === radioButtonWidget;
|
||||
const isCheckbox = props?.type === "checkbox";
|
||||
|
||||
const defaultData = isCheckbox ? defaultCheckbox : defaultValue;
|
||||
|
||||
const readOnlyWithoutValue =
|
||||
isReadOnly && !defaultValue && status !== "optional";
|
||||
const isCheckbox = props?.type === "checkbox";
|
||||
const WidgetLayout = ["checkbox", radioButtonWidget].includes(props.type)
|
||||
? layout
|
||||
: null;
|
||||
@@ -157,13 +156,15 @@ function DropdownWidgetOption(props) {
|
||||
};
|
||||
|
||||
|
||||
const handleDefaultCheck = (index) => {
|
||||
const getDefaultCheck = defaultCheckbox.includes(index);
|
||||
if (getDefaultCheck) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const handleSelectDefaultCheckbox = (e, index) => {
|
||||
const checked = e.target.checked;
|
||||
setDefaultCheckbox((prev) =>
|
||||
checked
|
||||
? prev.includes(index)
|
||||
? prev
|
||||
: [...prev, index]
|
||||
: prev.filter((i) => i !== index)
|
||||
);
|
||||
};
|
||||
return (
|
||||
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
|
||||
@@ -201,21 +202,8 @@ function DropdownWidgetOption(props) {
|
||||
{props.type === "checkbox" && props.isShowAdvanceFeature && (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={handleDefaultCheck(index)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
const getDefaultCheck =
|
||||
defaultCheckbox?.includes(index);
|
||||
if (!getDefaultCheck) {
|
||||
setDefaultCheckbox((prev) => [...prev, index]);
|
||||
}
|
||||
} else {
|
||||
const removeOption = defaultCheckbox.filter(
|
||||
(data) => data !== index
|
||||
);
|
||||
setDefaultCheckbox(removeOption);
|
||||
}
|
||||
}}
|
||||
checked={defaultCheckbox?.includes(index)}
|
||||
onChange={(e) => handleSelectDefaultCheckbox(e, index)}
|
||||
className="op-checkbox focus:outline-none hover:border-base-content mr-[5px]"
|
||||
/>
|
||||
)}
|
||||
@@ -270,7 +258,8 @@ function DropdownWidgetOption(props) {
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
{props.type !== "checkbox" && !isPrefillExist && (
|
||||
{((props.type !== "checkbox" && !isPrefillExist) ||
|
||||
isPrefillExist) && (
|
||||
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
|
||||
{statusArr.map((data, ind) => (
|
||||
<div
|
||||
|
||||
@@ -54,6 +54,10 @@ const EditTemplate = ({
|
||||
AllowModifications: template?.AllowModifications || false,
|
||||
TimeToCompleteDays: template?.TimeToCompleteDays || 15,
|
||||
});
|
||||
const pensList = ["blue", "red", "black"];
|
||||
const [selectedColors, setSelectedColors] = useState(
|
||||
template?.PenColors || pensList
|
||||
);
|
||||
const [isUpdate, setIsUpdate] = useState(false);
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
const [uploadPdf, setUploadPdf] = useState({
|
||||
@@ -211,6 +215,7 @@ const EditTemplate = ({
|
||||
IsEnableOTP: IsEnableOTP,
|
||||
IsTourEnabled: isTourEnabled,
|
||||
AllowModifications: allowModify,
|
||||
PenColors: selectedColors,
|
||||
...reminderDate
|
||||
};
|
||||
onSuccess(data);
|
||||
@@ -245,6 +250,19 @@ const EditTemplate = ({
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleColorsChange = (color) => {
|
||||
setSelectedColors((prev) => {
|
||||
// If user tries to uncheck the last remaining color → block it
|
||||
if (prev.length === 1 && prev.includes(color)) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
// Normal toggle behavior
|
||||
return prev.includes(color)
|
||||
? prev.filter((c) => c !== color) // remove
|
||||
: [...prev, color]; // add
|
||||
});
|
||||
};
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen
|
||||
@@ -485,6 +503,41 @@ const EditTemplate = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3 mb-4">
|
||||
<label htmlFor="penColors">
|
||||
{t("pen-colors")}
|
||||
<a data-tooltip-id="pen-colors-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
<Tooltip id="pen-colors-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("pen-colors")}</p>
|
||||
<div>{t("pen-colors-help")}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
|
||||
{pensList.map((color) => (
|
||||
<div
|
||||
key={color}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
name="penColors"
|
||||
checked={selectedColors.includes(color)}
|
||||
onChange={() => handleColorsChange(color)}
|
||||
/>
|
||||
<div className="hover:underline underline-offset-2 cursor-default capitalize">
|
||||
{color}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs mt-3">
|
||||
<SignersInput
|
||||
label={t("Bcc")}
|
||||
@@ -524,6 +577,7 @@ const EditTemplate = ({
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
min={1}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Quill } from "react-quill-new";
|
||||
// Use a third-party plugin so editors can toggle raw HTML view.
|
||||
import htmlEditButton from "quill-html-edit-button";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import Tooltip from "../../primitives/Tooltip";
|
||||
import ReactQuill from "react-quill-new";
|
||||
import "../../styles/quill.css";
|
||||
@@ -19,7 +18,6 @@ export function EmailBody(props) {
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
value={props.requestSubject}
|
||||
onChange={(e) => {
|
||||
props?.setRequestSubject(e.target.value);
|
||||
props?.setCustomizeMail((prev) => ({
|
||||
...prev,
|
||||
subject: e.target.value
|
||||
@@ -37,7 +35,6 @@ export function EmailBody(props) {
|
||||
theme="snow"
|
||||
value={props.requestBody}
|
||||
placeholder={t("email-placeholder")}
|
||||
ref={props.editorRef}
|
||||
modules={module1}
|
||||
formats={formats}
|
||||
onChange={props.handleOnchangeRequest}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
handleToPrint,
|
||||
} from "../../constant/Utils";
|
||||
@@ -31,7 +31,6 @@ 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);
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
fontColorArr,
|
||||
fontsizeArr,
|
||||
getContainerScale,
|
||||
handleCopyNextToWidget,
|
||||
handleHeighlightWidget,
|
||||
onChangeInput,
|
||||
radioButtonWidget,
|
||||
@@ -15,32 +14,29 @@ import {
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
selectFormat,
|
||||
randomId,
|
||||
getYear,
|
||||
getMonth,
|
||||
months,
|
||||
years,
|
||||
getDefaultDate,
|
||||
getDefaultFormat
|
||||
getDefaultFormat,
|
||||
handleBackground
|
||||
} from "../../constant/Utils";
|
||||
import PlaceholderType from "./PlaceholderType";
|
||||
import moment from "moment";
|
||||
import "../../styles/opensigndrive.css";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
setIsShowModal,
|
||||
setPrefillImg
|
||||
} from "../../redux/reducers/widgetSlice";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
|
||||
import { themeColor } from "../../constant/const";
|
||||
import { useGuidelinesContext } from "../../context/GuidelinesContext";
|
||||
import DatePicker from "react-datepicker";
|
||||
import DateWidgetModal from "../../primitives/DateWidgetModal";
|
||||
import { dateFormat } from "../../utils";
|
||||
|
||||
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 =
|
||||
@@ -52,11 +48,18 @@ function Placeholder(props) {
|
||||
const [clickonWidget, setClickonWidget] = useState({});
|
||||
const [formdata, setFormdata] = useState({
|
||||
status: props?.pos?.options?.status || "required",
|
||||
isReadOnly: props?.pos?.options?.isReadOnly || false
|
||||
isReadOnly: props?.pos?.options?.isReadOnly || false,
|
||||
name: props?.pos?.options?.name
|
||||
});
|
||||
const [isToday, setIsToday] = useState(
|
||||
props.pos?.options?.response === "today" ? true : false
|
||||
);
|
||||
const selectedFormatIndex = dateFormatList?.findIndex(
|
||||
(item) =>
|
||||
item.format === selectDate?.format ||
|
||||
item?.format === props.pos?.options?.validation?.format
|
||||
);
|
||||
const isSelfSign = props.isSignYourself || props.isSelfSign;
|
||||
const startDate = props?.pos?.options?.response
|
||||
? getDefaultDate(
|
||||
props?.pos?.options?.response,
|
||||
@@ -70,20 +73,6 @@ function Placeholder(props) {
|
||||
setContainerScale(props.containerWH.width / getPdfPageWidth.width);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.containerWH.width, props.pdfOriginalWH]);
|
||||
const dateFormatArr = [
|
||||
"L",
|
||||
"DD-MM-YYYY",
|
||||
"YYYY-MM-DD",
|
||||
"MM.DD.YYYY",
|
||||
"MM-DD-YYYY",
|
||||
"MMM DD, YYYY",
|
||||
"LL",
|
||||
"DD MMM, YYYY",
|
||||
"DD MMMM, YYYY",
|
||||
"DD.MM.YYYY",
|
||||
"DD/MM/YYYY"
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (props?.pos?.type === "date") {
|
||||
const isDateChange = true;
|
||||
@@ -96,24 +85,30 @@ function Placeholder(props) {
|
||||
}, [widgetData]);
|
||||
|
||||
//function is used to convert date according to selected format
|
||||
const handleGetFormatDate = (data, isDateChange) => {
|
||||
const handleGetFormatDate = (data, isDateChange, isNewDate) => {
|
||||
const format =
|
||||
data?.format ||
|
||||
selectDate?.format ||
|
||||
props.pos?.options?.validation?.format;
|
||||
const isSpecialDateFormat =
|
||||
data?.format &&
|
||||
["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(data?.format);
|
||||
["dd-MM-yyyy", "dd.MM.yyyy", "dd/MM/yyyy"].includes(format);
|
||||
let updateDate = data.date;
|
||||
let date = "";
|
||||
if (isSpecialDateFormat && updateDate) {
|
||||
date = isDateChange
|
||||
? moment(updateDate).format(changeDateToMomentFormat(data.format))
|
||||
? moment(updateDate).format(changeDateToMomentFormat(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)
|
||||
);
|
||||
date = moment(newDate.getTime()).format(changeDateToMomentFormat(format));
|
||||
} else if (isNewDate) {
|
||||
//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();
|
||||
date = moment(newDate.getTime()).format(changeDateToMomentFormat(format));
|
||||
}
|
||||
setSelectDate({ date: date, format: data?.format });
|
||||
setSelectDate({ date: date, format: format });
|
||||
return date;
|
||||
};
|
||||
const saveDateSetting = (data) => {
|
||||
@@ -128,8 +123,15 @@ function Placeholder(props) {
|
||||
};
|
||||
//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);
|
||||
const date =
|
||||
props.isPlaceholder && isToday
|
||||
? "today"
|
||||
: handleGetFormatDate(data, isDateChange);
|
||||
//`onChangeInput` is used to save data related to date in a placeholder field
|
||||
const format =
|
||||
data?.format ||
|
||||
selectDate?.format ||
|
||||
props.pos?.options?.validation?.format;
|
||||
onChangeInput(
|
||||
date,
|
||||
props.pos,
|
||||
@@ -138,13 +140,12 @@ function Placeholder(props) {
|
||||
props.setXyPosition,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
data?.format,
|
||||
format,
|
||||
props.fontSize || props.pos?.options?.fontSize || 12,
|
||||
props.fontColor || props.pos?.options?.fontColor || "black",
|
||||
formdata
|
||||
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)
|
||||
@@ -155,7 +156,7 @@ function Placeholder(props) {
|
||||
//function change format array list with selected date and format
|
||||
const changeDateFormat = () => {
|
||||
const updateDate = [];
|
||||
dateFormatArr.map((data) => {
|
||||
dateFormat.map((data) => {
|
||||
let date = selectDate?.date || new Date();
|
||||
const isString = typeof date === "string";
|
||||
if (selectDate.format === "dd-MM-yyyy" && isString) {
|
||||
@@ -179,7 +180,7 @@ function Placeholder(props) {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isPlaceholder || props.isSignYourself || props.isSelfSign) {
|
||||
if (props.isPlaceholder || isSelfSign) {
|
||||
selectDate && changeDateFormat();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -221,16 +222,17 @@ function Placeholder(props) {
|
||||
//placeholder, template flow
|
||||
else if (props.isPlaceholder && !props.isDragging) {
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
|
||||
if (
|
||||
props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
props?.data?.Role !== "prefill"
|
||||
) {
|
||||
props.handleLinkUser(props.data.Id);
|
||||
props.setUniqueId(props.data.Id);
|
||||
if (props?.data?.Role !== "prefill") {
|
||||
//this condition is used open signers attach modal but it should only open when widgets already selected
|
||||
if (props?.currWidgetsDetails?.key === props.pos?.key) {
|
||||
props.handleLinkUser(props.data.Id);
|
||||
}
|
||||
props.setUniqueId && props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPosition.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
props?.setRoleName("");
|
||||
} else if (props?.data?.Role === "prefill") {
|
||||
dispatch(setIsShowModal({ [props.pos.key]: true }));
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
@@ -248,10 +250,7 @@ function Placeholder(props) {
|
||||
props?.setIsCheckbox(true);
|
||||
}
|
||||
// cells widget settings in sign yourself flow
|
||||
else if (
|
||||
props.pos.type === cellsWidget &&
|
||||
(props.isSignYourself || props.isSelfSign)
|
||||
) {
|
||||
else if (props.pos.type === cellsWidget && isSelfSign) {
|
||||
props.handleCellSettingModal && props.handleCellSettingModal();
|
||||
}
|
||||
//condition to handle setting icon for signyour-self flow for all type text widgets
|
||||
@@ -264,10 +263,11 @@ function Placeholder(props) {
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.pos.type) &&
|
||||
(props.isSignYourself || props.isSelfSign)
|
||||
isSelfSign
|
||||
) {
|
||||
props.handleTextSettingModal(true);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
props?.handleNameModal && props?.handleNameModal(true);
|
||||
}
|
||||
|
||||
@@ -305,37 +305,8 @@ function Placeholder(props) {
|
||||
) {
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
}
|
||||
|
||||
//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
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
props.setIsPageCopy(true);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
};
|
||||
|
||||
const setCellCount = (key, newCount) => {
|
||||
@@ -405,8 +376,7 @@ function Placeholder(props) {
|
||||
"job title",
|
||||
"email",
|
||||
...isSettingForCells
|
||||
].includes(props.pos.type) &&
|
||||
(props.isSignYourself || props.isSelfSign) ? (
|
||||
].includes(props.pos.type) && isSelfSign ? (
|
||||
<i
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -528,6 +498,7 @@ function Placeholder(props) {
|
||||
className="fa-light fa-circle-xmark icon text-[#188ae2] -right-[8px] -top-[18px] "
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
showGuidelines(false);
|
||||
//condition for template and placeholder flow
|
||||
if (props.data) {
|
||||
props.handleDeleteWidget(props.pos.key, props.data.Id);
|
||||
@@ -540,6 +511,7 @@ function Placeholder(props) {
|
||||
//for mobile and tablet touch event
|
||||
onTouchEnd={(e) => {
|
||||
e.stopPropagation();
|
||||
showGuidelines(false);
|
||||
//condition for template and placeholder flow
|
||||
if (props.data) {
|
||||
props.handleDeleteWidget(props.pos.key, props.data?.Id);
|
||||
@@ -599,7 +571,7 @@ function Placeholder(props) {
|
||||
}
|
||||
}
|
||||
};
|
||||
//function to calculate font size
|
||||
// function to calculate font size
|
||||
const calculateFont = (size, isMinHeight) => {
|
||||
const containerScale = getContainerScale(
|
||||
props.pdfOriginalWH,
|
||||
@@ -607,7 +579,7 @@ function Placeholder(props) {
|
||||
props.containerWH
|
||||
);
|
||||
const fontSize = (size || 12) * containerScale * props.scale;
|
||||
//isMinHeight to set text box minimum height
|
||||
// isMinHeight to set text box minimum height
|
||||
if (isMinHeight) {
|
||||
return fontSize * 1.5 + "px";
|
||||
} else {
|
||||
@@ -629,22 +601,6 @@ 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";
|
||||
|
||||
@@ -666,17 +622,19 @@ 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 "
|
||||
className="border-gray-400 rounded-[50px] border-[1px] px-3 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>
|
||||
<i className={`${value ? "ml-[5px]" : "w-20"} fa-light fa-calendar `}></i>
|
||||
</div>
|
||||
));
|
||||
ExampleCustomInput.displayName = "ExampleCustomInput";
|
||||
|
||||
const handleChangeFormat = (e) => {
|
||||
const selectedIndex = e.target.value;
|
||||
e.stopPropagation();
|
||||
@@ -690,39 +648,83 @@ function Placeholder(props) {
|
||||
format: dateFormatList[selectedIndex]?.format
|
||||
}));
|
||||
} else {
|
||||
if (
|
||||
props?.data?.Role === "prefill" &&
|
||||
(formdata.status === "optional" || props.pos?.status === "optional")
|
||||
) {
|
||||
setSelectDate({
|
||||
date: "",
|
||||
format: dateFormatList[selectedIndex]?.format
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectDate(dateFormatList[selectedIndex]);
|
||||
}
|
||||
};
|
||||
const handleClearDate = () => {
|
||||
setIsToday(false);
|
||||
setSelectDate((prev) => ({
|
||||
...prev,
|
||||
date: ""
|
||||
}));
|
||||
setSelectDate((prev) => ({ ...prev, date: "" }));
|
||||
};
|
||||
const handleCloseDateModal = () => {
|
||||
setIsDateModal(false);
|
||||
};
|
||||
const handleDateStatus = (data) => {
|
||||
if (data === "Optional" && props?.data?.Role === "prefill") {
|
||||
setSelectDate((prev) => ({ ...prev, date: "" }));
|
||||
} else if (
|
||||
props?.data?.Role === "prefill" &&
|
||||
data === "Required" &&
|
||||
props?.pos?.options?.status === "optional"
|
||||
) {
|
||||
const isNewDate = true;
|
||||
handleGetFormatDate(selectDate, null, isNewDate);
|
||||
}
|
||||
setFormdata({
|
||||
...formdata,
|
||||
status: data.toLowerCase()
|
||||
});
|
||||
};
|
||||
|
||||
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. */}
|
||||
{props.pos?.options?.response &&
|
||||
props.pos.key !== props?.currWidgetsDetails?.key &&
|
||||
props.pos.type === textWidget ? (
|
||||
<span
|
||||
<textarea
|
||||
readOnly
|
||||
onClick={() => {
|
||||
if (props?.ispublicTemplate) return;
|
||||
props.setCurrWidgetsDetails &&
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
props?.setRoleName && props?.setRoleName("prefill");
|
||||
props.setUniqueId && props.setUniqueId(props?.data?.Id);
|
||||
}}
|
||||
style={{
|
||||
fontFamily: "Arial, sans-serif",
|
||||
position: "absolute",
|
||||
left: xPos(props.pos, props.isSignYourself),
|
||||
top: yPos(props.pos, props.isSignYourself),
|
||||
...(props.pos?.Width ? { width: props.pos?.Width - 5 } : {}),
|
||||
...(props.pos?.Height ? { height: props.pos?.Height } : {}),
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
zIndex: 99
|
||||
zIndex: 99,
|
||||
cursor: getCursor(),
|
||||
resize: "none",
|
||||
background: "transparent",
|
||||
width: props.posWidth(props.pos, props.isSignYourself),
|
||||
whiteSpace: "pre-wrap",
|
||||
overflow: "hidden",
|
||||
outline: "none",
|
||||
border: "none"
|
||||
}}
|
||||
>
|
||||
{props.pos?.options?.response}
|
||||
</span>
|
||||
{
|
||||
props.pos?.options?.response
|
||||
}
|
||||
</textarea>
|
||||
) : (
|
||||
<Rnd
|
||||
id={props.pos.key}
|
||||
@@ -778,7 +780,11 @@ function Placeholder(props) {
|
||||
background:
|
||||
props?.data?.Role === "prefill"
|
||||
? "transparent"
|
||||
: handleBackground() //handle block color of widget for
|
||||
: handleBackground(
|
||||
props?.data,
|
||||
props?.isNeedSign,
|
||||
props?.uniqueId
|
||||
)
|
||||
}}
|
||||
onDrag={(_, d) => {
|
||||
props?.handleTabDrag?.(props.pos.key);
|
||||
@@ -870,7 +876,7 @@ function Placeholder(props) {
|
||||
top={-11}
|
||||
pos={props.pos}
|
||||
posHeight={props.posHeight}
|
||||
isSignYourself={props.isSignYourself || props.isSelfSign}
|
||||
isSignYourself={isSelfSign}
|
||||
/>
|
||||
) : props.data &&
|
||||
props.isNeedSign &&
|
||||
@@ -948,113 +954,147 @@ function Placeholder(props) {
|
||||
</div>
|
||||
</Rnd>
|
||||
)}
|
||||
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
|
||||
<DateWidgetModal isOpen={isDateModal} title={t("widget-info")}>
|
||||
<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">
|
||||
<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"
|
||||
defaultValue={""}
|
||||
onChange={(e) => handleChangeFormat(e)}
|
||||
>
|
||||
<option value="" disabled>
|
||||
{t("select-date-format")}
|
||||
</option>
|
||||
{dateFormatList.map((data, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={ind} key={ind}>
|
||||
{data?.date ? data?.date : "nodata"}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</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>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
saveDateSetting(selectDate);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-3">
|
||||
{!isSelfSign && (
|
||||
<div className="mb-[0.75rem] text-[13px] md:w-[60%] w-full">
|
||||
<label htmlFor="name">
|
||||
{t("name")}
|
||||
<span className="text-[red]"> *</span>
|
||||
</label>
|
||||
<span
|
||||
onClick={() => handleClearDate()}
|
||||
className="underline text-blue-500 cursor-pointer md:ml-2"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
<input
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
name="name"
|
||||
value={formdata.name}
|
||||
onChange={(e) =>
|
||||
setFormdata({
|
||||
...formdata,
|
||||
name: e.target.value
|
||||
})
|
||||
}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col md:items-center md:flex-row gap-y-3">
|
||||
<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"
|
||||
value={selectedFormatIndex >= 0 ? selectedFormatIndex : ""}
|
||||
onChange={(e) => handleChangeFormat(e)}
|
||||
>
|
||||
<option value="" disabled>
|
||||
{t("select-date-format")}
|
||||
</option>
|
||||
{dateFormatList.map((data, ind) => {
|
||||
return (
|
||||
<option className="text-[13px]" value={ind} key={ind}>
|
||||
{data?.date ? data?.date : "nodata"}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
<span className="text-xs text-gray-400 ml-1 uppercase">
|
||||
{selectDate.format || props.pos?.options?.validation?.format}
|
||||
</span>
|
||||
</div>
|
||||
{props?.data?.Role !== "prefill" && props?.isPlaceholder && (
|
||||
<>
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-2">
|
||||
<span>{t("default-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 mb-0">
|
||||
<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 ml-2"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!isSelfSign && (
|
||||
<div className="flex flex-row items-center gap-[10px]">
|
||||
{statusArr.map((data, ind) => {
|
||||
return (
|
||||
@@ -1066,12 +1106,7 @@ function Placeholder(props) {
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
name="status"
|
||||
onChange={() =>
|
||||
setFormdata({
|
||||
...formdata,
|
||||
status: data.toLowerCase()
|
||||
})
|
||||
}
|
||||
onChange={() => handleDateStatus(data)}
|
||||
checked={
|
||||
formdata.status.toLowerCase() === data.toLowerCase()
|
||||
}
|
||||
@@ -1083,82 +1118,92 @@ function Placeholder(props) {
|
||||
);
|
||||
})}
|
||||
</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 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" && (
|
||||
<div className="flex items-center gap-1 w-max">
|
||||
<input
|
||||
name="isReadOnly"
|
||||
id="isReadOnly"
|
||||
type="checkbox"
|
||||
checked={formdata?.isReadOnly}
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
onChange={() =>
|
||||
setFormdata({
|
||||
...formdata,
|
||||
isReadOnly: !formdata?.isReadOnly
|
||||
})
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="isReadOnly"
|
||||
className="capitalize ml-[2px] mb-0 cursor-pointer"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</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>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-primary"
|
||||
onClick={() => {
|
||||
saveDateSetting(selectDate);
|
||||
}}
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button className="op-btn op-btn-primary" type="submit">
|
||||
{t("save")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost text-base-content ml-1"
|
||||
onClick={() => handleCloseDateModal()}
|
||||
>
|
||||
{t("cancel")}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</ModalUi>
|
||||
</DateWidgetModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { handleCopyNextToWidget, randomId } from "../../constant/Utils";
|
||||
import {
|
||||
drawWidget,
|
||||
handleCopyNextToWidget,
|
||||
randomId
|
||||
} from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setPrefillImg } from "../../redux/reducers/widgetSlice";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
function PlaceholderCopy(props) {
|
||||
const { prefillImg } = useSelector((state) => state.widget);
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const copyType = [
|
||||
{ id: 1, type: "All pages" },
|
||||
{ id: 2, type: "All pages but last" },
|
||||
@@ -121,7 +129,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,
|
||||
@@ -134,7 +142,6 @@ function PlaceholderCopy(props) {
|
||||
);
|
||||
const existPlaceholderPosition =
|
||||
existPlaceholder[0] && existPlaceholder[0].pos;
|
||||
|
||||
//function for get copy to requested location of placeholder position
|
||||
const getPlaceholderObj = getCopyPlaceholderPosition(
|
||||
type,
|
||||
@@ -146,6 +153,23 @@ function PlaceholderCopy(props) {
|
||||
newPlaceholderPosition.push(getPlaceholderObj);
|
||||
}
|
||||
newPageNumber++;
|
||||
if (
|
||||
filterSignerPosition[0]?.Role === "prefill" &&
|
||||
currentPlaceholder?.options?.response &&
|
||||
(currentPlaceholder?.type === "image" ||
|
||||
currentPlaceholder?.type === drawWidget)
|
||||
) {
|
||||
const getPrefillImg = prefillImg?.find(
|
||||
(x) => x.id === currentPlaceholder?.key
|
||||
);
|
||||
const base64Img = getPrefillImg?.base64;
|
||||
dispatch(
|
||||
setPrefillImg({
|
||||
id: newId,
|
||||
base64: base64Img
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
let updatedSignerPlaceholder;
|
||||
if (props?.signerObjId) {
|
||||
@@ -240,6 +264,23 @@ function PlaceholderCopy(props) {
|
||||
props.setXyPosition,
|
||||
props?.Id
|
||||
);
|
||||
if (
|
||||
filterSignerPosition[0]?.Role === "prefill" &&
|
||||
currentXYposition?.options?.response &&
|
||||
(currentXYposition?.type === "image" ||
|
||||
currentXYposition?.type === drawWidget)
|
||||
) {
|
||||
const getPrefillImg = prefillImg?.find(
|
||||
(x) => x.id === currentXYposition?.key
|
||||
);
|
||||
const base64Img = getPrefillImg?.base64;
|
||||
dispatch(
|
||||
setPrefillImg({
|
||||
id: newId,
|
||||
base64: base64Img
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const getIndex = props?.xyPosition.findIndex(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { useEffect, useState, forwardRef } from "react";
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
forwardRef,
|
||||
useRef
|
||||
} from "react";
|
||||
import {
|
||||
onChangeInput,
|
||||
getMonth,
|
||||
getYear,
|
||||
radioButtonWidget,
|
||||
@@ -9,8 +15,8 @@ import {
|
||||
months,
|
||||
years,
|
||||
selectCheckbox,
|
||||
checkRegularExpress,
|
||||
isBase64
|
||||
isBase64,
|
||||
drawWidget
|
||||
} from "../../constant/Utils";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
@@ -19,6 +25,7 @@ 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 =
|
||||
@@ -26,6 +33,7 @@ const widgetCls =
|
||||
function PlaceholderType(props) {
|
||||
const selectWidgetCls = `w-full h-full absolute left-0 top-0 focus:outline-none text-base-content`;
|
||||
const { t } = useTranslation();
|
||||
const textRef = useRef();
|
||||
const prefillImg = useSelector((state) => state.widget.prefillImg);
|
||||
const prefillImgLoad = useSelector((state) => state.widget.prefillImgLoad);
|
||||
const type = props?.pos?.type;
|
||||
@@ -40,10 +48,8 @@ function PlaceholderType(props) {
|
||||
// prefer the latest response value over any default value
|
||||
const widgetData =
|
||||
props.pos?.options?.response ?? props.pos?.options?.defaultValue ?? "";
|
||||
const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
|
||||
const [widgetValue, setwidgetValue] = useState();
|
||||
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
|
||||
const [hint, setHint] = useState("");
|
||||
const [imgUrl, setImgUrl] = useState("");
|
||||
const fontSize = props.calculateFont(props.pos.options?.fontSize);
|
||||
const fontColor = props.pos.options?.fontColor || "black";
|
||||
@@ -65,18 +71,16 @@ 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);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.pos]);
|
||||
}, [props.pos, widgetData, type]);
|
||||
|
||||
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
style={{
|
||||
@@ -95,14 +99,8 @@ function PlaceholderType(props) {
|
||||
ExampleCustomInput.displayName = "ExampleCustomInput";
|
||||
|
||||
const handleRadioCheck = (data) => {
|
||||
const defaultData = props.pos.options?.defaultValue;
|
||||
if (widgetValue === data) {
|
||||
return true;
|
||||
} else if (defaultData === data) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const defaultData = widgetValue || props.pos.options?.defaultValue;
|
||||
return defaultData === data;
|
||||
};
|
||||
//function is used to get prefill image's signedUrl after expired
|
||||
useEffect(() => {
|
||||
@@ -125,6 +123,47 @@ function PlaceholderType(props) {
|
||||
loadImage();
|
||||
}, [props.pos.SignUrl]);
|
||||
|
||||
const formatWidgetName = () => {
|
||||
const widgetName = props?.pos?.options?.name;
|
||||
const name = widgetName ? widgetName?.split(`-`) : ["-", "-", "-"];
|
||||
const lastWord = name.length > 1 ? `-${name[name.length - 1]}` : "";
|
||||
const title =
|
||||
props?.pos?.type === name[0] ? `${name[0]}${lastWord}` : widgetName;
|
||||
return props?.pos?.options?.hint || title;
|
||||
};
|
||||
//useEffect is used to increase auto height of text/textInput widget when user using multiline option and enter value in next line
|
||||
useEffect(() => {
|
||||
if (!textRef.current) return;
|
||||
if (
|
||||
(type === "text" || type === textInputWidget) &&
|
||||
textRef.current &&
|
||||
widgetValue
|
||||
) {
|
||||
const el = textRef.current;
|
||||
// Count actual number of lines
|
||||
const lines = widgetValue?.split("\n")?.length || props.pos?.Height;
|
||||
// Get line height from computed style
|
||||
const lineHeight = parseInt(window.getComputedStyle(el).lineHeight);
|
||||
|
||||
const textWidgetHeight = lines * lineHeight;
|
||||
// Set widget box height (your logic)
|
||||
onChangeInput(
|
||||
null,
|
||||
props.pos,
|
||||
props?.xyPosition,
|
||||
props.index,
|
||||
props?.setXyPosition,
|
||||
props.data && props.data.Id,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
textWidgetHeight
|
||||
);
|
||||
}
|
||||
}, [widgetValue]);
|
||||
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return props.pos.SignUrl ? (
|
||||
@@ -145,7 +184,7 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{hint || widgetTypeTranslation}
|
||||
{formatWidgetName()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -169,7 +208,7 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{hint || widgetTypeTranslation}
|
||||
{formatWidgetName()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -216,23 +255,27 @@ function PlaceholderType(props) {
|
||||
case textInputWidget:
|
||||
return props.isSignYourself || iswidgetEnable ? (
|
||||
<textarea
|
||||
placeholder={hint || t("widgets-name.text")}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
placeholder={formatWidgetName()}
|
||||
ref={textRef}
|
||||
rows={50}
|
||||
value={
|
||||
widgetValue
|
||||
}
|
||||
className={`w-full resize-none overflow-hidden text-base-content item-center outline-none ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: isReadOnly ? props.data?.blockColor : "white",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
name="text"
|
||||
readOnly
|
||||
disabled={props.isNeedSign && isReadOnly}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
<span className="ml-0.5">{formatWidgetName()}</span>
|
||||
</div>
|
||||
);
|
||||
case cellsWidget: {
|
||||
@@ -251,12 +294,14 @@ function PlaceholderType(props) {
|
||||
isEnabled={iswidgetEnable}
|
||||
count={count}
|
||||
height={height}
|
||||
value={cells.join("")}
|
||||
value={
|
||||
cells.join("")
|
||||
}
|
||||
editable={isEditable}
|
||||
resizable={props?.isAllowModify}
|
||||
fontSize={fontSize}
|
||||
fontColor={fontColor}
|
||||
hint={hint}
|
||||
hint={formatWidgetName()}
|
||||
onCellCountChange={handleCellResize}
|
||||
/>
|
||||
);
|
||||
@@ -290,7 +335,7 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium text-center"
|
||||
>
|
||||
{hint || widgetTypeTranslation}
|
||||
{formatWidgetName()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -299,9 +344,11 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
placeholder={formatWidgetName()}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
value={
|
||||
widgetValue
|
||||
}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
@@ -314,16 +361,18 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
|
||||
<span> {props.pos?.options?.hint || widgetTypeTranslation}</span>
|
||||
<span className="ml-0.5">{formatWidgetName()}</span>
|
||||
</div>
|
||||
);
|
||||
case "company":
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
placeholder={formatWidgetName()}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
value={
|
||||
widgetValue
|
||||
}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
@@ -336,16 +385,18 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
<span className="ml-0.5">{formatWidgetName()}</span>
|
||||
</div>
|
||||
);
|
||||
case "job title":
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
placeholder={formatWidgetName()}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
value={
|
||||
widgetValue
|
||||
}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
@@ -358,7 +409,7 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
<span className="ml-0.5">{formatWidgetName()}</span>
|
||||
</div>
|
||||
);
|
||||
case "date":
|
||||
@@ -407,7 +458,7 @@ function PlaceholderType(props) {
|
||||
) : (
|
||||
<div
|
||||
style={textWidgetStyle}
|
||||
className="select-none-cls overflow-hidden"
|
||||
className="select-none-cls overflow-hidden uppercase"
|
||||
>
|
||||
<span>
|
||||
{props?.selectDate?.format ||
|
||||
@@ -439,7 +490,7 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium text-center"
|
||||
>
|
||||
{hint || widgetTypeTranslation}
|
||||
{formatWidgetName()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -448,9 +499,11 @@ function PlaceholderType(props) {
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
placeholder={formatWidgetName()}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
value={
|
||||
widgetValue
|
||||
}
|
||||
className={`${textWidgetCls} ${isReadOnly ? "select-none" : ""}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
@@ -463,7 +516,7 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
<span className="ml-0.5">{formatWidgetName()}</span>
|
||||
</div>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
@@ -505,20 +558,52 @@ function PlaceholderType(props) {
|
||||
case textWidget:
|
||||
return (
|
||||
<textarea
|
||||
name="text"
|
||||
ref={textRef}
|
||||
readOnly
|
||||
placeholder={t("widgets-name.text")}
|
||||
rows={1}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
rows={50}
|
||||
cols={50}
|
||||
defaultValue={
|
||||
widgetValue
|
||||
}
|
||||
style={{
|
||||
fontFamily: "Arial, sans-serif",
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: "white"
|
||||
}}
|
||||
cols="50"
|
||||
className="w-full resize-none overflow-hidden text-base-content item-center outline-none"
|
||||
/>
|
||||
);
|
||||
case drawWidget:
|
||||
return prefillImgLoad[props.pos?.key] ? (
|
||||
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-white/30 z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
) : widgetValue ? (
|
||||
<img
|
||||
alt="signature"
|
||||
draggable="false"
|
||||
src={widgetValue}
|
||||
className={`${props.pos.signatureType !== "type" ? "object-contain" : ""} w-full h-full select-none-cls`}
|
||||
/>
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
{props.pos.type && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos
|
||||
? props.calculateFontsize(props.pos)
|
||||
: "11px"
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{formatWidgetName()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return props.pos.SignUrl ? (
|
||||
<div className="pointer-events-none">
|
||||
@@ -541,7 +626,7 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{hint || widgetTypeTranslation}
|
||||
{formatWidgetName()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
||||
import React, { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import {
|
||||
getMonth,
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
changeDateToMomentFormat,
|
||||
convertBase64ToFile,
|
||||
generatePdfName,
|
||||
drawWidget,
|
||||
getBase64MimeType,
|
||||
isBase64
|
||||
} from "../../constant/Utils";
|
||||
import DatePicker from "react-datepicker";
|
||||
@@ -27,7 +29,13 @@ import {
|
||||
setPrefillImg
|
||||
} from "../../redux/reducers/widgetSlice";
|
||||
import * as utils from "../../utils";
|
||||
import Draw from "./tab/Draw";
|
||||
import PenColorComponent from "./tab/PenColorComponent";
|
||||
|
||||
const widgetTitle = "font-medium";
|
||||
const widgetLabelCss = (isRequired = false) => {
|
||||
return `${isRequired ? "after:content-['_*'] after:text-red-500" : ""} block text-xs font-semibold`;
|
||||
};
|
||||
const ShowTextWidget = ({ position, handleWidgetDetails }) => {
|
||||
const inputRef = useRef(null);
|
||||
const [inputValue, setInputValue] = useState(position.options.response || "");
|
||||
@@ -45,26 +53,85 @@ 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={`${widgetLabelCss(props?.position?.options?.status === "required")} ${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?.id] = 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?.id]?.click()}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
onChange={(e) => props?.onImageChange?.(e, props?.position)}
|
||||
className="filetype"
|
||||
accept="image/png,image/jpeg"
|
||||
ref={(el) => (imageRefs.current[props?.id] = 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();
|
||||
const canvasRefs = useRef({});
|
||||
const [penColor, setPenColor] = useState("blue");
|
||||
// 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 [image, setImage] = useState(null);
|
||||
const [imageLoaders, setImageLoaders] = useState({});
|
||||
const [currentWidget, setCurrentWidget] = useState("");
|
||||
const [userList, setUserList] = useState([]);
|
||||
const [totalImages, setTotalImages] = useState(0);
|
||||
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(() => {
|
||||
@@ -93,6 +160,7 @@ function PrefillWidgetModal(props) {
|
||||
|
||||
return flatArray || [];
|
||||
}, [props.prefillData]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(resetWidgetState([]));
|
||||
}, []);
|
||||
@@ -105,15 +173,15 @@ function PrefillWidgetModal(props) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [props?.isPrefillModal]);
|
||||
|
||||
useEffect(() => {
|
||||
//function is used to save all image base64 in redux state to display prefill images
|
||||
const savePrefillImg = async () => {
|
||||
const prefillImg = await utils?.savePrefillImg(props.xyPosition);
|
||||
if (Array.isArray(prefillImg)) {
|
||||
setLoading(true);
|
||||
prefillImg.forEach((img) => dispatch(setPrefillImg(img)));
|
||||
}
|
||||
setImageLoaders({});
|
||||
setLoading(false);
|
||||
};
|
||||
savePrefillImg();
|
||||
}, [props.xyPosition]);
|
||||
@@ -145,85 +213,7 @@ function PrefillWidgetModal(props) {
|
||||
}
|
||||
}
|
||||
}, [props?.isPrefillModal, uniqueWidget]);
|
||||
|
||||
// The getDatePickerDate function retrieves the date in the correct format supported by the DatePicker.
|
||||
const getDatePickerDate = (selectedDate, format = "dd-MM-yyyy") => {
|
||||
let date;
|
||||
if (format && format === "dd-MM-yyyy") {
|
||||
const [day, month, year] = selectedDate?.split("-");
|
||||
date = new Date(`${year}-${month}-${day}`);
|
||||
} else if (format && format === "dd.MM.yyyy") {
|
||||
const [day, month, year] = selectedDate?.split(".");
|
||||
date = new Date(`${year}.${month}.${day}`);
|
||||
} else if (format && format === "dd/MM/yyyy") {
|
||||
const [day, month, year] = selectedDate?.split("/");
|
||||
date = new Date(`${year}/${month}/${day}`);
|
||||
} else {
|
||||
date = new Date(selectedDate);
|
||||
}
|
||||
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" }}
|
||||
@@ -239,8 +229,8 @@ function PrefillWidgetModal(props) {
|
||||
|
||||
const handleDate = (position) => {
|
||||
// The getDatePickerDate function retrieves the date in the correct format supported by the DatePicker.
|
||||
const getDate = getDatePickerDate(
|
||||
position?.options.response || new Date(),
|
||||
const getDate = utils.getDatePickerDate(
|
||||
position?.options.response,
|
||||
position?.options?.validation?.format
|
||||
);
|
||||
return getDate;
|
||||
@@ -264,16 +254,16 @@ function PrefillWidgetModal(props) {
|
||||
handleWidgetDetails(position, newDate);
|
||||
};
|
||||
|
||||
const handleSavePrefillImg = async (widgetDetails) => {
|
||||
setImageLoaders((prev) => ({ ...prev, [widgetDetails?.key]: true }));
|
||||
const handleSavePrefillImg = async (base64) => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const imageName = generatePdfName(16);
|
||||
const imgType = image?.imgType || getBase64MimeType(base64);
|
||||
const imageUrl = await convertBase64ToFile(
|
||||
imageName,
|
||||
image.src,
|
||||
image.imgType
|
||||
base64 || image.src,
|
||||
imgType
|
||||
);
|
||||
setImageLoaders({});
|
||||
if (imageUrl) {
|
||||
return imageUrl;
|
||||
}
|
||||
@@ -288,7 +278,7 @@ function PrefillWidgetModal(props) {
|
||||
const getPlaceholder = getPrefill?.placeHolder;
|
||||
let imgUrl;
|
||||
if (widgetDetails?.type === "image") {
|
||||
imgUrl = await handleSavePrefillImg(widgetDetails);
|
||||
imgUrl = await handleSavePrefillImg(response);
|
||||
}
|
||||
const updatedData = getPlaceholder.map((page) => ({
|
||||
...page,
|
||||
@@ -386,7 +376,7 @@ function PrefillWidgetModal(props) {
|
||||
if (item.options.name === position.options.name) {
|
||||
return {
|
||||
...item,
|
||||
SignUrl: "",
|
||||
...(item.SignUrl !== undefined && { SignUrl: "" }),
|
||||
options: {
|
||||
...position.options,
|
||||
response: ""
|
||||
@@ -414,13 +404,35 @@ function PrefillWidgetModal(props) {
|
||||
// (tracks how many images have finished loading)
|
||||
setLoadedImages((prev) => prev + 1);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
const handleWidgetType = (position) => {
|
||||
//function for set signature url
|
||||
const handleSignatureChange = (drawImage, position) => {
|
||||
handleWidgetDetails(position, drawImage);
|
||||
};
|
||||
const getCanvasRef = (widgetId) => {
|
||||
if (!canvasRefs.current[widgetId]) {
|
||||
canvasRefs.current[widgetId] = React.createRef();
|
||||
}
|
||||
return canvasRefs.current[widgetId];
|
||||
};
|
||||
const clearCanvasById = (widgetId) => {
|
||||
const canvasRef = canvasRefs.current?.[widgetId];
|
||||
|
||||
if (canvasRef?.current) {
|
||||
canvasRef.current.clear();
|
||||
}
|
||||
};
|
||||
const handleWidgetType = (position, id) => {
|
||||
switch (position?.type) {
|
||||
case "checkbox":
|
||||
return (
|
||||
<>
|
||||
<span className={widgetTitle}>{position.options?.name}</span>
|
||||
<span
|
||||
className={`${widgetLabelCss(position?.options?.status === "required")} ${widgetTitle}`}
|
||||
>
|
||||
{position.options?.name}
|
||||
</span>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
{position.options?.values?.map((data, ind) => (
|
||||
<div
|
||||
@@ -432,9 +444,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}`}
|
||||
@@ -450,7 +462,11 @@ function PrefillWidgetModal(props) {
|
||||
case textWidget:
|
||||
return (
|
||||
<>
|
||||
<span className={widgetTitle}>{position.options?.name}</span>
|
||||
<span
|
||||
className={`${widgetLabelCss(position?.options?.status === "required")} ${widgetTitle}`}
|
||||
>
|
||||
{position.options?.name}
|
||||
</span>
|
||||
<ShowTextWidget
|
||||
position={position}
|
||||
handleWidgetDetails={handleWidgetDetails}
|
||||
@@ -460,16 +476,18 @@ function PrefillWidgetModal(props) {
|
||||
case "dropdown":
|
||||
return (
|
||||
<>
|
||||
<span className={widgetTitle}>{position.options?.name}</span>
|
||||
<span
|
||||
className={`${widgetLabelCss(position?.options?.status === "required")} ${widgetTitle}`}
|
||||
>
|
||||
{position.options?.name}
|
||||
</span>
|
||||
<select
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-base-content w-full"
|
||||
id="myDropdown"
|
||||
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>
|
||||
@@ -486,7 +504,11 @@ function PrefillWidgetModal(props) {
|
||||
case "date":
|
||||
return (
|
||||
<>
|
||||
<span className={widgetTitle}>{position.options?.name}</span>
|
||||
<span
|
||||
className={`${widgetLabelCss(position?.options?.status === "required")} ${widgetTitle}`}
|
||||
>
|
||||
{position.options?.name}
|
||||
</span>
|
||||
<DatePicker
|
||||
portalId="datepicker-portal-root"
|
||||
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
||||
@@ -519,9 +541,7 @@ 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"}
|
||||
/>
|
||||
@@ -531,13 +551,20 @@ function PrefillWidgetModal(props) {
|
||||
return (
|
||||
<ImageComponent
|
||||
position={position}
|
||||
docId={props?.docId}
|
||||
onImageChange={onImageChange}
|
||||
handleImageLoaded={handleImageLoaded}
|
||||
handleClearImage={handleClearImage}
|
||||
id={id}
|
||||
/>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
return (
|
||||
<>
|
||||
<span className={widgetTitle}>{position.options?.name}</span>
|
||||
<span
|
||||
className={`${widgetLabelCss(position?.options?.status === "required")} ${widgetTitle}`}
|
||||
>
|
||||
{position.options?.name}
|
||||
</span>
|
||||
<div className="flex flex-col gap-y-1">
|
||||
{position.options?.values.map((data, ind) => (
|
||||
<div
|
||||
@@ -549,9 +576,7 @@ 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}`}
|
||||
@@ -564,6 +589,38 @@ function PrefillWidgetModal(props) {
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
case drawWidget:
|
||||
return (
|
||||
<div>
|
||||
<span
|
||||
className={`${widgetLabelCss(position?.options?.status === "required")} ${widgetTitle}`}
|
||||
>
|
||||
{position.options?.name}
|
||||
</span>
|
||||
<Draw
|
||||
penColor={penColor}
|
||||
canvasRef={getCanvasRef(position.key)}
|
||||
currWidgetsDetails={position}
|
||||
handleSignatureChange={handleSignatureChange}
|
||||
prefillCls={"prefillCanvas"}
|
||||
/>
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent
|
||||
penColor={penColor}
|
||||
setPenColor={setPenColor}
|
||||
/>
|
||||
<span
|
||||
onClick={() => {
|
||||
clearCanvasById(position?.key);
|
||||
handleClearImage(position);
|
||||
}}
|
||||
className="flex justify-start text-blue-500 underline cursor-pointer"
|
||||
>
|
||||
{t("clear")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return position?.SignUrl ? (
|
||||
<div className="pointer-events-none">
|
||||
@@ -583,6 +640,66 @@ function PrefillWidgetModal(props) {
|
||||
};
|
||||
|
||||
const handleEmbedPrefill = async (item) => {
|
||||
//checking is there any draw widget response have base64 url then generate that base64 to url and save it
|
||||
const prefillWidgets = props.xyPosition?.find((x) => x.Role === "prefill");
|
||||
let updatedXyPosition = [];
|
||||
if (prefillWidgets) {
|
||||
const isDrawWidget = (prefillWidgets.placeHolder ?? []).some((ph) =>
|
||||
(ph?.pos ?? []).some((p) => p?.type === drawWidget)
|
||||
);
|
||||
if (isDrawWidget) {
|
||||
const getPrefill = props.xyPosition.find((x) => x?.Role === "prefill");
|
||||
const getPlaceholder = getPrefill?.placeHolder ?? [];
|
||||
const updatedData = await Promise.all(
|
||||
getPlaceholder.map(async (page) => ({
|
||||
...page,
|
||||
pos: await Promise.all(
|
||||
page.pos.map(async (item) => {
|
||||
if (item?.type === drawWidget) {
|
||||
const widgetResponse = item?.options?.response;
|
||||
|
||||
// Skip if no response
|
||||
if (!widgetResponse) return item;
|
||||
|
||||
// If already URL, do not re-upload
|
||||
if (
|
||||
typeof widgetResponse === "string" &&
|
||||
widgetResponse.startsWith("http")
|
||||
) {
|
||||
return item;
|
||||
}
|
||||
|
||||
// Convert THIS widget's base64 → URL
|
||||
const drawUrl = await handleSavePrefillImg(widgetResponse);
|
||||
return {
|
||||
...item,
|
||||
options: {
|
||||
...item.options,
|
||||
response: drawUrl
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return item;
|
||||
})
|
||||
)
|
||||
}))
|
||||
);
|
||||
|
||||
updatedXyPosition = props.xyPosition.map((obj) => {
|
||||
if (obj.Role === "prefill") {
|
||||
return {
|
||||
...obj,
|
||||
placeHolder: updatedData
|
||||
};
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
|
||||
props.setXyPosition(updatedXyPosition);
|
||||
}
|
||||
}
|
||||
|
||||
await props.handleCreateDocument();
|
||||
};
|
||||
//`loadOptions` function to use show all list of signer in dropdown
|
||||
@@ -590,9 +707,9 @@ function PrefillWidgetModal(props) {
|
||||
try {
|
||||
const baseURL = localStorage.getItem("baseUrl");
|
||||
const url = `${baseURL}functions/getsigners`;
|
||||
const token = {
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
|
||||
const token =
|
||||
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
@@ -655,7 +772,7 @@ function PrefillWidgetModal(props) {
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-10 gap-y-4 w-full">
|
||||
{uniqueWidget.map((x, id) => (
|
||||
<div key={id} className="flex flex-col gap-2 w-full">
|
||||
{handleWidgetType(x.widget)}
|
||||
{handleWidgetType(x.widget, id)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -670,7 +787,7 @@ function PrefillWidgetModal(props) {
|
||||
{t("recipients")}
|
||||
</h1>
|
||||
)}
|
||||
<div className="py-3 px-[10px] op-card border-[1px] border-gray-400 md:mx-3 text-base-content flex flex-col relative">
|
||||
<div className="py-3 px-[10px] op-card border-[1px] border-gray-400 text-base-content flex flex-col relative">
|
||||
{props.forms?.map((field, id) => {
|
||||
return (
|
||||
<div className="flex flex-col" key={field?.value}>
|
||||
@@ -697,9 +814,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"
|
||||
}}
|
||||
@@ -710,7 +827,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>
|
||||
@@ -724,7 +841,7 @@ function PrefillWidgetModal(props) {
|
||||
</div>
|
||||
<div className="flex gap-2 mx-4 mb-3">
|
||||
<button
|
||||
disabled={isAnyLoaderActive || props?.isSubmit}
|
||||
disabled={props?.isSubmit}
|
||||
className="op-btn op-btn-primary op-btn-sm w-[80px]"
|
||||
onClick={() => handleEmbedPrefill(props?.item)}
|
||||
>
|
||||
|
||||
@@ -48,7 +48,7 @@ function RenderAllPdfPage(props) {
|
||||
};
|
||||
|
||||
// Use setTimeout to wait for the transition to complete
|
||||
const timer = setTimeout(updateSize, 100); // match the transition duration
|
||||
const timer = setTimeout(updateSize, 150); // match the transition duration
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isSidebar, pageContainer, props?.containerWH]);
|
||||
|
||||
@@ -22,19 +22,28 @@ 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 { guideline, showGuidelines } = useGuidelinesContext();
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
drop: (item, monitor) => {
|
||||
props?.addPositionOfSignature(item, monitor);
|
||||
showGuidelines(false);
|
||||
}
|
||||
});
|
||||
const [scaledHeight, setScaledHeight] = useState();
|
||||
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 () => {
|
||||
@@ -271,6 +280,7 @@ function RenderPdf(props) {
|
||||
setIsReqSignTourDisabled={props.setIsReqSignTourDisabled}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={props?.currWidgetsDetails}
|
||||
handleNameModal={props?.handleNameModal}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
@@ -334,7 +344,7 @@ function RenderPdf(props) {
|
||||
}}
|
||||
ref={(node) => {
|
||||
pdfContainerRef.current = node;
|
||||
props.drop && props.drop(node);
|
||||
drop && drop(node);
|
||||
}}
|
||||
id="container"
|
||||
>
|
||||
@@ -484,6 +494,8 @@ function RenderPdf(props) {
|
||||
setIsReqSignTourDisabled={
|
||||
props.setIsReqSignTourDisabled
|
||||
}
|
||||
unSignedWidgetId={props.unSignedWidgetId}
|
||||
handleNameModal={props.handleNameModal}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
@@ -502,9 +514,10 @@ function RenderPdf(props) {
|
||||
props.setPdfLoad(true);
|
||||
props.pageDetails(pdf);
|
||||
}}
|
||||
onClick={() =>
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
onClick={() => {
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({});
|
||||
showGuidelines(false);
|
||||
}}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
<Page
|
||||
@@ -529,6 +542,25 @@ 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>
|
||||
</>
|
||||
|
||||
@@ -10,7 +10,8 @@ function SelectLanguage(props) {
|
||||
{ value: "fr", text: "Français" }, //french
|
||||
{ value: "it", text: "Italiano" }, //italian
|
||||
{ value: "de", text: "Deutsch" }, //german
|
||||
{ value: "hi", text: "हिन्दी" } //hindi
|
||||
{ value: "hi", text: "हिन्दी" }, //hindi
|
||||
{ value: "kr", text: "한국어" } //korean
|
||||
];
|
||||
const defaultLanguage = i18next.language || "en";
|
||||
const [lang, setLang] = useState(defaultLanguage);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { 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,
|
||||
@@ -9,108 +8,58 @@ import {
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
textWidget,
|
||||
widgets
|
||||
widgets,
|
||||
drawWidget
|
||||
} from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useWidgetDrag } from "../../hook/useWidgetDrag";
|
||||
|
||||
function WidgetComponent(props) {
|
||||
const { t } = useTranslation();
|
||||
const signRef = useRef(null);
|
||||
const userInformation = localStorage.getItem("UserInformation");
|
||||
const [isSignersModal, setIsSignersModal] = useState(false);
|
||||
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([]);
|
||||
// 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" },
|
||||
{ id: 17, text: drawWidget }
|
||||
];
|
||||
|
||||
// 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 handleModal = () => {
|
||||
setIsSignersModal(!isSignersModal);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
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);
|
||||
const updated = widgets.map((obj, index) => ({
|
||||
...obj,
|
||||
ref: widgetRefs[index]?.dragRef || null
|
||||
}));
|
||||
setWidgetList(updated);
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
//allow only (signature,stamp,initials,text,name, job title, company,email) widget when isAllowModification true and user have session token
|
||||
const modifiedWidgets = widget.filter(
|
||||
// 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(
|
||||
(data) =>
|
||||
![
|
||||
"dropdown",
|
||||
@@ -118,11 +67,12 @@ function WidgetComponent(props) {
|
||||
textInputWidget,
|
||||
"date",
|
||||
"image",
|
||||
"checkbox"
|
||||
"checkbox",
|
||||
drawWidget
|
||||
].includes(data.type)
|
||||
);
|
||||
//allow only (signature,stamp,initials,text) widget when isAllowModification true and user does not have session token
|
||||
const unlogedInUserWidgets = widget.filter(
|
||||
// allow only (signature, stamp, initials, text, cells) widget when isAllowModification true and user does not have session token
|
||||
const unlogedInUserWidgets = widgetList.filter(
|
||||
(data) =>
|
||||
![
|
||||
"dropdown",
|
||||
@@ -134,18 +84,19 @@ function WidgetComponent(props) {
|
||||
"name",
|
||||
"email",
|
||||
"job title",
|
||||
"company"
|
||||
"company",
|
||||
drawWidget
|
||||
].includes(data.type)
|
||||
);
|
||||
const selfSignWidgets = widget.filter(
|
||||
const selfSignWidgets = widgetList.filter(
|
||||
(data) =>
|
||||
!["dropdown", radioButtonWidget, textInputWidget].includes(data.type)
|
||||
!["dropdown", radioButtonWidget, textInputWidget,drawWidget].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 = widget.filter((data) =>
|
||||
const prefillAllowWidgets = widgetList.filter((data) =>
|
||||
(props.isPrefillDropdown ? ["dropdown"] : [])
|
||||
.concat([radioButtonWidget, textWidget, "date", "image", "checkbox"])
|
||||
.concat([radioButtonWidget, textWidget, "date", "image", "checkbox",drawWidget])
|
||||
.includes(data.type)
|
||||
);
|
||||
//function to show widget on the base of conditionAdd commentMore actions
|
||||
@@ -161,7 +112,7 @@ function WidgetComponent(props) {
|
||||
return unlogedInUserWidgets;
|
||||
}
|
||||
} else if (props?.roleName !== "prefill") {
|
||||
return widget.filter((data) => ![textWidget].includes(data.type));
|
||||
return widgetList.filter((data) => ![textWidget,drawWidget].includes(data.type));
|
||||
}
|
||||
};
|
||||
const handleSelectRecipient = () => {
|
||||
@@ -263,7 +214,12 @@ function WidgetComponent(props) {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="p-[15px] flex flex-col pt-4" data-tut="addWidgets">
|
||||
<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"
|
||||
>
|
||||
<WidgetList
|
||||
updateWidgets={handleWidgetType}
|
||||
handleDivClick={props.handleDivClick}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import "../../styles/AddUser.css";
|
||||
import RegexParser from "regex-parser";
|
||||
@@ -8,12 +8,16 @@ import {
|
||||
cellsWidget,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
import {
|
||||
widgetNamesArr
|
||||
} from "../../utils";
|
||||
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const WidgetNameModal = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const signTypes = props?.signatureType || signatureTypes;
|
||||
const [lastSubmittedName, setLastSubmittedName] = useState("");
|
||||
const [formdata, setFormdata] = useState({
|
||||
name: "",
|
||||
defaultValue: "",
|
||||
@@ -21,13 +25,32 @@ 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 handleHintPlaceholder = () => {
|
||||
const type = props.defaultdata?.type;
|
||||
|
||||
if (type === "signature") {
|
||||
@@ -48,32 +71,48 @@ const WidgetNameModal = (props) => {
|
||||
name: props.defaultdata?.options?.name || "",
|
||||
defaultValue: props.defaultdata?.options?.defaultValue || "",
|
||||
status: props.defaultdata?.options?.status || "required",
|
||||
hint: props.defaultdata?.options?.hint || handleHint(),
|
||||
hint: props.defaultdata?.options?.hint || "",
|
||||
textvalidate:
|
||||
props.defaultdata?.options?.validation?.type === "regex"
|
||||
? 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,
|
||||
});
|
||||
setLastSubmittedName(props.defaultdata?.options?.name || "");
|
||||
} else {
|
||||
setFormdata({
|
||||
...formdata,
|
||||
name: props.defaultdata?.options?.name || "",
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5
|
||||
cellCount: props.defaultdata?.options?.cellCount || 5,
|
||||
});
|
||||
setLastSubmittedName(props.defaultdata?.options?.name || "");
|
||||
}
|
||||
|
||||
if (signTypes.length > 0) {
|
||||
const defaultSignatureType = signTypes || [];
|
||||
setSignatureType(defaultSignatureType);
|
||||
}
|
||||
// eslint-disable-next-line
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.defaultdata]);
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if (props.handleData) {
|
||||
if (["signature", "initials"].includes(props.defaultdata?.type)) {
|
||||
const widgetNames = widgetNamesArr(
|
||||
props.widgetsSource,
|
||||
props?.activeSignerId
|
||||
);
|
||||
if (lastSubmittedName && lastSubmittedName !== formdata.name) {
|
||||
const widgetNameExist = widgetNames?.find(
|
||||
(widget) => widget === formdata.name
|
||||
);
|
||||
if (widgetNameExist) {
|
||||
alert(t("duplicate-widget-name-error"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isSignOrInitials) {
|
||||
const enabledSignTypes = signatureType?.filter((x) => x.enabled);
|
||||
const isDefaultSignTypeOnly =
|
||||
enabledSignTypes?.length === 1 &&
|
||||
@@ -87,9 +126,11 @@ 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.
|
||||
@@ -110,7 +151,7 @@ const WidgetNameModal = (props) => {
|
||||
status: "required",
|
||||
hint: "",
|
||||
textvalidate: "",
|
||||
cellCount: 5
|
||||
cellCount: 5,
|
||||
});
|
||||
setSignatureType(signTypes);
|
||||
}
|
||||
@@ -148,10 +189,11 @@ const WidgetNameModal = (props) => {
|
||||
} else {
|
||||
setIsValid(true);
|
||||
}
|
||||
const val =
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? e.target.value.slice(0, formdata.cellCount)
|
||||
: e.target.value;
|
||||
|
||||
const val = isCellWidget
|
||||
? e.target.value.slice(0, formdata.cellCount)
|
||||
: e.target.value;
|
||||
|
||||
setFormdata({ ...formdata, [e.target.name]: val });
|
||||
};
|
||||
|
||||
@@ -160,7 +202,7 @@ const WidgetNameModal = (props) => {
|
||||
case "email":
|
||||
return "/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/";
|
||||
case "number":
|
||||
return "/^\\d+$/";
|
||||
return "^\\d+(?:\\.\\d+)?$"; // "/^\\d+$/";
|
||||
case "text":
|
||||
//allow space in text regex
|
||||
return "/^[a-zA-Z ]+$/";
|
||||
@@ -180,27 +222,24 @@ const WidgetNameModal = (props) => {
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalUi
|
||||
isOpen={props.isOpen}
|
||||
handleClose={props.handleClose && props.handleClose}
|
||||
title={
|
||||
["signature", "initials"].includes(props.defaultdata?.type)
|
||||
? t("signature-setting")
|
||||
: t("widget-info")
|
||||
}
|
||||
title={isSignOrInitials ? t("signature-setting") : t("widget-info")}
|
||||
>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className={`${
|
||||
[textInputWidget, cellsWidget].includes(props.defaultdata?.type)
|
||||
? "pt-0"
|
||||
: ["signature", "initials"].includes(props.defaultdata?.type)
|
||||
: isSignOrInitials
|
||||
? "pt-2"
|
||||
: ""
|
||||
} p-[20px] text-base-content`}
|
||||
>
|
||||
{!["signature", "initials"].includes(props.defaultdata?.type) && (
|
||||
{!isSignOrInitials && (
|
||||
<div className="mb-[0.75rem] text-[13px]">
|
||||
<label htmlFor="name">
|
||||
{t("name")}
|
||||
@@ -217,7 +256,7 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{props.defaultdata?.type === cellsWidget && (
|
||||
{isCellWidget && (
|
||||
<div className="mb-[0.75rem] text-[13px]">
|
||||
<label htmlFor="cellCount">{t("cell-count")}</label>
|
||||
<input
|
||||
@@ -231,7 +270,10 @@ const WidgetNameModal = (props) => {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) &&
|
||||
{[
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
].includes(props.defaultdata?.type) &&
|
||||
props?.roleName !== "prefill" && (
|
||||
<>
|
||||
<div className="mb-[0.75rem]">
|
||||
@@ -244,11 +286,7 @@ const WidgetNameModal = (props) => {
|
||||
value={formdata.defaultValue}
|
||||
onChange={(e) => handledefaultChange(e)}
|
||||
autoComplete="off"
|
||||
maxLength={
|
||||
props.defaultdata?.type === cellsWidget
|
||||
? formdata.cellCount
|
||||
: undefined
|
||||
}
|
||||
maxLength={isCellWidget ? formdata.cellCount : undefined}
|
||||
onBlur={() => {
|
||||
if (isValid === false) {
|
||||
setFormdata({ ...formdata, defaultValue: "" });
|
||||
@@ -262,7 +300,7 @@ const WidgetNameModal = (props) => {
|
||||
style={{ fontSize: 12 }}
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-exclamation-circle text-[15px]"
|
||||
className="fa-light fa-exclamation-circle text-[15px] mr-1"
|
||||
style={{ color: "#fab005" }}
|
||||
></i>
|
||||
{t("invalid-default-value")}
|
||||
@@ -271,71 +309,70 @@ const WidgetNameModal = (props) => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!["signature", "initials"].includes(props.defaultdata?.type) &&
|
||||
props?.roleName !== "prefill" && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<div className="flex flex-row gap-[10px] mb-[0.5rem]">
|
||||
{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] font-medium">
|
||||
{t(`widget-status.${data}`)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{[textInputWidget, cellsWidget].includes(
|
||||
props.defaultdata?.type
|
||||
) &&
|
||||
props?.roleName !== "prefill" && (
|
||||
<div className="flex items-center">
|
||||
{!props?.isSelfSign && !isSignOrInitials && (
|
||||
<div className={showFontControls ? "mb-[0.5rem]" : "mb-[0.75rem]"}>
|
||||
<div className="flex flex-row gap-[10px] mb-[0.5rem]">
|
||||
{statusArr.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
id="isReadOnly"
|
||||
name="isReadOnly"
|
||||
type="checkbox"
|
||||
checked={formdata.isReadOnly}
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
name="status"
|
||||
onChange={() =>
|
||||
setFormdata((prev) => ({
|
||||
setFormdata({
|
||||
...formdata,
|
||||
isReadOnly: !prev.isReadOnly
|
||||
}))
|
||||
status: data.toLowerCase()
|
||||
})
|
||||
}
|
||||
checked={
|
||||
formdata.status.toLowerCase() === data.toLowerCase()
|
||||
}
|
||||
/>
|
||||
<label
|
||||
className="ml-1.5 mb-0 capitalize text-[13px]"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
<div className="text-[13px] font-medium">
|
||||
{t(`widget-status.${data}`)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{["signature", "initials"].includes(props.defaultdata?.type) && (
|
||||
{[
|
||||
textInputWidget,
|
||||
cellsWidget,
|
||||
].includes(props.defaultdata?.type) && (
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="isReadOnly"
|
||||
name="isReadOnly"
|
||||
type="checkbox"
|
||||
checked={formdata.isReadOnly}
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
onChange={() =>
|
||||
setFormdata((prev) => ({
|
||||
...formdata,
|
||||
isReadOnly: !prev.isReadOnly
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
className="ml-1.5 mb-0 capitalize text-[13px]"
|
||||
htmlFor="isreadonly"
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{isSignOrInitials && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="signaturetype" className="text-[14px] mb-[0.7rem]">
|
||||
{t("allowed-signature-types")}
|
||||
</label>
|
||||
<div className=" ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
|
||||
<div className="ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
|
||||
{signatureType.map((type, i) => {
|
||||
return (
|
||||
<div key={i} className="flex flex-row gap-[5px] items-center">
|
||||
@@ -358,7 +395,7 @@ const WidgetNameModal = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{props?.roleName !== "prefill" && (
|
||||
{!props?.isSelfSign && props?.roleName !== "prefill" && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="hint" className="text-[13px]">
|
||||
{t("hint")}
|
||||
@@ -367,20 +404,13 @@ const WidgetNameModal = (props) => {
|
||||
maxLength={40}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
name="hint"
|
||||
placeholder={handleHintPlaceholder()}
|
||||
value={formdata.hint}
|
||||
onChange={(e) => handleChange(e)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{[
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
cellsWidget,
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"email"
|
||||
].includes(props.defaultdata?.type) && (
|
||||
{showFontControls && (
|
||||
<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>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,15 +3,20 @@ import { isMobile } from "../../constant/Utils";
|
||||
// `getWidgetType` is used to load ui of widget in side list
|
||||
const getWidgetType = (item, widgetName) => {
|
||||
return (
|
||||
<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
|
||||
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="w-full h-full flex md:justify-between items-center">
|
||||
<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">
|
||||
<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">
|
||||
{widgetName}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-[20px] op-btn op-btn-primary rounded-none w-[40px] h-full flex justify-center items-center">
|
||||
<div className="text-[13px] lg:text-[14px] op-btn op-btn-primary rounded-none w-[20px] h-full flex justify-center items-center">
|
||||
<i className={item.icon}></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
function DefaultSignature(props) {
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className={`${props?.currWidgetsDetails?.type === "initials" ? "intialSignatureCanvas" : "signatureCanvas"} border-[1.3px] border-gray-300 rounded-[4px]`}
|
||||
>
|
||||
<img
|
||||
src={props?.defaultSignImg}
|
||||
draggable="false"
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DefaultSignature;
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useEffect } from "react";
|
||||
import SignatureCanvas from "react-signature-canvas";
|
||||
import { drawWidget } from "../../../constant/Utils";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
//this component is used for signature widget "Draw tab" and prefill user Draw widget
|
||||
function Draw(props) {
|
||||
const { prefillImg } = useSelector((state) => state.widget);
|
||||
useEffect(() => {
|
||||
handleDrawResponse();
|
||||
}, [prefillImg]);
|
||||
|
||||
//function is used to show draw widget response
|
||||
const handleDrawResponse = async () => {
|
||||
if (
|
||||
props?.currWidgetsDetails?.type === drawWidget &&
|
||||
props?.canvasRef.current
|
||||
) {
|
||||
const getPrefillImg = prefillImg?.find(
|
||||
(x) => x.id === props?.currWidgetsDetails?.key
|
||||
);
|
||||
const base64 = getPrefillImg?.base64;
|
||||
props?.canvasRef.current.fromDataURL(base64);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<SignatureCanvas
|
||||
ref={props?.canvasRef}
|
||||
penColor={props?.penColor}
|
||||
canvasProps={{
|
||||
className: `${
|
||||
props?.prefillCls
|
||||
? "prefillCanvas"
|
||||
: props?.currWidgetsDetails?.type === "initials"
|
||||
? "intialSignatureCanvas"
|
||||
: "signatureCanvas"
|
||||
} border-[1.3px] border-gray-300 rounded-[10px]`
|
||||
}}
|
||||
onEnd={() =>
|
||||
props?.handleSignatureChange(
|
||||
props?.canvasRef.current?.toDataURL(),
|
||||
props?.currWidgetsDetails
|
||||
)
|
||||
}
|
||||
dotSize={1}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Draw;
|
||||
@@ -0,0 +1,62 @@
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ALLOWED_COLORS = ["blue", "red", "black"];
|
||||
|
||||
const COLOR_CLASS = {
|
||||
blue: "text-blue-600",
|
||||
red: "text-red-600",
|
||||
black: "text-black",
|
||||
white: "text-white"
|
||||
};
|
||||
|
||||
function PenColorComponent({
|
||||
providedColors,
|
||||
penColor,
|
||||
convertToImg,
|
||||
fontSelect,
|
||||
typedSignature,
|
||||
setPenColor,
|
||||
hideLabel = false,
|
||||
penSize
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const pensList = useMemo(() => {
|
||||
const allowed = new Set(ALLOWED_COLORS);
|
||||
const filtered = Array.isArray(providedColors)
|
||||
? providedColors
|
||||
.map((c) => String(c).toLowerCase())
|
||||
.filter((c) => allowed.has(c))
|
||||
: [];
|
||||
|
||||
// remove duplicates while preserving order
|
||||
return filtered.length ? [...new Set(filtered)] : ALLOWED_COLORS;
|
||||
}, [providedColors]);
|
||||
|
||||
const PEN_SIZE = penSize === "sm" ? "text-[14px]" : "text-[16px]";
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-row items-center m-[5px] ${penSize === "sm" ? "gap-2" : "gap-3"}`}
|
||||
>
|
||||
{!hideLabel && <span className="text-base-content">{t("options")}</span>}
|
||||
{pensList.map((color) => {
|
||||
const selected = penColor === color;
|
||||
return (
|
||||
<i
|
||||
key={color}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={`Select ${color} pen`}
|
||||
onClick={() => {
|
||||
setPenColor?.(color);
|
||||
convertToImg && convertToImg?.(fontSelect, typedSignature, color);
|
||||
}}
|
||||
className={`${COLOR_CLASS[color] || "text-base-content"} ${selected ? "border-current" : "border-white"} border-b-[2px] pb-0.5 cursor-pointer ${PEN_SIZE} fa-light fa-pen-nib`}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default PenColorComponent;
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function TypeSignature(props) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center tabWidth rounded-[4px]">
|
||||
<span className="ml-[5px] text-[12px] text-base-content">
|
||||
{props?.currWidgetsDetails?.type === "initials"
|
||||
? t("initial-teb")
|
||||
: t("signature-tab")}
|
||||
:
|
||||
</span>
|
||||
<input
|
||||
maxLength={props?.currWidgetsDetails?.type === "initials" ? 3 : 30}
|
||||
style={{ fontFamily: props?.fontSelect, color: props?.penColor }}
|
||||
type="text"
|
||||
className="bg-transparent ml-1 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-[20px]"
|
||||
placeholder={
|
||||
props?.currWidgetsDetails?.type === "initials"
|
||||
? t("initial-type")
|
||||
: t("signature-type")
|
||||
}
|
||||
value={props?.typedSignature}
|
||||
onChange={(e) => {
|
||||
props?.setTypedSignature(e.target.value);
|
||||
if (e.target.value?.trim()?.length > 0) {
|
||||
props?.convertToImg(props?.fontSelect, e.target.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-[1px] border-[#d6d3d3] mt-[10px] rounded-[4px] tabWidth">
|
||||
{props?.fontOptions.map((font, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
fontFamily: font.value,
|
||||
backgroundColor:
|
||||
props?.fontSelect === font.value && "rgb(206 225 247)"
|
||||
}}
|
||||
onClick={() => {
|
||||
props?.setFontSelect(font.value);
|
||||
props?.convertToImg(font.value, props?.typedSignature);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="py-[5px] px-[10px] text-[20px]"
|
||||
style={{ color: props?.penColor }}
|
||||
>
|
||||
{props?.typedSignature
|
||||
? props?.typedSignature
|
||||
: t("Your-Signature")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TypeSignature;
|
||||
@@ -0,0 +1,58 @@
|
||||
import React, { useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function UploadImage(props) {
|
||||
const { t } = useTranslation();
|
||||
const imageRefs = useRef({});
|
||||
|
||||
const getImageRef = (key) => {
|
||||
if (!imageRefs.current[key]) {
|
||||
imageRefs.current[key] = React.createRef();
|
||||
}
|
||||
return imageRefs.current[key];
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(props?.isImageSelect || props?.isStampOrImage) && !props?.image ? (
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className={`${props?.currWidgetsDetails?.type === "initials" ? "intialSignatureCanvas" : "signatureCanvas"} bg-white border-[1.3px] border-gray-300 flex flex-col justify-center items-center mb-[6px] cursor-pointer`}
|
||||
onClick={() =>
|
||||
getImageRef(props?.currWidgetsDetails?.key).current.click()
|
||||
}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
onChange={props?.onImageChange}
|
||||
className="filetype"
|
||||
accept="image/png,image/jpeg"
|
||||
ref={getImageRef(props?.currWidgetsDetails?.key)}
|
||||
hidden
|
||||
/>
|
||||
<i className="fa-light fa-cloud-upload-alt uploadImgLogo text-base-content"></i>
|
||||
<div className="text-[10px] text-base-content">{t("upload")}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className={`${props?.currWidgetsDetails?.type === "initials" ? "intialSignatureCanvas" : "signatureCanvas"} bg-white border-[1.3px] border-gray-300 mb-[6px] overflow-hidden`}
|
||||
>
|
||||
<img
|
||||
alt="print img"
|
||||
ref={getImageRef(props?.currWidgetsDetails?.key)}
|
||||
src={props?.image?.src}
|
||||
draggable="false"
|
||||
className="object-contain h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UploadImage;
|
||||
@@ -0,0 +1,82 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
|
||||
//This file is for both Text and textInput widgets
|
||||
export default function TextInput(props) {
|
||||
const [isMultiline, setIsMultiline] = useState();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (!isMultiline && e.key === "Enter") {
|
||||
e.preventDefault(); // disable new line in single-line mode
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (props?.widgetValue) {
|
||||
const isNextLine = props?.widgetValue.includes("\n");
|
||||
if (isNextLine) {
|
||||
setIsMultiline(true);
|
||||
}
|
||||
}
|
||||
}, [props?.widgetValue]);
|
||||
return (
|
||||
<div style={{ position: "relative", width: "100%" }}>
|
||||
{isMultiline ? (
|
||||
<textarea
|
||||
onBlur={props?.handleInputBlur}
|
||||
ref={props?.widgetRef}
|
||||
value={props?.widgetValue ?? ""} //use an empty string "" as a default
|
||||
onChange={(e) => props?.handleOnchangeTextBox(e)}
|
||||
onKeyDown={handleKeyDown}
|
||||
rows={3}
|
||||
placeholder={t("widgets-name.text")}
|
||||
// className={`${textInputcls} resize-none outline-none max-h-[100px] `}
|
||||
style={{
|
||||
width: "100%",
|
||||
padding: "12px 40px 12px 12px",
|
||||
borderRadius: "20px",
|
||||
border: "1px solid #dcdcdc",
|
||||
fontSize: "16px",
|
||||
resize: "none",
|
||||
outline: "none"
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
name="text"
|
||||
ref={props?.widgetRef}
|
||||
type="text"
|
||||
placeholder={t("widgets-name.text")}
|
||||
value={props?.widgetValue ?? ""} //use an empty string "" as a default
|
||||
onChange={(e) => props?.handleOnchangeTextBox(e)}
|
||||
className={`${props?.textInputcls} pr-4`}
|
||||
onBlur={props?.handleInputBlur}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!isMultiline && (
|
||||
<>
|
||||
<i
|
||||
data-tooltip-id="toggle-multiline"
|
||||
className="fa-light fa-file-lines"
|
||||
size={20}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: "12px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: "#444",
|
||||
cursor: "pointer"
|
||||
}}
|
||||
onClick={() => setIsMultiline((prev) => !prev)}
|
||||
></i>
|
||||
|
||||
<Tooltip id={"toggle-multiline"}>
|
||||
<span>Toggle Multiline Text</span>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,16 +10,16 @@ const DateFormatSelector = (props) => {
|
||||
|
||||
const dateFormats = [
|
||||
"MM/DD/YYYY",
|
||||
"MMMM DD, YYYY",
|
||||
"DD MMMM, YYYY",
|
||||
"DD-MM-YYYY",
|
||||
"DD MMM, YYYY",
|
||||
"YYYY-MM-DD",
|
||||
"MM-DD-YYYY",
|
||||
"MM.DD.YYYY",
|
||||
"MMM DD, YYYY",
|
||||
"DD/MM/YYYY",
|
||||
"DD-MM-YYYY",
|
||||
"DD.MM.YYYY",
|
||||
"DD/MM/YYYY"
|
||||
"YYYY-MM-DD",
|
||||
"MMM DD, YYYY",
|
||||
"MMMM DD, YYYY",
|
||||
"DD MMM, YYYY",
|
||||
"DD MMMM, YYYY"
|
||||
];
|
||||
|
||||
// Handle format change
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
import { useState, useEffect } 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";
|
||||
import { withSessionValidation } from "../../utils";
|
||||
|
||||
const MailTemplateEditor = ({
|
||||
info,
|
||||
tenantId,
|
||||
}) => {
|
||||
const appName = localStorage.getItem("appname") || "OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
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 = withSessionValidation(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 = withSessionValidation(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 = withSessionValidation(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 = (html) => {
|
||||
if (html) {
|
||||
setRequestBody(html);
|
||||
}
|
||||
};
|
||||
const handleOnchangeCompletion = (html) => {
|
||||
if (html) {
|
||||
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"
|
||||
modules={module1}
|
||||
formats={formats}
|
||||
onChange={(value) => handleOnchangeRequest(value)}
|
||||
/>
|
||||
</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"
|
||||
modules={module2}
|
||||
formats={formats}
|
||||
onChange={(value) => handleOnchangeCompletion(value)}
|
||||
/>
|
||||
</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;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import MailTemplateEditor from "../MailTemplateEditor";
|
||||
|
||||
const EmailTab = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
tenantInfo
|
||||
} = useSelector((state) => state.user);
|
||||
return (
|
||||
<div className="flex flex-col mb-4">
|
||||
<MailTemplateEditor
|
||||
info={
|
||||
tenantInfo
|
||||
}
|
||||
tenantId={tenantInfo?.objectId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailTab;
|
||||
@@ -0,0 +1,268 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { dateFormat, formatDate, withSessionValidation } from "../../../utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
setAlertInfo,
|
||||
setLoader,
|
||||
setUserInfo
|
||||
} from "../../../redux/reducers/userReducer";
|
||||
import DatePicker from "../../DatePicker";
|
||||
import DateFormat from "../../DateFormat";
|
||||
import {
|
||||
changeDateToMomentFormat,
|
||||
selectFormat
|
||||
} from "../../../constant/Utils";
|
||||
import moment from "moment";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
|
||||
|
||||
const WidgetsTab = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const { userInfo } = useSelector((state) => state.user);
|
||||
const [selectDate, setSelectDate] = useState({
|
||||
date: "",
|
||||
format: "MM/dd/yyyy"
|
||||
});
|
||||
const [dateFormatList, setDateFormatList] = useState([]);
|
||||
const [dateWidget, setDateWidget] = useState({
|
||||
isSigningDate: false,
|
||||
isReadOnly: false,
|
||||
date: "",
|
||||
format: "MM/dd/yyyy"
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
updateStates();
|
||||
}, [userInfo]);
|
||||
|
||||
const updateStates = () => {
|
||||
const widgetPreferences = userInfo?.WidgetPreferences || [];
|
||||
const dateWidgetPref = widgetPreferences.find((w) => w.type === "date") || {
|
||||
isSigningDate: false,
|
||||
isReadOnly: false
|
||||
};
|
||||
setDateWidget({
|
||||
isSigningDate: !!dateWidgetPref.isSigningDate,
|
||||
isReadOnly: !!dateWidgetPref.isReadOnly,
|
||||
date: dateWidgetPref.date,
|
||||
format: dateWidgetPref.format
|
||||
});
|
||||
setSelectDate({ date: dateWidgetPref.date, format: dateWidgetPref.format });
|
||||
const formatted = moment(
|
||||
dateWidgetPref.date,
|
||||
changeDateToMomentFormat(dateWidgetPref.format),
|
||||
true
|
||||
).toISOString();
|
||||
formatDateList(formatted);
|
||||
};
|
||||
|
||||
const handleSigningDateChange = (e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectDate({ date: "", format: selectDate.format });
|
||||
}
|
||||
setDateWidget((prev) => ({
|
||||
...prev,
|
||||
date: "",
|
||||
isSigningDate: e.target.checked
|
||||
}));
|
||||
};
|
||||
|
||||
const handleReadOnlyChange = (e) => {
|
||||
setDateWidget({ ...dateWidget, isReadOnly: e.target.checked });
|
||||
};
|
||||
|
||||
const showAlert = (type, msg) => {
|
||||
dispatch(setAlertInfo({ type, msg }));
|
||||
setTimeout(() => {
|
||||
dispatch(setAlertInfo({ type: "success", msg: "" }));
|
||||
}, 2000);
|
||||
};
|
||||
// `handleSave` is used save updated value signature type
|
||||
const handleSave = withSessionValidation(async () => {
|
||||
dispatch(setLoader(true));
|
||||
try {
|
||||
if (
|
||||
dateWidget?.isReadOnly &&
|
||||
!selectDate.date &&
|
||||
!dateWidget.isSigningDate
|
||||
) {
|
||||
alert(t("read-only-date-error"));
|
||||
return;
|
||||
}
|
||||
const res = await Parse.Cloud.run("setwidgetpreferences", {
|
||||
dateWidget: dateWidget
|
||||
});
|
||||
if (res) {
|
||||
const widgetPreferences = userInfo?.WidgetPreferences || [];
|
||||
// Normalize booleans (simple + safe)
|
||||
const dateOpt = { type: "date", ...dateWidget };
|
||||
// Upsert: replace if exists, otherwise append
|
||||
const updatedWidgetPreferences =
|
||||
widgetPreferences?.length > 0
|
||||
? widgetPreferences.map((w) => (w.type === "date" ? dateOpt : w))
|
||||
: [...widgetPreferences, dateOpt];
|
||||
const userdata = { ...userInfo };
|
||||
userdata.WidgetPreferences = updatedWidgetPreferences;
|
||||
dispatch(setUserInfo(userdata));
|
||||
showAlert("success", t("saved-successfully"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Widgets preferences error: ", error);
|
||||
showAlert("danger", error.message);
|
||||
} finally {
|
||||
dispatch(setLoader(false));
|
||||
}
|
||||
});
|
||||
const handleDateChange = (date) => {
|
||||
//function to save date and format in local array
|
||||
const formattedDate = formatDate({
|
||||
date: date,
|
||||
format: selectDate.format
|
||||
});
|
||||
setDateWidget({
|
||||
...dateWidget,
|
||||
isSigningDate: false,
|
||||
date: formattedDate,
|
||||
format: selectDate.format
|
||||
});
|
||||
setSelectDate({ date: formattedDate, format: selectDate.format });
|
||||
formatDateList(date);
|
||||
};
|
||||
|
||||
const formatDateList = (selecteddate) => {
|
||||
let date = selecteddate || new Date();
|
||||
const list = dateFormat.map((dateFormat) => {
|
||||
const format = selectFormat(dateFormat);
|
||||
return { date: formatDate({ date, format }), format: format };
|
||||
});
|
||||
setDateFormatList(list);
|
||||
};
|
||||
const handleChangeFormat = (e) => {
|
||||
e.stopPropagation();
|
||||
const selectedIndex = Number(e.target.value);
|
||||
const format = dateFormatList[selectedIndex]?.format;
|
||||
const dateObj =
|
||||
selectDate.date && !dateWidget?.isSigningDate
|
||||
? { date: dateFormatList[selectedIndex]?.date, format: format }
|
||||
: { format: format };
|
||||
setDateWidget((prev) => ({ ...prev, ...dateObj }));
|
||||
setSelectDate((prev) => ({ ...prev, ...dateObj }));
|
||||
};
|
||||
const handleClear = () => {
|
||||
setSelectDate((prev) => ({ ...prev, date: "" }));
|
||||
setDateWidget((prev) => ({ ...prev, date: "" }));
|
||||
};
|
||||
return (
|
||||
<div id="panel-widgets">
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 md:gap-x-8 gap-y-6">
|
||||
{/* Left Column - Signature Settings */}
|
||||
<div className="md:col-span-6 flex flex-col">
|
||||
<div>
|
||||
<label className="inline-flex text-[14px] mb-0 font-medium">
|
||||
{t("date-widget")}
|
||||
<a data-tooltip-id="date-widget-tooltip" className="ml-1">
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
<ReactTooltip id="date-widget-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold"> {t("date-widget")}</p>
|
||||
<p>{t("date-pref-help-sub-title")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold capitalize">
|
||||
{t("format")}:{" "}
|
||||
</span>
|
||||
<span>{t("date-pref-help-format")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold capitalize">
|
||||
{t("default-date")}:{" "}
|
||||
</span>
|
||||
<span>{t("date-pref-help-default-date")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold capitalize">
|
||||
{t("signing-date")}:{" "}
|
||||
</span>
|
||||
<span>{t("date-pref-help-signing-date")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold capitalize">
|
||||
{t("read-only")}:{" "}
|
||||
</span>
|
||||
<span>{t("date-pref-help-read-only")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<DateFormat
|
||||
selectDate={selectDate}
|
||||
dateFormatList={dateFormatList}
|
||||
handleChangeFormat={handleChangeFormat}
|
||||
/>
|
||||
<DatePicker
|
||||
selectDate={selectDate}
|
||||
onChange={handleDateChange}
|
||||
handleClear={handleClear}
|
||||
/>
|
||||
<div className="mt-3 flex flex-col gap-2">
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<input
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
id="date-widget-signingdate"
|
||||
name="isSigningDate"
|
||||
onChange={handleSigningDateChange}
|
||||
checked={dateWidget.isSigningDate}
|
||||
/>
|
||||
<label
|
||||
htmlFor="date-widget-signingdate"
|
||||
className="text-sm font-medium text-base-content hover:underline underline-offset-2 cursor-pointer capitalize mb-0"
|
||||
title={t("signing-date")}
|
||||
>
|
||||
{t("signing-date")}
|
||||
</label>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<input
|
||||
className="op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
id="date-widget-readonly"
|
||||
name="isReadOnly"
|
||||
onChange={handleReadOnlyChange}
|
||||
checked={dateWidget.isReadOnly}
|
||||
/>
|
||||
<label
|
||||
htmlFor="date-widget-readonly"
|
||||
className="text-sm font-medium text-base-content hover:underline underline-offset-2 cursor-pointer capitalize mb-0"
|
||||
title={t("read-only")}
|
||||
>
|
||||
{t("read-only")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save Button - Full Width */}
|
||||
<div className="md:col-span-12 flex justify-start mt-3">
|
||||
<button
|
||||
className="op-btn op-btn-primary w-[110px]"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WidgetsTab;
|
||||
@@ -0,0 +1,4 @@
|
||||
import EmailTab from "./Email";
|
||||
import WidgetsTab from "./Widgets";
|
||||
|
||||
export { EmailTab, WidgetsTab };
|
||||
@@ -0,0 +1,214 @@
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
/**
|
||||
* CellsInput (cells for a single word/value)
|
||||
*
|
||||
* Props:
|
||||
* - count: number (how many cells)
|
||||
* - value: string (controlled)
|
||||
* - onChange: (nextValue: string) => void
|
||||
* - hint
|
||||
* - textSize: number (px) default 12
|
||||
* - textColor: string default "#000000"
|
||||
* - only: "all" | "digits" | "letters" | "alphanumeric"
|
||||
* - autoFocus: boolean
|
||||
* - disabled: boolean
|
||||
*/
|
||||
export default function CellsInput({
|
||||
count = 1,
|
||||
value,
|
||||
onChange,
|
||||
hint = "",
|
||||
textSize = 12,
|
||||
textColor = "#000000",
|
||||
only = "all",
|
||||
autoFocus = false,
|
||||
disabled = false,
|
||||
className = ""
|
||||
}) {
|
||||
const isControlled = value !== undefined && typeof onChange === "function";
|
||||
const [internal, setInternal] = useState("");
|
||||
|
||||
const currentValue = isControlled ? value : internal;
|
||||
|
||||
const safeCount = useMemo(() => clampInt(count, 1, 64, 6), [count]);
|
||||
|
||||
// refs for focusing next/prev
|
||||
const inputsRef = useRef([]);
|
||||
|
||||
// normalize value length
|
||||
useEffect(() => {
|
||||
const normalized = normalizeValue(currentValue, safeCount, only);
|
||||
if (normalized !== currentValue) emit(normalized);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [safeCount, only]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoFocus && !disabled) {
|
||||
queueMicrotask(() => inputsRef.current?.[0]?.focus?.());
|
||||
}
|
||||
}, [autoFocus, disabled]);
|
||||
|
||||
function emit(next) {
|
||||
if (isControlled) onChange(next);
|
||||
else setInternal(next);
|
||||
}
|
||||
|
||||
const chars = useMemo(() => {
|
||||
const v = normalizeValue(currentValue, safeCount, only);
|
||||
const arr = Array.from({ length: safeCount }, (_, i) => v[i] || "");
|
||||
return arr;
|
||||
}, [currentValue, safeCount, only]);
|
||||
|
||||
const inputStyle = {
|
||||
fontSize: `${Number(textSize || 12)}px`,
|
||||
color: textColor && String(textColor).trim() ? textColor : "#000000"
|
||||
};
|
||||
|
||||
function focusIndex(i) {
|
||||
const el = inputsRef.current?.[i];
|
||||
if (el) {
|
||||
el.focus();
|
||||
el.select?.();
|
||||
}
|
||||
}
|
||||
|
||||
function setCharAt(index, ch) {
|
||||
const v = normalizeValue(currentValue, safeCount, only);
|
||||
const nextArr = Array.from({ length: safeCount }, (_, i) => v[i] || "");
|
||||
nextArr[index] = ch;
|
||||
emit(nextArr.join(""));
|
||||
}
|
||||
|
||||
function handleChange(index, raw) {
|
||||
if (disabled) return;
|
||||
|
||||
// user typed multiple chars (mobile suggestions/paste) -> distribute
|
||||
const cleaned = filterString(raw, only);
|
||||
if (!cleaned) {
|
||||
setCharAt(index, "");
|
||||
return;
|
||||
}
|
||||
|
||||
const v = normalizeValue(currentValue, safeCount, only);
|
||||
const nextArr = Array.from({ length: safeCount }, (_, i) => v[i] || "");
|
||||
|
||||
let writeAt = index;
|
||||
for (const ch of cleaned) {
|
||||
if (writeAt >= safeCount) break;
|
||||
nextArr[writeAt] = ch;
|
||||
writeAt++;
|
||||
}
|
||||
emit(nextArr.join(""));
|
||||
|
||||
// move focus
|
||||
const nextFocus = Math.min(writeAt, safeCount - 1);
|
||||
if (nextFocus !== index) focusIndex(nextFocus);
|
||||
}
|
||||
|
||||
function handleKeyDown(index, e) {
|
||||
if (disabled) return;
|
||||
|
||||
const key = e.key;
|
||||
|
||||
if (key === "Backspace") {
|
||||
e.preventDefault();
|
||||
const v = normalizeValue(currentValue, safeCount, only);
|
||||
if (v[index]) {
|
||||
setCharAt(index, "");
|
||||
return;
|
||||
}
|
||||
// if empty, go previous
|
||||
if (index > 0) {
|
||||
focusIndex(index - 1);
|
||||
setCharAt(index - 1, "");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "ArrowLeft") {
|
||||
e.preventDefault();
|
||||
if (index > 0) focusIndex(index - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "ArrowRight") {
|
||||
e.preventDefault();
|
||||
if (index < safeCount - 1) focusIndex(index + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === " " && only !== "all") {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePaste(index, e) {
|
||||
if (disabled) return;
|
||||
e.preventDefault();
|
||||
const text = e.clipboardData?.getData("text") || "";
|
||||
const cleaned = filterString(text, only);
|
||||
if (!cleaned) return;
|
||||
|
||||
const v = normalizeValue(currentValue, safeCount, only);
|
||||
const nextArr = Array.from({ length: safeCount }, (_, i) => v[i] || "");
|
||||
|
||||
let writeAt = index;
|
||||
for (const ch of cleaned) {
|
||||
if (writeAt >= safeCount) break;
|
||||
nextArr[writeAt] = ch;
|
||||
writeAt++;
|
||||
}
|
||||
emit(nextArr.join(""));
|
||||
|
||||
const nextFocus = Math.min(writeAt, safeCount - 1);
|
||||
focusIndex(nextFocus);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`w-full ${className}`}>
|
||||
<div className="flex items-center justify-center gap-0">
|
||||
{chars.map((ch, i) => (
|
||||
<input
|
||||
key={i}
|
||||
ref={(el) => (inputsRef.current[i] = el)}
|
||||
type="text"
|
||||
inputMode={only === "digits" ? "numeric" : "text"}
|
||||
autoComplete="one-time-code"
|
||||
className="op-input op-input-bordered op-input-sm w-9 text-center rounded-sm focus:outline-none hover:border-base-content disabled:opacity-60"
|
||||
style={inputStyle}
|
||||
value={ch}
|
||||
// maxLength={safeCount} // allows multi-char suggestions; we still distribute
|
||||
disabled={disabled}
|
||||
onChange={(e) => handleChange(i, e.target.value)}
|
||||
onKeyDown={(e) => handleKeyDown(i, e)}
|
||||
onPaste={(e) => handlePaste(i, e)}
|
||||
placeholder={hint[i]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ---------------- helpers ---------------- */
|
||||
|
||||
function clampInt(v, min, max, fallback) {
|
||||
const n = parseInt(String(v), 10);
|
||||
if (Number.isNaN(n)) return fallback;
|
||||
return Math.max(min, Math.min(max, n));
|
||||
}
|
||||
|
||||
function filterString(str, only) {
|
||||
const s = String(str ?? "");
|
||||
if (only === "digits") return s.replace(/[^0-9]/g, "");
|
||||
if (only === "letters") return s.replace(/[^a-zA-Z]/g, "");
|
||||
if (only === "alphanumeric") return s.replace(/[^a-zA-Z0-9]/g, "");
|
||||
return s.replace(/\s/g, ""); // for "all" we still drop spaces for OTP-like behavior
|
||||
}
|
||||
|
||||
function normalizeValue(v, count, only) {
|
||||
const cleaned = filterString(v || "", only);
|
||||
return cleaned.slice(0, count);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import Parse from "parse";
|
||||
import Alert from "../../../primitives/Alert";
|
||||
import Loader from "../../../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { withSessionValidation } from "../../../utils";
|
||||
|
||||
const CreateFolder = ({ parentFolderId, onSuccess, folderCls, onBack }) => {
|
||||
const folderPtr = {
|
||||
@@ -18,7 +19,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls, onBack }) => {
|
||||
setAlert({ type: type, message: msg });
|
||||
setTimeout(() => setAlert({ type: type, message: "" }), 1000);
|
||||
};
|
||||
const handleCreateFolder = async (event) => {
|
||||
const handleCreateFolder = withSessionValidation(async (event) => {
|
||||
event.preventDefault();
|
||||
handleLoader(true);
|
||||
if (name) {
|
||||
@@ -41,6 +42,12 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls, onBack }) => {
|
||||
template.set("Folder", folderPtr);
|
||||
}
|
||||
template.set("CreatedBy", Parse.User.createWithoutData(currentUser.id));
|
||||
const ExtCls = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
template.set("ExtUserPtr", {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Users",
|
||||
objectId: ExtCls[0].objectId
|
||||
});
|
||||
const res = await template.save();
|
||||
if (res) {
|
||||
handleLoader(false);
|
||||
@@ -52,7 +59,7 @@ const CreateFolder = ({ parentFolderId, onSuccess, folderCls, onBack }) => {
|
||||
handleLoader(false);
|
||||
showToast("info", t("fill-folder-name"));
|
||||
}
|
||||
};
|
||||
});
|
||||
const handleLoader = (status) => setIsLoader(status);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,23 +1,98 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useState, useLayoutEffect, useEffect, useRef } from "react";
|
||||
import { findContact } from "../../../constant/Utils";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function findScrollParent(el) {
|
||||
let parent = el?.parentElement;
|
||||
while (parent) {
|
||||
const style = window.getComputedStyle(parent);
|
||||
const overflowY = style.overflowY;
|
||||
if (overflowY === "auto" || overflowY === "scroll") return parent;
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return window; // fallback
|
||||
}
|
||||
|
||||
const SuggestionInput = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [inputValue, setInputValue] = useState(props?.value || "");
|
||||
const [suggestions, setSuggestions] = useState([]);
|
||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||
const ref = useRef(null);
|
||||
const inputRef = useRef(null);
|
||||
const dropdownRef = useRef(null);
|
||||
const [dropdownPos, setDropdownPos] = useState({
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
placement: "bottom" // bottom | top
|
||||
});
|
||||
|
||||
const updateDropdownPosition = () => {
|
||||
const inputEl = inputRef.current;
|
||||
if (!inputEl) return;
|
||||
|
||||
const rect = inputEl.getBoundingClientRect();
|
||||
const desiredHeight = 120; // roughly max-h (adjust if you want)
|
||||
const gap = 6;
|
||||
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const openUpwards = spaceBelow < desiredHeight + gap;
|
||||
|
||||
const top = openUpwards
|
||||
? Math.max(gap, rect.top - desiredHeight - gap)
|
||||
: rect.bottom + gap;
|
||||
|
||||
setDropdownPos({
|
||||
top,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
placement: openUpwards ? "top" : "bottom"
|
||||
});
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!showSuggestions) return;
|
||||
updateDropdownPosition();
|
||||
}, [showSuggestions, inputValue, suggestions.length]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("mousedown", Clickout);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", Clickout);
|
||||
const Clickout = (e) => {
|
||||
if (
|
||||
inputRef.current?.contains(e.target) ||
|
||||
dropdownRef.current?.contains(e.target)
|
||||
)
|
||||
return;
|
||||
setShowSuggestions(false);
|
||||
};
|
||||
document.addEventListener("mousedown", Clickout);
|
||||
return () => document.removeEventListener("mousedown", Clickout);
|
||||
}, []);
|
||||
|
||||
const Clickout = (event) => {
|
||||
if (ref.current && !ref.current.contains(event.target)) {
|
||||
setShowSuggestions(false);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!showSuggestions) return;
|
||||
|
||||
const inputEl = inputRef.current;
|
||||
if (!inputEl) return;
|
||||
|
||||
const scrollParent = findScrollParent(inputEl);
|
||||
|
||||
const onScroll = () => updateDropdownPosition();
|
||||
const onResize = () => updateDropdownPosition();
|
||||
|
||||
// capture scroll on window + scroll parent
|
||||
window.addEventListener("scroll", onScroll, true);
|
||||
window.addEventListener("resize", onResize);
|
||||
if (scrollParent !== window)
|
||||
scrollParent.addEventListener("scroll", onScroll, { passive: true });
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("scroll", onScroll, true);
|
||||
window.removeEventListener("resize", onResize);
|
||||
if (scrollParent !== window)
|
||||
scrollParent.removeEventListener("scroll", onScroll);
|
||||
};
|
||||
}, [showSuggestions]);
|
||||
// create debounce to avoid unnecessay api calls
|
||||
useEffect(() => {
|
||||
let timer;
|
||||
@@ -63,32 +138,55 @@ const SuggestionInput = (props) => {
|
||||
props.onChange(suggestion);
|
||||
}
|
||||
};
|
||||
|
||||
const dropdown =
|
||||
showSuggestions && suggestions.length > 0
|
||||
? createPortal(
|
||||
<ul
|
||||
ref={dropdownRef}
|
||||
className="fixed z-[1002] max-h-[250px] overflow-y-auto bg-base-200 border border-gray-300 rounded shadow-md text-base-content"
|
||||
style={{
|
||||
top: dropdownPos?.top,
|
||||
left: dropdownPos?.left,
|
||||
maxWidth: dropdownPos?.width > 200 ? dropdownPos?.width : 200
|
||||
}}
|
||||
>
|
||||
{suggestions.map((suggestion, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="block py-2 px-2 w-full text-sm cursor-pointer hover:bg-base-300"
|
||||
// use onMouseDown so click registers BEFORE input blur
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
handleSuggestionClick(suggestion);
|
||||
}}
|
||||
>
|
||||
{suggestion.Name} {"<" + suggestion.Email + ">"}
|
||||
</li>
|
||||
))}
|
||||
</ul>,
|
||||
document.body
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center relative text-base-content">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type={props?.type || "text"}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Enter Email..."
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs lowercase"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="none"
|
||||
placeholder={`${t("enter-value", { value: t("Email") })}...`}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs lowercase placeholder:capitalize"
|
||||
required={props.required}
|
||||
onFocus={() => {
|
||||
if (suggestions.length) setShowSuggestions(true);
|
||||
}}
|
||||
/>
|
||||
{showSuggestions && (
|
||||
<ul
|
||||
ref={ref}
|
||||
className="absolute z-50 left-0 top-[2.55rem] w-full max-h-[100px] overflow-y-auto bg-base-200 border border-gray-300 rounded shadow-md"
|
||||
>
|
||||
{suggestions.map((suggestion, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="py-2 px-2 w-full text-sm cursor-pointer hover:bg-base-300"
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
>
|
||||
{suggestion.Name} {"<" + suggestion.Email + ">"}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
{dropdown}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { NavLink } from "react-router";
|
||||
|
||||
const Menu = ({ item, isOpen, closeSidebar }) => {
|
||||
@@ -7,6 +7,8 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
|
||||
"OpenSign™";
|
||||
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
|
||||
const { t } = useTranslation();
|
||||
const { selectedMenu } = useSelector((state) => state.sidebar);
|
||||
|
||||
return (
|
||||
<li key={item.title} role="none" className="my-0.5">
|
||||
<NavLink
|
||||
@@ -16,11 +18,9 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
|
||||
: `/${item.objectId}`
|
||||
}
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? " bg-base-300 text-base-content" : ""
|
||||
} flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
`${isActive && selectedMenu ? "bg-base-300 text-base-content" : ""} flex gap-x-5 items-center justify-start text-left p-3 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
onClick={() => closeSidebar(item.title)}
|
||||
tabIndex={isOpen ? 0 : -1}
|
||||
role="menuitem"
|
||||
>
|
||||
|
||||
@@ -7,7 +7,10 @@ import sidebarList, { subSetting } from "../../json/menuJson";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useWindowSize } from "../../hook/useWindowSize";
|
||||
import { toggleSidebar } from "../../redux/reducers/sidebarReducer";
|
||||
import {
|
||||
setSelectedMenu,
|
||||
toggleSidebar
|
||||
} from "../../redux/reducers/sidebarReducer";
|
||||
|
||||
const Sidebar = () => {
|
||||
const { width } = useWindowSize();
|
||||
@@ -29,6 +32,7 @@ const Sidebar = () => {
|
||||
}, []);
|
||||
|
||||
const closeSidebar = () => {
|
||||
dispatch(setSelectedMenu(true));
|
||||
if (width <= 1023) {
|
||||
dispatch(toggleSidebar(false));
|
||||
}
|
||||
@@ -58,10 +62,12 @@ const Sidebar = () => {
|
||||
};
|
||||
|
||||
const toggleSubmenu = (title) => {
|
||||
dispatch(setSelectedMenu(false));
|
||||
setSubmenuOpen({ [title]: !submenuOpen[title] });
|
||||
};
|
||||
|
||||
const handleMenuItem = () => {
|
||||
dispatch(setSelectedMenu(true));
|
||||
closeSidebar();
|
||||
setSubmenuOpen({});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
import { NavLink } from "react-router";
|
||||
|
||||
const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
@@ -8,6 +8,8 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
|
||||
const { t } = useTranslation();
|
||||
const { title, icon, children } = item;
|
||||
const { selectedMenu } = useSelector((state) => state.sidebar);
|
||||
|
||||
return (
|
||||
<li role="none" className="my-0.5">
|
||||
<button
|
||||
@@ -45,11 +47,9 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
: `/${childItem.objectId}`
|
||||
}
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? "bg-base-300 text-base-content " : ""
|
||||
} pl-4 flex items-center gap-x-5 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
`${isActive && selectedMenu ? "bg-base-300 text-base-content" : ""} pl-4 flex items-center gap-x-5 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
onClick={() => closeSidebar(childItem.title)}
|
||||
role="menuitem"
|
||||
tabIndex={submenuOpen ? 0 : -1}
|
||||
>
|
||||
|
||||
+592
-476
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,10 @@ 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 = {
|
||||
@@ -29,7 +33,6 @@ 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
|
||||
@@ -39,20 +42,12 @@ export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
|
||||
await axios.put(
|
||||
`${serverUrl}/classes/partners_TenantCredits/${response[0].objectId}`,
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
}
|
||||
{ headers: commonheader }
|
||||
);
|
||||
} else {
|
||||
data = { usedStorage: size, PartnersTenant: tenantPtr };
|
||||
await axios.post(`${serverUrl}/classes/partners_TenantCredits`, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
headers: commonheader
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -69,14 +64,9 @@ 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: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseAppId
|
||||
}
|
||||
headers: commonheader
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("err in save usage ", err);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDrag } from "react-dnd";
|
||||
import { getEmptyImage } from "react-dnd-html5-backend";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
export const useWidgetDrag = (item) => {
|
||||
const [, dragRef, preview] = useDrag({
|
||||
type: "BOX",
|
||||
item,
|
||||
collect: (monitor) => ({
|
||||
isDragging: monitor.isDragging()
|
||||
})
|
||||
});
|
||||
|
||||
// Hide browser default drag preview
|
||||
useEffect(() => {
|
||||
preview(getEmptyImage(), { captureDraggingState: true });
|
||||
}, [preview]);
|
||||
|
||||
return { dragRef };
|
||||
};
|
||||
@@ -25,7 +25,7 @@ i18n
|
||||
interpolation: {
|
||||
escapeValue: false // Not needed for react as it escapes by default
|
||||
},
|
||||
whitelist: ["en", "es", "fr", "it", "de", "hi"] // List of allowed languages
|
||||
whitelist: ["en", "es", "fr", "it", "de", "hi", "kr"] // List of allowed languages
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
+11
-56
@@ -22,7 +22,7 @@ body {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 766px) {
|
||||
.reactour__close {
|
||||
.reactour__close-button {
|
||||
width: 17px !important;
|
||||
padding-left: 3px !important;
|
||||
}
|
||||
@@ -119,47 +119,24 @@ body {
|
||||
}
|
||||
|
||||
/* React-tour modal styling */
|
||||
.reactour__helper {
|
||||
.reactour__popover {
|
||||
background-color: #1f2937 !important;
|
||||
color: #e5e7eb !important;
|
||||
border: 1px solid #374151 !important;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
.reactour__close {
|
||||
.reactour__close-button {
|
||||
color: #e5e7eb !important;
|
||||
background-color: #374151 !important;
|
||||
border: 1px solid #4b5563 !important;
|
||||
}
|
||||
|
||||
.reactour__close:hover {
|
||||
.reactour__close-button:hover {
|
||||
background-color: #4b5563 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* React-tour navigation buttons */
|
||||
.reactour__controls {
|
||||
background-color: #1f2937 !important;
|
||||
border-top: 1px solid #374151 !important;
|
||||
}
|
||||
|
||||
.reactour__controls button {
|
||||
background-color: #374151 !important;
|
||||
color: #e5e7eb !important;
|
||||
border: 1px solid #4b5563 !important;
|
||||
}
|
||||
|
||||
.reactour__controls button:hover {
|
||||
background-color: #4b5563 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.reactour__controls button:disabled {
|
||||
background-color: #3c3c3c !important;
|
||||
color: #858585 !important;
|
||||
border-color: #565656 !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
/* ReactTooltip styling */
|
||||
.react-tooltip {
|
||||
@@ -187,41 +164,19 @@ body {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.reactour__helper h1,
|
||||
.reactour__helper h2,
|
||||
.reactour__helper h3,
|
||||
.reactour__helper h4,
|
||||
.reactour__helper h5,
|
||||
.reactour__helper h6 {
|
||||
.reactour__popover h1,
|
||||
.reactour__popover h2,
|
||||
.reactour__popover h3,
|
||||
.reactour__popover h4,
|
||||
.reactour__popover h5,
|
||||
.reactour__popover h6 {
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
.reactour__helper p {
|
||||
.reactour__popover p {
|
||||
color: #e5e7eb !important;
|
||||
}
|
||||
|
||||
/* VS Code-style tour buttons */
|
||||
.reactour__controls .op-btn {
|
||||
background-color: #007acc !important;
|
||||
color: #ffffff !important;
|
||||
border: 1px solid #007acc !important;
|
||||
}
|
||||
|
||||
.reactour__controls .op-btn:hover {
|
||||
background-color: #0086d1 !important;
|
||||
border-color: #0086d1 !important;
|
||||
}
|
||||
|
||||
.reactour__controls .op-btn-secondary {
|
||||
background-color: #374151 !important;
|
||||
color: #e5e7eb !important;
|
||||
border: 1px solid #4b5563 !important;
|
||||
}
|
||||
|
||||
.reactour__controls .op-btn-secondary:hover {
|
||||
background-color: #4b5563 !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* React-datepicker dark mode styling */
|
||||
.react-datepicker {
|
||||
|
||||
@@ -533,3 +533,23 @@ export default function reportJson(id) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const usersActions = [
|
||||
{
|
||||
btnId: "4741",
|
||||
hoverLabel: "Delete",
|
||||
btnColor: "op-btn-secondary",
|
||||
btnIcon: "fa-light fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete",
|
||||
restrictAdmin: true
|
||||
},
|
||||
{
|
||||
btnId: "1910",
|
||||
hoverLabel: "Reset password",
|
||||
btnIcon: "fa-light fa-key",
|
||||
redirectUrl: "",
|
||||
action: "resetpassword",
|
||||
restrictAdmin: true
|
||||
}
|
||||
];
|
||||
|
||||
@@ -5,19 +5,19 @@ export const templateReportTour = [
|
||||
selector: "[data-tut=reactourFirst]",
|
||||
content: <Trans i18nKey="tour-mssg.report-1" />,
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "11px" }
|
||||
},
|
||||
{
|
||||
selector: "[data-tut=reactourSecond]",
|
||||
content: <Trans i18nKey="tour-mssg.redirect" />,
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: "[data-tut=tourbulksend]",
|
||||
content: <Trans i18nKey="tour-mssg.bulksend" />,
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: "[data-tut=reactourThird]",
|
||||
@@ -42,6 +42,6 @@ export const templateReportTour = [
|
||||
</Trans>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
nonPresentMaskCss
|
||||
} from "../constant/Utils";
|
||||
import Header from "../components/Header";
|
||||
import Footer from "../components/Footer";
|
||||
import Sidebar from "../components/sidebar/Sidebar";
|
||||
@@ -6,7 +9,9 @@ import Tour from "../primitives/Tour";
|
||||
import axios from "axios";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Parse from "parse";
|
||||
import { useNavigate, Outlet } from "react-router";
|
||||
import {
|
||||
Outlet
|
||||
} from "react-router";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { sessionStatus } from "../redux/reducers/userReducer";
|
||||
@@ -16,9 +21,8 @@ const HomeLayout = () => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const arr = useSelector((state) => state.TourSteps);
|
||||
const tourArr = useSelector((state) => state.TourSteps);
|
||||
const isValidSession = useSelector((state) => state.user.isValidSession);
|
||||
const [isLoader, setIsLoader] = useState(true);
|
||||
const [isCloseBtn, setIsCloseBtn] = useState(true);
|
||||
@@ -55,7 +59,7 @@ const HomeLayout = () => {
|
||||
dispatch(sessionStatus(true));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("catch");
|
||||
console.error("error in authentication:", error?.message);
|
||||
// Session token is invalid or there was an error
|
||||
dispatch(sessionStatus(false));
|
||||
}
|
||||
@@ -68,40 +72,36 @@ const HomeLayout = () => {
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (arr && arr.length > 0) {
|
||||
if (tourArr && tourArr.length > 0) {
|
||||
handleDynamicSteps();
|
||||
} else {
|
||||
setIsTour(false);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [arr]);
|
||||
}, [tourArr]);
|
||||
|
||||
const handleDynamicSteps = () => {
|
||||
const github = "https://github.com/OpenSignLabs/OpenSign";
|
||||
if (arr && arr.length > 0) {
|
||||
// const resArr = arr;
|
||||
const resArr = arr.map((obj, index) => {
|
||||
if (arr.length - 1 === index) {
|
||||
return { ...obj };
|
||||
if (tourArr && tourArr.length > 0) {
|
||||
const resArr = tourArr.map((obj, index) => {
|
||||
if (tourArr.length - 1 === index) {
|
||||
return obj;
|
||||
} else {
|
||||
return {
|
||||
...obj,
|
||||
actions: () => {
|
||||
setIsCloseBtn(false);
|
||||
}
|
||||
};
|
||||
return { ...obj, actions: () => setIsCloseBtn(false) };
|
||||
}
|
||||
});
|
||||
setTourConfigs([
|
||||
{
|
||||
selector: '[data-tut="nonpresentmask"]',
|
||||
content: t("tour-mssg.home-layout-1"),
|
||||
position: "center"
|
||||
position: "center",
|
||||
styles: { fontSize: "13px", maskArea: nonPresentMaskCss }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="tourbutton"]',
|
||||
content: t("tour-mssg.home-layout-2"),
|
||||
position: "top"
|
||||
position: "top",
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
...resArr,
|
||||
{
|
||||
@@ -122,20 +122,19 @@ const HomeLayout = () => {
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
position: "center"
|
||||
position: "center",
|
||||
styles: { fontSize: "13px", maskArea: nonPresentMaskCss }
|
||||
}
|
||||
]);
|
||||
checkTourStatus();
|
||||
}
|
||||
};
|
||||
const closeTour = async () => {
|
||||
// console.log("closeTour");
|
||||
setIsTour(false);
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const appId = localStorage.getItem("parseAppId");
|
||||
const json = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
const extUserId = json && json.length > 0 && json[0].objectId;
|
||||
// console.log("extUserId ", extUserId)
|
||||
|
||||
let updatedTourStatus = [];
|
||||
if (tourStatusArr.length > 0) {
|
||||
@@ -209,15 +208,15 @@ const HomeLayout = () => {
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<Tour
|
||||
onRequestClose={closeTour}
|
||||
steps={tourConfigs}
|
||||
isOpen={isTour}
|
||||
disableKeyboardNavigation={["esc"]}
|
||||
// disableInteraction={true}
|
||||
scrollOffset={-100}
|
||||
showCloseButton={isCloseBtn}
|
||||
/>
|
||||
{isTour && (
|
||||
<Tour
|
||||
onRequestClose={closeTour}
|
||||
steps={tourConfigs}
|
||||
isOpen={isTour}
|
||||
// scrollOffset={-100}
|
||||
showCloseButton={isCloseBtn}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -8,41 +8,70 @@ function ChangePassword() {
|
||||
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) {
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
alert(t("password-update-alert-4"));
|
||||
}
|
||||
@@ -80,34 +109,93 @@ function ChangePassword() {
|
||||
<label htmlFor="newpassword" className="text-xs block ml-1">
|
||||
{t("new-password")}
|
||||
</label>
|
||||
<input
|
||||
type="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
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="newpassword" className="text-xs block ml-1">
|
||||
<label htmlFor="confirmpassword" className="text-xs block ml-1">
|
||||
{t("confirm-password")}
|
||||
</label>
|
||||
<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 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>
|
||||
</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"
|
||||
|
||||
@@ -42,7 +42,8 @@ const Dashboard = () => {
|
||||
return {
|
||||
selector: `[data-tut=${col.widget.data.tourSection}]`,
|
||||
content: t(`tour-mssg.${col.widget.label}`),
|
||||
position: "top"
|
||||
position: "top",
|
||||
styles: { fontSize: "13px" }
|
||||
// style: { backgroundColor: "#abd4d2" },
|
||||
};
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { sessionStatus } from "../redux/reducers/userReducer";
|
||||
import { withSessionValidation } from "../utils";
|
||||
|
||||
// `Form` render all type of Form on this basis of their provided in path
|
||||
function Form() {
|
||||
@@ -84,6 +85,8 @@ const Forms = (props) => {
|
||||
const [isCorrectPass, setIsCorrectPass] = useState(true);
|
||||
const [isAdvanceOpt, setIsAdvanceOpt] = useState(false);
|
||||
const [bcc, setBcc] = useState([]);
|
||||
const pensList = ["blue", "red", "black"];
|
||||
const [selectedColors, setSelectedColors] = useState(pensList);
|
||||
|
||||
const handleStrInput = (e) => {
|
||||
setIsCorrectPass(true);
|
||||
@@ -130,7 +133,7 @@ const Forms = (props) => {
|
||||
e.target.value = "";
|
||||
}
|
||||
};
|
||||
const handleFileInput = async (e) => {
|
||||
const handleFileInput = withSessionValidation(async (e) => {
|
||||
setpercentage(0);
|
||||
try {
|
||||
const files = Array.from(e.target.files);
|
||||
@@ -314,8 +317,8 @@ const Forms = (props) => {
|
||||
"application/pdf"
|
||||
);
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
progress: (progressValue, loaded, total) => {
|
||||
if (progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
@@ -342,6 +345,7 @@ const Forms = (props) => {
|
||||
setSelectedFiles([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("error ", error);
|
||||
if (error?.code === 209) {
|
||||
dispatch(sessionStatus(false));
|
||||
} else {
|
||||
@@ -350,7 +354,7 @@ const Forms = (props) => {
|
||||
setSelectedFiles([]);
|
||||
removeFile(e);
|
||||
}
|
||||
};
|
||||
});
|
||||
// `isValidURL` is used to check valid webhook url
|
||||
function isValidURL(value) {
|
||||
try {
|
||||
@@ -360,7 +364,7 @@ const Forms = (props) => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const handleSubmit = async (e) => {
|
||||
const handleSubmit = withSessionValidation(async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (fileupload) {
|
||||
@@ -388,13 +392,16 @@ const Forms = (props) => {
|
||||
object.set("Description", formData?.Description);
|
||||
object.set("Note", formData?.Note);
|
||||
if (props.title === "Request Signatures") {
|
||||
if (
|
||||
extUserData?.TenantId?.RequestBody &&
|
||||
extUserData?.TenantId?.RequestSubject
|
||||
) {
|
||||
object.set("RequestBody", extUserData?.TenantId?.RequestBody);
|
||||
object.set("RequestSubject", extUserData?.TenantId?.RequestSubject);
|
||||
}
|
||||
if (
|
||||
extUserData?.TenantId?.RequestBody &&
|
||||
extUserData?.TenantId?.RequestSubject
|
||||
) {
|
||||
object.set("RequestBody", extUserData?.TenantId?.RequestBody);
|
||||
object.set(
|
||||
"RequestSubject",
|
||||
extUserData?.TenantId?.RequestSubject
|
||||
);
|
||||
}
|
||||
}
|
||||
if (props.title !== "Sign Yourself") {
|
||||
const isChecked = formData.SendinOrder === "false" ? false : true;
|
||||
@@ -413,6 +420,8 @@ const Forms = (props) => {
|
||||
object.set("RemindOnceInEvery", remindOnceInEvery);
|
||||
object.set("IsTourEnabled", isTourEnabled);
|
||||
object.set("TimeToCompleteDays", TimeToCompleteDays);
|
||||
object.set("PenColors", selectedColors);
|
||||
|
||||
object.set("AllowModifications", false);
|
||||
object.set("IsEnableOTP", false);
|
||||
if (formData.NotifyOnSignatures !== undefined) {
|
||||
@@ -452,6 +461,7 @@ const Forms = (props) => {
|
||||
if (res) {
|
||||
setSigners([]);
|
||||
setBcc([]);
|
||||
setSelectedColors(pensList);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
@@ -503,7 +513,7 @@ const Forms = (props) => {
|
||||
} else {
|
||||
alert(t("file-alert-3"));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const handleFolder = (data) => {
|
||||
setFolder(data);
|
||||
@@ -534,6 +544,7 @@ const Forms = (props) => {
|
||||
setIsReset(true);
|
||||
setSigners([]);
|
||||
setBcc([]);
|
||||
setSelectedColors(pensList);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
@@ -582,8 +593,8 @@ const Forms = (props) => {
|
||||
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) {
|
||||
progress: (progressValue, loaded, total) => {
|
||||
if (progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
@@ -647,6 +658,20 @@ const Forms = (props) => {
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
|
||||
};
|
||||
|
||||
const handleColorsChange = (color) => {
|
||||
setSelectedColors((prev) => {
|
||||
// If user tries to uncheck the last remaining color → block it
|
||||
if (prev.length === 1 && prev.includes(color)) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
// Normal toggle behavior
|
||||
return prev.includes(color)
|
||||
? prev.filter((c) => c !== color) // remove
|
||||
: [...prev, color]; // add
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${isSubmit ? "" : "shadow-md rounded-box my-[2px] p-3 bg-base-100 text-base-content"}`}
|
||||
@@ -674,7 +699,7 @@ const Forms = (props) => {
|
||||
value={formData.password}
|
||||
onChange={(e) => handleStrInput(e)}
|
||||
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
|
||||
placeholder="Enter pdf password"
|
||||
placeholder={t("enter-pdf-password")}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
@@ -744,16 +769,16 @@ const Forms = (props) => {
|
||||
<div className="flex gap-1 justify-center items-center">
|
||||
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm w-full h-full text-[13px]">
|
||||
<div className="break-all cursor-default">
|
||||
{t("file-selected")}: {selectedFiles.join(", ")}
|
||||
{t("files-selected")}: {selectedFiles.join(", ")}
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
setFileUpload("");
|
||||
setSelectedFiles([]);
|
||||
}}
|
||||
className="cursor-pointer px-[10px] text-[20px] font-bold text-red-500"
|
||||
className="cursor-pointer ml-[10px] text-[20px] font-bold"
|
||||
>
|
||||
<i className="fa-light fa-xmark"></i>
|
||||
<i className="fa-light fa-xmark inline-flex h-7 w-7 items-center justify-center rounded-full hover:bg-red-100 text-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -935,6 +960,7 @@ const Forms = (props) => {
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
min={1}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -1034,6 +1060,7 @@ const Forms = (props) => {
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
min={1}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -1084,6 +1111,7 @@ const Forms = (props) => {
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
min={1}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
@@ -1210,6 +1238,46 @@ const Forms = (props) => {
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
/>
|
||||
</div>
|
||||
{props.title !== "Sign Yourself" && (
|
||||
<div className="text-xs mt-3">
|
||||
<label htmlFor="penColors">
|
||||
{t("pen-colors")}
|
||||
<a
|
||||
data-tooltip-id="pen-colors-tooltip"
|
||||
className="ml-1"
|
||||
>
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
<Tooltip id="pen-colors-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("pen-colors")}</p>
|
||||
<div>{t("pen-colors-help")}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
|
||||
{pensList.map((color) => (
|
||||
<div
|
||||
key={color}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
name="penColors"
|
||||
checked={selectedColors.includes(color)}
|
||||
onChange={() => handleColorsChange(color)}
|
||||
/>
|
||||
<div className="hover:underline underline-offset-2 cursor-default capitalize">
|
||||
{color}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -463,7 +463,7 @@ function GuestLogin() {
|
||||
e.preventDefault();
|
||||
setIsOptionalDetails(!isOptionalDetails);
|
||||
}}
|
||||
className="op-link op-link-secondary max-w-fit text-xs"
|
||||
className="text-base-content/60 no-underline hover:underline focus:outline-none max-w-fit text-xs"
|
||||
>
|
||||
{isOptionalDetails
|
||||
? t("hide-optional-details")
|
||||
|
||||
@@ -47,10 +47,14 @@ function Login() {
|
||||
const [image, setImage] = useState();
|
||||
const [errMsg, setErrMsg] = useState();
|
||||
useEffect(() => {
|
||||
checkUserExt();
|
||||
handleUserExist();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const handleUserExist = async () => {
|
||||
checkUserExt();
|
||||
};
|
||||
|
||||
|
||||
const setLocalVar = (user) => {
|
||||
localStorage.setItem("accesstoken", user.sessionToken);
|
||||
|
||||
@@ -12,7 +12,15 @@ import { SaveFileSize } from "../constant/saveFileSize";
|
||||
import Alert from "../primitives/Alert";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { sanitizeFileName } from "../utils";
|
||||
import { sanitizeFileName, withSessionValidation } from "../utils";
|
||||
|
||||
const COLOR_CLASS = {
|
||||
blue: "text-blue-600",
|
||||
red: "text-red-600",
|
||||
black: "text-black",
|
||||
white: "text-white"
|
||||
};
|
||||
|
||||
const ManageSign = () => {
|
||||
const { t } = useTranslation();
|
||||
const [penColor, setPenColor] = useState("blue");
|
||||
@@ -29,10 +37,12 @@ const ManageSign = () => {
|
||||
const [isInitials, setIsInitials] = useState(false);
|
||||
const [id, setId] = useState("");
|
||||
const [imgInitials, setImgInitials] = useState("");
|
||||
const [stamp, setStamp] = useState();
|
||||
const canvasRef = useRef(null);
|
||||
const imageRef = useRef(null);
|
||||
const initailsRef = useRef(null);
|
||||
const imgInitialsRef = useRef(null);
|
||||
const stampRef = useRef(null);
|
||||
useEffect(() => {
|
||||
fetchUserSign();
|
||||
// eslint-disable-next-line
|
||||
@@ -46,10 +56,9 @@ const ManageSign = () => {
|
||||
objectId: User.id
|
||||
};
|
||||
try {
|
||||
const signCls = "contracts_Signature";
|
||||
const signQuery = new Parse.Query(signCls);
|
||||
signQuery.equalTo("UserId", userId);
|
||||
const signRes = await signQuery.first();
|
||||
const signRes = await Parse.Cloud.run("getdefaultsignature", {
|
||||
userId: User.id
|
||||
});
|
||||
if (signRes) {
|
||||
const res = signRes.toJSON();
|
||||
setId(res.objectId);
|
||||
@@ -64,6 +73,9 @@ const ManageSign = () => {
|
||||
setIsInitials(true);
|
||||
setImgInitials(res?.Initials);
|
||||
}
|
||||
if (res && res?.Stamp) {
|
||||
setStamp(res?.Stamp);
|
||||
}
|
||||
} else {
|
||||
if (User?.get("name")) {
|
||||
const sanitizename = generateTitleFromFilename(User?.get("name"));
|
||||
@@ -128,7 +140,7 @@ const ManageSign = () => {
|
||||
setIsValue(false);
|
||||
}
|
||||
};
|
||||
const handleSubmit = async (e) => {
|
||||
const handleSubmit = withSessionValidation(async (e) => {
|
||||
e.preventDefault();
|
||||
const isUrl = image?.includes("https") || image?.includes("http");
|
||||
if (!isvalue) {
|
||||
@@ -172,15 +184,28 @@ const ManageSign = () => {
|
||||
} else {
|
||||
initialsUrl = imgInitials;
|
||||
}
|
||||
const isStampUrl = stamp?.includes("https") || stamp?.includes("http");
|
||||
|
||||
let stampFile;
|
||||
if (stamp && !isStampUrl) {
|
||||
stampFile = base64StringtoFile(stamp, `${replaceSpace}_stamp`);
|
||||
}
|
||||
let stampUrl;
|
||||
if (stampFile && !isStampUrl) {
|
||||
stampUrl = await uploadFile(stampFile);
|
||||
} else {
|
||||
stampUrl = stamp;
|
||||
}
|
||||
if (imgUrl) {
|
||||
await saveEntry({
|
||||
name: signName,
|
||||
url: imgUrl,
|
||||
initialsUrl: initialsUrl
|
||||
initialsUrl: initialsUrl,
|
||||
stampUrl: stampUrl
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
function base64StringtoFile(base64String, filename) {
|
||||
let arr = base64String.split(","),
|
||||
// type of uploaded image
|
||||
@@ -226,44 +251,24 @@ const ManageSign = () => {
|
||||
};
|
||||
|
||||
const saveEntry = async (obj) => {
|
||||
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);
|
||||
}
|
||||
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,
|
||||
stamp: obj?.stampUrl
|
||||
});
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -296,6 +301,28 @@ const ManageSign = () => {
|
||||
setIsValue(false);
|
||||
}
|
||||
};
|
||||
const handleClearStamp = () => {
|
||||
if (stampRef.current) {
|
||||
stampRef.current.clear();
|
||||
}
|
||||
if (stampRef.current) {
|
||||
stampRef.current.value = "";
|
||||
}
|
||||
setStamp("");
|
||||
if (image) {
|
||||
setIsValue(true);
|
||||
}
|
||||
};
|
||||
const onStampChange = async (event) => {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const file = event.target.files[0];
|
||||
const base64Img = await toDataUrl(file);
|
||||
setStamp(base64Img);
|
||||
setIsValue(true);
|
||||
} else {
|
||||
setStamp("");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="relative h-full bg-base-100 text-base-content flex shadow-md rounded-box overflow-auto">
|
||||
{isLoader && (
|
||||
@@ -309,9 +336,9 @@ const ManageSign = () => {
|
||||
<div className="text-[20px] font-semibold m-[10px] md:m-0 mb-2">
|
||||
{t("my-signature")}
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-0 md:gap-[12px]">
|
||||
<div className="flex flex-col md:flex-row gap-2 md:gap-5">
|
||||
<div className="relative">
|
||||
<span className="font-medium select-none flex mb-[10px] pl-[10px]">
|
||||
<span className="font-medium select-none flex mb-[10px]">
|
||||
{t("signature")}
|
||||
</span>
|
||||
<input
|
||||
@@ -322,7 +349,7 @@ const ManageSign = () => {
|
||||
ref={imageRef}
|
||||
hidden
|
||||
/>
|
||||
<div className="relative">
|
||||
<div>
|
||||
{image ? (
|
||||
<div className="mysignatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden">
|
||||
<img
|
||||
@@ -345,23 +372,24 @@ const ManageSign = () => {
|
||||
dotSize={1}
|
||||
/>
|
||||
)}
|
||||
<div className="penContainerDefault flex flex-row justify-between">
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div>
|
||||
{!image && (
|
||||
<div className="flex flex-row gap-1.5 m-[5px]">
|
||||
{allColor.map((data, key) => {
|
||||
<div className="flex flex-row gap-1 m-[5px]">
|
||||
{allColor.map((color, key) => {
|
||||
const selected = penColor === color;
|
||||
return (
|
||||
<i
|
||||
key={key}
|
||||
onClick={() => setPenColor(allColor[key])}
|
||||
className={`border-b-[2px] ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
|
||||
onClick={() => setPenColor(color)}
|
||||
className={`${COLOR_CLASS[color] ?? ""} ${selected ? "border-current" : "border-white"} cursor-pointer border-b-[2px] pb-[2px] text-[16px] fa-light fa-pen-nib`}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 text-sm md:text-base mr-1">
|
||||
<div className="flex flex-row gap-2 text-sm pr-2">
|
||||
<div
|
||||
type="button"
|
||||
className="op-link"
|
||||
@@ -386,7 +414,8 @@ const ManageSign = () => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative">
|
||||
|
||||
<div>
|
||||
<span className="font-medium select-none flex mb-[10px] pl-[10px]">
|
||||
{t("initials")}
|
||||
</span>
|
||||
@@ -418,26 +447,29 @@ const ManageSign = () => {
|
||||
dotSize={1}
|
||||
/>
|
||||
)}
|
||||
<div className="flex flex-row justify-between w-[183px]">
|
||||
<div
|
||||
className={`${isInitials ? "justify-between" : ""} flex flex-row gap-1`}
|
||||
>
|
||||
<div>
|
||||
{!isInitials && (
|
||||
<div className="flex flex-row gap-1.5 m-[5px]">
|
||||
{allColor.map((data, key) => {
|
||||
<div className="flex flex-row gap-1 m-[5px]">
|
||||
{allColor.map((color, key) => {
|
||||
const selected = initialPen === color;
|
||||
return (
|
||||
<i
|
||||
key={key}
|
||||
onClick={() => setInitialPen(allColor[key])}
|
||||
className={`border-b-[2px] ${key === 0 && initialPen === "blue" ? "border-blue-600" : key === 1 && initialPen === "red" ? "border-red-500" : key === 2 && initialPen === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
|
||||
onClick={() => setInitialPen(color)}
|
||||
className={`${COLOR_CLASS[color] ?? ""} ${selected ? "border-current" : "border-white"} cursor-pointer border-b-[2px] pb-[2px] text-[16px] fa-light fa-pen-nib`}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row gap-1">
|
||||
<div className="flex flex-row gap-1.5 text-sm">
|
||||
<div
|
||||
type="button"
|
||||
className="op-link text-sm md:text-base mr-1"
|
||||
className="op-link"
|
||||
onClick={() => handleUploadInitials()}
|
||||
>
|
||||
{t("upload")}
|
||||
@@ -445,7 +477,7 @@ const ManageSign = () => {
|
||||
<div>
|
||||
<div
|
||||
type="button"
|
||||
className="op-link text-sm md:text-base mr-1"
|
||||
className="op-link"
|
||||
onClick={() => handleClearInitials()}
|
||||
>
|
||||
{t("clear")}
|
||||
@@ -455,6 +487,48 @@ const ManageSign = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="font-medium select-none flex mb-[10px] pl-[10px]">
|
||||
Stamp
|
||||
</span>
|
||||
|
||||
<div
|
||||
onClick={() => stampRef.current.click()}
|
||||
className="mystampCanvas opensigndark:bg-[#1f2937] cursor-pointer border-[2px] opensigncss:border-[#888] opensigndark:border-[#4b5563] rounded-box flex flex-col overflow-hidden w-full h-full aspect-[5/2] justify-center items-center mr-2"
|
||||
>
|
||||
{stamp ? (
|
||||
<img
|
||||
alt="signature"
|
||||
src={stamp}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<input
|
||||
onChange={onStampChange}
|
||||
type="file"
|
||||
className="filetype"
|
||||
accept="image/png,image/jpeg"
|
||||
hidden
|
||||
ref={stampRef}
|
||||
/>
|
||||
<i className="fa-light text-base-content fa-cloud-upload-alt text-[28px]"></i>
|
||||
<div className="text-[15px] text-base-content">
|
||||
{t("upload")}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-end pr-3">
|
||||
<div
|
||||
type="button"
|
||||
className="op-link text-sm"
|
||||
onClick={() => handleClearStamp()}
|
||||
>
|
||||
{t("clear")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pt-3 ml-2 md:ml-0">
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState, useRef, useCallback } from "react";
|
||||
import { lazyWithRetry } from "../utils";
|
||||
import { lazyWithRetry, withSessionValidation } from "../utils";
|
||||
import "../styles/opensigndrive.css";
|
||||
import {
|
||||
getThemeIconColor,
|
||||
@@ -20,7 +20,7 @@ const DriveBody = lazyWithRetry(
|
||||
() => import("../components/opensigndrive/DriveBody")
|
||||
);
|
||||
const dropdowncss =
|
||||
"absolute right-0 py-2 text-[1rem] text-left bg-white border-[1px] border-gray-300 min-w-1 hover:bg-gray-300 rounded-[0.25rem] z-[800]";
|
||||
"absolute right-0 py-2 text-[1rem] text-left bg-base-100 border-[1px] border-gray-300 min-w-1 rounded-[0.25rem] z-[800]";
|
||||
const AppLoader = () => {
|
||||
return (
|
||||
<div className="h-[100vh] flex justify-center items-center">
|
||||
@@ -98,7 +98,7 @@ function Opensigndrive() {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourSecond"]',
|
||||
@@ -109,7 +109,7 @@ function Opensigndrive() {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourThird"]',
|
||||
@@ -120,7 +120,7 @@ function Opensigndrive() {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourForth"]',
|
||||
@@ -131,7 +131,7 @@ function Opensigndrive() {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
const getDetails = async () => {
|
||||
@@ -161,7 +161,7 @@ function Opensigndrive() {
|
||||
/>
|
||||
),
|
||||
position: "bottom",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourSixth"]',
|
||||
@@ -172,7 +172,7 @@ function Opensigndrive() {
|
||||
/>
|
||||
),
|
||||
position: "bottom",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourSeventh"]',
|
||||
@@ -183,7 +183,7 @@ function Opensigndrive() {
|
||||
/>
|
||||
),
|
||||
position: "bottom",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
let newTour = [...tourConfigs, ...addMoreTour];
|
||||
@@ -279,7 +279,7 @@ function Opensigndrive() {
|
||||
setNewFolderName(value);
|
||||
};
|
||||
//function for create folder
|
||||
const handleAddFolder = async (e) => {
|
||||
const handleAddFolder = withSessionValidation(async (e) => {
|
||||
e.preventDefault();
|
||||
if (newFolderName) {
|
||||
setIsFolderLoader(true);
|
||||
@@ -313,7 +313,12 @@ function Opensigndrive() {
|
||||
const template = new Parse.Object(foldercls);
|
||||
template.set("Name", newFolderName);
|
||||
template.set("Type", "Folder");
|
||||
|
||||
const ExtCls = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
template.set("ExtUserPtr", {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Users",
|
||||
objectId: ExtCls[0].objectId
|
||||
});
|
||||
if (parentId) {
|
||||
template.set("Folder", folderPtr);
|
||||
}
|
||||
@@ -338,7 +343,7 @@ function Opensigndrive() {
|
||||
} else {
|
||||
setError(t("fill-field"));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
//function to use sorting document list according to type and order
|
||||
const sortedBy = (appInfo, type, order) => {
|
||||
@@ -688,7 +693,7 @@ function Opensigndrive() {
|
||||
</div>
|
||||
{/* Mobile search toggle */}
|
||||
<button
|
||||
className="md:hidden p-2 flex justify-center items-center focus:outline-none rounded-md hover:bg-gray-200 text-[18px]"
|
||||
className="md:hidden p-2 flex justify-center items-center focus:outline-none rounded-md hover:bg-base-300 text-[18px]"
|
||||
aria-label="Search"
|
||||
onClick={() => setMobileSearchOpen((open) => !open)}
|
||||
>
|
||||
@@ -699,7 +704,7 @@ function Opensigndrive() {
|
||||
</button>
|
||||
<div
|
||||
id="folder-menu"
|
||||
className={`${isOptions ? "dropdown show dropDownStyle" : "dropdown"} hidden md:block cursor-pointer hover:bg-gray-200 p-2 rounded-md`}
|
||||
className={`${isOptions ? "dropdown show dropDownStyle" : "dropdown"} hidden md:block cursor-pointer hover:bg-base-300 p-2 rounded-md`}
|
||||
onClick={handleFolderOptions}
|
||||
>
|
||||
<div data-tut="reactourSecond">
|
||||
@@ -749,7 +754,7 @@ function Opensigndrive() {
|
||||
>
|
||||
<div
|
||||
data-tut="reactourThird"
|
||||
className="cursor-pointer flex flex-row items-center justify-center p-2 hover:bg-gray-200 rounded-md"
|
||||
className="cursor-pointer flex flex-row items-center justify-center p-2 hover:bg-base-300 rounded-md"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<i
|
||||
@@ -767,9 +772,7 @@ function Opensigndrive() {
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
isShowSort ? "dropdown-menu show" : "dropdown-menu"
|
||||
}
|
||||
className={`${isShowSort ? "dropdown-menu show" : "dropdown-menu"} bg-base-100 border-[1px] border-gray-300`}
|
||||
aria-labelledby="dropdownMenuButton"
|
||||
aria-expanded={isShowSort ? "true" : "false"}
|
||||
>
|
||||
@@ -817,7 +820,7 @@ function Opensigndrive() {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="cursor-pointer p-2 hover:bg-gray-200 rounded-md flex justify-center items-center"
|
||||
className="cursor-pointer p-2 hover:bg-base-300 rounded-md flex justify-center items-center"
|
||||
data-tut="reactourForth"
|
||||
onClick={handleViewOption}
|
||||
>
|
||||
@@ -833,7 +836,7 @@ function Opensigndrive() {
|
||||
onClick={() => setIsOptions(!isOptions)}
|
||||
>
|
||||
<div
|
||||
className="p-3 flex items-center justify-center cursor-pointer rounded-md hover:bg-gray-200"
|
||||
className="p-3 flex items-center justify-center cursor-pointer rounded-md hover:bg-base-300"
|
||||
data-tut="reactourSecond"
|
||||
>
|
||||
<i
|
||||
|
||||
@@ -3,7 +3,6 @@ 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";
|
||||
@@ -15,7 +14,8 @@ import {
|
||||
setDefaultSignImg,
|
||||
resetWidgetState,
|
||||
setPrefillImg,
|
||||
setTypedSignFont
|
||||
setTypedSignFont,
|
||||
setMyStamp
|
||||
} from "../redux/reducers/widgetSlice";
|
||||
import {
|
||||
contractDocument,
|
||||
@@ -71,11 +71,13 @@ import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
|
||||
import TextFontSetting from "../components/pdf/TextFontSetting";
|
||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
|
||||
import * as utils from "../utils";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
|
||||
function PdfRequestFiles(
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const windowSize = useWindowSize();
|
||||
const prefillImg = useSelector((state) => state.widget.prefillImg);
|
||||
const isShowModal = useSelector((state) => state.widget.isShowModal);
|
||||
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
|
||||
@@ -160,12 +162,6 @@ function PdfRequestFiles(
|
||||
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;
|
||||
@@ -214,10 +210,10 @@ function PdfRequestFiles(
|
||||
};
|
||||
|
||||
// Use setTimeout to wait for the transition to complete
|
||||
const timer = setTimeout(updateSize, 100); // match the transition duration
|
||||
const timer = setTimeout(updateSize, 150); // match the transition duration
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [divRef.current, isSidebar]);
|
||||
}, [divRef.current, isSidebar, windowSize?.width]);
|
||||
const redirectUrl = pdfDetails?.[0]?.RedirectUrl || "";
|
||||
useEffect(() => {
|
||||
if (isredirectCanceled) return; // Stop the redirect timer if canceled
|
||||
@@ -340,7 +336,6 @@ function PdfRequestFiles(
|
||||
const getCurrentSigner = getSigners?.find(
|
||||
(data) => data.UserId.objectId === jsonSender?.objectId
|
||||
);
|
||||
|
||||
currUserId = getCurrentSigner?.objectId
|
||||
? getCurrentSigner.objectId
|
||||
: contactBookId ||
|
||||
@@ -414,6 +409,35 @@ function PdfRequestFiles(
|
||||
} else {
|
||||
const obj = documentData?.[0];
|
||||
setSendInOrder(obj?.SendinOrder || false);
|
||||
if (
|
||||
obj?.Signers?.length &&
|
||||
obj?.Placeholders?.length &&
|
||||
currUserId !== "undefined"
|
||||
) {
|
||||
const params = {
|
||||
event: "viewed",
|
||||
contactId: currUserId,
|
||||
body: {
|
||||
objectId: documentData?.[0].objectId,
|
||||
}
|
||||
};
|
||||
try {
|
||||
await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}functions/triggerevent`,
|
||||
params,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id":
|
||||
localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
console.log("Err ", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let signers = [];
|
||||
@@ -508,6 +532,7 @@ function PdfRequestFiles(
|
||||
const initials = defaultSignRes?.res?.defaultInitial || "";
|
||||
dispatch(setDefaultSignImg(sign));
|
||||
dispatch(setMyInitial(initials));
|
||||
dispatch(setMyStamp(defaultSignRes?.res?.defaultStamp));
|
||||
} else {
|
||||
dispatch(setSaveSignCheckbox({ isVisible: true }));
|
||||
}
|
||||
@@ -734,7 +759,7 @@ function PdfRequestFiles(
|
||||
docId,
|
||||
contactId,
|
||||
objectId,
|
||||
widgets
|
||||
widgets,
|
||||
);
|
||||
if (resSign && resSign.status === "success") {
|
||||
dispatch(setTypedSignFont("Fasthand"));
|
||||
@@ -770,12 +795,16 @@ function PdfRequestFiles(
|
||||
sendmail !== "false" &&
|
||||
sendInOrder
|
||||
) {
|
||||
const mailBody =
|
||||
tenantMailTemplate?.body;
|
||||
const mailSubject =
|
||||
tenantMailTemplate?.subject;
|
||||
const requestBody =
|
||||
updatedDoc.updatedPdfDetails?.[0]?.RequestBody ||
|
||||
tenantMailTemplate?.body;
|
||||
mailBody;
|
||||
const requestSubject =
|
||||
updatedDoc.updatedPdfDetails?.[0]?.RequestSubject ||
|
||||
tenantMailTemplate?.subject;
|
||||
mailSubject;
|
||||
if (user) {
|
||||
const expireDate = expiry;
|
||||
const newDate = new Date(expireDate);
|
||||
@@ -1075,7 +1104,7 @@ function PdfRequestFiles(
|
||||
? t("signature-validate-alert", { minRequiredCount })
|
||||
: t("signature-validate-alert-2"),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
//function for get pdf page details
|
||||
@@ -1099,10 +1128,11 @@ 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, {
|
||||
@@ -1231,7 +1261,7 @@ function PdfRequestFiles(
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourFirst"]',
|
||||
@@ -1243,7 +1273,7 @@ function PdfRequestFiles(
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="pdfArea"]',
|
||||
@@ -1255,7 +1285,7 @@ function PdfRequestFiles(
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="pdftools"]',
|
||||
@@ -1267,7 +1297,7 @@ function PdfRequestFiles(
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourFifth"]',
|
||||
@@ -1279,7 +1309,7 @@ function PdfRequestFiles(
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
const signedByStep = {
|
||||
@@ -1292,7 +1322,7 @@ function PdfRequestFiles(
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
};
|
||||
//checking if signed by user component exist then add signed by tour step
|
||||
const signedBy =
|
||||
@@ -1311,7 +1341,7 @@ function PdfRequestFiles(
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
};
|
||||
//checking if AllowModifications is true then add allow widgets panel tour step
|
||||
const allModifyWidgets = {
|
||||
@@ -1324,7 +1354,7 @@ function PdfRequestFiles(
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
};
|
||||
|
||||
//handle signed by panel index if signed by exist then 2 else 1 to add tour step
|
||||
@@ -1366,10 +1396,10 @@ function PdfRequestFiles(
|
||||
};
|
||||
|
||||
const clickOnZoomIn = () => {
|
||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||
onClickZoomIn(zoomPercent, setScale, setZoomPercent);
|
||||
};
|
||||
const clickOnZoomOut = () => {
|
||||
onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale);
|
||||
onClickZoomOut(zoomPercent, setZoomPercent, setScale);
|
||||
};
|
||||
const handleDownloadBtn = async () => {
|
||||
const url = pdfDetails?.[0]?.SignedUrl || pdfDetails?.[0]?.URL;
|
||||
@@ -1488,7 +1518,10 @@ function PdfRequestFiles(
|
||||
const widgetHeight =
|
||||
defaultWidthHeight(dragTypeValue).height * containerScale;
|
||||
const extUser = localStorage.getItem("Extand_Class");
|
||||
const parseUser = extUser && JSON.parse(extUser)[0];
|
||||
let parseUser =
|
||||
(extUser && JSON.parse(extUser)[0]) ||
|
||||
JSON.parse(localStorage.getItem("signer"));
|
||||
|
||||
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
|
||||
//adding and updating drop position in array when user drop signature button in div
|
||||
if (item === "onclick") {
|
||||
@@ -1530,12 +1563,16 @@ 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;
|
||||
const getXPosition = signBtnPosition[0]
|
||||
? x - signBtnPosition[0].xPos
|
||||
: x;
|
||||
const getYPosition = signBtnPosition[0]
|
||||
? y - signBtnPosition[0].yPos
|
||||
: y;
|
||||
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;
|
||||
}
|
||||
|
||||
dropObj = {
|
||||
xPosition: getXPosition / (containerScale * scale),
|
||||
yPosition: getYPosition / (containerScale * scale),
|
||||
@@ -1572,6 +1609,7 @@ 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) =>
|
||||
@@ -1582,9 +1620,17 @@ 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] }
|
||||
? {
|
||||
...x,
|
||||
placeHolder: [...x.placeHolder, placeHolder].sort(
|
||||
(a, b) => a.pageNumber - b.pageNumber
|
||||
)
|
||||
}
|
||||
: x.Id === uniqueId
|
||||
? { ...x, placeHolder: [placeHolder] }
|
||||
? {
|
||||
...x,
|
||||
placeHolder: [placeHolder]
|
||||
}
|
||||
: x
|
||||
);
|
||||
setSignerPos(updatesignerPos);
|
||||
@@ -1778,6 +1824,7 @@ function PdfRequestFiles(
|
||||
!alreadySign &&
|
||||
requestSignTourFunction()}
|
||||
<Tour
|
||||
key={`tour-min-required-${minRequiredCount || 0}`}
|
||||
showNumber={false}
|
||||
showNavigation={false}
|
||||
showNavigationNumber={false}
|
||||
@@ -2033,7 +2080,6 @@ function PdfRequestFiles(
|
||||
{containerWH && (
|
||||
<RenderPdf
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
drop={drop}
|
||||
pageNumber={pageNumber}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pdfNewWidth={pdfNewWidth}
|
||||
@@ -2071,6 +2117,8 @@ function PdfRequestFiles(
|
||||
setIsReqSignTourDisabled={handleCloseTour}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
isShowModal={isShowModal}
|
||||
signBtnPosition={signBtnPosition}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -2178,6 +2226,7 @@ function PdfRequestFiles(
|
||||
setUniqueId={setUniqueId}
|
||||
signatureTypes={signatureType}
|
||||
allowCellResize={false}
|
||||
penColors={pdfDetails?.[0]?.PenColors}
|
||||
/>
|
||||
)}
|
||||
<DownloadPdfZip
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Alert from "../primitives/Alert";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Loader from "../primitives/Loader";
|
||||
@@ -11,35 +11,29 @@ import {
|
||||
} 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 { withSessionValidation } from "../utils";
|
||||
import { WidgetsTab, EmailTab } from "../components/preferences/tabs";
|
||||
import {
|
||||
setUserInfo,
|
||||
setTenantInfo,
|
||||
setLoader,
|
||||
setTopLoader,
|
||||
setAlertInfo
|
||||
} from "../redux/reducers/userReducer";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
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);
|
||||
const dispatch = useDispatch();
|
||||
const { isLoader, isTopLoader, alertInfo } = useSelector(
|
||||
(state) => state.user
|
||||
);
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
const [errMsg, setErrMsg] = useState("");
|
||||
const [isNotifyOnSignatures, setIsNotifyOnSignatures] = useState();
|
||||
@@ -56,10 +50,6 @@ 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");
|
||||
|
||||
useEffect(() => {
|
||||
@@ -67,14 +57,22 @@ const Preferences = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const fetchSignType = async () => {
|
||||
setIsTopLoader(true);
|
||||
const EmailTab =
|
||||
extClass?.[0]?.UserRole === "contracts_Admin"
|
||||
? [{ name: "email", title: t("email"), icon: "fa-light fa-envelope" }]
|
||||
: [];
|
||||
const showAlert = (type, msg) => {
|
||||
dispatch(setAlertInfo({ type, msg }));
|
||||
setTimeout(() => {
|
||||
dispatch(setAlertInfo({ type: "success", msg: "" }));
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const fetchSignType = withSessionValidation(async () => {
|
||||
dispatch(setTopLoader(true));
|
||||
const EmailTab = [
|
||||
{ name: "email", title: t("email"), icon: "fa-light fa-envelope" }
|
||||
];
|
||||
|
||||
const arr = [
|
||||
generaltab,
|
||||
{ name: "widgets", title: t("widgets"), icon: "fa-light fa-list" },
|
||||
...EmailTab,
|
||||
];
|
||||
setTab(arr);
|
||||
@@ -85,73 +83,88 @@ const Preferences = () => {
|
||||
)
|
||||
);
|
||||
const tenantDetails = await getTenantDetails(user?.objectId);
|
||||
await tenantEmailTemplate(tenantDetails);
|
||||
dispatch(setTenantInfo(tenantDetails));
|
||||
const signatureType = tenantDetails?.SignatureType || [];
|
||||
const tenantSignTypes = signatureType?.filter((x) => x.enabled === true);
|
||||
const getUser = await Parse.Cloud.run("getUserDetails");
|
||||
if (getUser) {
|
||||
const _getUser = JSON.parse(JSON.stringify(getUser));
|
||||
const notifyOnSignatures =
|
||||
_getUser?.NotifyOnSignatures !== undefined
|
||||
? _getUser?.NotifyOnSignatures
|
||||
: true;
|
||||
setIsNotifyOnSignatures(notifyOnSignatures);
|
||||
setTimezone(_getUser?.Timezone || usertimezone);
|
||||
if (tenantSignTypes?.length > 0) {
|
||||
const signatureType = _getUser?.SignatureType || signatureTypes;
|
||||
const updatedSignatureType = await handleSignatureType(
|
||||
tenantSignTypes,
|
||||
signatureType
|
||||
);
|
||||
setSignatureType(updatedSignatureType);
|
||||
} else {
|
||||
const SignatureType = _getUser?.SignatureType || signatureTypes;
|
||||
setSignatureType(SignatureType);
|
||||
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 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";
|
||||
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);
|
||||
);
|
||||
const getUser = extUser?.data?.result;
|
||||
if (!getUser) {
|
||||
setErrMsg(t("something-went-wrong-mssg"));
|
||||
return;
|
||||
}
|
||||
const _getUser = JSON.parse(JSON.stringify(getUser));
|
||||
dispatch(setUserInfo(_getUser));
|
||||
setIsNotifyOnSignatures(
|
||||
_getUser?.NotifyOnSignatures !== undefined
|
||||
? _getUser?.NotifyOnSignatures
|
||||
: true
|
||||
);
|
||||
setTimezone(_getUser?.Timezone || usertimezone);
|
||||
if (tenantSignTypes?.length > 0) {
|
||||
const signatureType = _getUser?.SignatureType || signatureTypes;
|
||||
const updatedSignatureType = await handleSignatureType(
|
||||
tenantSignTypes,
|
||||
signatureType
|
||||
);
|
||||
setSignatureType(updatedSignatureType);
|
||||
} else {
|
||||
setSignatureType(_getUser?.SignatureType || signatureTypes);
|
||||
}
|
||||
setSendinOrder(
|
||||
_getUser?.SendinOrder !== undefined ? _getUser?.SendinOrder : true
|
||||
);
|
||||
setIsTourEnabled(
|
||||
_getUser?.IsTourEnabled !== undefined ? _getUser?.IsTourEnabled : true
|
||||
);
|
||||
setDateFormat(
|
||||
_getUser?.DateFormat !== undefined ? _getUser?.DateFormat : "MM/DD/YYYY"
|
||||
);
|
||||
setIs12HourTime(
|
||||
_getUser?.Is12HourTime !== undefined ? _getUser?.Is12HourTime : false
|
||||
);
|
||||
setIsLTVEnabled(
|
||||
_getUser?.IsLTVEnabled !== undefined ? _getUser?.IsLTVEnabled : false
|
||||
);
|
||||
const downloadFilenameFormat =
|
||||
_getUser?.DownloadFilenameFormat || "DOCNAME";
|
||||
setFileNameFormat(downloadFilenameFormat);
|
||||
} catch (err) {
|
||||
console.log("err while getting user details", err);
|
||||
console.error("Error while getting user details: ", err);
|
||||
setErrMsg(t("something-went-wrong-mssg"));
|
||||
} finally {
|
||||
setIsTopLoader(false);
|
||||
dispatch(setTopLoader(false));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// `handleCheckboxChange` is trigger when user enable/disable checkbox of respective type
|
||||
const handleCheckboxChange = (index) => {
|
||||
// Create a copy of the signatureType array
|
||||
const updatedSignatureType = [...signatureType];
|
||||
// Toggle the enabled value for the clicked item
|
||||
updatedSignatureType[index].enabled = !updatedSignatureType[index].enabled;
|
||||
// Update the state with the modified array
|
||||
setSignatureType(updatedSignatureType);
|
||||
// // Create a copy of the signatureType array
|
||||
// const updatedSignatureType = [...signatureType];
|
||||
// // Toggle the enabled value for the clicked item
|
||||
// updatedSignatureType[index].enabled = !updatedSignatureType[index].enabled;
|
||||
// // Update the state with the modified array
|
||||
// setSignatureType(updatedSignatureType);
|
||||
|
||||
setSignatureType((prev) =>
|
||||
prev.map((item, i) =>
|
||||
i === index ? { ...item, enabled: !item.enabled } : item
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// `handleSave` is used save updated value signature type
|
||||
const handleSave = async () => {
|
||||
setIsLoader(true);
|
||||
const handleSave = withSessionValidation(async () => {
|
||||
dispatch(setLoader(true));
|
||||
const Timezone = timezone || usertimezone;
|
||||
if (
|
||||
signatureType.length > 0 ||
|
||||
@@ -165,20 +178,12 @@ const Preferences = () => {
|
||||
enabledSignTypes?.length === 1 &&
|
||||
enabledSignTypes[0]?.name === "default";
|
||||
if (enabledSignTypes.length === 0) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("at-least-one-signature-type")
|
||||
});
|
||||
setIsLoader(false);
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
|
||||
showAlert("danger", t("at-least-one-signature-type"));
|
||||
dispatch(setLoader(false));
|
||||
return;
|
||||
} else if (isDefaultSignTypeOnly) {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("expect-default-one-signature-type")
|
||||
});
|
||||
setIsLoader(false);
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
|
||||
showAlert("danger", t("expect-default-one-signature-type"));
|
||||
dispatch(setLoader(false));
|
||||
return;
|
||||
} else {
|
||||
params = { ...params, SignatureType: signatureType };
|
||||
@@ -199,7 +204,7 @@ const Preferences = () => {
|
||||
};
|
||||
const updateRes = await Parse.Cloud.run("updatepreferences", params);
|
||||
if (updateRes) {
|
||||
setIsAlert({ type: "success", msg: t("saved-successfully") });
|
||||
showAlert("success", t("saved-successfully"));
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
@@ -215,194 +220,26 @@ const Preferences = () => {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Error updating signature type", err);
|
||||
setIsAlert({ type: "danger", msg: err.message });
|
||||
console.error("Error updating signature type: ", err);
|
||||
showAlert("danger", err.message);
|
||||
}
|
||||
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
dispatch(setLoader(false));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
|
||||
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>}
|
||||
{alertInfo.msg && <Alert type={alertInfo.type}>{alertInfo.msg}</Alert>}
|
||||
{isTopLoader ? (
|
||||
<div className="flex justify-center items-center h-screen">
|
||||
<Loader />
|
||||
@@ -436,20 +273,25 @@ const Preferences = () => {
|
||||
onClick={() => setactiveTab(ind)}
|
||||
key={ind}
|
||||
role="tab"
|
||||
className={`${activeTab === ind ? "op-tab-active" : ""} op-tab text-xs md:text-base pb-2 md:pb-0 transition-all `}
|
||||
className={` op-tab text-xs md:text-base pb-2 md:pb-0 transition-all`}
|
||||
aria-selected={activeTab === ind}
|
||||
aria-controls={`panel-${tabData.title}`}
|
||||
>
|
||||
<i className={tabData.icon}></i>
|
||||
<span className="ml-1 md:ml-2">{tabData.title}</span>
|
||||
<span
|
||||
className={`${activeTab === ind ? "block" : "hidden"} md:block ml-1`}
|
||||
title={tabData?.title}
|
||||
>
|
||||
{tabData.title}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="panel-general"
|
||||
id={`panel-${activeTab}`}
|
||||
className="px-6 pt-4 pb-6"
|
||||
aria-labelledby="tab-general"
|
||||
aria-labelledby={`tab-${activeTab}`}
|
||||
role="tabpanel"
|
||||
>
|
||||
{tabName(activeTab) === "general" && (
|
||||
@@ -483,7 +325,9 @@ const Preferences = () => {
|
||||
<div className="p-[5px] ml-2">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">Draw: </span>
|
||||
<span className="font-bold">
|
||||
{t("draw")}:{" "}
|
||||
</span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l1")}
|
||||
</span>
|
||||
@@ -495,7 +339,9 @@ const Preferences = () => {
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">Upload: </span>
|
||||
<span className="font-bold">
|
||||
{t("upload")}:{" "}
|
||||
</span>
|
||||
<span>
|
||||
{t("allowed-signature-types-help.l3")}
|
||||
</span>
|
||||
@@ -791,168 +637,8 @@ const Preferences = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{tabName(activeTab) === "email" && (
|
||||
<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">
|
||||
{
|
||||
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"
|
||||
// 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" && (
|
||||
<>
|
||||
</>
|
||||
)}
|
||||
{tabName(activeTab) === "widgets" && <WidgetsTab />}
|
||||
{tabName(activeTab) === "email" && <EmailTab />}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
+128
-100
@@ -10,6 +10,7 @@ import ColumnSelector from "../components/ColumnSelector";
|
||||
import TemplatesReport from "../reports/template/TemplatesReport";
|
||||
import DocumentsReport from "../reports/document/DocumentsReport";
|
||||
import { templateReportTour } from "../json/ReportTour";
|
||||
import { withSessionValidation } from "../utils";
|
||||
|
||||
const Report = () => {
|
||||
const { id } = useParams();
|
||||
@@ -31,7 +32,9 @@ const Report = () => {
|
||||
const [columnLabels, setColumnLabels] = useState({});
|
||||
const [defaultColumns, setDefaultColumns] = useState([]);
|
||||
const [isColumnModal, setIsColumnModal] = useState(false);
|
||||
const [searchLoader, setSearchLoader] = useState(false);
|
||||
const debounceTimer = useRef(null);
|
||||
const searchAbortRef = useRef(null); // holds AbortController
|
||||
// Number of documents to display per page (should always be half of docLimit for proper pagination)
|
||||
const docPerPage = 10;
|
||||
// Number of documents to fetch per API call
|
||||
@@ -61,6 +64,10 @@ const Report = () => {
|
||||
setIsNextRecord(false);
|
||||
// Here it'll abort the fetch
|
||||
abortController.abort();
|
||||
if (searchAbortRef.current) {
|
||||
searchAbortRef.current.abort();
|
||||
searchAbortRef.current = null;
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line
|
||||
}, [id]);
|
||||
@@ -73,13 +80,23 @@ const Report = () => {
|
||||
// eslint-disable-next-line
|
||||
}, [isNextRecord]);
|
||||
|
||||
const handleSearchChange = async (e) => {
|
||||
const handleSearchChange = withSessionValidation(async (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
setSearchTerm(term);
|
||||
if (debounceTimer.current) {
|
||||
clearTimeout(debounceTimer.current);
|
||||
}
|
||||
// 2) abort any in-flight request triggered by older debounce
|
||||
if (searchAbortRef.current) {
|
||||
searchAbortRef.current.abort();
|
||||
searchAbortRef.current = null;
|
||||
}
|
||||
debounceTimer.current = setTimeout(async () => {
|
||||
// create controller for THIS request
|
||||
const controller = new AbortController();
|
||||
searchAbortRef.current = controller;
|
||||
|
||||
setSearchLoader(true);
|
||||
try {
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
@@ -90,21 +107,31 @@ const Report = () => {
|
||||
const res = await axios.post(
|
||||
url,
|
||||
{ reportId: id, searchTerm: term, skip: 0, limit: docPerPage },
|
||||
{ headers }
|
||||
{ headers: headers, signal: controller.signal } // ✅ axios abort
|
||||
);
|
||||
// if you want to be extra safe (ignore late responses)
|
||||
if (controller.signal.aborted) return;
|
||||
|
||||
const data = res.data?.result || [];
|
||||
if (!data.error) {
|
||||
setList(data);
|
||||
setIsMoreDocs(data.length >= docPerPage);
|
||||
setIsNextRecord(false);
|
||||
setIsSearchResult(true);
|
||||
setSearchLoader(false);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Search error:", err);
|
||||
// ✅ ignore abort errors
|
||||
const isAbort =
|
||||
err?.name === "CanceledError" ||
|
||||
err?.code === "ERR_CANCELED" ||
|
||||
err?.message?.toLowerCase?.().includes("canceled");
|
||||
if (!isAbort) console.error("Search error:", err);
|
||||
setSearchLoader(false);
|
||||
}
|
||||
}, 300);
|
||||
setIsSearchResult(false);
|
||||
};
|
||||
});
|
||||
|
||||
const handleSearchPaste = (e) => {
|
||||
setTimeout(() => {
|
||||
@@ -119,113 +146,113 @@ const Report = () => {
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
const getReportData = async (
|
||||
skipUserRecord = 0,
|
||||
limit = 20,
|
||||
term = searchTerm
|
||||
) => {
|
||||
// setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
if (json) {
|
||||
setActions(json.actions);
|
||||
const savedCols = JSON.parse(
|
||||
localStorage.getItem("reportColumns") || "{}"
|
||||
);
|
||||
const visible = savedCols[id]?.visible || json.heading;
|
||||
const labels = savedCols[id]?.labels || {};
|
||||
if (!savedCols[id] || id === "contacts") {
|
||||
savedCols[id] = { visible: json.heading, labels: {} };
|
||||
localStorage.setItem("reportColumns", JSON.stringify(savedCols));
|
||||
}
|
||||
setVisibleColumns(visible);
|
||||
setColumnLabels(labels);
|
||||
setHeading(visible);
|
||||
setDefaultColumns(json.heading);
|
||||
setReportName(json.reportName);
|
||||
setReportHelp(json?.helpMsg);
|
||||
const currentUser = Parse.User.current().id;
|
||||
const getReportData = withSessionValidation(
|
||||
async (skipUserRecord = 0, limit = 20, term = searchTerm) => {
|
||||
// setIsLoader(true);
|
||||
const json = reportJson(id);
|
||||
if (json) {
|
||||
setActions(json.actions);
|
||||
const savedCols = JSON.parse(
|
||||
localStorage.getItem("reportColumns") || "{}"
|
||||
);
|
||||
const visible = savedCols[id]?.visible || json.heading;
|
||||
const labels = savedCols[id]?.labels || {};
|
||||
if (!savedCols[id] || id === "contacts") {
|
||||
savedCols[id] = { visible: json.heading, labels: {} };
|
||||
localStorage.setItem("reportColumns", JSON.stringify(savedCols));
|
||||
}
|
||||
setVisibleColumns(visible);
|
||||
setColumnLabels(labels);
|
||||
setHeading(visible);
|
||||
setDefaultColumns(json.heading);
|
||||
setReportName(json.reportName);
|
||||
setReportHelp(json?.helpMsg);
|
||||
const currentUser = Parse.User.current().id;
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
try {
|
||||
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
|
||||
const limitRecord = id === "4Hhwbp482K" ? 200 : limit;
|
||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||
if (term) {
|
||||
params.searchTerm = term;
|
||||
}
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(url, params, {
|
||||
headers: headers,
|
||||
signal: abortController.signal // is used to cancel fetch query
|
||||
});
|
||||
const extraHeads =
|
||||
id === "4Hhwbp482K" ? [...extraCols, "Expiry Date"] : extraCols;
|
||||
setAllColumns(Array.from(new Set([...json.heading, ...extraHeads])));
|
||||
if (id === "6TeaPr321t") {
|
||||
setTourData(templateReportTour);
|
||||
}
|
||||
if (id === "4Hhwbp482K") {
|
||||
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj?.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessiontoken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
try {
|
||||
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
|
||||
const limitRecord = id === "4Hhwbp482K" ? 200 : limit;
|
||||
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
|
||||
if (term) {
|
||||
params.searchTerm = term;
|
||||
}
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
|
||||
const res = await axios.post(url, params, {
|
||||
headers: headers,
|
||||
signal: abortController.signal // is used to cancel fetch query
|
||||
});
|
||||
const extraHeads =
|
||||
id === "4Hhwbp482K" ? [...extraCols, "Expiry Date"] : extraCols;
|
||||
setAllColumns(Array.from(new Set([...json.heading, ...extraHeads])));
|
||||
if (id === "6TeaPr321t") {
|
||||
setTourData(templateReportTour);
|
||||
}
|
||||
if (id === "4Hhwbp482K") {
|
||||
const listData = res.data?.result.filter(
|
||||
(x) => x.Signers.length > 0
|
||||
);
|
||||
if (isSigner) {
|
||||
let isRecord;
|
||||
if (obj?.AuditTrail && obj?.AuditTrail.length > 0) {
|
||||
isRecord = obj?.AuditTrail.some(
|
||||
(item) =>
|
||||
item?.UserPtr?.UserId?.objectId === currentUser &&
|
||||
item.Activity === "Signed"
|
||||
);
|
||||
} else {
|
||||
isRecord = false;
|
||||
}
|
||||
if (isRecord === false) {
|
||||
arr.push(obj);
|
||||
let arr = [];
|
||||
for (const obj of listData) {
|
||||
const isSigner = obj?.Signers?.some(
|
||||
(item) => item.UserId.objectId === currentUser
|
||||
);
|
||||
if (isSigner) {
|
||||
let isRecord;
|
||||
if (obj?.AuditTrail && obj?.AuditTrail.length > 0) {
|
||||
isRecord = obj?.AuditTrail.some(
|
||||
(item) =>
|
||||
item?.UserPtr?.UserId?.objectId === currentUser &&
|
||||
item.Activity === "Signed"
|
||||
);
|
||||
} else {
|
||||
isRecord = false;
|
||||
}
|
||||
if (isRecord === false) {
|
||||
arr.push(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (arr.length === docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
||||
);
|
||||
} else {
|
||||
if (res.data.result.length >= docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
if (!res.data.result.error) {
|
||||
setIsNextRecord(false);
|
||||
if (arr.length === docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0
|
||||
? [...prevRecord, ...res.data.result]
|
||||
: res.data.result
|
||||
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
|
||||
);
|
||||
} else {
|
||||
if (res.data.result.length >= docPerPage) {
|
||||
setIsMoreDocs(true);
|
||||
} else {
|
||||
setIsMoreDocs(false);
|
||||
}
|
||||
if (!res.data.result.error) {
|
||||
setIsNextRecord(false);
|
||||
setList((prevRecord) =>
|
||||
prevRecord.length > 0
|
||||
? [...prevRecord, ...res.data.result]
|
||||
: res.data.result
|
||||
);
|
||||
}
|
||||
}
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
const isCancel = axios.isCancel(err);
|
||||
if (!isCancel) {
|
||||
console.error("getreport error", err);
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
} catch (err) {
|
||||
const isCancel = axios.isCancel(err);
|
||||
if (!isCancel) {
|
||||
console.log("err ", err);
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
}
|
||||
};
|
||||
);
|
||||
|
||||
const commonProps = {
|
||||
ReportName: reportName,
|
||||
@@ -243,6 +270,7 @@ const Report = () => {
|
||||
handleSearchPaste,
|
||||
isSearchResult,
|
||||
columnLabels,
|
||||
searchLoader,
|
||||
openColumnModal: () => setIsColumnModal(true)
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,6 @@ 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 {
|
||||
@@ -64,16 +63,22 @@ import {
|
||||
setDefaultSignImg,
|
||||
setIsShowModal,
|
||||
resetWidgetState,
|
||||
setTypedSignFont
|
||||
setTypedSignFont,
|
||||
setMyStamp
|
||||
} from "../redux/reducers/widgetSlice";
|
||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
|
||||
import WidgetNameModal from "../components/pdf/WidgetNameModal";
|
||||
import CellsSettingModal from "../components/pdf/CellsSettingModal";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
import {
|
||||
applyNumberFormulasToPages,
|
||||
} from "../utils";
|
||||
//For signYourself inProgress section signer can add sign and complete doc sign.
|
||||
function SignYourSelf() {
|
||||
const { t } = useTranslation();
|
||||
const { docId } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
const windowSize = useWindowSize();
|
||||
const isShowModal = useSelector((state) => state.widget.isShowModal);
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
@@ -140,13 +145,9 @@ 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;
|
||||
});
|
||||
@@ -190,11 +191,10 @@ function SignYourSelf() {
|
||||
};
|
||||
|
||||
// Use setTimeout to wait for the transition to complete
|
||||
const timer = setTimeout(updateSize, 100); // match the transition duration
|
||||
const timer = setTimeout(updateSize, 150); // match the transition duration
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [divRef.current, isSidebar]);
|
||||
|
||||
}, [divRef.current, isSidebar, windowSize?.width]);
|
||||
//function for get document details for perticular signer with signer'object id
|
||||
const getDocumentDetails = async (showComplete) => {
|
||||
try {
|
||||
@@ -269,6 +269,7 @@ function SignYourSelf() {
|
||||
);
|
||||
dispatch(setDefaultSignImg(defaultSignRes?.res?.defaultSignature));
|
||||
dispatch(setMyInitial(defaultSignRes?.res?.defaultInitial));
|
||||
dispatch(setMyStamp(defaultSignRes?.res?.defaultStamp));
|
||||
} else {
|
||||
dispatch(setSaveSignCheckbox({ isVisible: true }));
|
||||
}
|
||||
@@ -382,7 +383,8 @@ function SignYourSelf() {
|
||||
dragTypeValue,
|
||||
getWidgetValue,
|
||||
owner,
|
||||
xyPosition
|
||||
xyPosition,
|
||||
true
|
||||
)
|
||||
};
|
||||
dropData.push(dropObj);
|
||||
@@ -394,15 +396,34 @@ 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;
|
||||
const getXPosition = signBtnPosition[0] ? x - signBtnPosition[0].xPos : x;
|
||||
const getYPosition = signBtnPosition[0] ? y - signBtnPosition[0].yPos : y;
|
||||
let getXPosition = signBtnPosition[0] ? x - signBtnPosition[0].xPos : x;
|
||||
let 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),
|
||||
@@ -416,7 +437,9 @@ function SignYourSelf() {
|
||||
dragTypeValue,
|
||||
getWidgetValue,
|
||||
owner,
|
||||
xyPosition
|
||||
xyPosition,
|
||||
pdfDetails?.[0],
|
||||
true
|
||||
),
|
||||
scale: containerScale
|
||||
};
|
||||
@@ -451,9 +474,7 @@ function SignYourSelf() {
|
||||
setFontColor("black");
|
||||
}
|
||||
dispatch(setIsShowModal({ [key]: true }));
|
||||
// if (dragTypeValue !== "checkbox") {
|
||||
setCurrWidgetsDetails(dropObj);
|
||||
// }
|
||||
};
|
||||
|
||||
//`handleResend` function is used to resend otp for email verification
|
||||
@@ -586,9 +607,23 @@ function SignYourSelf() {
|
||||
}
|
||||
if (!isEnableOTP || isEmailVerified) {
|
||||
let showAlert = false,
|
||||
isSignatureExist = false;
|
||||
widgetKey,
|
||||
tourPageNumber;
|
||||
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"
|
||||
);
|
||||
@@ -597,34 +632,24 @@ function SignYourSelf() {
|
||||
for (let i = 0; i < requiredWidgets?.length; i++) {
|
||||
checkSigned = requiredWidgets[i]?.options?.response;
|
||||
if (!checkSigned) {
|
||||
const checkSignUrl = requiredWidgets[i]?.pos?.SignUrl;
|
||||
const checkSignUrl = requiredWidgets[i]?.SignUrl;
|
||||
let checkDefaultSigned =
|
||||
requiredWidgets[i]?.options?.defaultValue;
|
||||
if (!checkSignUrl && !checkDefaultSigned && !showAlert) {
|
||||
showAlert = true;
|
||||
widgetKey = requiredWidgets[i]?.key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//condition to check exist signature widget or not
|
||||
if (!isSignatureExist) {
|
||||
isSignatureExist = xyPosition[i].pos.some(
|
||||
(data) => data?.type === "signature"
|
||||
);
|
||||
if (showAlert) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
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")
|
||||
});
|
||||
if (showAlert) {
|
||||
setUnSignedWidgetId(widgetKey);
|
||||
setPageNumber(tourPageNumber);
|
||||
setWidgetsTour(true);
|
||||
return;
|
||||
} else {
|
||||
setIsUiLoading(true);
|
||||
@@ -771,9 +796,7 @@ function SignYourSelf() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (filterDropPos?.length > 0) {
|
||||
const getXYdata = xyPosition[index].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSign = getPosData.map((url) => {
|
||||
const addSign = xyPosition[index]?.pos?.map((url) => {
|
||||
if (url.key === dragKey) {
|
||||
return {
|
||||
...url,
|
||||
@@ -875,7 +898,7 @@ function SignYourSelf() {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourSecond"]',
|
||||
@@ -887,7 +910,7 @@ function SignYourSelf() {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="pdftools"]',
|
||||
@@ -899,7 +922,7 @@ function SignYourSelf() {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -958,11 +981,9 @@ 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 = getPosData.map((position) => {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (addOption) {
|
||||
return {
|
||||
@@ -1024,9 +1045,7 @@ function SignYourSelf() {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
return {
|
||||
...position,
|
||||
@@ -1083,6 +1102,8 @@ 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(
|
||||
@@ -1096,20 +1117,19 @@ function SignYourSelf() {
|
||||
name: defaultdata?.name || position.options?.name || "Cells",
|
||||
cellCount: count,
|
||||
defaultValue: (defaultdata?.defaultValue || "").slice(0, count),
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor:
|
||||
newFontColor || position.options?.fontColor || "black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata?.name || position.options?.name,
|
||||
fontSize: newFontSize || position.options?.fontSize || 12,
|
||||
fontColor:
|
||||
newFontColor || position.options?.fontColor || "black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1119,7 +1139,7 @@ function SignYourSelf() {
|
||||
const updateXYposition = xyPosition.map((obj, ind) =>
|
||||
ind === index ? { ...obj, pos: updatePos } : obj
|
||||
);
|
||||
setXyPosition(updateXYposition);
|
||||
setXyPosition(applyNumberFormulasToPages(updateXYposition));
|
||||
}
|
||||
setFontSize();
|
||||
setFontColor();
|
||||
@@ -1146,10 +1166,10 @@ function SignYourSelf() {
|
||||
setCurrWidgetsDetails({});
|
||||
};
|
||||
const clickOnZoomIn = () => {
|
||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||
onClickZoomIn(zoomPercent, setScale, setZoomPercent);
|
||||
};
|
||||
const clickOnZoomOut = () => {
|
||||
onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale);
|
||||
onClickZoomOut(zoomPercent, setZoomPercent, setScale);
|
||||
};
|
||||
//`handleRotationFun` function is used to roatate pdf particular page
|
||||
const handleRotationFun = async (rotateDegree) => {
|
||||
@@ -1196,6 +1216,14 @@ function SignYourSelf() {
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
setShowRotateAlert({ status: false, degree: 0 });
|
||||
};
|
||||
const WidgetTourConfig = [
|
||||
{
|
||||
selector: '[data-tut="IsSigned"]',
|
||||
content: t("signature-validate-alert-2"),
|
||||
position: "top",
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
return (
|
||||
<>
|
||||
{isLoading.isLoad ? (
|
||||
@@ -1241,6 +1269,14 @@ 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}
|
||||
@@ -1370,7 +1406,6 @@ function SignYourSelf() {
|
||||
pageNumber={pageNumber}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pdfNewWidth={pdfNewWidth}
|
||||
drop={drop}
|
||||
successEmail={successEmail}
|
||||
nodeRef={nodeRef}
|
||||
handleTabDrag={handleTabDrag}
|
||||
@@ -1408,6 +1443,9 @@ function SignYourSelf() {
|
||||
divRef={divRef}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
isShowModal={isShowModal}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
signBtnPosition={signBtnPosition}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1459,6 +1497,8 @@ function SignYourSelf() {
|
||||
setFontSize={setFontSize}
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
widgetsSource={xyPosition}
|
||||
isSelfSign={true}
|
||||
/>
|
||||
<CellsSettingModal
|
||||
isOpen={isCellsSetting}
|
||||
|
||||
@@ -3,7 +3,6 @@ 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";
|
||||
@@ -39,7 +38,8 @@ import {
|
||||
getOriginalWH,
|
||||
defaultMailBody,
|
||||
defaultMailSubject,
|
||||
handleDeleteWidget
|
||||
handleDeleteWidget,
|
||||
nonPresentMaskCss
|
||||
} from "../constant/Utils";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import "../styles/AddUser.css";
|
||||
@@ -64,18 +64,16 @@ import Alert from "../primitives/Alert";
|
||||
import LottieWithLoader from "../primitives/DotLottieReact";
|
||||
import * as utils from "../utils";
|
||||
import CustomizeMail from "../components/pdf/CustomizeMail";
|
||||
import {
|
||||
resetWidgetState,
|
||||
setPrefillImg
|
||||
} from "../redux/reducers/widgetSlice.js";
|
||||
import { resetWidgetState, setPrefillImg } from "../redux/reducers/widgetSlice";
|
||||
import ShareButton from "../primitives/ShareButton";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
|
||||
const TemplatePlaceholder = () => {
|
||||
const { t } = useTranslation();
|
||||
const journey = "Use Template";
|
||||
const { templateId } = useParams();
|
||||
const windowSize = useWindowSize();
|
||||
const dispatch = useDispatch();
|
||||
const prefillImg = useSelector((state) => state.widget.prefillImg);
|
||||
const { prefillImg, isBulkLoader } = useSelector((state) => state.widget);
|
||||
const divRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
const numPages = 1;
|
||||
@@ -124,11 +122,6 @@ 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("");
|
||||
@@ -166,6 +159,7 @@ const TemplatePlaceholder = () => {
|
||||
const [documentDetails, setDocumentDetails] = useState();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [isUseTemplate, setIsUseTemplate] = useState(false);
|
||||
const [prevRole, setPrevRole] = useState("");
|
||||
const currentUser = localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
@@ -191,11 +185,11 @@ const TemplatePlaceholder = () => {
|
||||
};
|
||||
|
||||
// Use setTimeout to wait for the transition to complete
|
||||
const timer = setTimeout(updateSize, 100); // match the transition duration
|
||||
const timer = setTimeout(updateSize, 150); // match the transition duration
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [divRef.current, isSidebar]);
|
||||
}, [divRef.current, isSidebar, windowSize?.width]);
|
||||
|
||||
|
||||
//function to fetch tenant Details
|
||||
@@ -215,28 +209,21 @@ const TemplatePlaceholder = () => {
|
||||
const filterSignTypes = signatureType?.filter(
|
||||
(x) => x.enabled === true
|
||||
);
|
||||
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
|
||||
});
|
||||
}
|
||||
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 });
|
||||
return filterSignTypes;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -247,7 +234,7 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
};
|
||||
// `fetchTemplate` function in used to get Template from server and setPlaceholder ,setSigner if present
|
||||
const fetchTemplate = async () => {
|
||||
const fetchTemplate = utils.withSessionValidation(async () => {
|
||||
try {
|
||||
const tenantSignTypes = await fetchTenantDetails();
|
||||
const params = { templateId: templateId };
|
||||
@@ -431,7 +418,7 @@ const TemplatePlaceholder = () => {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
//function for setting position after drop signature button over pdf
|
||||
const addPositionOfSignature = (item, monitor) => {
|
||||
@@ -498,12 +485,33 @@ const TemplatePlaceholder = () => {
|
||||
.getBoundingClientRect();
|
||||
const x = offset.x - containerRect.left;
|
||||
const y = offset.y - containerRect.top;
|
||||
const getXPosition = signBtnPosition[0]
|
||||
let getXPosition = signBtnPosition[0]
|
||||
? x - signBtnPosition[0].xPos
|
||||
: x;
|
||||
const getYPosition = signBtnPosition[0]
|
||||
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)
|
||||
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),
|
||||
@@ -620,7 +628,7 @@ const TemplatePlaceholder = () => {
|
||||
selector: '[data-tut="reactourAddbtn"]',
|
||||
content: t("add-user-template"),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -662,9 +670,7 @@ const TemplatePlaceholder = () => {
|
||||
(data) => data.pageNumber === pageNumber
|
||||
);
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((url) => {
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((url) => {
|
||||
if (url.key === keyValue) {
|
||||
return {
|
||||
...url,
|
||||
@@ -756,7 +762,11 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (pdfDetails?.[0]?.CreatedBy?.objectId !== user?.objectId) return;
|
||||
if (
|
||||
pdfDetails?.[0]?.CreatedBy?.objectId !== user?.objectId ||
|
||||
isPrefillModal
|
||||
)
|
||||
return;
|
||||
const timer = setTimeout(() => {
|
||||
autosavedetails();
|
||||
}, 2000);
|
||||
@@ -947,30 +957,38 @@ const TemplatePlaceholder = () => {
|
||||
);
|
||||
if (res?.status === "unfilled") {
|
||||
setIsUiLoading(false);
|
||||
const emptyWidget = res?.emptyResponseObjects.map((item, index) => (
|
||||
<span className="font-medium" key={index}>
|
||||
{item.options.name}
|
||||
</span>
|
||||
));
|
||||
const emptyWidget = res?.emptyResponseObjects
|
||||
?.map((item) => item.options.name)
|
||||
?.join(", ");
|
||||
const timeInMiliSec = 6000;
|
||||
showAlert(
|
||||
"danger",
|
||||
<>
|
||||
The following required field(s) cannot be left empty:{" "}
|
||||
{emptyWidget.map((item, index) => (
|
||||
<span key={index}>
|
||||
{index > 0 && ", "}
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
. Please fill them out to proceed.
|
||||
</>,
|
||||
6000
|
||||
t("prefill-unfilled-widget", {
|
||||
emptyWidget: emptyWidget ? `[${emptyWidget}]` : ""
|
||||
}),
|
||||
timeInMiliSec
|
||||
);
|
||||
} else if (res?.status === "unattach signer") {
|
||||
setIsUiLoading(false);
|
||||
showAlert("danger", "please attach all role to signer");
|
||||
} else if (res?.status === "success") {
|
||||
setDocumentId(res.id);
|
||||
const ownerId = pdfDetails[0].ExtUserPtr?.UserId?.objectId;
|
||||
const firstSigner = signersdata[0];
|
||||
const isOwner = firstSigner?.UserId?.objectId === ownerId;
|
||||
if (pdfDetails[0]?.SendinOrder && isOwner) {
|
||||
setCurrUserId(firstSigner?.objectId);
|
||||
setIsSend(true);
|
||||
return;
|
||||
} else {
|
||||
const currentSigner = signersdata?.find(
|
||||
(x) => x?.UserId?.objectId === ownerId
|
||||
);
|
||||
if (currentSigner) {
|
||||
setCurrUserId(currentSigner?.objectId);
|
||||
}
|
||||
}
|
||||
|
||||
setIsMailModal(true);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -985,7 +1003,7 @@ const TemplatePlaceholder = () => {
|
||||
pdfDetails,
|
||||
signerPos,
|
||||
signersdata,
|
||||
updatedPdfUrl
|
||||
updatedPdfUrl,
|
||||
);
|
||||
if (res.status === "success") {
|
||||
navigate(`/placeHolderSign/${res.id}`, {
|
||||
@@ -1014,7 +1032,7 @@ const TemplatePlaceholder = () => {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourAddbtn"]',
|
||||
@@ -1027,19 +1045,31 @@ const TemplatePlaceholder = () => {
|
||||
),
|
||||
position: "top",
|
||||
observe: '[data-tut="reactourAddbtn--observe"]',
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
// {
|
||||
// selector: '[data-tut="addWidgets"]',
|
||||
// content: () => (
|
||||
// <TourContentWithBtn
|
||||
// message={t("tour-mssg.template-placeholder-2")}
|
||||
// isDontShowCheckbox={!checkTourStatus}
|
||||
// isChecked={handleDontShow}
|
||||
// />
|
||||
// ),
|
||||
// position: "top",
|
||||
// styles: { fontSize: "13px" }
|
||||
// },
|
||||
{
|
||||
selector: '[data-tut="addWidgets"]',
|
||||
selector: '[data-tut="nonpresentmask"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={t("tour-mssg.template-placeholder-2")}
|
||||
isDontShowCheckbox={!checkTourStatus}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
position: "center",
|
||||
styles: { fontSize: "13px", maskArea: nonPresentMaskCss },
|
||||
action: () => handleCloseRoleModal()
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="pdftools"]',
|
||||
@@ -1051,7 +1081,7 @@ const TemplatePlaceholder = () => {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="headerArea"]',
|
||||
@@ -1063,7 +1093,7 @@ const TemplatePlaceholder = () => {
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1107,6 +1137,7 @@ const TemplatePlaceholder = () => {
|
||||
// `handleAddSigner` is used to open Add Role Modal
|
||||
const handleAddSigner = () => {
|
||||
setIsModalRole(true);
|
||||
setPrevRole(roleName || "");
|
||||
setRoleName("");
|
||||
};
|
||||
|
||||
@@ -1134,6 +1165,7 @@ const TemplatePlaceholder = () => {
|
||||
setSignerPos((prev) => [...prev, signerPosObj]);
|
||||
setIsModalRole(false);
|
||||
setRoleName("");
|
||||
setCurrWidgetsDetails({});
|
||||
setUniqueId(Id);
|
||||
setBlockColor(color[index]);
|
||||
setIsSelectId(index);
|
||||
@@ -1264,6 +1296,10 @@ const TemplatePlaceholder = () => {
|
||||
const RedirectUrl = updateTemplate?.[0]?.RedirectUrl
|
||||
? { RedirectUrl: updateTemplate?.[0]?.RedirectUrl }
|
||||
: {};
|
||||
const penColors =
|
||||
updateTemplate?.[0]?.PenColors?.length > 0
|
||||
? { PenColors: updateTemplate?.[0]?.PenColors }
|
||||
: {};
|
||||
const data = {
|
||||
...(updateTemplate?.[0]?.URL ? { URL: updateTemplate?.[0]?.URL } : {}),
|
||||
Name: updateTemplate?.[0]?.Name || "",
|
||||
@@ -1280,6 +1316,7 @@ const TemplatePlaceholder = () => {
|
||||
: false,
|
||||
TimeToCompleteDays:
|
||||
parseInt(updateTemplate?.[0]?.TimeToCompleteDays) || 15,
|
||||
...penColors,
|
||||
...Bcc,
|
||||
...RedirectUrl
|
||||
};
|
||||
@@ -1295,6 +1332,7 @@ const TemplatePlaceholder = () => {
|
||||
};
|
||||
|
||||
const handleCloseRoleModal = () => {
|
||||
setRoleName(prevRole || "");
|
||||
setIsModalRole(false);
|
||||
};
|
||||
const handleSaveWidgetsOptions = (
|
||||
@@ -1310,6 +1348,9 @@ const TemplatePlaceholder = () => {
|
||||
isHideLabel,
|
||||
layout
|
||||
) => {
|
||||
const isPrefill = signerPos.some(
|
||||
(x) => x?.Role === "prefill" && x.Id === uniqueId
|
||||
);
|
||||
const filterSignerPos = signerPos.filter((data) => data.Id === uniqueId);
|
||||
if (filterSignerPos.length > 0) {
|
||||
const getPlaceHolder = filterSignerPos[0].placeHolder;
|
||||
@@ -1319,10 +1360,9 @@ const TemplatePlaceholder = () => {
|
||||
);
|
||||
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (currWidgetsDetails?.type === radioButtonWidget) {
|
||||
if (addOption) {
|
||||
@@ -1351,12 +1391,8 @@ const TemplatePlaceholder = () => {
|
||||
defaultValue: defaultValue,
|
||||
isReadOnly: isReadOnly || false,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1382,20 +1418,21 @@ const TemplatePlaceholder = () => {
|
||||
...position.options,
|
||||
name: dropdownName,
|
||||
values: dropdownOptions,
|
||||
validation: {
|
||||
minRequiredCount: minCount,
|
||||
maxRequiredCount: maxCount
|
||||
},
|
||||
...(!isPrefill
|
||||
? {
|
||||
validation: {
|
||||
minRequiredCount: minCount,
|
||||
maxRequiredCount: maxCount
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
layout: layout,
|
||||
isReadOnly: isReadOnly || false,
|
||||
defaultValue: defaultValue,
|
||||
isHideLabel: isHideLabel || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
...(isPrefill ? { status } : {})
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1408,12 +1445,8 @@ const TemplatePlaceholder = () => {
|
||||
status: status,
|
||||
values: dropdownOptions,
|
||||
defaultValue: defaultValue,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black",
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
...(isReadOnly ? { isReadOnly: isReadOnly || false } : {})
|
||||
}
|
||||
};
|
||||
@@ -1428,9 +1461,11 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
const recalculatedPlaceholders =
|
||||
utils.applyNumberFormulasToPages(newUpdateSignPos);
|
||||
const newUpdateSigner = signerPos.map((obj) => {
|
||||
if (obj.Id === uniqueId) {
|
||||
return { ...obj, placeHolder: newUpdateSignPos };
|
||||
return { ...obj, placeHolder: recalculatedPlaceholders };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
@@ -1463,9 +1498,9 @@ const TemplatePlaceholder = () => {
|
||||
);
|
||||
|
||||
if (getPageNumer.length > 0) {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
const textSize = fontSize || currWidgetsDetails?.options?.fontSize;
|
||||
const textColor = fontColor || currWidgetsDetails?.options?.fontColor;
|
||||
const addSignPos = getPageNumer?.[0]?.pos?.map((position) => {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === textInputWidget) {
|
||||
return {
|
||||
@@ -1479,12 +1514,8 @@ const TemplatePlaceholder = () => {
|
||||
isReadOnly: defaultdata?.isReadOnly || false,
|
||||
validation:
|
||||
{},
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
} else if (position.type === cellsWidget) {
|
||||
@@ -1503,21 +1534,21 @@ const TemplatePlaceholder = () => {
|
||||
validation:
|
||||
{},
|
||||
isReadOnly: defaultdata?.isReadOnly || false,
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black"
|
||||
}
|
||||
};
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
}
|
||||
else if (["signature", "initials"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata.name,
|
||||
hint: defaultdata?.hint || ""
|
||||
hint: defaultdata?.hint || "",
|
||||
...(defaultdata?.penColors?.length > 0 && {
|
||||
penColors: defaultdata?.penColors
|
||||
})
|
||||
}
|
||||
};
|
||||
} else {
|
||||
@@ -1529,12 +1560,8 @@ const TemplatePlaceholder = () => {
|
||||
status: defaultdata.status,
|
||||
defaultValue: defaultdata.defaultValue,
|
||||
hint: defaultdata?.hint || "",
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
fontColor ||
|
||||
currWidgetsDetails?.options?.fontColor ||
|
||||
"black"
|
||||
fontSize: textSize || 12,
|
||||
fontColor: textColor || "black",
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1588,10 +1615,10 @@ const TemplatePlaceholder = () => {
|
||||
};
|
||||
|
||||
const clickOnZoomIn = () => {
|
||||
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
|
||||
onClickZoomIn(zoomPercent, setScale, setZoomPercent);
|
||||
};
|
||||
const clickOnZoomOut = () => {
|
||||
onClickZoomOut(zoomPercent, scale, setZoomPercent, setScale);
|
||||
onClickZoomOut(zoomPercent, setZoomPercent, setScale);
|
||||
};
|
||||
//`handleRotationFun` function is used to roatate pdf particular page
|
||||
const handleRotationFun = async (rotateDegree) => {
|
||||
@@ -1629,7 +1656,7 @@ const TemplatePlaceholder = () => {
|
||||
selector: '[data-tut="isSignatureWidget"]',
|
||||
content: t("signature-field-widget", { signersName }),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
const showAlert = (type, message, duration = 1500) => {
|
||||
@@ -1641,7 +1668,7 @@ const TemplatePlaceholder = () => {
|
||||
selector: '[data-tut="IsSigned"]',
|
||||
content: t("text-field-tour"),
|
||||
position: "top",
|
||||
style: { fontSize: "13px" }
|
||||
styles: { fontSize: "13px" }
|
||||
}
|
||||
];
|
||||
const copytoclipboard = (text) => {
|
||||
@@ -1700,7 +1727,6 @@ const TemplatePlaceholder = () => {
|
||||
setIsCreateDocModal(true);
|
||||
setIsUiLoading(false);
|
||||
};
|
||||
|
||||
const handleUseButton = async () => {
|
||||
setIsCreateDocModal(false);
|
||||
//this function is used to open modal to show signers list
|
||||
@@ -1709,8 +1735,9 @@ const TemplatePlaceholder = () => {
|
||||
};
|
||||
|
||||
const handleRecipientSign = (docId, currUserId) => {
|
||||
const docObjId = docId || documentId;
|
||||
if (currUserId) {
|
||||
navigate(`/recipientSignPdf/${docId}/${currUserId}`);
|
||||
navigate(`/recipientSignPdf/${docObjId}/${currUserId}`);
|
||||
} else {
|
||||
navigate(`/recipientSignPdf/${docId}`);
|
||||
}
|
||||
@@ -1950,7 +1977,6 @@ const TemplatePlaceholder = () => {
|
||||
numPages={numPages}
|
||||
pageDetails={pageDetails}
|
||||
placeholder={true}
|
||||
drop={drop}
|
||||
handleDeleteWidget={handleDeleteWidgetObj}
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
@@ -1987,6 +2013,8 @@ const TemplatePlaceholder = () => {
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
setRoleName={setRoleName}
|
||||
isShowModal={isShowModal}
|
||||
signBtnPosition={signBtnPosition}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -2063,9 +2091,6 @@ const TemplatePlaceholder = () => {
|
||||
initial={true}
|
||||
isTemplateFlow={true}
|
||||
roleName={roleName}
|
||||
Add
|
||||
commentMore
|
||||
actions
|
||||
isPrefillDropdown={true}
|
||||
/>
|
||||
</div>
|
||||
@@ -2125,6 +2150,8 @@ const TemplatePlaceholder = () => {
|
||||
fontColor={fontColor}
|
||||
setFontColor={setFontColor}
|
||||
roleName={roleName}
|
||||
widgetsSource={signerPos}
|
||||
activeSignerId={uniqueId}
|
||||
/>
|
||||
{isPrefillModal && (
|
||||
<PrefillWidgetModal
|
||||
@@ -2140,7 +2167,6 @@ const TemplatePlaceholder = () => {
|
||||
navigatePageToDoc={navigatePageToDoc}
|
||||
setIsNewContact={setIsNewContact}
|
||||
isNewContact={isNewContact}
|
||||
documentFlow={"template"}
|
||||
docId={pdfDetails[0]?.objectId}
|
||||
isSubmit={isUseTemplate}
|
||||
/>
|
||||
@@ -2159,6 +2185,8 @@ const TemplatePlaceholder = () => {
|
||||
isSave={true}
|
||||
setUniqueId={setUniqueId}
|
||||
signatureTypes={signatureType}
|
||||
penColors={pdfDetails?.[0]?.PenColors}
|
||||
role={roleName}
|
||||
/>
|
||||
)}
|
||||
<RotateAlert
|
||||
@@ -2183,11 +2211,13 @@ const TemplatePlaceholder = () => {
|
||||
<ModalUi
|
||||
isOpen={isSend}
|
||||
title={
|
||||
mailStatus === "success"
|
||||
? t("mails-sent")
|
||||
: mailStatus === "quotareached"
|
||||
? t("quota-mail-head")
|
||||
: t("mail-not-delivered")
|
||||
!pdfDetails[0]?.SendinOrder
|
||||
? mailStatus === "success"
|
||||
? t("mails-sent")
|
||||
: mailStatus === "quotareached"
|
||||
? t("quota-mail-head")
|
||||
: t("mail-not-delivered")
|
||||
: t("mail-status-head")
|
||||
}
|
||||
handleClose={() => {
|
||||
setIsSend(false);
|
||||
@@ -2195,66 +2225,103 @@ const TemplatePlaceholder = () => {
|
||||
}}
|
||||
>
|
||||
<div className="h-[100%] p-[20px] text-base-content">
|
||||
{mailStatus === "success" ? (
|
||||
<div className="text-center mb-[10px]">
|
||||
<LottieWithLoader />
|
||||
{documentDetails.SendinOrder ? (
|
||||
<p>
|
||||
{currUserId
|
||||
? t("placeholder-mail-alert-you")
|
||||
: t("placeholder-mail-alert", {
|
||||
name: signersdata[0]?.Name
|
||||
})}
|
||||
</p>
|
||||
<div className="flex flex-col items-center gap-5">
|
||||
<div>
|
||||
{mailStatus === "success" ? (
|
||||
<div className="text-center mb-[10px]">
|
||||
<LottieWithLoader />
|
||||
{documentDetails.SendinOrder ? (
|
||||
<p>
|
||||
{currUserId
|
||||
? t("placeholder-mail-alert-you")
|
||||
: t("placeholder-mail-alert", {
|
||||
name: signersdata[0]?.Name
|
||||
})}
|
||||
</p>
|
||||
) : (
|
||||
<p>{t("placeholder-alert-4")}</p>
|
||||
)}
|
||||
</div>
|
||||
) : mailStatus === "quotareached" ? (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
<div className="my-3">{handleShareList()}</div>
|
||||
</div>
|
||||
) : mailStatus === "failed" ? (
|
||||
<p>{t("mail-failed")} </p>
|
||||
) : (
|
||||
<p>{t("placeholder-alert-4")}</p>
|
||||
<div className="mb-[10px]">
|
||||
{!pdfDetails[0]?.SendinOrder &&
|
||||
(mailStatus === "dailyquotareached" ? (
|
||||
<p>{t("daily-quota-reached")}</p>
|
||||
) : (
|
||||
<p>{t("placeholder-alert-6")}</p>
|
||||
))}
|
||||
{currUserId && (
|
||||
<span className="mt-1">{t("placeholder-alert-5")}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{currUserId && <p>{t("placeholder-alert-5")}</p>}
|
||||
</div>
|
||||
) : mailStatus === "quotareached" ? (
|
||||
<div className="flex flex-col gap-y-3">
|
||||
<div className="my-3">{handleShareList()}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-[10px]">
|
||||
{mailStatus === "dailyquotareached" ? (
|
||||
<p>{t("daily-quota-reached")}</p>
|
||||
) : (
|
||||
<p>{t("placeholder-alert-6")}</p>
|
||||
)}
|
||||
{currUserId && <p className="mt-1">{t("placeholder-alert-5")}</p>}
|
||||
</div>
|
||||
)}
|
||||
{!mailStatus && (
|
||||
<div className="w-full h-[1px] bg-[#9f9f9f] my-[15px]"></div>
|
||||
)}
|
||||
{mailStatus !== "quotareached" && (
|
||||
<div
|
||||
className={
|
||||
mailStatus === "success" ? "flex justify-center mt-1" : ""
|
||||
}
|
||||
>
|
||||
{currUserId && (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleRecipientSign(documentDetails?.objectId, currUserId)
|
||||
{mailStatus !== "quotareached" && (
|
||||
<div
|
||||
className={
|
||||
mailStatus === "success"
|
||||
? "flex justify-center mt-1"
|
||||
: "flex items-center justify-center mt-7"
|
||||
}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary mr-1"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
{currUserId && (
|
||||
<button
|
||||
onClick={() =>
|
||||
handleRecipientSign(
|
||||
documentDetails?.objectId,
|
||||
currUserId
|
||||
)
|
||||
}
|
||||
type="button"
|
||||
className="op-btn op-btn-primary mr-1"
|
||||
>
|
||||
{t("sign-now")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate("/report/1MwEuxLEkF");
|
||||
}}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost text-base-content"
|
||||
>
|
||||
{currUserId ? t("no") : t("close")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate("/report/1MwEuxLEkF");
|
||||
}}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost text-base-content"
|
||||
>
|
||||
{currUserId ? t("no") : t("close")}
|
||||
</button>
|
||||
</div>
|
||||
{mailStatus !== "success" &&
|
||||
currUserId &&
|
||||
pdfDetails[0]?.SendinOrder && (
|
||||
<div className="op-divider text-base-content mx-[0%] my-1 font-medium">
|
||||
{t("or")}
|
||||
</div>
|
||||
)}
|
||||
{mailStatus !== "success" &&
|
||||
currUserId &&
|
||||
pdfDetails[0]?.SendinOrder && (
|
||||
<div
|
||||
className="op-btn op-btn-outline w-[50%] md:w-[35%] mt-1"
|
||||
onClick={() => {
|
||||
setIsSend(false);
|
||||
setIsMailModal(true);
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className="fa-regular fa-envelope"
|
||||
style={{ color: "#002864", fontSize: "19px" }}
|
||||
></i>{" "}
|
||||
<span>{t("send-to-email")}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!mailStatus && (
|
||||
<div className="op-divider text-base-content mx-[0%] mt-3"></div>
|
||||
)}
|
||||
</div>
|
||||
</ModalUi>
|
||||
|
||||
@@ -13,26 +13,9 @@ import {
|
||||
import DeleteUserModal from "../primitives/DeleteUserModal";
|
||||
import axios from "axios";
|
||||
import PasswordResetModal from "../primitives/PasswordResetModal";
|
||||
import { usersActions } from "../json/ReportJson";
|
||||
import { withSessionValidation } from "../utils";
|
||||
|
||||
const actions = [
|
||||
{
|
||||
btnId: "4741",
|
||||
hoverLabel: "Delete",
|
||||
btnColor: "op-btn-secondary",
|
||||
btnIcon: "fa-light fa-trash",
|
||||
redirectUrl: "",
|
||||
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 = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -186,7 +169,7 @@ const UserList = () => {
|
||||
};
|
||||
const handleClose = () => setIsActiveModal({});
|
||||
|
||||
const handleToggleSubmit = async (user) => {
|
||||
const handleToggleSubmit = withSessionValidation(async (user) => {
|
||||
const index = userList.findIndex((obj) => obj.objectId === user.objectId);
|
||||
if (index !== -1) {
|
||||
setIsActiveModal({});
|
||||
@@ -211,7 +194,7 @@ const UserList = () => {
|
||||
setIsActLoader({});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
const handleToggleBtn = (user) => {
|
||||
setIsActiveModal({ [user.objectId]: true });
|
||||
};
|
||||
@@ -222,7 +205,7 @@ const UserList = () => {
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), timer);
|
||||
};
|
||||
|
||||
const handleDeleteAccount = async (item) => {
|
||||
const handleDeleteAccount = withSessionValidation(async (item) => {
|
||||
setDeleting(true);
|
||||
if (item?.UserId?.objectId) {
|
||||
const url = localStorage.getItem("baseUrl")?.replace(/\/app\/?$/, "/");
|
||||
@@ -248,16 +231,16 @@ const UserList = () => {
|
||||
setDeleteUserRes(t("something-went-wrong-mssg"));
|
||||
setDeleting(false);
|
||||
}
|
||||
};
|
||||
});
|
||||
const handleCloseModal = () => {
|
||||
setIsActModal({});
|
||||
setDeleteUserRes("");
|
||||
setDeleting(false);
|
||||
};
|
||||
|
||||
const handleActionBtn = async (act, item) => {
|
||||
const handleActionBtn = withSessionValidation(async (act, item) => {
|
||||
setIsActModal({ [`${act.action}_${item.objectId}`]: true });
|
||||
};
|
||||
});
|
||||
const handleBtnVisibility = (act, item) => {
|
||||
if (act.restrictAdmin) {
|
||||
if (item?.UserRole === "contracts_Admin") {
|
||||
@@ -282,7 +265,7 @@ const UserList = () => {
|
||||
}
|
||||
};
|
||||
|
||||
async function submitPassword(userId, password) {
|
||||
const submitPassword = withSessionValidation(async (userId, password) => {
|
||||
setIsLoader(true);
|
||||
setIsActModal({});
|
||||
try {
|
||||
@@ -295,7 +278,7 @@ const UserList = () => {
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
return (
|
||||
<div className="relative">
|
||||
{isLoader && (
|
||||
@@ -342,7 +325,7 @@ const UserList = () => {
|
||||
{t(`report-heading.${item}`)}
|
||||
</th>
|
||||
))}
|
||||
{actions?.length > 0 && (
|
||||
{usersActions?.length > 0 && (
|
||||
<th className="p-2 text-transparent pointer-events-none">
|
||||
{t("action")}
|
||||
</th>
|
||||
@@ -375,10 +358,14 @@ const UserList = () => {
|
||||
</td>
|
||||
{handleActiveToggleVisibility(item) ? (
|
||||
<td className="px-4 py-2 font-semibold">
|
||||
<label className="cursor-pointer relative block items-center mb-0">
|
||||
<label
|
||||
htmlFor={`isdisabled-${item.objectId}`}
|
||||
className="cursor-pointer relative block items-center mb-0"
|
||||
>
|
||||
<input
|
||||
id={`isdisabled-${item.objectId}`}
|
||||
type="checkbox"
|
||||
className="op-toggle transition-all op-toggle-secondary"
|
||||
className="op-toggle checked:[--tglbg:#3368ff] transition-all checked:text-white"
|
||||
checked={item?.IsDisabled !== true}
|
||||
onChange={() => handleToggleBtn(item)}
|
||||
/>
|
||||
@@ -425,8 +412,8 @@ const UserList = () => {
|
||||
{isAdmin && (
|
||||
<td className="px-3 py-2">
|
||||
<div className="text-base-content min-w-max flex flex-row gap-x-2 gap-y-1 justify-start items-center">
|
||||
{actions?.length > 0 &&
|
||||
actions.map((act, index) => (
|
||||
{usersActions?.length > 0 &&
|
||||
usersActions?.map((act, index) => (
|
||||
<React.Fragment key={index}>
|
||||
{handleBtnVisibility(act, item) && (
|
||||
<div
|
||||
@@ -454,37 +441,29 @@ const UserList = () => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<DeleteUserModal
|
||||
title={t("delete-account")}
|
||||
deleting={deleting}
|
||||
userEmail={item?.Email}
|
||||
isOpen={
|
||||
isActModal[
|
||||
"delete_" + item.objectId
|
||||
]
|
||||
}
|
||||
onConfirm={() =>
|
||||
handleDeleteAccount(item)
|
||||
}
|
||||
deleteRes={deleteUserRes}
|
||||
handleClose={handleCloseModal}
|
||||
/>
|
||||
<PasswordResetModal
|
||||
isOpen={
|
||||
isActModal[
|
||||
"resetpassword_" + item.objectId
|
||||
]
|
||||
}
|
||||
userId={item?.UserId?.objectId}
|
||||
onClose={handleCloseModal}
|
||||
onSubmit={submitPassword}
|
||||
showAlert={showAlert}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
<DeleteUserModal
|
||||
title={t("delete-account")}
|
||||
deleting={deleting}
|
||||
userEmail={item?.Email}
|
||||
isOpen={isActModal["delete_" + item.objectId]}
|
||||
onConfirm={() => handleDeleteAccount(item)}
|
||||
deleteRes={deleteUserRes}
|
||||
handleClose={handleCloseModal}
|
||||
/>
|
||||
<PasswordResetModal
|
||||
isOpen={
|
||||
isActModal["resetpassword_" + item.objectId]
|
||||
}
|
||||
userId={item?.UserId?.objectId}
|
||||
onClose={handleCloseModal}
|
||||
onSubmit={submitPassword}
|
||||
showAlert={showAlert}
|
||||
/>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -6,12 +6,12 @@ import { Navigate, useNavigate } from "react-router";
|
||||
import Parse from "parse";
|
||||
import { SaveFileSize } from "../constant/saveFileSize";
|
||||
import dp from "../assets/images/dp.png";
|
||||
import { sanitizeFileName } from "../utils";
|
||||
import { sanitizeFileName, withSessionValidation } from "../utils";
|
||||
import axios from "axios";
|
||||
import Tooltip from "../primitives/Tooltip";
|
||||
import {
|
||||
getSecureUrl,
|
||||
handleSendOTP,
|
||||
handleSendOTP
|
||||
} from "../constant/Utils";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import Loader from "../primitives/Loader";
|
||||
@@ -124,7 +124,7 @@ function UserProfile() {
|
||||
};
|
||||
|
||||
// `updateExtUser` is used to update user details in extended class
|
||||
const updateExtUser = async (obj) => {
|
||||
const updateExtUser = withSessionValidation(async (obj) => {
|
||||
try {
|
||||
const extData = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
const ExtUserId = extData?.[0]?.objectId;
|
||||
@@ -152,10 +152,10 @@ function UserProfile() {
|
||||
const json = JSON.parse(JSON.stringify([res]));
|
||||
const extRes = JSON.stringify(json);
|
||||
localStorage.setItem("Extand_Class", extRes);
|
||||
} catch (e) {
|
||||
console.log("error in save data in contracts_Users class");
|
||||
} catch (err) {
|
||||
console.log("error in save data in contracts_Users class", err);
|
||||
}
|
||||
};
|
||||
});
|
||||
// file upload function
|
||||
const fileUpload = async (file) => {
|
||||
if (file) {
|
||||
@@ -172,8 +172,8 @@ function UserProfile() {
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
progress: (progressValue, loaded, total) => {
|
||||
if (progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
// console.log("percentCompleted ", percentCompleted);
|
||||
setpercentage(percentCompleted);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// below code is used to handle undefined Promise.withResolvers for safari
|
||||
// below code is used to handle undefined Promise.withResolvers for safari for react-pdf
|
||||
if (typeof Promise.withResolvers === "undefined") {
|
||||
Promise.withResolvers = function () {
|
||||
let resolve, reject;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import Loader from "./Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import axios from "axios";
|
||||
import { getTenantDetails } from "../constant/Utils";
|
||||
import { emailRegex } from "../constant/const";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { sessionStatus } from "../redux/reducers/userReducer";
|
||||
|
||||
const AddContact = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const [name, setName] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
@@ -108,6 +112,7 @@ const AddContact = (props) => {
|
||||
}
|
||||
} else {
|
||||
setIsLoader(false);
|
||||
dispatch(sessionStatus(false));
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
}
|
||||
@@ -139,7 +144,9 @@ const AddContact = (props) => {
|
||||
)}
|
||||
<div className="w-full mx-auto p-[8px]">
|
||||
{!props?.isDisableTitle && (
|
||||
<div className="text-[14px] font-[700]">{t("add-contact")}</div>
|
||||
<div className="text-[14px] font-[700] text-base-content mb-1">
|
||||
{t("add-contact")}
|
||||
</div>
|
||||
)}
|
||||
{isUserExist && props?.isAddYourSelfCheckbox && (
|
||||
<div className="mb-[0.75rem] flex items-center mt-1">
|
||||
@@ -248,7 +255,7 @@ const AddContact = (props) => {
|
||||
e.preventDefault();
|
||||
setIsOptionalDetails(!isOptionalDetails);
|
||||
}}
|
||||
className="op-link op-link-secondary no-underline hover:underline"
|
||||
className="text-base-content/60 no-underline hover:underline focus:outline-none"
|
||||
>
|
||||
{isOptionalDetails
|
||||
? t("hide-optional-details")
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import "../styles/signature.css";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
const DateWidgetModal = ({
|
||||
children,
|
||||
title,
|
||||
isOpen
|
||||
}) => {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return createPortal(
|
||||
<div id="dateWidgetModal" className="items-center op-modal op-modal-open">
|
||||
<div className="md:min-w-[500px] op-modal-box p-0 max-h-90 overflow-y-auto hide-scrollbar text-sm">
|
||||
{title && (
|
||||
<h3 className="text-base-content text-left font-bold text-lg pt-[15px] px-[20px]">
|
||||
{title}
|
||||
</h3>
|
||||
)}
|
||||
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
export default DateWidgetModal;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import "../styles/signature.css";
|
||||
import Loader from "./Loader";
|
||||
|
||||
const ModalUi = ({
|
||||
children,
|
||||
@@ -10,11 +10,14 @@ const ModalUi = ({
|
||||
showClose = true,
|
||||
reduceWidth,
|
||||
position,
|
||||
crossColor
|
||||
crossColor,
|
||||
showScrollBar = false,
|
||||
isLoader = false
|
||||
}) => {
|
||||
const width = reduceWidth;
|
||||
const isBottom = position === "bottom" ? "items-end pb-2 !bg-black/10" : "";
|
||||
const crossBtnColor = crossColor ?? "text-base-content";
|
||||
const hideScrollBar = !showScrollBar ? "hide-scrollbar" : "";
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
@@ -26,10 +29,13 @@ const ModalUi = ({
|
||||
content: { zIndex: 1001, overflow: "visible" } // Ensure modal doesn’t clip content
|
||||
}}
|
||||
>
|
||||
{isLoader && (
|
||||
<div className="absolute z-[999] h-full w-full flex justify-center items-center bg-black bg-opacity-30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
width || "md:min-w-[500px]"
|
||||
} op-modal-box p-0 max-h-90 overflow-y-auto hide-scrollbar text-sm`}
|
||||
className={`${width || "md:min-w-[500px]"} op-modal-box p-0 max-h-90 overflow-y-auto ${hideScrollBar} text-sm`}
|
||||
>
|
||||
{showHeader && (
|
||||
<>
|
||||
|
||||
@@ -144,7 +144,7 @@ export default function PasswordResetModal({
|
||||
<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"
|
||||
placeholder={t("enter-password-or-click-autogenerate")}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
autoFocus
|
||||
|
||||
@@ -55,7 +55,7 @@ function CustomModal(props) {
|
||||
className="op-btn op-btn-primary px-6 ml-[20px] mb-3 mt-1"
|
||||
onClick={() => handleExtendBtn()}
|
||||
>
|
||||
Extend
|
||||
{t("extend")}
|
||||
</button>
|
||||
)}
|
||||
{props.isDownloadBtn && (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { formatDate } from "../constant/Utils";
|
||||
import { formatDateToDdMmmYyyy } from "../constant/Utils";
|
||||
import SignerCell from "./SignerCell";
|
||||
|
||||
function isValidDateString(str) {
|
||||
@@ -14,12 +14,12 @@ const formatRow = (row) => {
|
||||
if (Array.isArray(row)) {
|
||||
return row.map((x) => x.Name).join(", ");
|
||||
} else if (typeof row === "object" && row !== null) {
|
||||
return row?.iso ? formatDate(row?.iso) : row?.Name || "-";
|
||||
return row?.iso ? formatDateToDdMmmYyyy(row?.iso) : row?.Name || "-";
|
||||
} else if (typeof row === "boolean" && row !== null) {
|
||||
return row ? row?.toString() : "false";
|
||||
} else if (isValidDateString(row) && row !== null) {
|
||||
// handle createdAt and updatedAt
|
||||
return formatDate(row) || "-";
|
||||
return formatDateToDdMmmYyyy(row) || "-";
|
||||
} else {
|
||||
return row || "-";
|
||||
}
|
||||
@@ -32,7 +32,8 @@ export const RenderReportCell = ({
|
||||
startIndex,
|
||||
handleDownload,
|
||||
handleRemovePrefill,
|
||||
reportName
|
||||
reportName,
|
||||
handleItemClick
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const appName =
|
||||
@@ -52,14 +53,18 @@ export const RenderReportCell = ({
|
||||
<div className="font-semibold break-words">{rowData?.Name}</div>
|
||||
{rowData?.ExpiryDate?.iso && (
|
||||
<div className="text-gray-500">
|
||||
{t("expires")} {formatDate(rowData?.ExpiryDate?.iso)}
|
||||
{t("expires")} {formatDateToDdMmmYyyy(rowData?.ExpiryDate?.iso)}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
case "Reason":
|
||||
return (
|
||||
<td key={col} className="p-2 text-center">
|
||||
<td
|
||||
key={col}
|
||||
className="p-2 text-center cursor-pointer"
|
||||
onClick={() => handleItemClick(col, rowData?.DeclineReason)}
|
||||
>
|
||||
{rowData?.DeclineReason?.length > 25
|
||||
? rowData?.DeclineReason?.slice(0, 25) + "..."
|
||||
: rowData?.DeclineReason || "-"}
|
||||
@@ -68,7 +73,12 @@ export const RenderReportCell = ({
|
||||
case "Note":
|
||||
return (
|
||||
<td key={col} className="p-2 text-center">
|
||||
<p className="truncate w-[100px]">{rowData?.Note || "-"}</p>
|
||||
<p
|
||||
className="truncate w-[100px] cursor-pointer"
|
||||
onClick={() => handleItemClick(col, rowData?.Note)}
|
||||
>
|
||||
{rowData?.Note || "-"}
|
||||
</p>
|
||||
</td>
|
||||
);
|
||||
case "Folder":
|
||||
@@ -123,25 +133,29 @@ export const RenderReportCell = ({
|
||||
case "Created Date":
|
||||
return (
|
||||
<td key={col} className="p-2 text-center">
|
||||
{rowData?.createdAt ? formatDate(rowData?.createdAt) : "-"}
|
||||
{rowData?.createdAt ? formatDateToDdMmmYyyy(rowData?.createdAt) : "-"}
|
||||
</td>
|
||||
);
|
||||
case "Updated Date":
|
||||
return (
|
||||
<td key={col} className="p-2 text-center">
|
||||
{rowData?.updatedAt ? formatDate(rowData?.updatedAt) : "-"}
|
||||
{rowData?.updatedAt ? formatDateToDdMmmYyyy(rowData?.updatedAt) : "-"}
|
||||
</td>
|
||||
);
|
||||
case "Expiry Date":
|
||||
return (
|
||||
<td key={col} className="p-2 text-center">
|
||||
{rowData?.ExpiryDate ? formatDate(rowData?.ExpiryDate?.iso) : "-"}
|
||||
{rowData?.ExpiryDate
|
||||
? formatDateToDdMmmYyyy(rowData?.ExpiryDate?.iso)
|
||||
: "-"}
|
||||
</td>
|
||||
);
|
||||
case "Sent Date":
|
||||
return (
|
||||
<td key={col} className="p-2 text-center">
|
||||
{rowData?.DocSentAt ? formatDate(rowData?.DocSentAt?.iso) : "-"}
|
||||
{rowData?.DocSentAt
|
||||
? formatDateToDdMmmYyyy(rowData?.DocSentAt?.iso)
|
||||
: "-"}
|
||||
</td>
|
||||
);
|
||||
case "Signers":
|
||||
|
||||
@@ -158,7 +158,7 @@ const SignerCell = ({ reportName, item, handleRemovePrefill }) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{item.Placeholders.map(
|
||||
{item?.Placeholders?.map(
|
||||
(x, i) =>
|
||||
x.Role !== "prefill" && (
|
||||
<tr key={i} className="text-sm font-medium">
|
||||
|
||||
@@ -14,7 +14,7 @@ const Tooltip = ({
|
||||
onClick={() =>
|
||||
handleOnlickHelp ? handleOnlickHelp() : openInNewTab(url)
|
||||
}
|
||||
className={"text-center cursor-pointer"}
|
||||
className="text-center cursor-pointer focus:outline-none"
|
||||
>
|
||||
<sup>
|
||||
<i
|
||||
|
||||
@@ -1,33 +1,84 @@
|
||||
import ReactTour from "reactour";
|
||||
import { TourProvider, useTour } from "@reactour/tour";
|
||||
import { useEffect, useRef } from "react";
|
||||
import {
|
||||
clearAllBodyScrollLocks,
|
||||
disableBodyScroll,
|
||||
enableBodyScroll
|
||||
} from "body-scroll-lock";
|
||||
|
||||
function TourStateSync({ isOpen, onRequestClose }) {
|
||||
const { isOpen: contextIsOpen, setIsOpen } = useTour();
|
||||
const previousIsOpen = useRef(contextIsOpen);
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(isOpen);
|
||||
}, [isOpen, setIsOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (previousIsOpen.current && !contextIsOpen && onRequestClose) {
|
||||
onRequestClose();
|
||||
}
|
||||
previousIsOpen.current = contextIsOpen;
|
||||
}, [contextIsOpen, onRequestClose]);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function Tour({
|
||||
steps,
|
||||
isOpen,
|
||||
rounded,
|
||||
rounded = 5,
|
||||
className,
|
||||
showNumber,
|
||||
closeWithMask = false,
|
||||
onRequestClose,
|
||||
showNavigation,
|
||||
showCloseButton,
|
||||
showNavigationNumber,
|
||||
disableKeyboardNavigation
|
||||
showNavigationNumber
|
||||
}) {
|
||||
const radius = rounded || 5;
|
||||
function disableBody() {
|
||||
if (typeof window !== "undefined") {
|
||||
disableBodyScroll(document.body);
|
||||
}
|
||||
}
|
||||
|
||||
function enableBody() {
|
||||
if (typeof window !== "undefined") {
|
||||
enableBodyScroll(document.body);
|
||||
clearAllBodyScrollLocks();
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ReactTour
|
||||
<TourProvider
|
||||
className={className}
|
||||
steps={steps}
|
||||
isOpen={isOpen}
|
||||
rounded={radius}
|
||||
scrollOffset={-100}
|
||||
closeWithMask={closeWithMask}
|
||||
disableKeyboardNavigation={disableKeyboardNavigation}
|
||||
showCloseButton={showCloseButton}
|
||||
onRequestClose={onRequestClose}
|
||||
showNumber={showNumber}
|
||||
defaultOpen={isOpen}
|
||||
// scrollOffset={-100} // available only for v1
|
||||
inViewThreshold={{ y: 150 }}
|
||||
onClickMask={() => {}}
|
||||
disableKeyboardNavigation={["esc"]}
|
||||
showBadge={showNumber}
|
||||
showNavigation={showNavigation}
|
||||
showNavigationNumber={showNavigationNumber}
|
||||
/>
|
||||
showPrevNextButtons={showNavigation}
|
||||
showDots={showNavigationNumber ?? showNavigation}
|
||||
showCloseButton={showCloseButton}
|
||||
padding={rounded}
|
||||
onClickClose={(clickProps) => {
|
||||
clickProps.setIsOpen(false);
|
||||
}}
|
||||
styles={{
|
||||
popover: (base) => ({
|
||||
...base,
|
||||
borderRadius: rounded,
|
||||
padding: "18px",
|
||||
fontSize: "13px"
|
||||
}),
|
||||
maskArea: (base) => ({ ...base, rx: rounded }),
|
||||
close: (base) => ({ ...base, right: 10, top: 10, outline: "none" })
|
||||
}}
|
||||
afterOpen={disableBody}
|
||||
beforeClose={enableBody}
|
||||
scrollSmooth
|
||||
>
|
||||
<TourStateSync isOpen={isOpen} onRequestClose={onRequestClose} />
|
||||
</TourProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user