Merge pull request #1250 from nxglabs/sync-to-public_repo-17512737859

Merge pull request
This commit is contained in:
prafull-opensignlabs
2025-09-08 04:57:25 +00:00
parent 85bc2fbbb0
commit b01d8125d9
176 changed files with 16765 additions and 27633 deletions
+3 -3
View File
@@ -3,20 +3,20 @@
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="The fastest way to sign PDFs & request signatures from others" />
<!-- <link rel="apple-touch-icon" href="/logo192.png" /> -->
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous" />
<link rel="stylesheet" href="https://cdn.opensignlabs.com/fonts.css" />
<title>...Loading</title>
</head>
<body>
<div id="root" style="touch-action:pan-x pan-y;"></div>
<script type="module" src="/src/index.jsx"></script>
</body>
</html>
</html>
-17834
View File
File diff suppressed because it is too large Load Diff
+26 -28
View File
@@ -1,18 +1,18 @@
{
"name": "open_sign",
"version": "2.21.1",
"version": "2.26.0",
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@imgly/background-removal": "^1.6.0",
"@lottiefiles/dotlottie-react": "^0.14.2",
"@formkit/auto-animate": "^0.8.4",
"@imgly/background-removal": "^1.7.0",
"@lottiefiles/dotlottie-react": "^0.15.2",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.2.1",
"@reduxjs/toolkit": "^2.8.2",
"axios": "^1.10.0",
"axios": "^1.11.0",
"date-fns-tz": "^3.2.0",
"file-saver": "^2.0.5",
"i18next": "^25.3.0",
"i18next": "^25.4.0",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"jszip": "^3.10.1",
@@ -24,11 +24,11 @@
"print-js": "^1.6.0",
"prismjs": "^1.30.0",
"quill-html-edit-button": "^3.0.0",
"radix-ui": "^1.4.2",
"radix-ui": "^1.4.3",
"react": "^18.3.1",
"react-bootstrap": "^2.10.10",
"react-confetti": "^6.4.0",
"react-datepicker": "^8.4.0",
"react-datepicker": "^8.7.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^9.0.0",
@@ -36,15 +36,15 @@
"react-dom": "^18.3.1",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.5.3",
"react-konva": "^18.2.10",
"react-i18next": "^15.7.3",
"react-konva": "^18.2.12",
"react-pdf": "^9.2.1",
"react-quill-new": "^3.4.6",
"react-quill-new": "^3.6.0",
"react-redux": "^9.2.0",
"react-rnd": "^10.5.2",
"react-router": "^7.6.3",
"react-router": "^7.8.2",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.10.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",
@@ -53,7 +53,7 @@
"regex-parser": "^2.3.1",
"serve": "^14.2.4",
"styled-components": "^5.3.11",
"web-vitals": "^5.0.3",
"web-vitals": "^5.1.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"scripts": {
@@ -94,36 +94,34 @@
}
},
"devDependencies": {
"@babel/core": "^7.27.7",
"@babel/preset-env": "^7.27.2",
"@babel/core": "^7.28.3",
"@babel/preset-env": "^7.28.3",
"@babel/preset-react": "^7.27.1",
"@babel/runtime-corejs2": "^7.27.6",
"@testing-library/jest-dom": "^6.6.3",
"@babel/runtime-corejs2": "^7.28.3",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/react": "^18.3.23",
"@vitejs/plugin-react": "^4.6.0",
"@vitejs/plugin-react-swc": "^3.10.2",
"@types/react": "^19.1.12",
"@vitejs/plugin-react": "^4.7.0",
"@vitest/ui": "^3.2.4",
"autoprefixer": "^10.4.21",
"babel-loader": "^10.0.0",
"commitizen": "^4.3.1",
"concurrently": "^9.2.0",
"concurrently": "^9.2.1",
"css-loader": "^7.1.2",
"daisyui": "^4.12.24",
"dotenv": "^16.6.1",
"eslint": "^9.30.0",
"eslint-plugin-prettier": "^5.5.1",
"eslint": "^9.34.0",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-react": "^7.37.5",
"jsdom": "^26.1.0",
"lint-staged": "^16.1.2",
"lint-staged": "^16.1.6",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"pretty-quick": "^4.2.2",
"rollup-plugin-node-polyfills": "^0.2.1",
"tailwindcss": "^3.4.17",
"vite": "^6.3.5",
"vite-plugin-svgr": "^4.3.0",
"vite": "^7.1.4",
"vite-plugin-svgr": "^4.5.0",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.2.4"
},
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

@@ -1,4 +1,4 @@
{
{
"header-news": "Neue Funktion: Benutzer des Teams-Plans können jetzt ihre eigenen AWS S3-Buckets für die Dateispeicherung integrieren",
"header-news-btn": "Jetzt einrichten",
"sandbox-news": "Dies ist eine Sandbox-Umgebung. Bitte nicht für produktive Zwecke verwenden.",
@@ -189,7 +189,18 @@
"Logs": "Protokolle",
"Expiry-date": "Ablaufdatum",
"Company": "Unternehmen",
"JobTitle": "Berufsbezeichnung"
"JobTitle": "Berufsbezeichnung",
"Time to complete (Days)": "Zeit zur Fertigstellung (Tage)",
"Auto reminder": "Automatische Erinnerung",
"Remind once in every (Days)": "Erinnere einmal alle (Tage)",
"Enable OTP verification": "OTP-Verifizierung aktivieren",
"Enable Tour": "Tour aktivieren",
"Notify on signatures": "Bei Signaturen benachrichtigen",
"Allow modifications": "Änderungen zulassen",
"Redirect url": "Weiterleitungs-URL",
"Created Date": "Erstellungsdatum",
"Updated Date": "Aktualisierungsdatum",
"Expiry Date": "Ablaufdatum"
},
"report-help": {
"Draft Documents": "Dies sind Dokumente, die Sie begonnen, aber noch nicht zum Versenden fertiggestellt haben.",
@@ -287,7 +298,7 @@
"deactivate": "deaktivieren",
"this-user": "diesen Benutzer",
"delete-user": "Benutzer löschen",
"delete": "löschen",
"delete": "Löschen",
"add-user": "Benutzer hinzufügen",
"password-generateed": "Das Passwort wird nur einmal generiert; stellen Sie sicher, dass Sie es kopieren.",
"Team status": "Teamstatus",
@@ -412,7 +423,7 @@
"upload": "Hochladen",
"initial-teb": "Initialen",
"signature-tab": "Unterschrift",
"initial-alert": "Meine Initiale nicht gefunden",
"initial-alert": "Meine Initiale wurde nicht gefunden",
"copy-title": "Widget kopieren zu",
"contact-delete-alert": "Sind Sie sicher, dass Sie diesen Kontakt löschen möchten?",
"reset-password-alert-1": "Ein Link zum Zurücksetzen des Passworts wurde an Ihre E-Mail-Adresse gesendet.",
@@ -477,7 +488,7 @@
"add-recipients": "Empfänger hinzufügen",
"loading-mssg": "Das könnte etwas Zeit in Anspruch nehmen",
"send-mail": "E-Mail senden",
"signature-field-widget": "Für jeden Benutzer muss mindestens ein Signaturfeld hinzugefügt werden. Sie haben keine Signaturfelder für {{signersName}} hinzugefügt",
"signature-field-widget": "Jeder Benutzer muss mindestens ein Signatur-Widget haben. Sie haben kein Signatur-Widget für {{signersName}} hinzugefügt.",
"placeholder-alert-1": "Bitte stellen Sie sicher, dass für alle Empfänger mindestens ein Signatur-Widget hinzugefügt wurde.",
"placeholder-alert-2": "Bitte bestätigen Sie, dass Sie das Textfeld ausgefüllt haben.",
"placeholder-alert-3": "Sind Sie sicher, dass Sie dieses Dokument zur Unterzeichnung senden möchten?",
@@ -612,7 +623,8 @@
"tour-mssg": {
"home-layout-1": "Sie haben sich erfolgreich angemeldet! Werfen wir einen Blick darauf.",
"home-layout-2": "Um Dokumente zum Selbstunterzeichnen hochzuladen oder um Unterschriften anderer anzufordern, wählen Sie einfach die entsprechenden Schaltflächen.",
"home-layout-3": "Sie sind bereit, {{appName}} zu verwenden! Wenn Sie Unterstützung benötigen, kontaktieren Sie uns gerne.",
"home-layout-3": "Sie sind bereit, {{appName}} zu verwenden!",
"home-layout-4": "Markiere uns auf",
"generate-token": "Jetzt upgraden, um ein Produktions-API-Token zu generieren.",
"opensign-drive-1": "Klicken Sie auf die Breadcrumb-Links, um einfach durch die Ordnerhierarchie zu navigieren und die Dokumente in jedem Ordner anzuzeigen.",
"opensign-drive-2": "Klicken Sie auf die Schaltfläche Hinzufügen, um einen neuen Ordner oder ein neues Dokument zu erstellen.",
@@ -622,28 +634,26 @@
"opensign-drive-6": "Klicken Sie mit der rechten Maustaste auf ein Dokument, um Optionen wie Herunterladen, Umbenennen, Verschieben und Löschen anzuzeigen. Klicken Sie auf das Dokument, um es zu öffnen.",
"opensign-drive-7": "Klicken Sie mit der rechten Maustaste auf einen Ordner, um Optionen anzuzeigen. Wählen Sie Umbenennen, um den Namen des Ordners zu ändern, oder klicken Sie auf den Ordner, um seinen Inhalt anzuzeigen.",
"pdf-request-file-1": "Liste der Unterzeichner, die das Dokument noch unterschreiben müssen.",
"pdf-request-file-2": "Klicken Sie auf die Platzhalter im Dokument, um zu unterschreiben. Sie sehen dann Optionen, Ihre Unterschrift zu zeichnen, einzugeben oder ein Bild hochzuladen.",
"pdf-request-file-3": "Klicken Sie auf die Schaltflächen Ablehnen oder Fertig stellen, um durch Ihr Dokument zu navigieren. Verwenden Sie das Auslassungsmenü für weitere Optionen, einschließlich der Schaltfläche Herunterladen.",
"pdf-request-file-2": "Klicken Sie auf eines der Felder im Dokument, um zu beginnen. Danach können Sie die erforderlichen Informationen eingeben.",
"pdf-request-file-3": "Sobald Sie alle erforderlichen Felder ausgefüllt haben, klicken Sie auf „Fertigstellen“. Danach können Sie das unterschriebene Dokument herunterladen. Je nach den Einstellungen des Absenders erhalten Sie möglicherweise eine Kopie des abgeschlossenen Dokuments zusammen mit dem Abschlusszertifikat, sobald alle Empfänger unterschrieben haben.",
"pdf-request-file-4": "Liste der Unterzeichner, die das Dokument bereits unterzeichnet haben.",
"pdf-request-file-5": "Sie können auf Automatisch alles unterschreiben klicken, um automatisch an allen für Sie vorgesehenen Stellen zu unterschreiben. Überprüfen Sie das Dokument gründlich, bevor Sie diese Schaltfläche verwenden.",
"pdf-request-file-6": "Bitte füllen Sie die Felder auf Seite {{pagenumbers}} aus, die alle zur einfachen Identifizierung in der gleichen Farbe hervorgehoben sind.",
"placeholder-sign-1": "Wählen Sie einen Empfänger aus dieser Liste aus, um einen Platzhalter hinzuzufügen, an dem er unterschreiben soll. Der Platzhalter wird in der gleichen Farbe wie der Empfängername angezeigt, sobald Sie ihn im Dokument ablegen.",
"placeholder-sign-1": "Wählen Sie einen Empfänger aus dieser Liste, um ein Widget hinzuzufügen. Das Widget wird in derselben Farbe wie der Empfängername angezeigt, sobald Sie es auf das Dokument ziehen.",
"placeholder-sign-2": "Das Klicken auf die Schaltfläche 'Empfänger hinzufügen' ermöglicht es Ihnen, weitere Unterzeichner hinzuzufügen.",
"placeholder-sign-3": "Klicken Sie auf diese Schaltfläche, um weitere Empfänger/Unterzeichner zum Dokument hinzuzufügen.",
"placeholder-sign-4": "Ziehen Sie ein Feld in das Dokument oder klicken Sie darauf, um es hinzuzufügen.",
"placeholder-sign-5": "Der PDF-Inhaltsbereich zeigt bereits die vorhandenen Platzhalter der Vorlage an. Diese Platzhalter entsprechen der Farbe des Empfängernamens, um sie leicht erkennbar zu machen.",
"placeholder-sign-4": "Ziehen oder klicken Sie auf ein Widget, um es dem Dokument hinzuzufügen.",
"placeholder-sign-5": "Der PDF-Inhaltsbereich zeigt bereits die vorhandenen Widgets der Vorlage an. Diese Widgets entsprechen der Farbe des Empfängernamens, um sie leicht erkennbar zu machen.",
"placeholder-sign-6": "Mit einem Klick auf 'Weiter' wird das Dokument gespeichert. Im nächsten Schritt können Sie die E-Mails, die an die Empfänger versendet werden sollen, anpassen oder die Signaturlinks kopieren und diese selbst mit den Empfängern teilen.",
"report-1":"Klicken Sie auf die Schaltfläche „Hinzufügen“, um eine neue Vorlage zu erstellen. Vorlagen sind wiederverwendbare Dokumente, mit denen schnell neue Dokumente mit derselben Struktur und unterschiedlichen Unterzeichnern erstellt werden können. Eine HR-Vorlage für die Einarbeitung könnte beispielsweise vordefinierte Rollen wie „Personalleiter“ und „Neuer Mitarbeiter“ enthalten. Bei jeder Verwendung der Vorlage können Sie die Rolle „Neuer Mitarbeiter“ verschiedenen neuen Mitarbeitern zuweisen, während die Rolle „Personalleiter“ unverändert bleibt. So wird ein nahtloser Einarbeitungsprozess für jeden neuen Mitarbeiter ermöglicht.",
"report-1": "Klicken Sie auf die Schaltfläche „Hinzufügen“, um eine neue Vorlage zu erstellen. Vorlagen sind wiederverwendbare Dokumente, mit denen schnell neue Dokumente mit derselben Struktur und unterschiedlichen Unterzeichnern erstellt werden können. Eine HR-Vorlage für die Einarbeitung könnte beispielsweise vordefinierte Rollen wie „Personalleiter“ und „Neuer Mitarbeiter“ enthalten. Bei jeder Verwendung der Vorlage können Sie die Rolle „Neuer Mitarbeiter“ verschiedenen neuen Mitarbeitern zuweisen, während die Rolle „Personalleiter“ unverändert bleibt. So wird ein nahtloser Einarbeitungsprozess für jeden neuen Mitarbeiter ermöglicht.",
"redirect": "Klicken Sie auf die Schaltfläche 'Verwenden', um ein neues Dokument aus einer bestehenden Vorlage zu erstellen.",
"bulksend": "Um schnell mehrere Dokumente mithilfe einer vorhandenen Vorlage zu versenden, indem Sie einfach die E-Mail-Adressen der Empfänger erstellen, klicken Sie auf die Schaltfläche Massenversand",
"option": "Dieses Menü zeigt weitere Optionen wie Bearbeiten und Löschen. Verwenden Sie die Schaltfläche 'Bearbeiten', um Unterzeichnerrollen hinzuzufügen, Felder zu ändern und Ihre Vorlage zu aktualisieren. Änderungen gelten für alle zukünftigen Dokumente, die aus dieser Vorlage erstellt werden, wirken sich jedoch nicht auf vorhandene Dokumente aus. Verwenden Sie die Schaltfläche 'Löschen', um die Vorlage zu entfernen.",
"signyour-self-1": "Wählen und ziehen Sie Ihre bevorzugten Widgets auf das PDF, um Ihr Dokument vor dem Unterzeichnen anzupassen. Wählen Sie die perfekten Stellen für jede Anpassung aus, um das Dokument an Ihre Bedürfnisse anzupassen.",
"bulksend": "Um schnell mehrere Dokumente mit einer vorhandenen Vorlage zu versenden, geben Sie einfach die E-Mail-Adressen der Empfänger ein und klicken Sie auf die Schaltfläche 'Massenversand'. Sie können bis zu 50 Empfänger erreichen.",
"option": "Dieses Menü zeigt weitere Optionen wie Bearbeiten, Löschen, Umbenennen, Duplizieren, Teilen usw. <1>Klicken Sie hier</1>, um mehr über alle verfügbaren Optionen zu erfahren. <3>Hinweis: Änderungen an einer vorhandenen Vorlage gelten für alle zukünftigen Dokumente, die aus dieser Vorlage erstellt werden, betreffen jedoch nicht bereits versandte Dokumente.</3>",
"signyour-self-1": "Wählen oder ziehen Sie Ihre bevorzugten Widgets auf das PDF, um Ihr Dokument vor der Unterzeichnung anzupassen. Platzieren Sie die Widgets an den perfekten Stellen, um das Dokument an Ihre Bedürfnisse anzupassen.",
"signyour-self-2": "Ziehen Sie ein Widget irgendwo in diesen Bereich. Sie können es später in der Größe ändern und verschieben.",
"template-placeholder-1": "Das Klicken auf die Schaltfläche 'Rolle hinzufügen' ermöglicht es Ihnen, verschiedene Unterzeichnerrollen hinzuzufügen. Sie können Benutzern diese Rollen in den folgenden Schritten zuweisen.",
"template-placeholder-2": "Sobald Rollen hinzugefügt wurden, wählen Sie eine Rolle aus der Liste aus, um einen Platzhalter hinzuzufügen, an dem der Unterzeichner unterschreiben soll. Der Platzhalter wird in der gleichen Farbe wie der Rollenname angezeigt, sobald Sie ihn im Dokument ablegen.",
"template-placeholder-3": "Ziehen Sie ein Feld in das Dokument oder klicken Sie darauf, um es hinzuzufügen.",
"template-placeholder-4": "Ziehen Sie den Platzhalter für eine Rolle an eine beliebige Stelle im Dokument. Denken Sie daran, dass er in der gleichen Farbe wie der Name des Empfängers angezeigt wird, um die Zuordnung zu erleichtern.",
"template-placeholder-5": "Das Klicken auf 'Weiter' speichert die aktuelle Vorlage. Nach dem Speichern werden Sie gefragt, ob Sie ein neues Dokument aus dieser Vorlage erstellen möchten.",
"template-placeholder-2": "Nachdem Sie Rollen hinzugefügt haben, wählen Sie eine aus der Liste, um ein Widget für diesen Empfänger zu platzieren. Sie können entweder auf das Widget klicken oder es auf das Dokument ziehen. Sobald es platziert ist, wird das Widget in derselben Farbe wie die ausgewählte Rolle angezeigt.",
"template-placeholder-3": "Das Klicken auf 'Weiter' speichert die aktuelle Vorlage. Nach dem Speichern werden Sie gefragt, ob Sie ein neues Dokument aus dieser Vorlage erstellen möchten.",
"webhook-1": "Jetzt upgraden, um einen Webhook einzurichten",
"Need your Signature": "Das Klicken auf diese Karte führt Sie zu einer Liste der Dokumente, die auf Ihre Überprüfung warten.",
"Out for signatures": "Das Klicken auf diese Karte führt Sie zu einer Liste der Dokumente, die auf eine Signatur warten.",
@@ -1044,7 +1054,7 @@
"not-calculated": "Nicht berechnet",
"not-found-in-signature": "Nicht in Signatur gefunden",
"readonly-error": "Das schreibgeschützte {{widgetName}}-Widget muss einen Standardwert haben oder kann optional gemacht werden.",
"choose-one":"Wählen Sie eine aus",
"choose-one": "Wählen Sie eine aus",
"search-templates": "Vorlagen durchsuchen…",
"search-documents": "Dokumente suchen…",
"search-contacts": "Kontakte durchsuchen…",
@@ -1131,6 +1141,7 @@
"show-more": "Mehr anzeigen",
"browse-or-drag-to-replace-existing-file": "Durchsuchen oder per Drag & Drop eine neue Datei ziehen, um die vorhandene zu ersetzen",
"optional-details": "Optionale Angaben",
"hide-optional-details": "optionale Angaben ausblenden",
"mail-adapter-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um den Mail-Adapter einzurichten.",
"connect-to-mail": "Mit Gmail verbinden",
"custom-smtp": "Benutzerdefiniertes SMTP",
@@ -1141,5 +1152,63 @@
"username": "Benutzername",
"use-default-mail-adapter": "Möchten Sie wirklich die Standard-Mailserver von {{appName}} verwenden, um Ihre Signaturanfragen zu versenden? Wir empfehlen, Ihre eigenen Gmail- oder SMTP-Server zu verwenden, um die Zustellbarkeit zu verbessern.",
"verification-code-sent-registered-email": "Ein Bestätigungscode wurde an Ihre registrierte E-Mail-Adresse <1>{{useremail}}</1> gesendet. Bitte geben Sie den Code unten ein, um Ihre Einstellungen zu bestätigen.",
"smpt-credentials": "SMTP-Zugangsdaten"
"smpt-credentials": "SMTP-Zugangsdaten",
"delete-account": "Konto löschen",
"delete-account-que": "Sind Sie sicher, dass Sie Ihr Konto löschen möchten?",
"delete-account-que-user": "Sie sind dabei, diesen Benutzer und alle zugehörigen Daten dauerhaft zu löschen. Diese Aktion kann nicht rückgängig gemacht werden.",
"user-deleted-successfully": "Benutzer und alle zugehörigen Daten wurden erfolgreich gelöscht.",
"account-deletion-request-sent-via-mail": "Wir haben Ihnen einen Bestätigungslink per E-Mail gesendet. Genehmigen Sie die Anfrage, um die Löschung Ihres Kontos abzuschließen.",
"type-exact-email-delete": "Geben Sie die genaue E-Mail ein, um das Löschen zu aktivieren",
"email-does-not-match": "E-Mail stimmt nicht überein.",
"please-type-to-confirm": "Bitte geben Sie <1>{{userEmail}}</1> ein, um zu bestätigen:",
"email-settings": "E-Mail-Einstellungen",
"email-settings-help": "Um eine bessere Zustellbarkeit Ihrer Signaturanfrage-E-Mails im Posteingang zu gewährleisten, können Sie Ihren eigenen E-Mail-Anbieter verbinden. Wählen Sie eine der folgenden Optionen:",
"connect-to-gmail": "Mit Gmail verbinden: ",
"connect-to-gmail-help": "Verwenden Sie Ihr Gmail-Konto, um Signaturanfrage-E-Mails direkt aus Ihrem eigenen Posteingang zu senden. Dies verbessert die Zustellraten und Zuverlässigkeit.",
"connect-to-smtp": "Benutzerdefiniertes SMTP: ",
"connect-to-smtp-help": "Verwenden Sie Ihren eigenen SMTP-Server, um E-Mails über Ihre Domain zu versenden. Diese Option gibt Ihnen die volle Kontrolle über Ihre E-Mail-Infrastruktur und verbessert die Zustellbarkeit sowie die Markenkonsistenz.",
"connect-to-default": "Wenn Sie möchten, können Sie auch die Standard-Mailserver von {{appName}} verwenden, wir empfehlen jedoch für optimale Ergebnisse die Nutzung Ihrer eigenen.",
"email-settings-redirect-message": "Diese Einstellung wurde von der Konsole zu den Haupteinstellungen → Präferenzen verschoben. Diese Seite wird in zukünftigen Versionen entfernt.",
"go-to-preferences-menu": "Zum Menü 'Einstellungen' gehen",
"document-download-filename-format": "Dateiname-Format für Dokument-Download",
"preview": "Vorschau: ",
"download-filename-format-help": "Wählen Sie, wie heruntergeladene PDFs benannt werden. Ihre Auswahl wird in Ihrem Profil gespeichert und in der gesamten App verwendet.",
"delete-action-prohibited": "Diese Aktion ist nicht erlaubt. Bitte wenden Sie sich an Ihren Administrator, um die Löschung des Kontos zu beantragen.",
"not-verified": "Nicht verifiziert",
"verified": "Verifiziert",
"expires": "Läuft ab",
"fix-resend-error": "Sie können dieses Dokument nicht korrigieren und erneut senden, da es vorab ausgefüllte Widgets enthält.",
"duplicate-template-error": "Sie können diese Vorlage nicht duplizieren, da sie vorab ausgefüllte Widgets enthält.",
"save-as-template-error": "Dieses Dokument kann nicht als Vorlage gespeichert werden, da es vorab ausgefüllte Widgets enthält.",
"redirecting-you-in": "Sie werden in {{redirectTimeLeft}} Sek. weitergeleitet...",
"pdf-tools-tour": "Klicken Sie auf diese Schaltflächen, um Seiten hinzuzufügen, zu löschen, neu anzuordnen, zu drehen und zu zoomen.",
"widgets": "Widgets",
"prefill-tour": "Verwenden Sie diese Option, um Informationen im Voraus einzugeben, bevor Sie das Dokument an Empfänger senden.",
"empty-prefill-error": "Die folgenden Pflichtfelder dürfen nicht leer bleiben:",
"please-fill-out": "Bitte füllen Sie diese aus, um fortzufahren.",
"custom-signing-certificate": "Benutzerdefiniertes Signaturzertifikat",
"signing-certificate-help": "Sie können Ihr eigenes Dokumentensignaturzertifikat hochladen, das zum Signieren aller Ihrer Dokumente sowie Abschlusszertifikate verwendet wird. Die Zertifikatsdatei sollte ein P12-Zertifikat im PFX-Format sein.",
"certificate-file-p12-in-PFX-format": "Zertifikatsdatei (p12-Zertifikat im PFX-Format)",
"password-of-pfx-file": "Passwort der PFX-Datei eingeben",
"update": "Aktualisieren",
"use-default-certificate": "Standardzertifikat von {{appName}} verwenden",
"upgrade-to-team-plan": "Auf Team-Plan upgraden",
"setup-file-storage": "Dateispeicher einrichten",
"save-and-activate": "Speichern & aktivieren",
"logging-out-to-apply-settings": "Sie werden abgemeldet, um neue Einstellungen anzuwenden",
"reconnect-to-default": "Erneut mit {{appName}} verbinden",
"active-file-adapter": "Aktiver Dateiadapter",
"file-adapter-unique-name": "Eindeutiger Name des Dateiadapters",
"unique-name-for-file-adapter": "Eindeutigen Namen für Dateiadapter eingeben",
"storage-provider": "Speicheranbieter",
"enter-bucket-name": "Bucket-Namen eingeben",
"enter-space-name": "Space-Namen eingeben",
"enter-region-of-bucket": "Region des Buckets eingeben",
"enter-region-of-space": "Region des Spaces eingeben",
"enter-access-key": "Access Key eingeben",
"enter-secret-access-key": "Secret Access Key eingeben",
"otp-email": "Wir haben einen Bestätigungscode gesendet",
"save-as-temp-warn": "Hinweis: Dieses Dokument enthält vorausgefüllte Widgets, die automatisch entfernt werden, da sie bereits im Basisdokument integriert sind.",
"edit-document": "Dokument bearbeiten",
"otp-email": "Wir haben einen Bestätigungscode gesendet"
}
+108 -39
View File
@@ -189,7 +189,18 @@
"Logs": "Logs",
"Expiry-date": "Expiry date",
"Company": "Company",
"JobTitle": "Job title"
"JobTitle": "Job title",
"Time to complete (Days)": "Time to complete (Days)",
"Auto reminder": "Auto reminder",
"Remind once in every (Days)": "Remind once in every (Days)",
"Enable OTP verification": "Enable OTP verification",
"Enable Tour": "Enable Tour",
"Notify on signatures": "Notify on signatures",
"Allow modifications": "Allow modifications",
"Redirect url": "Redirect url",
"Created Date": "Created Date",
"Updated Date": "Updated Date",
"Expiry Date": "Expiry Date"
},
"report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -287,7 +298,7 @@
"deactivate": "deactivate",
"this-user": " this user",
"delete-user": "Delete user",
"delete": "delete",
"delete": "Delete",
"add-user": "Add user",
"password-generateed": "Password will only be generated once; make sure to copy it.",
"Team status": "Team status",
@@ -478,7 +489,7 @@
"add-recipients": "Add recipients",
"loading-mssg": "This might take some time",
"send-mail": "Send Mail",
"signature-field-widget": "A minimum of one signature field must be added for each user. You have not added signature fields for {{signersName}}",
"signature-field-widget": "Each user must have at least one signature widget. You have not added a signature widget for {{signersName}}",
"placeholder-alert-1": "Please ensure there's at least one signature widget added for all recipients.",
"placeholder-alert-2": "Please confirm that you have filled the text field.",
"placeholder-alert-3": " Are you sure you want to send out this document for signatures?",
@@ -613,7 +624,8 @@
"tour-mssg": {
"home-layout-1": "You have logged in successfully! Let's take a look.",
"home-layout-2": "To upload documents for self-signing or to request others' signatures, simply select the respective buttons.",
"home-layout-3": "You are ready to start using {{appName}}! If you need support feel free to contact us.",
"home-layout-3": "You are ready to start using {{appName}}!",
"home-layout-4": "Star us on",
"generate-token": "Upgrade now to generate production API token.",
"opensign-drive-1": "Click on the breadcrumb links to easily navigate through the folder hierarchy and view the documents within each folder.",
"opensign-drive-2": "Click the add button to create a new folder or document.",
@@ -622,35 +634,33 @@
"opensign-drive-5": "The document list is displayed according to the selected sorting option. Icons next to each document indicate its current status.",
"opensign-drive-6": "Right-click on a document to see options such as Download, Rename, Move, and Delete. Click on the document to open it.",
"opensign-drive-7": "Right-click on any folder to see options. Choose 'Rename' to change the folder's name or click on the folder to navigate through its contents.",
"pdf-request-file-1": "List of signers who still need to sign the document .",
"pdf-request-file-2": "Click any of the placeholders appearing on the document to sign. You will then see options to draw your signature, type it, or upload an image .",
"pdf-request-file-3": "Click Decline, or Finish buttons to navigate your document. Use the ellipsis menu for additional options, including the Download button .",
"pdf-request-file-1": "List of signers who still need to sign the document.",
"pdf-request-file-2": "Click any of the fields appearing on the document to begin. You will then be able to fill in the required information.",
"pdf-request-file-3": "Once you've filled in all the required fields, click “Finish.” You'll then be able to download the signed document. Depending on the sender's settings, you may receive a copy of the completed document along with the completion certificate once all recipients have finished signing.",
"pdf-request-file-4": "List of signers who have already signed the document .",
"pdf-request-file-5": "You can click 'Auto Sign All' to automatically sign at all the locations meant to be signed by you. Make sure that you review the document properly before you click this button .",
"pdf-request-file-6": "Please complete the fields on page number {{pagenumbers}}, all highlighted in the same color for easy identification.",
"placeholder-sign-1": "Select a recipient from this list to add a place-holder where he is supposed to sign.The placeholder will appear in the same colour as the recipient name once you drop it on the document.",
"placeholder-sign-1": "Select a recipient from this list to add a widgets. The widget will appear in the same colour as the recipient name once you drop it on the document.",
"placeholder-sign-2": "Clicking 'Add recipients' button will allow you to add more signers.",
"placeholder-sign-3": "Click this button to add more recipients/signers to the document.",
"placeholder-sign-4": "Drag or click on a field to add it to the document.",
"placeholder-sign-5": "The PDF content area already displays the template's existing placeholders. For your convenience, these placeholders will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-4": "Drag or click a widget to add it to the document.",
"placeholder-sign-5": "The PDF content area already displays the template's existing Widgets. For your convenience, these widgets will match the color of the recipient's name, making them easily identifiable.",
"placeholder-sign-6": "Clicking 'Next' will save the document. In the next step you can customize the emails to be sent out to the recipients or copy the signing links and share those with the recipients yourself.",
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like 'HR Manager' and 'New Employee'. Each time you use the template, you can assign the 'New Employee' role to different incoming staff members, while the 'HR Manager' role remains constant, facilitating a seamless onboarding process for each recruit. ",
"redirect": "Click the 'Use' button to create a new document from an existing template.",
"bulksend": "To quickly send multiple documents using an existing template by just creating the recipient email addresses, click the 'Bulk Send' button.",
"option": "This menu reveals more options such as Edit & Delete. Use the 'Edit' button to add signer roles, modify fields, and update your template. Changes will apply to all future documents created from this template but won't affect existing documents.Use the Delete button you can delete template. ",
"signyour-self-1": "Select and drag your preferred widgets onto the PDF to customize your document before signing. Choose the perfect spots for each modification to tailor the document to your needs.",
"bulksend": "To quickly send multiple documents using an existing template by just entering the recipient email addresses, click the 'Bulk Send' button. You can send to up to 50 recipients.",
"option": "This menu reveals more options such as Edit, Delete, Rename, Duplicate, Share, etc. <1>Click here</1> to read more about all available options. <3>Note: Changes to an existing template will apply to all future documents created from that template but won't affect documents that are already sent out.</3>",
"signyour-self-1": "Select or drag your preferred widgets onto the PDF to customize your document before signing. Choose the perfect spots for each widget to tailor the document to your needs.",
"signyour-self-2": "Drag and drop anywhere in this area. You can resize and move it later.",
"template-placeholder-1": "Clicking 'Add role' button will allow you to add various signer roles. You can attach users to each role in subsequent steps.",
"template-placeholder-2": "Once roles are added, select a role from list to add a place-holder where he is supposed to sign. The placeholder will appear in the same colour as the role name once you drop it on the document.",
"template-placeholder-3": "Drag or click on a field to add it to the document.",
"template-placeholder-4": "Drag the placeholder for a role anywhere on the document.Remember, it will appear in the same colour as the name of the recipient for easy reference.",
"template-placeholder-5": "Clicking 'Next' will store the current template. After saving, you'll be prompted to create a new document from this template if you wish.",
"template-placeholder-2": "After adding roles, choose one from the list to place a widget for that recipient. You can either click the widget or drag it onto the document. Once placed, the widget will display in the same color as the selected role.",
"template-placeholder-3": "Clicking 'Next' will store the current template. After saving, you'll be prompted to create a new document from this template if you wish.",
"webhook-1": "Upgrade now to set webhook",
"Need your Signature": "Clicking on this card will take you to the list of documents awaiting your review.",
"Out for signatures": "Clicking on this card will take you to a list of documents awaiting signature.",
"Recent signature requests": "This is a list of documents that are waiting for your signature.",
"Recently sent for signatures": "This is a list of documents you've sent to other parties for signature.",
"Drafts": "This are documents you have started but have not finalized for sending.",
"Drafts": "These are documents you have started but have not finalized for sending.",
"public-template": "This video demonstrates how to set up your personalized public profile, such as 'https://opensign.me/your-username'. You'll also learn how to customize your tagline and make your templates available for public signing.",
"allowModify-widgets": "You can drag and drop any of these fields onto the document, in addition to the fields already designated for you by the document creator."
},
@@ -741,7 +751,7 @@
"public-tour-message": "The template needs to be public before you can generate a shareable link.",
"add-user-template": "You need to add a role before you can add fields for it.",
"pdf-uncompatible": "This pdf is not compatible, please contact {{appName}}",
"text-field-tour": "'Prefill' fields must be filled in advance before the document is sent. If you need the signers to provide input, use signers fields instead.",
"text-field-tour": "'Prefill' fields must be filled in advance before the document is sent. If you need the signers to provide input, use signers fields instead.",
"attach-signer-tour": "You need to attach a Signer to every role. You can do that by clicking this icon. Once you select a Signer it will be attached to all the fields associated with that role which appear in the same colour.",
"allowed-signature-types": "Allowed signature types",
"at-least-one-signature-type": "At least one signature type should be enabled.",
@@ -989,20 +999,20 @@
"verify-identity": "Verify identity",
"passkey-verification-failed": "Passkey verification failed. Please try again or use OTP.",
"security-auth-help": {
"p1":"Manage your account's security settings to keep your data safe. OpenSign supports advanced authentication methods to enhance account protection.",
"2fa-auth-help":" Add an extra layer of security by enabling 2FA. This requires you to enter a verification code from an authenticator app after your password.",
"passkey-auth-help":"Use passkeys for passwordless sign-in with biometric or device-based verification, providing both strong security and convenience."
"p1": "Manage your account's security settings to keep your data safe. OpenSign supports advanced authentication methods to enhance account protection.",
"2fa-auth-help": " Add an extra layer of security by enabling 2FA. This requires you to enter a verification code from an authenticator app after your password.",
"passkey-auth-help": "Use passkeys for passwordless sign-in with biometric or device-based verification, providing both strong security and convenience."
},
"signer-already-present": "Signer already present",
"kiosk-sign": "Kiosk Sign",
"dont-have-access-to-template": "The template has been deleted or you don't have access. Please contact the sender.",
"kiosk-info": "Kiosk Mode lets you collect in-person signatures quickly and efficiently. Ideal for trade shows, events, or walk-in scenarios where all signers are physically present. ",
"learn-more": "Learn more",
"finish-mssg":" Are you sure you want to finish the document ?",
"review":"Review",
"next-field":"Next Field",
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left",
"verify-document": "Verify document",
"finish-mssg": " Are you sure you want to finish the document ?",
"review": "Review",
"next-field": "Next Field",
"required-mssg": "{{leftRequiredWidget}} of {{totalWidget}} fields left",
"verify-document": "Verify document",
"verify-document-signature": "Verify Document Signature",
"select-pdf-document": "Select PDF Document",
"selected-file": "Selected file",
@@ -1045,23 +1055,23 @@
"not-calculated": "Not calculated",
"not-found-in-signature": "Not found in signature",
"readonly-error": "Read-only {{widgetName}} widget must have a default value or you can make it optional.",
"choose-one":"Choose One",
"choose-one": "Choose One",
"search-templates": "Search templates…",
"search-documents": "Search documents…",
"search-contacts": "Search contacts…",
"add-role-alert":"Please add at least one role",
"edit-draft":"Edit draft",
"add-role-alert": "Please add at least one role",
"edit-draft": "Edit draft",
"invalid-email-found": "Invalid email found: {{email}}",
"duplicate-email-found": "Duplicate email found: {{email}}",
"vertical": "Vertical",
"horizontal": "Horizontal",
"billing": "Billing",
"console": "Console",
"prefill-widget":"Prefill Widgets",
"prefill-widget": "Prefill Widgets",
"action-prohibited": "This action isn't allowed for your email domain. Please contact your administrator for assistance.",
"must-have-at-least-one-vacant-role": "You must have at least one role unassigned before setting a template to 'public'.",
"remove-duplicate": "Please remove duplicate option",
"prefill-bulk-error":"Bulk send is not allowed when prefill widgets are added. Please remove the prefill widgets to proceed.",
"prefill-bulk-error": "Bulk send is not allowed when prefill widgets are added. Please remove the prefill widgets to proceed.",
"session-expired-title": "Session Expired",
"access-denied": "Access denied",
"upgrade": "Upgrade",
@@ -1085,9 +1095,9 @@
"app-name": "App Name",
"provide-domain-name": "provide your domain name",
"provide-app-name": "provide your app name",
"logo":"Logo",
"logo": "Logo",
"upload-app-logo": "upload your app logo",
"prefill-unfilled-widget" :"The following required field(s) cannot be left empty: {{emptyWidget}} Please fill them out to proceed.",
"prefill-unfilled-widget": "The following required field(s) cannot be left empty: {{emptyWidget}} Please fill them out to proceed.",
"Dashboard": "Dashboard",
"Analytics": "Analytics",
"Templates": "Templates",
@@ -1096,7 +1106,7 @@
"Completed": "Completed",
"Drafts": "Drafts",
"Declined": "Declined",
"Expired": "Expired",
"Expired": "Expired",
"Contactbook": "Contactbook",
"My Signature": "My signature",
"API Token": "API token",
@@ -1132,15 +1142,74 @@
"show-more": "Show More",
"browse-or-drag-to-replace-existing-file": "Browse or drag & drop a new file to replace the existing one",
"optional-details": "Optional details",
"hide-optional-details": "Hide optional details",
"mail-adapter-subscription-alert": "Please upgrade to Professional or Team plan to setup custom SMTP.",
"connect-to-mail": "Connect to Gmail",
"custom-smtp": "Custom SMTP",
"default-smtp": "{{appName}} default SMTP",
"host":"Host",
"port":"Port",
"host": "Host",
"port": "Port",
"sender-email": "Sender Email",
"username": "Username",
"use-default-mail-adapter": "Are you sure you want to use {{appName}}'s default mail servers to send your signature request emails? We recommend using your own Gmail or SMTP servers for improved inbox deliverability.",
"verification-code-sent-registered-email": "A verification code has been sent to your registered email <1>{{useremail}}</1> confirm your settings. Please enter the code below.",
"smpt-credentials": "SMTP Credentials"
}
"smpt-credentials": "SMTP Credentials",
"delete-account": "Delete account",
"delete-account-que": "Are you sure you want to delete your account?",
"delete-account-que-user": "You are about to permanently delete this user and all associated data. This action cannot be undone.",
"user-deleted-successfully": "User and all associated data deleted successfully.",
"account-deletion-request-sent-via-mail": "We've emailed you a confirmation link. Approve the request to complete your account deletion.",
"type-exact-email-delete": "Type the exact email to enable delete",
"email-does-not-match": "Email does not match.",
"please-type-to-confirm": "Please type <1>{{userEmail}}</1> to confirm:",
"email-settings": "Email settings",
"email-settings-help": " To ensure better inbox deliverability for your signature request emails, you can connect your own email provider. Choose one of the following options:",
"connect-to-gmail": "Connect to Gmail: ",
"connect-to-gmail-help": "Use your Gmail account to send signature request emails directly from your own inbox, improving delivery rates and reliability.",
"connect-to-smtp": "Custom SMTP: ",
"connect-to-smtp-help": "Use your custom SMTP server for sending emails through your domain. This option gives you full control over your email infrastructure, enhancing deliverability and brand consistency.",
"connect-to-default": "If you prefer, you can also Use {{appName}} default mail servers, though we recommend using your own for optimal results.",
"email-settings-redirect-message": "This setting has been moved from the console to Main Menu Settings → Preferences. This page will be removed in upcoming versions.",
"go-to-preferences-menu": "Go to Preferences menu",
"document-download-filename-format": "Document download filename format",
"preview": "Preview: ",
"download-filename-format-help": "Choose how downloaded PDFs are named. Your selection is saved to your profile and used across the app.",
"delete-action-prohibited": "This action is not permitted. Kindly contact your administrator to request account deletion.",
"not-verified": "Not verified",
"verified": "Verified",
"expires": "Expires",
"fix-resend-error": "You can't fix and resend this document because it contains prefill widgets.",
"duplicate-template-error": "You can't duplicate this template because it contains prefill widgets.",
"save-as-template-error": "This document cannot be saved as a template because it contains prefill widgets.",
"redirecting-you-in": "Redirecting you in {{redirectTimeLeft}} sec...",
"pdf-tools-tour": "Click these buttons to add, delete, rearrange, rotate and zoom pages.",
"widgets": "Widgets",
"prefill-tour": "Use this option to enter information in advance before sending the document to recipients.",
"empty-prefill-error": "The following required field(s) cannot be left empty:",
"please-fill-out": "Please fill them out to proceed.",
"custom-signing-certificate": "Custom signing certificate",
"signing-certificate-help": "You can upload your own document signing certificate which will be used to sign all your documents as well as completion certificates. The certificate file should be a P12 certificate in PFX format.",
"certificate-file-p12-in-PFX-format": "Certificate file (p12 certificate in PFX format)",
"password-of-pfx-file": "Enter password of pfx file",
"update": "Update",
"use-default-certificate": "Use default {{appName}} certificate",
"upgrade-to-team-plan": "Upgrade to Team Plan",
"setup-file-storage": "Setup file storage",
"save-and-activate": "Save & activate",
"logging-out-to-apply-settings": "You are being logged out to apply new settings",
"reconnect-to-default": "Reconnect to {{appName}}",
"active-file-adapter": "Active File Adapter",
"file-adapter-unique-name": "File Adapter unique name",
"unique-name-for-file-adapter": "Enter unique name for file adapter",
"storage-provider": "Storage Provider",
"enter-bucket-name": "Enter bucket name",
"enter-space-name": "Enter space name",
"enter-region-of-bucket": "Enter region of bucket",
"enter-region-of-space": "Enter region of space",
"enter-access-key": "Enter access key",
"enter-secret-access-key": "Enter secret access key",
"otp-email":"Weve sent a verification code to",
"save-as-temp-warn":"Note: This document includes prefilled widgets, which will be automatically removed as they are already incorporated into the base document.",
"edit-document": "Edit document",
"otp-email":"We've sent a verification code to"
}
@@ -189,7 +189,18 @@
"Logs": "Registros",
"Expiry-date": "Date d'expiration",
"Company": "Compañía",
"JobTitle": "Título del puesto"
"JobTitle": "Título del puesto",
"Time to complete (Days)": "Tiempo para completar (días)",
"Auto reminder": "Recordatorio automático",
"Remind once in every (Days)": "Recordar una vez cada (días)",
"Enable OTP verification": "Habilitar verificación OTP",
"Enable Tour": "Habilitar recorrido",
"Notify on signatures": "Notificar en las firmas",
"Allow modifications": "Permitir modificaciones",
"Redirect url": "URL de redirección",
"Created Date": "Fecha de creación",
"Updated Date": "Fecha de actualización",
"Expiry Date": "Fecha de vencimiento"
},
"report-help": {
"Draft Documents": "Estos son documentos que has iniciado pero no has finalizado para su envío.",
@@ -249,7 +260,6 @@
"submit": "Enviar",
"reset": "Volver a empezar",
"my-signature": "Mi firma",
"initial-alert": "Mi inicial no encontrada",
"signature": "Firma",
"upload-image": " Subir imagen",
"clear": "Limpiar",
@@ -288,7 +298,7 @@
"deactivate": "desactivar",
"this-user": " este usuario",
"delete-user": "Eliminar usuario",
"delete": "eliminar",
"delete": "Eliminar",
"add-user": "Agregar usuario",
"password-generateed": "La contraseña solo será generada una vez; asegúrate de copiarla.",
"Team status": "Estado del equipo",
@@ -413,7 +423,7 @@
"upload": "Subir",
"initial-teb": "Iniciales",
"signature-tab": "Firma",
"your-signature": "Tu firma",
"initial-alert": "Mi inicial no encontrada",
"copy-title": "Copiar widget a",
"contact-delete-alert": "¿En definitiva quieres eliminar este contacto?",
"reset-password-alert-1": "El enlace para restablecer la contraseña ha sido enviado a tu correo electrónico",
@@ -479,6 +489,7 @@
"add-recipients": "Agregar destinatarios",
"loading-mssg": "Esto podría llevar algún tiempo",
"send-mail": "Enviar correo",
"signature-field-widget": "Cada usuario debe tener al menos un widget de firma. No ha añadido un widget de firma para {{signersName}}.",
"placeholder-alert-1": "Por favor, asegúrate de que hay al menos un widget de firma para cada destinatario.",
"placeholder-alert-2": "Por favor, confirma que hayas rellenado el campo de texto.",
"placeholder-alert-3": " ¿En definitiva quieres enviar este documento para ser firmado?",
@@ -613,7 +624,8 @@
"tour-mssg": {
"home-layout-1": "¡Has iniciado sesión exitosamente! Echemos un vistazo.",
"home-layout-2": "Para subir documentos para firmar tú mismo o para solicitar la firma de otros, simplemente selecciona los botones correspondientes.",
"home-layout-3": "¡Ya puedes empezar a utilizar {{appName}}! Si necesitas soporte siéntete libre de contactarnos.",
"home-layout-3": "¡Ya puedes empezar a utilizar {{appName}}!",
"home-layout-4": "Danos una estrella en",
"generate-token": "Mejora ahora para generar un token API de producción.",
"opensign-drive-1": "Haz clic en los enlaces para navegar fácilmente por la estructura jerárquica de carpetas y ver los documentos de cada carpeta.",
"opensign-drive-2": "Haz clic en el botón «Agregar» para crear una nueva carpeta o documento.",
@@ -623,34 +635,32 @@
"opensign-drive-6": "Haz clic con el botón derecho en un documento para ver opciones como «Descargar», «Renombrar», «Mover» y «Eliminar». Haz clic en el documento para abrirlo.",
"opensign-drive-7": "Haz clic con el botón derecho en cualquier carpeta para ver las opciones. Elige «Renombrar» para cambiar el nombre de la carpeta o haz clic en ella para navegar por su contenido.",
"pdf-request-file-1": "Lista de firmantes que todavía tienen que firmar este documento.",
"pdf-request-file-2": "Haz clic en cualquiera de los marcadores de posición que aparecen en el documento para firmar. A continuación, verás opciones para dibujar tu firma, escribirla o subir una imagen .",
"pdf-request-file-3": "Haga clic en los botones Rechazar o Finalizar para navegar por el documento. Utilice el menú de puntos suspensivos para ver opciones adicionales, incluido el botón Descargar.",
"pdf-request-file-2": "Haga clic en cualquiera de los campos que aparecen en el documento para comenzar. Luego podrá completar la información requerida.",
"pdf-request-file-3": "Una vez que haya completado todos los campos obligatorios, haga clic en 'Finalizar'. Luego podrá descargar el documento firmado. Según la configuración del remitente, es posible que reciba una copia del documento completado junto con el certificado de finalización una vez que todos los destinatarios hayan firmado.",
"pdf-request-file-4": "Lista de firmantes que ya han firmado el documento.",
"pdf-request-file-5": "Puedes hacer clic en «Firmar todo automáticamente» para firmar de manera automática en todos los lugares que deban ser firmados por ti. Asegúrate de revisar correctamente el documento antes de hacer clic en ese botón.",
"pdf-request-file-6": "Por favor, complete los campos en la página {{pagenumbers}}, todos resaltados con el mismo color para facilitar la identificación.",
"placeholder-sign-1": "Selecciona un destinatario de esta lista para agregar un marcador de posición dónde este destinatario tendrá que firmar. El marcador de posición aparecerá con el mismo color que el nombre del destinatario una vez lo sueltes en el documento.",
"placeholder-sign-1": "Seleccione un destinatario de esta lista para añadir un widget. El widget aparecerá con el mismo color que el nombre del destinatario una vez que lo coloque en el documento.",
"placeholder-sign-2": "Hacer clic en el botón «Agregar destinatarios» te permitirá añadir más firmantes.",
"placeholder-sign-3": "Haz clic en este botón para agregar más destinatarios/firmantes al documento.",
"placeholder-sign-4": "Arrastra o haz clic en un capo para agregarlo al documento.",
"placeholder-sign-5": "El área de contenido del PDF ya muestra los marcadores de posición existentes en la plantilla. Para tu comodidad, estos marcadores de posición coincidirán con el color del nombre del destinatario, haciéndolos fácilmente identificables.",
"placeholder-sign-4": "Arrastre o haga clic en un widget para añadirlo al documento.",
"placeholder-sign-5": "El área de contenido del PDF ya muestra los widgets existentes de la plantilla. Para su comodidad, estos widgets coincidirán con el color del nombre del destinatario, lo que los hace fácilmente identificables.",
"placeholder-sign-6": "Al hacer clic en 'Siguiente' se guardará el documento. En el siguiente paso, puede personalizar los correos electrónicos que se enviarán a los destinatarios o copiar los enlaces de firma y compartirlos usted mismo con los destinatarios.",
"report-1": "Haz clic en el botón «Agregar» para crear una nueva plantilla. Las plantillas son documentos reusables diseñados para generar rápidamente nuevos documentos con la misma estructura pero diferentes firmantes. Por ejemplo, una plantilla de RR.HH. para el proceso de incorporación podría tener roles predefinidos como «Director de RR.HH.» y «Nuevo empleado». Cada vez que utilices la plantilla, puedes asignar el rol de «Nuevo empleado» a los distintos miembros del personal que se incorporen, mientras que el rol de « Director de RR.HH.» permanece constante, lo que facilita un proceso de incorporación fluido para cada persona contratada.",
"redirect": "Haz clic en el botón «Usar» para crear un nuevo documento desde una plantilla existente.",
"bulksend": "Para enviar rápidamente múltiples documentos utilizando una plantilla existente creando simplemente las direcciones de correo electrónico de los destinatarios, haz clic en el botón 'Envío Masivo'.",
"option": "Este menú revela más opciones como «Editar» y «Eliminar». Usa el botón «Editar» para agregar roles, modificar campos y actualizar tu plantilla. Los cambios aplicarán para todos los documentos futuros que sean creados usando esta plantilla pero no afectarán a los documentos ya existentes. Con el botón «Eliminar» puedes eliminar la plantilla.",
"signyour-self-1": "Para personalizar el documento antes de que sea firmado, selecciona y arrastra al documento los widgets que prefieras. Escoge el espacio perfecto para cada modificación para adaptar el documento a tus necesidades.",
"bulksend": "Para enviar rápidamente varios documentos utilizando una plantilla existente, simplemente introduzca las direcciones de correo electrónico de los destinatarios y haga clic en el botón 'Envío masivo'. Puede enviar hasta 50 destinatarios.",
"option": "Este menú muestra más opciones como Editar, Eliminar, Renombrar, Duplicar, Compartir, etc. <1>Haga clic aquí</1> para leer más sobre todas las opciones disponibles. <3>Nota: Los cambios en una plantilla existente se aplicarán a todos los futuros documentos creados a partir de esa plantilla, pero no afectarán a los documentos que ya se hayan enviado.</3>",
"signyour-self-1": "Seleccione o arrastre sus widgets preferidos al PDF para personalizar su documento antes de firmar. Elija los lugares perfectos para cada widget y adapte el documento a sus necesidades.",
"signyour-self-2": "Arrastra y suelta en cualquier lugar de esta área. Después podrás cambiar el tamaño y moverlo.",
"template-placeholder-1": "Hacer clic en el botón «Agregar rol» te permitirá agregar varios roles de firmantes. Puedes asociar usuarios a cada rol en los siguientes pasos.",
"template-placeholder-2": "Una vez que los roles hayan sido agregados, selecciona un rol de la lista para agregar un marcador de posición donde éste tendrá que firmar. El marcador de posición aparecerá con el mismo color que el nombre del rol una vez lo sueltes en el documento.",
"template-placeholder-3": "Arrastra o haz clic en un capo para agregarlo al documento.",
"template-placeholder-4": "Arrastra el marcador de posición para un rol a cualquier lugar del documento. Recuerda, aparecerá con el mismo color que el nombre del rol para poder referenciarlo fácilmente.",
"template-placeholder-5": "Hacer clic en «Siguiente» guardará la plantilla actual. Luego de guardar, se te consultará si deseas crear un nuevo documento usando esta plantilla.",
"template-placeholder-2": "Después de añadir roles, elija uno de la lista para colocar un widget para ese destinatario. Puede hacer clic en el widget o arrastrarlo al documento. Una vez colocado, el widget se mostrará en el mismo color que el rol seleccionado.",
"template-placeholder-3": "Hacer clic en «Siguiente» guardará la plantilla actual. Luego de guardar, se te consultará si deseas crear un nuevo documento usando esta plantilla.",
"webhook-1": "Mejora ahora para configurar un webhook",
"Need your Signature": "Hacer clic en esta tarjeta te llevará a la lista de documentos que esperan por tu revisión.",
"Out for signatures": "Hacer clic en esta tarjeta te llevará a la lista de documentos que esperan tu firma.",
"Recent signature requests": "Esta es una lista de documentos que esperan por tu firma.",
"Recently sent for signatures": "Esta es una lista de documentos que has enviado a otras partes para que sea firmado.",
"Drafts": "Estos son documentos que has empezado pero que todavía no finalizas para su envío.",
"Drafts": "Estos son documentos que ha comenzado pero que aún no ha finalizado para enviar.",
"public-template": "Este video muestra cómo puedes configurar tu perfil público personalizado, como «https://opensign.me/tu-nombre-de-usuario». También aprenderás cómo personalizar tu eslogan y cómo hacer tus plantillas disponibles para su firmado público.",
"allowModify-widgets": "Puede arrastrar y soltar cualquiera de estos campos en el documento, además de los campos ya designados para usted por el creador del documento"
},
@@ -1045,7 +1055,7 @@
"not-calculated": "No calculado",
"not-found-in-signature": "No encontrado en la firma",
"readonly-error": "El widget de solo lectura {{widgetName}} debe tener un valor predeterminado o puede hacerlo opcional.",
"choose-one":"Elige uno",
"choose-one": "Elige uno",
"search-templates": "Buscar plantillas…",
"search-documents": "Buscar documentos…",
"search-contacts": "Buscar contactos…",
@@ -1053,7 +1063,7 @@
"duplicate-email-found": "Correo electrónico duplicado encontrado: {{email}}",
"vertical": "Vertical",
"add-role-alert": "Por favor, agregue al menos un rol",
"edit-draft": "Editar borrador",
"edit-draft": "Editar borrador",
"horizontal": "Horizontal",
"billing": "Facturación",
"console": "Consola",
@@ -1132,6 +1142,7 @@
"show-more": "Mostrar más",
"browse-or-drag-to-replace-existing-file": "Busque o arrastre y suelte un nuevo archivo para reemplazar el existente",
"optional-details": "Detalles opcionales",
"hide-optional-details": "ocultar detalles opcionales",
"mail-adapter-subscription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um ein benutzerdefiniertes SMTP einzurichten.",
"connect-to-mail": "Conectar con Gmail",
"custom-smtp": "SMTP personalizado",
@@ -1142,5 +1153,63 @@
"username": "Nombre de usuario",
"use-default-mail-adapter": "¿Está seguro de que desea usar los servidores de correo predeterminados de {{appName}} para enviar solicitudes de firma? Recomendamos usar su propio servidor de Gmail o SMTP para una mejor entregabilidad.",
"verification-code-sent-registered-email": "Se ha enviado un código de verificación a su correo registrado <1>{{useremail}}</1>. Ingrese el código a continuación para confirmar su configuración.",
"smpt-credentials": "Credenciales SMTP"
"smpt-credentials": "Credenciales SMTP",
"delete-account": "Eliminar cuenta",
"delete-account-que": "¿Está seguro de que desea eliminar su cuenta?",
"delete-account-que-user": "Está a punto de eliminar permanentemente este usuario y todos los datos asociados. Esta acción no se puede deshacer.",
"user-deleted-successfully": "Usuario y todos los datos asociados eliminados correctamente.",
"account-deletion-request-sent-via-mail": "Le hemos enviado por correo electrónico un enlace de confirmación. Apruebe la solicitud para completar la eliminación de su cuenta.",
"type-exact-email-delete": "Escriba el correo electrónico exacto para habilitar la eliminación",
"email-does-not-match": "El correo electrónico no coincide.",
"please-type-to-confirm": "Por favor, escriba <1>{{userEmail}}</1> para confirmar:",
"email-settings": "Configuración de correo electrónico",
"email-settings-help": "Para garantizar una mejor entregabilidad en la bandeja de entrada de sus correos electrónicos de solicitud de firma, puede conectar su propio proveedor de correo electrónico. Elija una de las siguientes opciones:",
"connect-to-gmail": "Conectar con Gmail: ",
"connect-to-gmail-help": "Utilice su cuenta de Gmail para enviar correos electrónicos de solicitud de firma directamente desde su propia bandeja de entrada, mejorando la tasa de entrega y la fiabilidad.",
"connect-to-smtp": "SMTP personalizado: ",
"connect-to-smtp-help": "Utilice su propio servidor SMTP para enviar correos electrónicos a través de su dominio. Esta opción le brinda control total sobre su infraestructura de correo electrónico, mejorando la entregabilidad y la coherencia de la marca.",
"connect-to-default": "Si lo prefiere, también puede usar los servidores de correo predeterminados de {{appName}}, aunque recomendamos usar los suyos para obtener mejores resultados.",
"email-settings-redirect-message": "Esta configuración se ha movido de la consola a Configuración del menú principal → Preferencias. Esta página se eliminará en próximas versiones.",
"go-to-preferences-menu": "Ir al menú de preferencias",
"document-download-filename-format": "Formato de nombre de archivo para la descarga del documento",
"preview": "Vista previa: ",
"download-filename-format-help": "Elija cómo se nombran los archivos PDF descargados. Su selección se guarda en su perfil y se usa en toda la aplicación.",
"delete-action-prohibited": "Esta acción no está permitida. Por favor, póngase en contacto con su administrador para solicitar la eliminación de la cuenta.",
"not-verified": "No verificado",
"verified": "Verificado",
"expires": "Expira",
"fix-resend-error": "No puede corregir y reenviar este documento porque contiene widgets predefinidos.",
"duplicate-template-error": "No puede duplicar esta plantilla porque contiene widgets predefinidos.",
"save-as-template-error": "Este documento no se puede guardar como plantilla porque contiene widgets predefinidos.",
"redirecting-you-in": "Redirigiéndolo en {{redirectTimeLeft}} seg...",
"pdf-tools-tour": "Haga clic en estos botones para agregar, eliminar, reorganizar, rotar y acercar/alejar páginas.",
"widgets": "Widgets",
"prefill-tour": "Use esta opción para ingresar información por adelantado antes de enviar el documento a los destinatarios.",
"empty-prefill-error": "Los siguientes campos obligatorios no pueden quedar vacíos:",
"please-fill-out": "Por favor, complételos para continuar.",
"custom-signing-certificate": "Certificado de firma personalizado",
"signing-certificate-help": "Puede cargar su propio certificado de firma de documentos, que se utilizará para firmar todos sus documentos así como los certificados de finalización. El archivo del certificado debe ser un certificado P12 en formato PFX.",
"certificate-file-p12-in-PFX-format": "Archivo de certificado (certificado p12 en formato PFX)",
"password-of-pfx-file": "Ingrese la contraseña del archivo pfx",
"update": "Actualizar",
"use-default-certificate": "Usar el certificado predeterminado de {{appName}}",
"upgrade-to-team-plan": "Actualizar al plan de equipo",
"setup-file-storage": "Configurar almacenamiento de archivos",
"save-and-activate": "Guardar y activar",
"logging-out-to-apply-settings": "Se está cerrando la sesión para aplicar la nueva configuración",
"reconnect-to-default": "Volver a conectar con {{appName}}",
"active-file-adapter": "Adaptador de archivos activo",
"file-adapter-unique-name": "Nombre único del adaptador de archivos",
"unique-name-for-file-adapter": "Ingrese un nombre único para el adaptador de archivos",
"storage-provider": "Proveedor de almacenamiento",
"enter-bucket-name": "Ingrese el nombre del bucket",
"enter-space-name": "Ingrese el nombre del espacio",
"enter-region-of-bucket": "Ingrese la región del bucket",
"enter-region-of-space": "Ingrese la región del espacio",
"enter-access-key": "Ingrese la clave de acceso",
"enter-secret-access-key": "Ingrese la clave secreta de acceso",
"otp-email": "Hemos enviado un código de verificación",
"save-as-temp-warn": "Nota: Este documento incluye widgets prellenados, que se eliminarán automáticamente ya que ya están incorporados en el documento base.",
"edit-document": "Editar documento",
"otp-email": "Hemos enviado un código de verificación"
}
@@ -26,7 +26,7 @@
"Name": "Nom et Prénom",
"Date": "Date"
},
"folder": "Dossier",
"folder": "Dossier",
"pdf": "Pdf",
"context-menu": {
"Download": "Télécharger",
@@ -45,7 +45,6 @@
"upgrade-now": "Mettre à jour maintenant",
"contact-now": "Contacter maintenant",
"upgrade-to": "Mettre à niveau vers",
"pro": "PRO",
"plan": "Offre",
"connect": "Connecter",
"connect-to-g-drive": "Se connecter à Google Drive",
@@ -56,6 +55,7 @@
"subscribe-card-plan": "Débloquez des fonctionnalités premium à partir de seulement {{premiumPrice}}/mois. Bénéficiez de performances améliorées et de seulement {{addonPrice}} par crédit supplémentaire après vos crédits premium inclus.",
"user-name-limit-char": "Pour avoir un nom d'utilisateur de moins de 8 caractères s'il vous plaît s'abonner",
"tour-content": "Ne plus afficher",
"pro": "PRO",
"team": "Équipe",
"docs": "Documents",
"session-expired": "Votre session a expiré.",
@@ -163,10 +163,22 @@
"Logs": "Journaux",
"Expiry-date": "Fecha de caducidad",
"Company": "Organisation",
"JobTitle": "Votre Fonction"
"JobTitle": "Votre Fonction",
"Time to complete (Days)": "Temps pour terminer (jours)",
"Auto reminder": "Rappel automatique",
"Remind once in every (Days)": "Rappeler une fois tous les (jours)",
"Enable OTP verification": "Activer la vérification OTP",
"Enable Tour": "Activer la visite",
"Notify on signatures": "Notifier lors des signatures",
"Allow modifications": "Autoriser les modifications",
"Redirect url": "URL de redirection",
"Created Date": "Date de création",
"Updated Date": "Date de mise à jour",
"Expiry Date": "Date d'expiration"
},
"btnLabel": {
"sign": "Signer",
"Sign": "SIGNER",
"Resend": "Relancer",
"Rename": "Renommer",
"Revoke": "Révoquer",
@@ -248,7 +260,6 @@
"submit": "Soumettre",
"reset": "Réinitialiser",
"my-signature": "Ma signature",
"initial-alert": "Mon initiale introuvable",
"signature": "Signature",
"upload-image": "Télécharger une image",
"clear": "Vider",
@@ -287,7 +298,7 @@
"deactivate": "désactiver",
"this-user": "cet utilisateur ",
"delete-user": "Supprimer l'utilisateur",
"delete": "supprimer",
"delete": "Supprimer",
"add-user": "Ajouter un utilisateur",
"password-generateed": "Le mot de passe ne sera généré qu'une seule fois ; assurez-vous de le copier.",
"Team status": "Statut de l'équipe",
@@ -412,7 +423,7 @@
"upload": "Télécharger",
"initial-teb": "Initiales",
"signature-tab": "Signature",
"your-signature": "Votre signature",
"initial-alert": "Mon initiale est introuvable",
"copy-title": "Copier le widget vers",
"contact-delete-alert": "Êtes-vous sûr de vouloir supprimer ce contact ?",
"reset-password": "Le lien de réinitialisation du mot de passe a été envoyé à votre identifiant de messagerie",
@@ -477,7 +488,7 @@
"add-recipients": "Ajouter des destinataires",
"loading-mssg": "Cela pourrait prendre du temps",
"send-mail": "Envoyer un mail",
"signature-field-widget": "Au moins un champ de signature doit être ajouté pour chaque utilisateur. Vous n'avez pas ajouté de champs de signature pour {{signersName}}",
"signature-field-widget": "Chaque utilisateur doit avoir au moins un widget de signature. Vous navez pas ajouté de widget de signature pour {{signersName}}.",
"placeholder-alert-1": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
"placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.",
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
@@ -612,7 +623,8 @@
"tour-mssg": {
"home-layout-1": "Vous vous êtes connecté avec succès ! Jetons un coup d'oeil.",
"home-layout-2": "Pour télécharger des documents à auto-signature ou pour demander la signature d'autres personnes, sélectionnez simplement les boutons correspondants.",
"home-layout-3": "Vous êtes prêt à commencer à utiliser {{appName}} ! Si vous avez besoin d'aide, n'hésitez pas à nous contacter.",
"home-layout-3": "Vous êtes prêt à commencer à utiliser {{appName}} !",
"home-layout-4": "Ajoutez-nous une étoile sur",
"generate-token": "Effectuez la mise à niveau maintenant pour générer un jeton API de production.",
"opensign-drive-1": "Cliquez sur les liens du fil d'Ariane pour naviguer facilement dans la hiérarchie des dossiers et afficher les documents dans chaque dossier.",
"opensign-drive-2": "Cliquez sur le bouton Ajouter pour créer un nouveau dossier ou document.",
@@ -622,34 +634,32 @@
"opensign-drive-6": "Faites un clic droit sur un document pour voir les options telles que Télécharger, Renommer, Déplacer et Supprimer. Cliquez sur le document pour l'ouvrir.",
"opensign-drive-7": "Faites un clic droit sur n'importe quel dossier pour voir les options. Choisissez 'Renommer' pour changer le nom du dossier ou cliquez sur le dossier pour naviguer dans son contenu.",
"pdf-request-file-1": "Liste des signataires qui doivent encore signer le document.",
"pdf-request-file-2": "Cliquez sur l'un des espaces réservés apparaissant sur le document pour signer. Vous verrez alors des options pour dessiner votre signature, la saisir ou télécharger une image.",
"pdf-request-file-3": "Cliquez sur les boutons Refuser ou Terminer pour parcourir votre document. Utilisez le menu à points de suspension pour des options supplémentaires, notamment le bouton Télécharger.",
"pdf-request-file-2": "Cliquez sur l'un des champs figurant dans le document pour commencer. Vous pourrez ensuite remplir les informations requises.",
"pdf-request-file-3": "Une fois que vous avez rempli tous les champs obligatoires, cliquez sur « Terminer ». Vous pourrez ensuite télécharger le document signé. Selon les paramètres de l'expéditeur, vous pourriez recevoir une copie du document complété avec le certificat de finalisation une fois que tous les destinataires auront signé.",
"pdf-request-file-4": "Liste des signataires ayant déjà signé le document.",
"pdf-request-file-5": "Vous pouvez cliquer sur 'Signer automatiquement tout' pour signer automatiquement à tous les emplacements que vous êtes censé signer. Assurez-vous de lire correctement le document avant de cliquer sur ce bouton.",
"pdf-request-file-6": "Veuillez remplir les champs à la page {{pagenumbers}}, tous mis en évidence avec la même couleur pour une identification facile.",
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra de la même couleur que le nom du destinataire une fois que vous le déposerez sur le document.",
"placeholder-sign-1": "Sélectionnez un destinataire dans cette liste pour ajouter un widget. Le widget apparaîtra dans la même couleur que le nom du destinataire une fois déposé sur le document.",
"placeholder-sign-2": "Cliquer sur le bouton 'Ajouter des destinataires' vous permettra d'ajouter plus de signataires.",
"placeholder-sign-3": "Cliquez sur ce bouton pour ajouter plus de destinataires/signataires au document",
"placeholder-sign-4": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"placeholder-sign-5": "La zone de contenu PDF affiche déjà les espaces réservés existants du modèle. Pour votre commodité, ces espaces réservés correspondront à la couleur du nom du destinataire, ce qui les rendra facilement identifiables.",
"placeholder-sign-4": "Faites glisser ou cliquez sur un widget pour l'ajouter au document.",
"placeholder-sign-5": "La zone de contenu du PDF affiche déjà les widgets existants du modèle. Pour plus de commodité, ces widgets correspondront à la couleur du nom du destinataire, ce qui les rend facilement identifiables.",
"placeholder-sign-6": "En cliquant sur 'Suivant', le document sera enregistré. À l'étape suivante, vous pouvez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
"report-1": "Cliquez sur le bouton 'Ajouter' pour créer un nouveau modèle. Les modèles sont des documents réutilisables conçus pour générer rapidement de nouveaux documents avec la même structure et différents signataires. Par exemple, un modèle RH pour l'intégration peut avoir des rôles prédéfinis tels que 'Responsable RH' et ' Nouvel employé'. Chaque fois que vous utilisez le modèle, vous pouvez attribuer le rôle 'Nouvel employé' à différents membres du personnel entrants, tandis que le rôle 'Responsable RH' reste constant, facilitant ainsi un processus d'intégration fluide pour chaque recrue",
"redirect": "Cliquez sur le bouton 'Utiliser' pour créer un nouveau document à partir d'un modèle existant.",
"bulksend": "Pour envoyer rapidement plusieurs documents en utilisant un modèle existant en créant simplement les adresses e-mail des destinataires, cliquez sur le bouton 'Envoi en masse'.",
"option": "Ce menu révèle plus d'options telles que Modifier et Supprimer. Utilisez le bouton « Modifier » pour ajouter des rôles de signataire, modifier les champs et mettre à jour votre modèle. Les modifications s'appliqueront à tous les futurs documents créés à partir de ce modèle mais n'affecteront pas les documents existants. Utilisez le bouton Supprimer, vous pouvez supprimer le modèle ",
"signyour-self-1": "Sélectionnez et faites glisser vos widgets préférés sur le PDF pour personnaliser votre document avant de le signer. Choisissez les endroits parfaits pour chaque modification afin d'adapter le document à vos besoins.",
"bulksend": "Pour envoyer rapidement plusieurs documents en utilisant un modèle existant, saisissez simplement les adresses e-mail des destinataires et cliquez sur le bouton 'Envoi groupé'. Vous pouvez envoyer jusqu'à 50 destinataires.",
"option": "Ce menu affiche plus d'options comme Modifier, Supprimer, Renommer, Dupliquer, Partager, etc. <1>Cliquez ici</1> pour en savoir plus sur toutes les options disponibles. <3>Remarque : Les modifications apportées à un modèle existant s'appliqueront à tous les futurs documents créés à partir de ce modèle, mais n'affecteront pas les documents déjà envoyés.</3>",
"signyour-self-1": "Sélectionnez ou faites glisser vos widgets préférés sur le PDF pour personnaliser votre document avant de le signer. Choisissez les emplacements parfaits pour chaque widget afin d'adapter le document à vos besoins.",
"signyour-self-2": "Faites glisser et déposer n'importe où dans cette zone. Vous pourrez la redimensionner et la déplacer plus tard.",
"template-placeholder-1": "Cliquer sur le bouton 'Ajouter un rôle' vous permettra d'ajouter différents rôles de signataire. Vous pourrez attacher des utilisateurs à chaque rôle dans les étapes suivantes.",
"template-placeholder-2": "Une fois les rôles ajoutés, sélectionnez un rôle dans la liste pour ajouter un espace réservé à l'endroit où il est censé signer. L'espace réservé apparaîtra dans la même couleur que le nom du rôle une fois que vous l'aurez déposé sur le document.",
"template-placeholder-3": "Faites glisser ou cliquez sur un champ pour l'ajouter au document.",
"template-placeholder-4": "Faites glisser l'espace réservé pour un rôle n'importe où sur le document. N'oubliez pas qu'il apparaîtra dans la même couleur que le nom du destinataire pour une référence facile.",
"template-placeholder-5": "Cliquer sur 'Suivant' stockera le modèle actuel. Après l'enregistrement, vous serez invité à créer un nouveau document à partir de ce modèle si vous le souhaitez.",
"template-placeholder-2": "Après avoir ajouté des rôles, choisissez-en un dans la liste pour placer un widget pour ce destinataire. Vous pouvez cliquer sur le widget ou le faire glisser sur le document. Une fois placé, le widget s'affichera dans la même couleur que le rôle sélectionné.",
"template-placeholder-3": "Cliquer sur 'Suivant' stockera le modèle actuel. Après l'enregistrement, vous serez invité à créer un nouveau document à partir de ce modèle si vous le souhaitez.",
"webhook-1": "Mettez à niveau maintenant pour définir le webhook",
"Need your Signature": "En cliquant sur cette carte, vous accéderez à la liste des documents en attente de révision.",
"Out for signatures": "En cliquant sur cette carte, vous accéderez à une liste de documents en attente de signature.",
"Recent signature requests": "Voici une liste de documents qui attendent votre signature.",
"Recently sent for signatures": "Il s'agit d'une liste de documents que vous avez envoyés à d'autres parties pour signature.",
"Drafts": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
"Drafts": "Ce sont des documents que vous avez commencés mais que vous n'avez pas finalisés pour l'envoi.",
"public-template": "Cette vidéo montre comment configurer votre profil public personnalisé, tel que 'https://opensign.me/your-username'. Vous apprendrez également comment personnaliser votre slogan et rendre vos modèles disponibles pour la signature publique.",
"allowModify-widgets": "Vous pouvez faire glisser et déposer n'importe lequel de ces champs sur le document, en plus des champs déjà désignés pour vous par le créateur du document."
},
@@ -1000,7 +1010,7 @@
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
"review": "Revoir",
"next-field": "Champ suivant",
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants",
"required-mssg": "{{leftRequiredWidget}} champs sur {{totalWidget}} restants",
"verify-document": "Vérifier le document",
"verify-document-signature": "Vérifier la signature du document",
"select-pdf-document": "Sélectionner le document PDF",
@@ -1044,7 +1054,7 @@
"not-calculated": "Non calculé",
"not-found-in-signature": "Introuvable dans la signature",
"readonly-error": "Le widget en lecture seule {{widgetName}} doit avoir une valeur par défaut ou vous pouvez le rendre facultatif.",
"choose-one":"Choisissez-en un",
"choose-one": "Choisissez-en un",
"search-templates": "Rechercher des modèles…",
"search-documents": "Rechercher des documents…",
"search-contacts": "Rechercher des contacts…",
@@ -1131,6 +1141,7 @@
"show-more": "Afficher plus",
"browse-or-drag-to-replace-existing-file": "Parcourez ou faites glisser un nouveau fichier pour remplacer l'existant",
"optional-details": "Détails facultatifs",
"hide-optional-details": "masquer les détails facultatifs",
"mail-adapter-subscription-alert": "Veuillez passer au plan Professionnel ou Équipe pour configurer un SMTP personnalisé.",
"connect-to-mail": "Se connecter à Gmail",
"custom-smtp": "SMTP personnalisé",
@@ -1141,5 +1152,63 @@
"username": "Nom d'utilisateur",
"use-default-mail-adapter": "Voulez-vous vraiment utiliser les serveurs de messagerie par défaut de {{appName}} pour envoyer vos demandes de signature ? Nous vous recommandons d'utiliser votre propre serveur Gmail ou SMTP pour une meilleure délivrabilité.",
"verification-code-sent-registered-email": "Un code de vérification a été envoyé à votre email enregistré <1>{{useremail}}</1>. Veuillez entrer le code ci-dessous pour confirmer vos paramètres.",
"smpt-credentials": "Identifiants SMTP"
"smpt-credentials": "Identifiants SMTP",
"delete-account": "Supprimer le compte",
"delete-account-que": "Êtes-vous sûr de vouloir supprimer votre compte ?",
"delete-account-que-user": "Vous êtes sur le point de supprimer définitivement cet utilisateur et toutes les données associées. Cette action est irréversible.",
"user-deleted-successfully": "L'utilisateur et toutes les données associées ont été supprimés avec succès.",
"account-deletion-request-sent-via-mail": "Nous vous avons envoyé par e-mail un lien de confirmation. Approuvez la demande pour finaliser la suppression de votre compte.",
"type-exact-email-delete": "Saisissez l'adresse e-mail exacte pour activer la suppression",
"email-does-not-match": "L'adresse e-mail ne correspond pas.",
"please-type-to-confirm": "Veuillez saisir <1>{{userEmail}}</1> pour confirmer:",
"email-settings": "Paramètres de messagerie",
"email-settings-help": "Pour assurer une meilleure délivrabilité dans la boîte de réception de vos e-mails de demande de signature, vous pouvez connecter votre propre fournisseur de messagerie. Choisissez lune des options suivantes :",
"connect-to-gmail": "Se connecter à Gmail: ",
"connect-to-gmail-help": "Utilisez votre compte Gmail pour envoyer des e-mails de demande de signature directement depuis votre propre boîte de réception, améliorant ainsi les taux de délivrabilité et la fiabilité.",
"connect-to-smtp": "SMTP personnalisé: ",
"connect-to-smtp-help": "Utilisez votre propre serveur SMTP pour envoyer des e-mails via votre domaine. Cette option vous donne un contrôle total sur votre infrastructure de messagerie, améliorant la délivrabilité et la cohérence de la marque.",
"connect-to-default": "Si vous le souhaitez, vous pouvez également utiliser les serveurs de messagerie par défaut de {{appName}}, bien que nous recommandions dutiliser les vôtres pour des résultats optimaux.",
"email-settings-redirect-message": "Ce paramètre a été déplacé de la console vers Paramètres du menu principal → Préférences. Cette page sera supprimée dans les prochaines versions.",
"go-to-preferences-menu": "Aller au menu Préférences",
"document-download-filename-format": "Format du nom de fichier pour le téléchargement du document",
"preview": "Aperçu : ",
"download-filename-format-help": "Choisissez comment les fichiers PDF téléchargés sont nommés. Votre sélection est enregistrée dans votre profil et utilisée dans toute l'application.",
"delete-action-prohibited": "Cette action n'est pas autorisée. Veuillez contacter votre administrateur pour demander la suppression du compte.",
"not-verified": "Non vérifié",
"verified": "Vérifié",
"expires": "Expire",
"fix-resend-error": "Vous ne pouvez pas corriger et renvoyer ce document car il contient des widgets préremplis.",
"duplicate-template-error": "Vous ne pouvez pas dupliquer ce modèle car il contient des widgets préremplis.",
"save-as-template-error": "Ce document ne peut pas être enregistré comme modèle car il contient des widgets préremplis.",
"redirecting-you-in": "Redirection dans {{redirectTimeLeft}} s...",
"pdf-tools-tour": "Cliquez sur ces boutons pour ajouter, supprimer, réorganiser, faire pivoter et zoomer les pages.",
"widgets": "Widgets",
"prefill-tour": "Utilisez cette option pour saisir des informations à l'avance avant d'envoyer le document aux destinataires.",
"empty-prefill-error": "Les champs obligatoires suivants ne peuvent pas être laissés vides :",
"please-fill-out": "Veuillez les remplir pour continuer.",
"custom-signing-certificate": "Certificat de signature personnalisé",
"signing-certificate-help": "Vous pouvez télécharger votre propre certificat de signature de documents, qui sera utilisé pour signer tous vos documents ainsi que les certificats dachèvement. Le fichier du certificat doit être un certificat P12 au format PFX.",
"certificate-file-p12-in-PFX-format": "Fichier de certificat (certificat p12 au format PFX)",
"password-of-pfx-file": "Entrez le mot de passe du fichier pfx",
"update": "Mettre à jour",
"use-default-certificate": "Utiliser le certificat par défaut de {{appName}}",
"upgrade-to-team-plan": "Passer au plan Équipe",
"setup-file-storage": "Configurer le stockage de fichiers",
"save-and-activate": "Enregistrer et activer",
"logging-out-to-apply-settings": "Vous êtes déconnecté pour appliquer les nouveaux paramètres",
"reconnect-to-default": "Se reconnecter à {{appName}}",
"active-file-adapter": "Adaptateur de fichiers actif",
"file-adapter-unique-name": "Nom unique de l'adaptateur de fichiers",
"unique-name-for-file-adapter": "Entrez un nom unique pour l'adaptateur de fichiers",
"storage-provider": "Fournisseur de stockage",
"enter-bucket-name": "Entrez le nom du bucket",
"enter-space-name": "Entrez le nom de l'espace",
"enter-region-of-bucket": "Entrez la région du bucket",
"enter-region-of-space": "Entrez la région de l'espace",
"enter-access-key": "Entrez la clé d'accès",
"enter-secret-access-key": "Entrez la clé d'accès secrète",
"otp-email": "Nous avons envoyé un code de vérification",
"save-as-temp-warn": "Remarque : Ce document contient des widgets préremplis, qui seront automatiquement supprimés car ils sont déjà intégrés dans le document de base.",
"edit-document": "Modifier le document",
"otp-email": "Nous avons envoyé un code de vérification"
}
@@ -189,7 +189,18 @@
"Logs": "लॉग",
"Expiry-date": "समाप्ति तिथि",
"Company": "कंपनी",
"JobTitle": "पद"
"JobTitle": "पद",
"Time to complete (Days)": "पूरा करने का समय (दिन)",
"Auto reminder": "स्वचालित अनुस्मारक",
"Remind once in every (Days)": "हर (दिन) में एक बार याद दिलाएं",
"Enable OTP verification": "OTP सत्यापन सक्षम करें",
"Enable Tour": "टूर सक्षम करें",
"Notify on signatures": "हस्ताक्षर पर सूचित करें",
"Allow modifications": "संशोधन की अनुमति दें",
"Redirect url": "रीडायरेक्ट URL",
"Created Date": "निर्माण तिथि",
"Updated Date": "अद्यतन तिथि",
"Expiry Date": "समाप्ति तिथि"
},
"report-help": {
"Draft Documents": "ये वे दस्तावेज़ हैं जिन्हें आपने शुरू तो किया है लेकिन भेजने के लिए अंतिम रूप नहीं दिया है।",
@@ -477,7 +488,7 @@
"add-recipients": "प्राप्तकर्ता जोड़ें",
"loading-mssg": "इसमें कुछ समय लग सकता है",
"send-mail": "मेल भेजें",
"signature-field-widget": "प्रत्येक उपयोगकर्ता के लिए कम से कम एक हस्ताक्षर फ़ील्ड जोड़ा जाना चाहिए। आपने {{signersName}} के लिए हस्ताक्षर फ़ील्ड नहीं जोड़ है",
"signature-field-widget": "प्रत्येक उपयोगकर्ता के पास कम से कम एक हस्ताक्षर विजेट होना चाहिए। आपने {{signersName}} के लिए हस्ताक्षर विजेट नहीं जोड़ है",
"placeholder-alert-1": "कृपया सुनिश्चित करें कि सभी प्राप्तकर्ताओं के लिए कम से कम एक हस्ताक्षर विजेट जोड़ा गया है।",
"placeholder-alert-2": "कृपया पुष्टि करें कि आपने टेक्स्ट फ़ील्ड भर दिया है।",
"placeholder-alert-3": " क्या आप वाकई इस दस्तावेज़ को हस्ताक्षर के लिए भेजना चाहते हैं?",
@@ -612,7 +623,8 @@
"tour-mssg": {
"home-layout-1": "आप सफलतापूर्वक लॉग इन हो गए हैं! आइए एक नज़र डालते हैं।",
"home-layout-2": "स्व-हस्ताक्षर के लिए दस्तावेज़ अपलोड करने या दूसरों के हस्ताक्षर का अनुरोध करने के लिए, बस संबंधित बटन चुनें।",
"home-layout-3": "आप {{appName}} का उपयोग शुरू करने के लिए तैयार हैं! यदि आपको सहायता की आवश्यकता है तो हमसे संपर्क करने में संकोच न करें।",
"home-layout-3": "आप {{appName}} का उपयोग शुरू करने के लिए तैयार हैं!",
"home-layout-4": "हमें स्टार दें",
"generate-token": "उत्पादन एपीआई टोकन उत्पन्न करने के लिए अभी अपग्रेड करें।",
"opensign-drive-1": "फ़ोल्डर पदानुक्रम के माध्यम से आसानी से नेविगेट करने और प्रत्येक फ़ोल्डर के भीतर दस्तावेज़ देखने के लिए ब्रेडक्रंब लिंक पर क्लिक करें।",
"opensign-drive-2": "नया फ़ोल्डर या दस्तावेज़ बनाने के लिए जोड़ें बटन पर क्लिक करें।",
@@ -622,34 +634,32 @@
"opensign-drive-6": "डाउनलोड, नाम बदलें, स्थानांतरित करें और हटाएं जैसे विकल्प देखने के लिए किसी दस्तावेज़ पर राइट-क्लिक करें। इसे खोलने के लिए दस्तावेज़ पर क्लिक करें।",
"opensign-drive-7": "विकल्प देखने के लिए किसी भी फ़ोल्डर पर राइट-क्लिक करें। फ़ोल्डर का नाम बदलने के लिए 'नाम बदलें' चुनें या इसकी सामग्री के माध्यम से नेविगेट करने के लिए फ़ोल्डर पर क्लिक करें।",
"pdf-request-file-1": "उन हस्ताक्षरकर्ताओं की सूची जिन्हें अभी भी दस्तावेज़ पर हस्ताक्षर करने की आवश्यकता है।",
"pdf-request-file-2": "हस्ताक्षर करने के लिए दस्तावेज़ पर दिखाई देने वाले किसी भी प्लेसहोल्ड पर क्लिक करें। फिर आपको अपना हस्ताक्षर खींचने, टाइप करने या एक छवि अपलोड करने के विकल्प दिखाई देंगे।",
"pdf-request-file-3": "अपने दस्तावेज़ को नेविगेट करने के लिए अस्वीकार करें, या समाप्त करें बटन पर क्लिक करें। डाउनलोड बटन सहित अतिरिक्त विकल्पों के लिए दीर्घवृत्त मेनू का उपयोग करें।",
"pdf-request-file-2": "शुरू करने के लिए दस्तावेज़ में दिखाई देने वाले किसी भी फ़ील्ड पर क्लिक करें। उसके बाद आप आवश्यक जानकारी भर सकेंगे।",
"pdf-request-file-3": "एक बार जब आप सभी आवश्यक फ़ील्ड भर लें, तो 'समाप्त करें' पर क्लिक करें। उसके बाद आप हस्ताक्षरित दस्तावेज़ डाउनलोड कर सकेंगे। प्रेषक की सेटिंग्स के आधार पर, जब सभी प्राप्तकर्ता हस्ताक्षर कर लेंगे, तो आपको पूर्ण दस्तावेज़ की एक प्रति और पूर्णता प्रमाणपत्र प्राप्त हो सकता है।",
"pdf-request-file-4": "उन हस्ताक्षरकर्ताओं की सूची जिन्होंने पहले ही दस्तावेज़ पर हस्ताक्षर कर दिए हैं।",
"pdf-request-file-5": "आप उन सभी स्थानों पर स्वचालित रूप से हस्ताक्षर करने के लिए 'सभी पर स्वतः हस्ताक्षर करें' पर क्लिक कर सकते हैं, जिन पर आपके द्वारा हस्ताक्षर किए जाने हैं। सुनिश्चित करें कि आप इस बटन पर क्लिक करने से पहले दस्तावेज़ की ठीक से समीक्षा कर लें।",
"pdf-request-file-6": "कृपया पृष्ठ संख्या {{pagenumbers}} पर फ़ील्ड पूरी करें, सभी आसान पहचान के लिए एक ही रंग में हाइलाइट किए गए हैं।",
"placeholder-sign-1": "एक प्लेस-होल्डर जोड़ने के लिए इस सूची से एक प्राप्तकर्ता का चयन करें जहां उसे हस्ताक्षर करना है। प्लेसहोल्डर दस्तावेज़ पर छोड़ने के बाद प्राप्तकर्ता के नाम के समान रंग में दिखाई देगा।",
"placeholder-sign-1": "इस सूची से एक प्राप्तकर्ता चुनें ताकि विजेट जोड़ सकें। दस्तावेज़ पर रखने के बाद विजेट उसी रंग में दिखाई देगा जो प्राप्तकर्ता के नाम का है।",
"placeholder-sign-2": "'प्राप्तकर्ता जोड़ें' बटन पर क्लिक करने से आप अधिक हस्ताक्षरकर्ताओं को जोड़ सकेंगे।",
"placeholder-sign-3": "दस्तावेज़ में अधिक प्राप्तकर्ताओं/हस्ताक्षरकर्ताओं को जोड़ने के लिए इस बटन पर क्लिक करें।",
"placeholder-sign-4": "इसे दस्तावेज़ में जोड़ने के लिए किसी फ़ील्ड पर खींचें या क्लिक करें।",
"placeholder-sign-5": "पीडीएफ सामग्री क्षेत्र पहले से ही टेम्पलेट के मौजूदा प्लेसहोल्डर प्रदर्शित करता है। आपकी सुविधा के लिए, ये प्लेसहोल्डर प्राप्तकर्ता के नाम के रंग से मेल खाएगे, जिससे उन्हें आसानी से पहचान जा सकेगा।",
"placeholder-sign-4": "दस्तावेज़ में जोड़ने के लिए विजेट को खींचें या उस पर क्लिक करें।",
"placeholder-sign-5": "PDF सामग्री क्षेत्र पहले से ही टेम्पलेट के मौजूदा विजेट दिखाता है। आपकी सुविधा के लिए, ये विजेट प्राप्तकर्ता के नाम के रंग से मेल खाएगे ताकि वे आसानी से पहचान जा सके।",
"placeholder-sign-6": "'अगला' पर क्लिक करने से दस्तावेज़ सहेजा जाएगा। अगले चरण में आप प्राप्तकर्ताओं को भेजे जाने वाले ईमेल को अनुकूलित कर सकते हैं या हस्ताक्षर लिंक कॉपी कर सकते हैं और उन्हें स्वयं प्राप्तकर्ताओं के साथ साझा कर सकते हैं।",
"report-1": "नया टेम्पलेट बनाने के लिए 'जोड़ें' बटन पर क्लिक करें। टेम्पलेट पुन: प्रयोज्य दस्तावेज़ हैं जिन्हें समान संरचना और अलग-अलग हस्ताक्षरकर्ताओं के साथ नए दस्तावेज़ों को शीघ्रता से उत्पन्न करने के लिए डिज़ाइन किया गया है। उदाहरण के लिए, ऑनबोर्डिंग के लिए एक मानव संसाधन टेम्पलेट में 'मानव संसाधन प्रबंधक' और 'नया कर्मचारी' जैसी पूर्वनिर्धारित भूमिकाएँ हो सकती हैं। हर बार जब आप टेम्पलेट का उपयोग करते हैं, तो आप विभिन्न आने वाले कर्मचारियों को 'नया कर्मचारी' भूमिका सौंप सकते हैं, जबकि 'मानव संसाधन प्रबंधक' भूमिका स्थिर रहती है, जिससे प्रत्येक भर्ती के लिए एक सहज ऑनबोर्डिंग प्रक्रिया सुविधाजनक होती है। ",
"redirect": "किसी मौजूदा टेम्पलेट से नया दस्तावेज़ बनाने के लिए 'उपयोग करें' बटन पर क्लिक करें।",
"bulksend": "केवल प्राप्तकर्ता ईमेल पते बनाकर किसी मौजूदा टेम्पलेट का उपयोग करके कई दस्तावेज़ों को शीघ्रता से भेजने के लिए, 'थोक भेजें' बटन पर क्लिक करें।",
"option": "यह मेनू संपादन और हटाएं जैसे अधिक विकल्प प्रकट करता है। हस्ताक्षरकर्ता भूमिकाएं जोड़ने, फ़ील्ड संशोधित करने और अपने टेम्पलेट को अपडेट करने के लिए 'संपादित करें' बटन का उपयोग करें। परिवर्तन इस टेम्पलेट से बनाए गए सभी भविष्य के दस्तावेज़ों पर लागू होंगे लेकिन मौजूदा दस्तावेज़ों को प्रभावित नहीं करेंगे। हटाएं बटन का उपयोग करके आप टेम्पलेट हटा सकते हैं। ",
"signyour-self-1": "हस्ताक्षर करने से पहले अपने दस्तावेज़ को अनुकूलित करने के लिए अपन पसंदीदा विजेट चुनें और उन्हें पीडीएफ पर खींचें। अपनी आवश्यकताओं के अनुसार दस्तावेज़ को अनुकूलित करने के लिए प्रत्येक संशोधन के लिए सही स्थान चुनें।",
"bulksend": "किसी मौजूदा टेम्पलेट का उपयोग करके कई दस्तावेज़ जल्दी भेजने के लिए, केवल प्राप्तकर्ताओं के ईमेल पते दर्ज करें और 'बल्क सेंड' बटन पर क्लिक करें। आप अधिकतम 50 प्राप्तकर्ताओं को भेज सकते हैं।",
"option": "इस मेनू में और विकल्प दिखाई देंगे जैसे संपादित करें, हटाएं, नाम बदलें, डुप्लिकेट करें, साझा करें आदि। <1>यहां क्लिक करें</1> सभी उपलब्ध विकल्पों के बारे में और पढ़ने के लिए। <3>नोट: किसी मौजूदा टेम्पलेट में किए गए बदलाव उस टेम्पलेट से बनाए गए सभी भविष्य के दस्तावेज़ों पर लागू होंगे, लेकिन पहले से भेजे गए दस्तावेज़ों पर प्रभाव नहीं डालेंगे।</3>",
"signyour-self-1": "हस्ताक्षर करने से पहले अपने दस्तावेज़ को अनुकूलित करने के लिए अपन पसंद के विजेट चुनें या उन्हें PDF पर खींचें। अपनी आवश्यकता के अनुसार प्रत्येक विजेट को सही स्थान पर रखें।",
"signyour-self-2": "इस क्षेत्र में कहीं भी खींचें और छोड़ें। आप इसे बाद में आकार बदल सकते हैं और स्थानांतरित कर सकते हैं।",
"template-placeholder-1": "'भूमिका जोड़ें' बटन पर क्लिक करने से आप विभिन्न हस्ताक्षरकर्ता भूमिकाएँ जोड़ सकेंगे। आप बाद के चरणों में प्रत्येक भूमिका में उपयोगकर्ताओं को संलग्न कर सकते हैं।",
"template-placeholder-2": "भूमिकाएँ जोड़ने के बाद, सूची से एक भूमिका चुनें ताकि एक प्लेस-होल्डर जोड़ा जा सके जहाँ उसे हस्ताक्षर करना है। प्लेसहोल्डर दस्तावेज़ पर छोड़ने के बाद भूमिका के नाम के समान रंग में दिखाई देगा।",
"template-placeholder-3": "इसे दस्तावेज़ में जोड़ने के लिए किसी फ़ील्ड पर खींचें या क्लिक करें।",
"template-placeholder-4": "किसी भूमिका के लिए प्लेसहोल्डर को दस्तावेज़ पर कहीं भी खींचें। याद रखें, यह आसान संदर्भ के लिए प्राप्तकर्ता के नाम के समान रंग में दिखाई देगा।",
"template-placeholder-5": "'अगला' पर क्लिक करने से वर्तमान टेम्पलेट संग्रहीत हो जाएगा। सहेजने के बाद, यदि आप चाहें तो इस टेम्पलेट से एक नया दस्तावेज़ बनाने के लिए आपको संकेत दिया जाएगा।",
"template-placeholder-2": "भूमिकाएँ जोड़ने के बाद, सूची में से एक चुनें ताकि उस प्राप्तकर्ता के लिए विजेट रखा जा सके। आप विजेट पर क्लिक कर सकते हैं या उसे दस्तावेज़ पर खींच सकते हैं। एक बार रखे जाने के बाद, विजेट चयनित भूमिका के समान रंग में दिखाई देगा।",
"template-placeholder-3": "'अगला' पर क्लिक करने से वर्तमान टेम्पलेट संग्रहीत हो जाएगा। सहेजने के बाद, यदि आप चाहें तो इस टेम्पलेट से एक नया दस्तावेज़ बनाने के लिए आपको संकेत दिया जाएगा।",
"webhook-1": "वेबहूक सेट करने के लिए अभी अपग्रेड करें",
"Need your Signature": "इस कार्ड पर क्लिक करने से आप अपनी समीक्षा की प्रतीक्षा कर रहे दस्तावेज़ों की सूची पर पहुंच जाएंगे।",
"Out for signatures": "इस कार्ड पर क्लिक करने से आप हस्ताक्षर की प्रतीक्षा कर रहे दस्तावेज़ों की सूची पर पहुंच जाएंगे।",
"Recent signature requests": "यह उन दस्तावेज़ों की सूची है जो आपके हस्ताक्षर की प्रतीक्षा कर रहे हैं।",
"Recently sent for signatures": "यह उन दस्तावेज़ों की सूची है जिन्हें आपने हस्ताक्षर के लिए अन्य पक्षों को भेजा है।",
"Drafts": "ये वे दस्तावेज़ हैं जिन्हें आपने शुरू तो किया है लेकिन भेजने के लिए अंतिम रूप नहीं दिया है।",
"Drafts": "ये वे दस्तावेज़ हैं जिन्हें आपने शुरू किया है लेकिन भेजने के लिए अंतिम रूप नहीं दिया है।",
"public-template": "यह वीडियो दर्शाता है कि आप अपना व्यक्तिगत सार्वजनिक प्रोफ़ाइल कैसे सेट अप कर सकते हैं, जैसे 'https://opensign.me/your-username'। आप यह भी सीखेंगे कि अपनी टैगलाइन को कैसे अनुकूलित करें और अपने टेम्पलेट्स को सार्वजनिक हस्ताक्षर के लिए उपलब्ध कराएं।",
"allowModify-widgets": "आप इन फ़ील्ड में से किसी को भी दस्तावेज़ पर खींच और छोड़ सकते हैं, इसके अतिरिक्त दस्तावेज़ निर्माता द्वारा आपके लिए पहले से निर्दिष्ट फ़ील्ड के। "
},
@@ -1044,12 +1054,12 @@
"not-calculated": "गणना नहीं की गई",
"not-found-in-signature": "हस्ताक्षर में नहीं मिला",
"readonly-error": "रीड-ओनली {{widgetName}} विजेट में एक डिफ़ॉल्ट मान होना चाहिए या आप इसे वैकल्पिक बना सकते हैं।",
"choose-one":"एक का चयन",
"choose-one": "एक का चयन",
"search-templates": "टेम्पलेट खोजें…",
"search-documents": "दस्तावेज़ खोजें…",
"search-contacts": "संपर्क खोजें…",
"add-role-alert": "कृपया कम से कम एक भूमिका जोड़ें",
"edit-draft": "ड्राफ्ट संपादित करें",
"edit-draft": "ड्राफ्ट संपादित करें",
"invalid-email-found": "अमान्य ईमेल पाया गया: {{email}}",
"duplicate-email-found": "डुप्लिकेट ईमेल पाया गया: {{email}}",
"vertical": "वर्टिकल",
@@ -1131,6 +1141,7 @@
"show-more": "और दिखाएं",
"browse-or-drag-to-replace-existing-file": "नई फ़ाइल ब्राउज़ करें या खींचकर छोड़ें ताकि मौजूदा फ़ाइल को बदला जा सके",
"optional-details": "वैकल्पिक विवरण",
"hide-optional-details": "वैकल्पिक विवरण छिपाएं",
"mail-adapter-subscription-alert": "कस्टम SMTP सेटअप करने के लिए कृपया प्रोफेशनल या टीम प्लान में अपग्रेड करें।",
"connect-to-mail": "Gmail से कनेक्ट करें",
"custom-smtp": "कस्टम SMTP",
@@ -1141,5 +1152,63 @@
"username": "यूज़रनेम",
"use-default-mail-adapter": "क्या आप वाकई {{appName}} के डिफ़ॉल्ट मेल सर्वर का उपयोग करके सिग्नेचर अनुरोध भेजना चाहते हैं? बेहतर इनबॉक्स डिलीवरी के लिए हम अपने Gmail या SMTP सर्वर का उपयोग करने की सिफारिश करते हैं।",
"verification-code-sent-registered-email": "आपके पंजीकृत ईमेल <1>{{useremail}}</1> पर एक सत्यापन कोड भेजा गया है। कृपया अपनी सेटिंग की पुष्टि के लिए नीचे कोड दर्ज करें।",
"smpt-credentials": "SMTP क्रेडेंशियल्स"
"smpt-credentials": "SMTP क्रेडेंशियल्स",
"delete-account": "खाता हटाएं",
"delete-account-que": "क्या आप वाकई अपना खाता हटाना चाहते हैं?",
"delete-account-que-user": "आप इस उपयोगकर्ता और सभी संबंधित डेटा को स्थायी रूप से हटाने वाले हैं। इस क्रिया को पूर्ववत नहीं किया जा सकता।",
"user-deleted-successfully": "उपयोगकर्ता और सभी संबंधित डेटा सफलतापूर्वक हटाए गए।",
"account-deletion-request-sent-via-mail": "हमने आपको एक पुष्टि लिंक ईमेल किया है। अपने खाते को हटाने की प्रक्रिया पूरी करने के लिए अनुरोध को स्वीकृत करें।",
"type-exact-email-delete": "हटाने को सक्षम करने के लिए सटीक ईमेल टाइप करें",
"email-does-not-match": "ईमेल मेल नहीं खाता।",
"please-type-to-confirm": "कृपया पुष्टि करने के लिए <1>{{userEmail}}</1> टाइप करें:",
"email-settings": "ईमेल सेटिंग्स",
"email-settings-help": "अपने हस्ताक्षर अनुरोध ईमेल की बेहतर इनबॉक्स डिलीवरी सुनिश्चित करने के लिए, आप अपने स्वयं के ईमेल प्रदाता को कनेक्ट कर सकते हैं। निम्न विकल्पों में से एक चुनें:",
"connect-to-gmail": "Gmail से कनेक्ट करें: ",
"connect-to-gmail-help": "अपने Gmail खाते का उपयोग करके हस्ताक्षर अनुरोध ईमेल सीधे अपने इनबॉक्स से भेजें, जिससे डिलीवरी दर और विश्वसनीयता में सुधार होगा।",
"connect-to-smtp": "कस्टम SMTP: ",
"connect-to-smtp-help": "अपने डोमेन के माध्यम से ईमेल भेजने के लिए अपने कस्टम SMTP सर्वर का उपयोग करें। यह विकल्प आपको अपने ईमेल इंफ्रास्ट्रक्चर पर पूरा नियंत्रण देता है, जिससे डिलीवरी और ब्रांड की निरंतरता में सुधार होता है।",
"connect-to-default": "यदि आप चाहें, तो आप {{appName}} के डिफ़ॉल्ट मेल सर्वर का भी उपयोग कर सकते हैं, हालांकि सर्वोत्तम परिणामों के लिए हम अपने सर्वर का उपयोग करने की सलाह देते हैं।",
"email-settings-redirect-message": "यह सेटिंग कंसोल से मुख्य मेनू सेटिंग्स → प्रेफ़रेंस में स्थानांतरित कर दी गई है। यह पेज आने वाले संस्करणों में हटा दिया जाएगा।",
"go-to-preferences-menu": "प्रेफ़रेंस मेनू पर जाएं",
"document-download-filename-format": "दस्तावेज़ डाउनलोड फ़ाइलनाम प्रारूप",
"preview": "पूर्वावलोकन: ",
"download-filename-format-help": "डाउनलोड की गई PDF फ़ाइलों का नाम कैसे रखा जाए, चुनें। आपका चयन आपके प्रोफ़ाइल में सहेजा जाएगा और पूरे ऐप में उपयोग किया जाएगा।",
"delete-action-prohibited": "यह कार्रवाई अनुमत नहीं है। कृपया अपना खाता हटाने के लिए अपने व्यवस्थापक से संपर्क करें।",
"not-verified": "सत्यापित नहीं",
"verified": "सत्यापित",
"expires": "समाप्ति",
"fix-resend-error": "आप इस दस्तावेज़ को ठीक करके पुनः नहीं भेज सकते क्योंकि इसमें प्रीफ़िल विजेट्स हैं।",
"duplicate-template-error": "आप इस टेम्पलेट को डुप्लिकेट नहीं कर सकते क्योंकि इसमें प्रीफ़िल विजेट्स हैं।",
"save-as-template-error": "इस दस्तावेज़ को टेम्पलेट के रूप में सहेजा नहीं जा सकता क्योंकि इसमें प्रीफ़िल विजेट्स हैं।",
"redirecting-you-in": "{{redirectTimeLeft}} सेकंड में आपको रीडायरेक्ट किया जा रहा है...",
"pdf-tools-tour": "पृष्ठ जोड़ने, हटाने, पुनः व्यवस्थित करने, घुमाने और ज़ूम करने के लिए इन बटनों पर क्लिक करें।",
"widgets": "विजेट्स",
"prefill-tour": "दस्तावेज़ को प्राप्तकर्ताओं को भेजने से पहले अग्रिम रूप से जानकारी दर्ज करने के लिए इस विकल्प का उपयोग करें।",
"empty-prefill-error": "निम्न आवश्यक फ़ील्ड खाली नहीं छोड़े जा सकते:",
"please-fill-out": "कृपया आगे बढ़ने के लिए इन्हें भरें।",
"custom-signing-certificate": "कस्टम साइनिंग प्रमाणपत्र",
"signing-certificate-help": "आप अपना स्वयं का दस्तावेज़ हस्ताक्षर प्रमाणपत्र अपलोड कर सकते हैं, जिसका उपयोग आपके सभी दस्तावेज़ों और पूर्णता प्रमाणपत्रों पर हस्ताक्षर करने के लिए किया जाएगा। प्रमाणपत्र फ़ाइल PFX फॉर्मेट में P12 प्रमाणपत्र होनी चाहिए।",
"certificate-file-p12-in-PFX-format": "प्रमाणपत्र फ़ाइल (PFX फॉर्मेट में p12 प्रमाणपत्र)",
"password-of-pfx-file": "pfx फ़ाइल का पासवर्ड दर्ज करें",
"update": "अपडेट करें",
"use-default-certificate": "डिफ़ॉल्ट {{appName}} प्रमाणपत्र का उपयोग करें",
"upgrade-to-team-plan": "टीम प्लान में अपग्रेड करें",
"setup-file-storage": "फ़ाइल स्टोरेज सेटअप करें",
"save-and-activate": "सहेजें और सक्रिय करें",
"logging-out-to-apply-settings": "नई सेटिंग लागू करने के लिए आपको लॉग आउट किया जा रहा है",
"reconnect-to-default": "{{appName}} से फिर से कनेक्ट करें",
"active-file-adapter": "सक्रिय फ़ाइल एडेप्टर",
"file-adapter-unique-name": "फ़ाइल एडेप्टर का यूनिक नाम",
"unique-name-for-file-adapter": "फ़ाइल एडेप्टर के लिए यूनिक नाम दर्ज करें",
"storage-provider": "स्टोरेज प्रदाता",
"enter-bucket-name": "बकेट का नाम दर्ज करें",
"enter-space-name": "स्पेस का नाम दर्ज करें",
"enter-region-of-bucket": "बकेट का क्षेत्र दर्ज करें",
"enter-region-of-space": "स्पेस का क्षेत्र दर्ज करें",
"enter-access-key": "एक्सेस की दर्ज करें",
"enter-secret-access-key": "सीक्रेट एक्सेस की दर्ज करें",
"otp-email": "हमने एक सत्यापन कोड भेजा है",
"save-as-temp-warn": "नोट: इस दस्तावेज़ में पहले से भरे हुए विजेट शामिल हैं, जिन्हें स्वचालित रूप से हटा दिया जाएगा क्योंकि वे पहले से ही मूल दस्तावेज़ में सम्मिलित हैं।",
"edit-document": "दस्तावेज़ संपादित करें",
"otp-email": "हमने एक सत्यापन कोड भेजा है"
}
@@ -1,7 +1,7 @@
{
"header-news": "Nuova funzionalità: Gli utenti del piano Teams possono ora integrare i propri bucket AWS S3 per l'archiviazione dei file",
"header-news-btn": "Configura Ora",
"sandbox-news": "Questo è un ambiente sandbox. Si prega di non utilizzarlo per scopi di produzione.",
"sandbox-news": "Questo è un ambiente sandbox. Si prega di non utilizzarlo per scopi di produzione.",
"create-account": "Crea Account",
"login": "Accedi",
"language": "Lingua",
@@ -189,7 +189,18 @@
"Logs": "Log",
"Expiry-date": "Data di scadenza",
"Company": "Azienda",
"JobTitle": "Titolo professionale"
"JobTitle": "Titolo professionale",
"Time to complete (Days)": "Tempo per completare (giorni)",
"Auto reminder": "Promemoria automatico",
"Remind once in every (Days)": "Ricorda una volta ogni (giorni)",
"Enable OTP verification": "Abilita verifica OTP",
"Enable Tour": "Abilita tour",
"Notify on signatures": "Notifica sulle firme",
"Allow modifications": "Consenti modifiche",
"Redirect url": "URL di reindirizzamento",
"Created Date": "Data di creazione",
"Updated Date": "Data di aggiornamento",
"Expiry Date": "Data di scadenza"
},
"report-help": {
"Draft Documents": "Questi sono documenti che hai iniziato ma non hai ancora finalizzato per l'invio.",
@@ -287,7 +298,7 @@
"deactivate": "disattiva",
"this-user": "questo utente",
"delete-user": "Elimina utente",
"delete": "elimina",
"delete": "Elimina",
"add-user": "Aggiungi utente",
"password-generateed": "La password verrà generata solo una volta; assicurati di copiarla.",
"Team status": "Stato del Team",
@@ -412,7 +423,7 @@
"upload": "Caricamento",
"initial-teb": "Iniziali",
"signature-tab": "Firma",
"your-signature": "La tua firma",
"initial-alert": "La mia iniziale non trovata",
"copy-title": "Copia widget in",
"contact-delete-alert": "Sei sicuro di voler eliminare questo contatto?",
"reset-password-alert-1": "Il link per reimpostare la password è stato inviato al tuo indirizzo email",
@@ -477,7 +488,7 @@
"add-recipients": "Aggiungi destinatari",
"loading-mssg": "Questo potrebbe richiedere del tempo",
"send-mail": "Invia Mail",
"signature-field-widget":"È necessario aggiungere almeno un campo firma per ogni utente. Non hai aggiunto campi firma per {{signersName}}",
"signature-field-widget": "Ogni utente deve avere almeno un widget di firma. Non hai aggiunto un widget di firma per {{signersName}}.",
"placeholder-alert-1": "Assicurati che sia stato aggiunto almeno un widget per la firma per tutti i destinatari.",
"placeholder-alert-2": "Conferma di aver compilato il campo di testo.",
"placeholder-alert-3": "Sei sicuro di voler inviare questo documento per le firme?",
@@ -612,7 +623,8 @@
"tour-mssg": {
"home-layout-1": "Accesso effettuato con successo! Diamo un'occhiata.",
"home-layout-2": "Per caricare documenti da firmare personalmente o richiedere le firme di altri, seleziona semplicemente i pulsanti corrispondenti.",
"home-layout-3": "Sei pronto per iniziare a usare {{appName}}! Se hai bisogno di supporto, contattaci pure.",
"home-layout-3": "Sei pronto per iniziare a usare {{appName}}!",
"home-layout-4": "Dacci una stella su",
"generate-token": "Esegui l'upgrade ora per generare un token API di produzione.",
"opensign-drive-1": "Fai clic sui collegamenti breadcrumb per navigare facilmente attraverso la gerarchia delle cartelle e visualizzare i documenti in ciascuna cartella.",
"opensign-drive-2": "Fai clic sul pulsante aggiungi per creare una nuova cartella o documento.",
@@ -622,34 +634,32 @@
"opensign-drive-6": "Fai clic con il tasto destro su un documento per vedere opzioni come Scarica, Rinomina, Sposta e Elimina. Fai clic sul documento per aprirlo.",
"opensign-drive-7": "Fai clic con il tasto destro su qualsiasi cartella per vedere le opzioni. Scegli 'Rinomina' per cambiare il nome della cartella o fai clic sulla cartella per navigare nei suoi contenuti.",
"pdf-request-file-1": "Elenco dei firmatari che devono ancora firmare il documento.",
"pdf-request-file-2": "Fai clic su uno dei segnaposto che appaiono sul documento per firmare. Vedrai poi le opzioni per disegnare la tua firma, digitarla o caricare un'immagine.",
"pdf-request-file-3": "Clicca sui pulsanti Rifiuta o Fine per navigare nel tuo documento. Usa il menu con i puntini di sospensione per opzioni aggiuntive, incluso il pulsante Scarica.",
"pdf-request-file-2": "Fai clic su uno qualsiasi dei campi presenti nel documento per iniziare. Potrai quindi compilare le informazioni richieste.",
"pdf-request-file-3": "Dopo aver compilato tutti i campi richiesti, fai clic su 'Fine'. Potrai quindi scaricare il documento firmato. A seconda delle impostazioni del mittente, potresti ricevere una copia del documento completato insieme al certificato di completamento una volta che tutti i destinatari avranno firmato.",
"pdf-request-file-4": "Elenco dei firmatari che hanno già firmato il documento.",
"pdf-request-file-5": "Puoi fare clic su 'Firma Tutto Automaticamente' per firmare automaticamente in tutte le posizioni previste per te. Assicurati di esaminare attentamente il documento prima di fare clic su questo pulsante.",
"pdf-request-file-6": "Si prega di completare i campi alla pagina {{pagenumbers}}, tutti evidenziati con lo stesso colore per una facile identificazione.",
"placeholder-sign-1": "Seleziona un destinatario da questo elenco per aggiungere un segnaposto dove deve firmare. Il segnaposto apparirà dello stesso colore del nome del destinatario una volta posizionato sul documento.",
"placeholder-sign-1": "Seleziona un destinatario da questo elenco per aggiungere un widget. Il widget apparirà con lo stesso colore del nome del destinatario una volta posizionato sul documento.",
"placeholder-sign-2": "Facendo clic sul pulsante 'Aggiungi destinatari' potrai aggiungere più firmatari.",
"placeholder-sign-3": "Fai clic su questo pulsante per aggiungere altri destinatari/firmatari al documento.",
"placeholder-sign-4": "Trascina o fai clic su un campo per aggiungerlo al documento.",
"placeholder-sign-5": "L'area del contenuto PDF visualizza già i segnaposti esistenti del modello. Per tua comodità, questi segnaposti corrisponderanno al colore del nome del destinatario, rendendoli facilmente identificabili.",
"placeholder-sign-4": "Trascina o fai clic su un widget per aggiungerlo al documento.",
"placeholder-sign-5": "L'area di contenuto PDF mostra già i widget esistenti del modello. Per comodità, questi widget corrisponderanno al colore del nome del destinatario, rendendoli facilmente identificabili.",
"placeholder-sign-6": "Facendo clic su 'Invia' il documento verrà salvato. Nel passaggio successivo potrai personalizzare le email da inviare ai destinatari o copiare i link di firma e condividerli direttamente con i destinatari.",
"report-1": "Fai clic sul pulsante 'Aggiungi' per creare un nuovo modello. I modelli sono documenti riutilizzabili progettati per generare rapidamente nuovi documenti con la stessa struttura e firmatari diversi. Ad esempio, un modello HR per l'onboarding potrebbe avere ruoli predefinita come 'Responsabile HR' e 'Nuovo Dipendente'. Ogni volta che usi il modello, puoi assegnare il ruolo 'Nuovo Dipendente' a membri dello staff in arrivo, mentre il ruolo 'Responsabile HR' rimane costante, facilitando un processo di onboarding fluido per ogni nuovo assunto.",
"redirect": "Fai clic sul pulsante 'Usa' per creare un nuovo documento da un modello esistente.",
"bulksend": "Per inviare rapidamente più documenti utilizzando un modello esistente creando semplicemente gli indirizzi e-mail dei destinatari, fai clic sul pulsante 'Invio Multiplo'.",
"option": "Questo menu rivela altre opzioni come Modifica ed Elimina. Usa il pulsante 'Modifica' per aggiungere ruoli di firmatari, modificare i campi e aggiornare il modello. Le modifiche si applicheranno a tutti i futuri documenti creati da questo modello ma non influiranno sui documenti esistenti. Usa il pulsante Elimina per eliminare il modello.",
"signyour-self-1": "Seleziona e trascina i widget preferiti sul PDF per personalizzare il documento prima di firmarlo. Scegli i punti perfetti per ogni modifica per adattare il documento alle tue esigenze.",
"bulksend": "Per inviare rapidamente più documenti utilizzando un modello esistente, basta inserire gli indirizzi e-mail dei destinatari e fare clic sul pulsante 'Invio massivo'. Puoi inviare fino a 50 destinatari.",
"option": "Questo menu mostra ulteriori opzioni come Modifica, Elimina, Rinomina, Duplica, Condividi, ecc. <1>Fai clic qui</1> per saperne di più su tutte le opzioni disponibili. <3>Nota: Le modifiche a un modello esistente si applicheranno a tutti i futuri documenti creati da quel modello, ma non influenzeranno i documenti già inviati.</3>",
"signyour-self-1": "Seleziona o trascina i widget preferiti sul PDF per personalizzare il tuo documento prima della firma. Scegli i punti perfetti per ciascun widget per adattare il documento alle tue esigenze.",
"signyour-self-2": "Trascina e rilascia ovunque in quest'area. Puoi ridimensionarlo e spostarlo in seguito.",
"template-placeholder-1": "Facendo clic sul pulsante 'Aggiungi ruolo' potrai aggiungere vari ruoli di firmatari. Puoi assegnare utenti a ciascun ruolo nei passaggi successivi.",
"template-placeholder-2": "Una volta aggiunti i ruoli, seleziona un ruolo dall'elenco per aggiungere un segnaposto dove deve firmare. Il segnaposto apparirà dello stesso colore del nome del ruolo una volta posizionato sul documento.",
"template-placeholder-3": "Trascina o fai clic su un campo per aggiungerlo al documento.",
"template-placeholder-4": "Trascina il segnaposto per un ruolo ovunque sul documento. Ricorda, apparirà dello stesso colore del nome del destinatario per un facile riferimento.",
"template-placeholder-5": "Facendo clic su 'Avanti' il modello corrente verrà memorizzato. Dopo averlo salvato, ti verrà chiesto di creare un nuovo documento da questo modello, se lo desideri.",
"template-placeholder-2": "Dopo aver aggiunto i ruoli, scegli uno dall'elenco per posizionare un widget per quel destinatario. Puoi fare clic sul widget o trascinarlo nel documento. Una volta posizionato, il widget verrà visualizzato nello stesso colore del ruolo selezionato.",
"template-placeholder-3": "Facendo clic su 'Avanti' il modello corrente verrà memorizzato. Dopo averlo salvato, ti verrà chiesto di creare un nuovo documento da questo modello, se lo desideri.",
"webhook-1": "Esegui l'upgrade ora per configurare il webhook.",
"Need your Signature": "Facendo clic su questa scheda, accederai all'elenco dei documenti in attesa della tua revisione.",
"Out for signatures": "Facendo clic su questa scheda, accederai all'elenco dei documenti in attesa di firma.",
"Recent signature requests": "Questo è un elenco di documenti che aspettano la tua firma.",
"Recently sent for signatures": "Questo è un elenco di documenti che hai inviato ad altre parti per la firma.",
"Drafts": "Questi sono documenti che hai iniziato ma non hai finalizzato per l'invio.",
"Drafts": "Questi sono documenti che hai iniziato ma non hai ancora finalizzato per l'invio.",
"public-template": "Questo video dimostra come configurare il tuo profilo pubblico personalizzato, come 'https://opensign.me/tuo-username'. Imparerai anche come personalizzare il tuo slogan e rendere i tuoi modelli disponibili per la firma pubblica.",
"allowModify-widgets": "È possibile trascinare e rilasciare uno qualsiasi di questi campi nel documento, oltre ai campi già designati dal creatore del documento."
},
@@ -744,7 +754,7 @@
"attach-signer-tour": "Devi allegare un firmatario a ogni ruolo. Puoi farlo cliccando su questa icona. Una volta selezionato un firmatario, sarà associato a tutti i campi associati a quel ruolo che appariranno dello stesso colore.",
"allowed-signature-types": "Tipi di firma consentiti",
"at-least-one-signature-type": "Almeno un tipo di firma deve essere abilitato.",
"expect-default-one-more-signature-type": "Abilita un tipo di firma aggiuntivo oltre al tipo predefinita.",
"expect-default-one-signature-type": "Abilita un tipo di firma aggiuntivo oltre al tipo predefinita.",
"add-signer-note": "Nota - Questo contatto verrà aggiunto alla tua lista di contatti",
"allowed-signature-types-help": {
"p1": "Questa preferenza di firma controlla le opzioni di firma disponibili per i tuoi firmatari. Solo i tipi di firma che selezioni appariranno nel widget di firma quando un documento viene firmato. Ad esempio, se scegli solo l'opzione 'Disegna' nelle preferenze, il tuo firmatario vedrà solo l'opzione 'Disegna' nel widget di firma, mentre gli altri tre tipi non saranno disponibili.",
@@ -910,7 +920,7 @@
"note-length-alert": "La nota può contenere al massimo 200 caratteri.",
"description-length-alert": " La descrizione può contenere al massimo 500 caratteri.",
"fix-&-resend-document": "Correggi e reinvia il documento",
"do-you-want-recreate-document?": "Questo creerà una bozza da questo documento con tutti i campi intatti. Sei sicuro di voler ricreare questo documento?",
"do-you-want-recreate-document?": "Questo creerà una bozza da questo documento con tutti i campi intatti. Sei sicuro di voler ricreare questo documento?",
"start-editing": "Inizia a modificare",
"unsaved-changes-discard-them?": "Hai modifiche non salvate. Vuoi scartarle?",
"yes-discard": "Sì, scarta",
@@ -1000,7 +1010,7 @@
"finish-mssg": "Sei sicuro di voler completare il documento?",
"review": "Rivedere",
"next-field": "Campo successivo",
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
"required-mssg": "{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
"verify-document": "Verifica documento",
"verify-document-signature": "Verifica firma documento",
"select-pdf-document": "Seleziona documento PDF",
@@ -1044,7 +1054,7 @@
"not-calculated": "Non calcolato",
"not-found-in-signature": "Non trovato nella firma",
"readonly-error": "Il widget {{widgetName}} di sola lettura deve avere un valore predefinito oppure può essere reso facoltativo.",
"choose-one":"Scegline uno",
"choose-one": "Scegline uno",
"search-templates": "Cerca modelli…",
"search-documents": "Cerca documenti…",
"search-contacts": "Cerca contatti…",
@@ -1131,7 +1141,9 @@
"show-more": "Mostra di più",
"browse-or-drag-to-replace-existing-file": "Sfoglia o trascina un nuovo file per sostituire quello esistente",
"optional-details": "Dettagli facoltativi",
"mail-adapter-subscription-alert": "Esegui l'upgrade al piano Professional o Team per configurare un SMTP personalizzato.", "connect-to-mail": "Connetti a Gmail",
"hide-optional-details": "nascondi dettagli facoltativi",
"mail-adapter-subscription-alert": "Esegui l'upgrade al piano Professional o Team per configurare un SMTP personalizzato.",
"connect-to-mail": "Connetti a Gmail",
"custom-smtp": "SMTP personalizzato",
"default-smtp": "SMTP predefinito di {{appName}}",
"host": "Host",
@@ -1140,5 +1152,63 @@
"username": "Nome utente",
"use-default-mail-adapter": "Sei sicuro di voler usare i server di posta predefiniti di {{appName}} per inviare le richieste di firma? Ti consigliamo di usare i tuoi server Gmail o SMTP per una migliore consegna in posta in arrivo.",
"verification-code-sent-registered-email": "Un codice di verifica è stato inviato alla tua email registrata <1>{{useremail}}</1>. Inserisci il codice qui sotto per confermare le impostazioni.",
"smpt-credentials": "Credenziali SMTP"
"smpt-credentials": "Credenziali SMTP",
"delete-account": "Elimina account",
"delete-account-que": "Sei sicuro di voler eliminare il tuo account?",
"delete-account-que-user": "Stai per eliminare in modo permanente questo utente e tutti i dati associati. Questa azione non può essere annullata.",
"user-deleted-successfully": "Utente e tutti i dati associati eliminati con successo.",
"account-deletion-request-sent-via-mail": "Le abbiamo inviato via e-mail un link di conferma. Approvi la richiesta per completare l'eliminazione del suo account.",
"type-exact-email-delete": "Digita l'e-mail esatta per abilitare l'eliminazione",
"email-does-not-match": "L'e-mail non corrisponde.",
"please-type-to-confirm": "Per favore, digita <1>{{userEmail}}</1> per confermare:",
"email-settings": "Impostazioni e-mail",
"email-settings-help": "Per garantire una migliore consegna nella casella di posta in arrivo delle e-mail di richiesta firma, puoi collegare il tuo provider di posta elettronica. Scegli una delle seguenti opzioni:",
"connect-to-gmail": "Connetti a Gmail",
"connect-to-gmail-help": "Usa il tuo account Gmail per inviare e-mail di richiesta firma direttamente dalla tua casella di posta, migliorando il tasso di consegna e l'affidabilità.",
"connect-to-smtp": "SMTP personalizzato",
"connect-to-smtp-help": "Usa il tuo server SMTP personalizzato per inviare e-mail tramite il tuo dominio. Questa opzione ti offre il pieno controllo sulla tua infrastruttura e-mail, migliorando la consegna e la coerenza del marchio.",
"connect-to-default": "Se preferisci, puoi anche utilizzare i server di posta predefiniti di {{appName}}, anche se consigliamo di usare i tuoi per ottenere risultati ottimali.",
"email-settings-redirect-message": "Questa impostazione è stata spostata dalla console a Impostazioni del menu principale → Preferenze. Questa pagina verrà rimossa nelle prossime versioni.",
"go-to-preferences-menu": "Vai al menu Preferenze",
"document-download-filename-format": "Formato del nome file per il download del documento",
"preview": "Anteprima: ",
"download-filename-format-help": "Scegli come vengono nominati i PDF scaricati. La tua selezione viene salvata nel tuo profilo e utilizzata in tutta l'app.",
"delete-action-prohibited": "Questa azione non è consentita. Si prega di contattare l'amministratore per richiedere l'eliminazione dell'account.",
"not-verified": "Non verificato",
"verified": "Verificato",
"expires": "Scade",
"fix-resend-error": "Non è possibile correggere e reinviare questo documento perché contiene widget precompilati.",
"duplicate-template-error": "Non è possibile duplicare questo modello perché contiene widget precompilati.",
"save-as-template-error": "Questo documento non può essere salvato come modello perché contiene widget precompilati.",
"redirecting-you-in": "Reindirizzamento tra {{redirectTimeLeft}} sec...",
"pdf-tools-tour": "Fare clic su questi pulsanti per aggiungere, eliminare, riorganizzare, ruotare e ingrandire/ridurre le pagine.",
"widgets": "Widget",
"prefill-tour": "Usa questa opzione per inserire le informazioni in anticipo prima di inviare il documento ai destinatari.",
"empty-prefill-error": "I seguenti campi obbligatori non possono essere lasciati vuoti:",
"please-fill-out": "Si prega di compilarli per continuare.",
"custom-signing-certificate": "Certificato di firma personalizzato",
"signing-certificate-help": "Puoi caricare il tuo certificato di firma dei documenti, che verrà utilizzato per firmare tutti i tuoi documenti e i certificati di completamento. Il file del certificato deve essere un certificato P12 in formato PFX.",
"certificate-file-p12-in-PFX-format": "File di certificato (certificato p12 in formato PFX)",
"password-of-pfx-file": "Inserisci la password del file pfx",
"update": "Aggiorna",
"use-default-certificate": "Usa il certificato predefinito di {{appName}}",
"upgrade-to-team-plan": "Aggiorna al piano Team",
"setup-file-storage": "Configura archiviazione file",
"save-and-activate": "Salva e attiva",
"logging-out-to-apply-settings": "Stai effettuando il logout per applicare le nuove impostazioni",
"reconnect-to-default": "Riconnetti a {{appName}}",
"active-file-adapter": "File Adapter attivo",
"file-adapter-unique-name": "Nome univoco del File Adapter",
"unique-name-for-file-adapter": "Inserisci un nome univoco per il File Adapter",
"storage-provider": "Provider di archiviazione",
"enter-bucket-name": "Inserisci il nome del bucket",
"enter-space-name": "Inserisci il nome dello space",
"enter-region-of-bucket": "Inserisci la regione del bucket",
"enter-region-of-space": "Inserisci la regione dello space",
"enter-access-key": "Inserisci l'access key",
"enter-secret-access-key": "Inserisci la secret access key",
"otp-email": "Abbiamo inviato un codice di verifica",
"save-as-temp-warn": "Nota: Questo documento include widget precompilati, che verranno automaticamente rimossi poiché sono già incorporati nel documento di base.",
"edit-document": "Modifica documento",
"otp-email": "Abbiamo inviato un codice di verifica"
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

+4 -4
View File
@@ -1,4 +1,4 @@
Name,Email,Phone
John Doe,john@example.com,1234567890
Jane Smith,jane@example.com,9876543210
Foo Bar,foo@example.com,5555555555
Name,Email,Phone,Company,JobTitle
John Doe,john@example.com,1234567890,abc firm,dev
Jane Smith,jane@example.com,9876543210,xyz firm,manager
Foo Bar,foo@example.com,5555555555,xyz firm,director
1 Name Email Phone Company JobTitle
2 John Doe john@example.com 1234567890 abc firm dev
3 Jane Smith jane@example.com 9876543210 xyz firm manager
4 Foo Bar foo@example.com 5555555555 xyz firm director
Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.
+56 -88
View File
@@ -1,4 +1,5 @@
import { useState, useEffect, lazy } from "react";
import { useState, useEffect } from "react";
import { lazyWithRetry, hideUpgradeProgress } from "./utils";
import { Routes, Route, BrowserRouter } from "react-router";
import { pdfjs } from "react-pdf";
import Form from "./pages/Form";
@@ -13,24 +14,28 @@ import SignYourSelf from "./pages/SignyourselfPdf";
import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage";
import Lazy from "./primitives/LazyPage";
import Loader from "./primitives/Loader";
import UserList from "./pages/UserList";
import { serverUrl_fn } from "./constant/appinfo";
import DocSuccessPage from "./pages/DocSuccessPage";
import ValidateSession from "./primitives/ValidateSession";
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
const ChangePassword = lazy(() => import("./pages/ChangePassword"));
const UserProfile = lazy(() => import("./pages/UserProfile"));
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
const ManageSign = lazy(() => import("./pages/Managesign"));
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
const Preferences = lazy(() => import("./pages/Preferences"));
const Login = lazy(() => import("./pages/Login"));
const VerifyDocument = lazy(() => import("./pages/VerifyDocument"));
import DragProvider from "./components/DragProivder";
import Title from "./components/Title";
const DebugPdf = lazyWithRetry(() => import("./pages/DebugPdf"));
const ForgetPassword = lazyWithRetry(() => import("./pages/ForgetPassword"));
const GuestLogin = lazyWithRetry(() => import("./pages/GuestLogin"));
const ChangePassword = lazyWithRetry(() => import("./pages/ChangePassword"));
const UserProfile = lazyWithRetry(() => import("./pages/UserProfile"));
const Opensigndrive = lazyWithRetry(() => import("./pages/Opensigndrive"));
const ManageSign = lazyWithRetry(() => import("./pages/Managesign"));
const AddAdmin = lazyWithRetry(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazyWithRetry(
() => import("./pages/UpdateExistUserAdmin")
);
const Preferences = lazyWithRetry(() => import("./pages/Preferences"));
const Login = lazyWithRetry(() => import("./pages/Login"));
const VerifyDocument = lazyWithRetry(() => import("./pages/VerifyDocument"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
@@ -42,73 +47,46 @@ const AppLoader = () => {
function App() {
const [isloading, setIsLoading] = useState(true);
useEffect(() => {
handleCredentials();
// initialize creds
const id = process.env.REACT_APP_APPID ?? "opensign";
localStorage.setItem("parseAppId", id);
localStorage.setItem("baseUrl", `${serverUrl_fn()}/`);
hideUpgradeProgress();
localStorage.removeItem("showUpgradeProgress");
setIsLoading(false);
}, []);
const handleCredentials = () => {
const appId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID
: "opensign";
const baseurl = serverUrl_fn();
try {
localStorage.setItem("baseUrl", `${baseurl}/`);
localStorage.setItem("parseAppId", appId);
setIsLoading(false);
} catch (error) {
console.log("err ", error);
}
};
return (
<div className="bg-base-200">
{isloading ? (
<AppLoader />
) : (
<BrowserRouter>
<Title />
<Routes>
<Route element={<ValidateRoute />}>
<Route exact path="/" element={<LazyPage Page={Login} />} />
<Route
path="/addadmin"
element={<LazyPage Page={AddAdmin} />}
/>
<Route exact path="/" element={<Lazy Page={Login} />} />
<Route path="/addadmin" element={<Lazy Page={AddAdmin} />} />
<Route
path="/upgrade-2.1"
element={<LazyPage Page={UpdateExistUserAdmin} />}
element={<Lazy Page={UpdateExistUserAdmin} />}
/>
</Route>
<Route element={<Validate />}>
<Route
path="/load/template/:templateId"
element={<TemplatePlaceholder />}
/>
<Route
exact
path="/load/placeholdersign/:docId"
element={<PlaceHolderSign />}
/>
<Route
exact
path="/load/recipientSignPdf/:docId/:contactBookId"
element={<PdfRequestFiles />}
element={<DragProvider Page={PdfRequestFiles} />}
/>
</Route>
<Route
path="/loadmf/signmicroapp/login/:id/:userMail/:contactBookId/:serverUrl"
element={<LazyPage Page={GuestLogin} />}
/>
<Route
path="/login/:id/:userMail/:contactBookId/:serverUrl"
element={<LazyPage Page={GuestLogin} />}
/>
<Route
path="/login/:base64url"
element={<LazyPage Page={GuestLogin} />}
element={<Lazy Page={GuestLogin} />}
/>
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
<Route path="/debugpdf" element={<Lazy Page={DebugPdf} />} />
<Route
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
element={<Lazy Page={ForgetPassword} />}
/>
<Route
element={
@@ -117,62 +95,52 @@ function App() {
</ValidateSession>
}
>
<Route
path="/changepassword"
element={<LazyPage Page={ChangePassword} />}
/>
<Route path="/users" element={<UserList />} />
<Route
path="/changepassword"
element={<Lazy Page={ChangePassword} />}
/>
<Route path="/form/:id" element={<Form />} />
<Route path="/report/:id" element={<Report />} />
<Route path="/dashboard/:id" element={<Dashboard />} />
<Route
path="/profile"
element={<LazyPage Page={UserProfile} />}
/>
<Route
path="/drive"
element={<LazyPage Page={Opensigndrive} />}
/>
<Route
path="/managesign"
element={<LazyPage Page={ManageSign} />}
/>
<Route path="/profile" element={<Lazy Page={UserProfile} />} />
<Route path="/drive" element={<Lazy Page={Opensigndrive} />} />
<Route path="/managesign" element={<Lazy Page={ManageSign} />} />
<Route
path="/template/:templateId"
element={<TemplatePlaceholder />}
element={<DragProvider Page={TemplatePlaceholder} />}
/>
{/* signyouself route with no rowlevel data using docId from url */}
<Route path="/signaturePdf/:docId" element={<SignYourSelf />} />
<Route
path="/signaturePdf/:docId"
element={<DragProvider Page={SignYourSelf} />}
/>
{/* draft document route to handle and navigate route page according to document status */}
<Route path="/draftDocument" element={<DraftDocument />} />
<Route
path="/draftDocument"
element={<DragProvider Page={DraftDocument} />}
/>
{/* recipient placeholder set route with no rowlevel data using docId from url*/}
<Route
path="/placeHolderSign/:docId"
element={<PlaceHolderSign />}
/>
{/* for user signature (need your sign route) with row level data */}
<Route path="/pdfRequestFiles" element={<PdfRequestFiles />} />
{/* for user signature (need your sign route) with no row level data */}
<Route
path="/pdfRequestFiles/:docId"
element={<PdfRequestFiles />}
element={<DragProvider Page={PlaceHolderSign} />}
/>
{/* recipient signature route with no rowlevel data using docId from url */}
<Route
path="/recipientSignPdf/:docId/:contactBookId"
element={<PdfRequestFiles />}
element={<DragProvider Page={PdfRequestFiles} />}
/>
<Route
path="/recipientSignPdf/:docId"
element={<PdfRequestFiles />}
element={<DragProvider Page={PdfRequestFiles} />}
/>
<Route path="/users" element={<UserList />} />
<Route
path="/verify-document"
element={<LazyPage Page={VerifyDocument} />}
element={<Lazy Page={VerifyDocument} />}
/>
<Route
path="/preferences"
element={<LazyPage Page={Preferences} />}
element={<Lazy Page={Preferences} />}
/>
</Route>
<Route path="/success" element={<DocSuccessPage />} />
Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 40 KiB

+1 -1
View File
@@ -49,7 +49,7 @@ const AddSigner = (props) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(email)) {
alert("Please enter a valid email address.");
alert(t("valid-email-alert"));
} else {
setIsLoader(true);
if (localStorage.getItem("TenantId")) {
+7 -10
View File
@@ -1,6 +1,5 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
import {
copytoData,
@@ -72,7 +71,7 @@ const AddUser = (props) => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(formdata.email)) {
alert("Please enter a valid email address.");
alert(t("valid-email-alert"));
} else {
const localUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
setIsFormLoader(true);
@@ -100,7 +99,6 @@ const AddUser = (props) => {
};
const res = await Parse.Cloud.run("adduser", params);
const parseData = JSON.parse(JSON.stringify(res));
console.log("parseData ", parseData);
if (props.closePopup) {
props.closePopup();
}
@@ -155,7 +153,6 @@ const AddUser = (props) => {
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title={t("add-user")} />
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
@@ -166,7 +163,7 @@ const AddUser = (props) => {
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
className="block text-xs font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
@@ -187,7 +184,7 @@ const AddUser = (props) => {
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
className="block text-xs font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
@@ -206,7 +203,7 @@ const AddUser = (props) => {
/>
</div>
<div className="mb-3">
<label className="block text-xs text-gray-700 font-semibold">
<label className="block text-xs font-semibold">
{t("password")}
</label>
<div className="flex justify-between items-center op-input op-input-bordered op-input-sm text-base-content w-full h-full text-[13px]">
@@ -223,7 +220,7 @@ const AddUser = (props) => {
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
className="block text-xs font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
@@ -240,7 +237,7 @@ const AddUser = (props) => {
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
className="block text-xs font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
+12 -2
View File
@@ -17,6 +17,7 @@ const BulkSendUi = (props) => {
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
@@ -24,9 +25,14 @@ const BulkSendUi = (props) => {
//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 checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
const removePrefill = getPlaceholder.filter((x) => x?.Role !== "prefill");
const checkIsSignatureExistt = removePrefill?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
@@ -216,7 +222,11 @@ const BulkSendUi = (props) => {
<Loader />
</div>
)}
{!isDisableBulkSend ? (
{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 ? (
@@ -0,0 +1,63 @@
import React, { useState, useEffect } from "react";
import ModalUi from "../primitives/ModalUi";
import { useTranslation } from "react-i18next";
const ColumnSelector = ({
isOpen,
allColumns = [],
visibleColumns = [],
columnLabels = {},
defaultColumns = [],
onApply,
onClose
}) => {
const { t } = useTranslation();
const [selected, setSelected] = useState(visibleColumns);
const [names, setNames] = useState(columnLabels);
useEffect(() => {
setSelected(visibleColumns);
setNames(columnLabels);
}, [visibleColumns, columnLabels]);
const handleChange = (col) => {
setSelected((prev) =>
prev.includes(col) ? prev.filter((c) => c !== col) : [...prev, col]
);
};
const handleApply = () => {
onApply && onApply(selected, names);
onClose && onClose();
};
return (
<ModalUi isOpen={isOpen} title={t("select-columns")} handleClose={onClose}>
<div className="p-[20px] flex flex-col gap-2">
{allColumns.map((col, i) => (
<div key={col} className="flex justify-between items-center gap-2">
<span className="flex justify-center items-center h-full">
<input
id={col + "_" + i}
type="checkbox"
checked={selected.includes(col)}
onChange={() => handleChange(col)}
className="mb-0"
/>
<span className="whitespace-nowrap ml-1">
{t(`report-heading.${col}`, { defaultValue: col })}
</span>
</span>
</div>
))}
<div className="flex justify-start mt-2">
<button onClick={handleApply} className="op-btn op-btn-primary">
{t("apply")}
</button>
</div>
</div>
</ModalUi>
);
};
export default ColumnSelector;
@@ -0,0 +1,51 @@
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
import {
DndProvider,
TouchTransition,
MouseTransition,
Preview
} from "react-dnd-multi-backend";
import DragElement from "./pdf/DragElement";
import LazyPage from "../primitives/LazyPage";
import { GuidelinesProvider } from "../context/GuidelinesContext";
const HTML5toTouch = {
backends: [
{
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>
</DndProvider>
);
}
+26 -12
View File
@@ -12,32 +12,43 @@ import {
} from "../constant/Utils";
import { useTranslation } from "react-i18next";
import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { toggleSidebar } from "../redux/reducers/sidebarReducer.js";
const Header = ({ showSidebar, setIsMenu, isConsole }) => {
const Header = ({ isConsole, setIsLoggingOut }) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { width } = useWindowSize();
const dispatch = useDispatch();
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const isAdmin =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0]?.UserRole ===
"contracts_Admin";
const [isOpen, setIsOpen] = useState(false);
const [applogo, setAppLogo] = useState("");
const [isDarkTheme, setIsDarkTheme] = useState();
const toggleDropdown = () => {
setIsOpen(!isOpen);
if (width <= 768) {
setIsMenu(false);
closeSidebar();
};
const closeSidebar = () => {
if (width && width <= 768) {
dispatch(toggleSidebar(false));
}
};
useEffect(() => {
initializeHead();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
closeSidebar();
}, [width]);
const showSidebar = () => {
dispatch(toggleSidebar());
};
async function initializeHead() {
const applogo = await getAppLogo();
@@ -49,8 +60,9 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
}
}
const closeDropdown = async () => {
const handleLogout = async () => {
setIsOpen(false);
setIsLoggingOut(true);
try {
await Parse.User.logOut();
} catch (err) {
@@ -62,6 +74,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
let favicon = localStorage.getItem("favicon");
localStorage.clear();
saveLanguageInLocal(i18n);
@@ -71,7 +84,8 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
localStorage.setItem("favicon", favicon);
setIsLoggingOut(false);
navigate("/");
};
@@ -113,7 +127,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
}, []);
return (
<div>
<>
<div className="op-navbar bg-base-100 shadow touch-none">
<div className="flex-none">
<button
@@ -237,7 +251,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
</li>
</>
)}
<li onClick={closeDropdown}>
<li onClick={handleLogout}>
<span>
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
{t("log-out")}
@@ -247,7 +261,7 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
</div>
</div>
</div>
</div>
</>
);
};
+1 -1
View File
@@ -26,7 +26,7 @@ function RotateAlert(props) {
props.setShowRotateAlert({ status: false, degree: 0 })
}
type="button"
className="op-btn op-btn-ghost shadow-md"
className="op-btn op-btn-ghost text-base-content shadow-md"
>
{t("no")}
</button>
+73 -11
View File
@@ -1,20 +1,82 @@
import { Helmet } from "react-helmet";
import { useLocation, matchPath } from "react-router";
import { useTranslation } from "react-i18next";
import { useMemo } from "react";
import { useManifestUrl } from "../hook/useManifestUrl";
function Title({ title, drive }) {
const TITLE_MAP = {
"/": "login",
// Homelayout
"/dashboard/35KBoSgoAK": "Dashboard",
"/form/sHAnZphf69": "Sign Yourself",
"/form/8mZzFxbG1z": "Request Signatures",
"/form/template": "New Template",
"/report/6TeaPr321t": "Templates",
"/report/4Hhwbp482K": "Need your sign",
"/report/1MwEuxLEkF": "In Progress",
"/report/kQUoW4hUXz": "Completed",
"/report/ByHuevtCFY": "Drafts",
"/report/UPr2Fm5WY3": "Declined",
"/report/zNqBHXHsYH": "Expired",
"/report/contacts": "Contactbook",
"/drive": "Drive",
"/managesign": "My Signature",
"/preferences": "Preferences",
"/users": "Users",
"/profile": "profile",
"/changepassword": "change-password",
"/verify-document": "verify-document",
"/signaturePdf/:docId": "Sign Yourself",
"/placeHolderSign/:docId": "Request Signatures",
"/template/:templateId": "New Template",
"/recipientSignPdf/:docId": "Request Signatures",
"/recipientSignPdf/:docId/:contactBookId": "Request Signatures",
"/load/recipientSignPdf/:docId/:contactBookId": "Request Signatures",
// alone
"/debugpdf": "Debug Pdf",
"/forgetpassword": "forgot-password",
"/success": "success",
"/addadmin": "add-admin",
"/upgrade-2.1": "add-admin",
"/draftDocument": "New Document",
"/login/:base64url": "Request Signatures",
};
function resolveTitle(pathname, override) {
if (override) return override;
for (let [pattern, label] of Object.entries(TITLE_MAP)) {
if (matchPath({ path: pattern, end: true }, pathname)) {
return label;
}
}
return "";
}
export default function Title() {
const { pathname, state } = useLocation();
const { t } = useTranslation();
const appName =
"OpenSign™";
const logo = useMemo(() => localStorage.getItem("favicon"), []);
const prefix = useMemo(
() => resolveTitle(pathname, state?.title),
[pathname, state?.title]
);
const title = useMemo(
() => (prefix ? `${t(prefix)} - ${appName}` : appName),
[t, prefix, appName]
);
const manifestUrl = useManifestUrl(appName, logo);
return (
<Helmet>
<title>{drive ? title : `${title} - ${appName}`}</title>
<meta name="description" content={`${title} - ${appName}`} />
<link
rel="icon"
type="image/png"
href={localStorage.getItem("fev_Icon")}
sizes="40x40"
/>
<title>{title}</title>
<meta name="description" content={title} />
{logo && <link rel="icon" type="image/png" href={logo} />}
<link rel="manifest" href={manifestUrl} />
</Helmet>
);
}
export default Title;
@@ -1,6 +1,6 @@
import React, { useEffect, useState, useRef } from "react";
import { useEffect, useState, useRef } from "react";
import Parse from "parse";
import ReportTable from "../../primitives/GetReportDisplay";
import DocumentsReport from "../../reports/document/DocumentsReport";
import reportJson from "../../json/ReportJson";
import axios from "axios";
import Loader from "../../primitives/Loader";
@@ -198,7 +198,7 @@ function DashboardReport(props) {
) : (
<>
{reportName ? (
<ReportTable
<DocumentsReport
ReportName={reportName}
List={List}
setList={setList}
@@ -215,9 +215,9 @@ function DashboardReport(props) {
isSearchResult={isSearchResult}
/>
) : (
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded">
<div className="text-center">
<p className="text-xl text-base-content">{t("report-not-found")}</p>
<div className="flex items-center justify-center h-[100px] w-full bg-white rounded-box">
<div className="text-center text-xl text-base-content">
{t("report-not-found")}
</div>
</div>
)}
@@ -1,8 +1,9 @@
import React, { Suspense, lazy } from "react";
import React, { Suspense } from "react";
import { lazyWithRetry } from "../../utils";
import { useTranslation } from "react-i18next";
const DashboardButton = lazy(() => import("./DashboardButton"));
const DashboardCard = lazy(() => import("./DashboardCard"));
const DashboardReport = lazy(() => import("./DashboardReport"));
const DashboardButton = lazyWithRetry(() => import("./DashboardButton"));
const DashboardCard = lazyWithRetry(() => import("./DashboardCard"));
const DashboardReport = lazyWithRetry(() => import("./DashboardReport"));
const buttonList = [
{
label: "Sign yourself",
@@ -366,14 +366,14 @@ function DriveBody(props) {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[26px] h-[26px] fill-current op-text-secondary"
className="w-[26px] h-[26px] fill-current"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
<span className="text-[12px] font-medium">{data.Name}</span>
</td>
<td>_</td>
<td>Folder</td>
<td>{t("folder")}</td>
<td>_</td>
<td>_</td>
</tr>
@@ -390,7 +390,7 @@ function DriveBody(props) {
<span className="text-[12px] font-medium">{data.Name}</span>
</td>
<td>{createddate}</td>
<td>Pdf</td>
<td>{t("pdf")}</td>
<td>{t(`drive-document-status.${status}`)}</td>
<td>
<i
@@ -421,7 +421,7 @@ function DriveBody(props) {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[100px] h-[100px] fill-current op-text-secondary"
className="w-[100px] h-[100px] fill-current"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
@@ -458,14 +458,14 @@ function DriveBody(props) {
className="ContextMenuItem"
>
<i className="fa-light fa-font mr-[8px]"></i>
<span>Rename</span>
<span>{t(`context-menu.Rename`)}</span>
</ContextMenu.Item>
<ContextMenu.Item
onClick={() => handleMenuItemClick("Delete", data, data.Type)}
className="ContextMenuItem"
>
<i className="fa-light fa-trash mr-[8px]"></i>
<span>Delete</span>
<span>{t(`context-menu.Delete`)}</span>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
@@ -550,7 +550,6 @@ function DriveBody(props) {
)
)}
</div>
<ContextMenu.Portal>
<ContextMenu.Content
className="ContextMenuContent"
@@ -603,7 +602,6 @@ function DriveBody(props) {
{signersName()}
</>
)}
<HoverCard.Arrow className="HoverCardArrow" />
</HoverCard.Content>
</HoverCard.Portal>
@@ -627,21 +625,21 @@ function DriveBody(props) {
</tr>
</thead>
<tbody>
{props?.pdfData?.map((data, ind) => {
return (
<React.Fragment key={ind}>
{handleFolderData(data, ind, "table")}
</React.Fragment>
);
})}
{props?.pdfData?.map((data, ind) => (
<React.Fragment key={ind}>
{handleFolderData(data, ind, "table")}
</React.Fragment>
))}
</tbody>
</Table>
</div>
) : (
<div className="flex flex-row flex-wrap items-center mt-1 pb-[20px] mx-[5px]">
{props.pdfData.map((data, ind) => {
return <React.Fragment key={ind}>{handleFolderData(data, ind, "list")}</React.Fragment>;
})}
{props?.pdfData?.map((data, ind) => (
<React.Fragment key={ind}>
{handleFolderData(data, ind, "list")}
</React.Fragment>
))}
</div>
)}
@@ -33,7 +33,7 @@ const AddRoleModal = (props) => {
<button
onClick={props.handleCloseRoleModal}
type="button"
className="op-btn op-btn-ghost ml-2"
className="op-btn op-btn-ghost text-base-content ml-2"
>
{t("close")}
</button>
@@ -118,7 +118,7 @@ function AgreementContent(props) {
{t("agrre-button")}
</button>
<button
className="op-btn op-btn-ghost"
className="op-btn op-btn-ghost text-base-content"
onClick={() => props.setIsShowAgreeTerms(false)}
>
{t("close")}
@@ -28,7 +28,7 @@ function AgreementSign(props) {
<button
onClick={() => {
props.setIsAgree(true);
props.showFirstWidget()
props.showFirstWidget();
}}
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
>
@@ -0,0 +1,155 @@
import { useRef, useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import { EmailBody } from "./EmailBody";
import {
contractDocument,
sendEmailToSigners
} from "../../constant/Utils";
import { useTranslation } from "react-i18next";
import Loader from "../../primitives/Loader";
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);
const [isLoader, setIsLoader] = useState(false);
const handleCloseSendmailModal = () => {
if (props?.handleClose) {
props?.handleClose();
return;
}
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 handleEmailSendToSigners = async () => {
setIsLoader(true);
const documentData = await contractDocument(props?.documentId);
if (documentData && documentData?.length > 0) {
props?.setDocumentDetails(documentData[0]);
if (
documentData?.[0]?.SendinOrder &&
documentData?.[0]?.SendinOrder === true
) {
const ownerEmail = documentData[0].ExtUserPtr.Email;
const ownerDetails = documentData[0].Signers.find(
(x) => x.Email === ownerEmail
);
props?.setCurrUserId(ownerDetails?.objectId);
}
//function is used to send email to signers for sign the document
const mailRes = await sendEmailToSigners(
documentData,
props?.signerList,
props?.customizeMail,
props?.defaultMail,
isCustomize,
);
props?.setIsMailModal(false);
props?.setIsSend(true);
setIsLoader(false);
if (mailRes?.status === "success") {
props?.setMailStatus("success");
} else if (mailRes?.status === "quota-reached") {
props?.setMailStatus("quotareached");
} else if (mailRes?.status === "daily-quota-reached") {
props?.setMailStatus("dailyquotareached");
} else {
props?.setMailStatus("failed");
}
// setMailStatus(mail_status);
} else {
alert("something-went-wrong-mssg");
}
};
return (
<>
{isLoader ? (
<div className="absolute w-full h-full flex justify-center items-center bg-black/30 rounded-box z-30">
<Loader />
</div>
) : (
<ModalUi
isOpen={props?.isMailModal}
title={t("send-mail")}
handleClose={() => handleCloseSendmailModal()}
>
<div className="max-h-96 overflow-y-scroll scroll-hide p-[20px] text-base-content">
{!isCustomize && <span>{t("placeholder-alert-3")}</span>}
{
isCustomize && (
<>
<EmailBody
editorRef={editorRef}
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);
}}
>
<span>{t("reset-to-default")}</span>
</div>
</>
)
}
<div className="flex flex-row items-center gap-2 md:gap-6 mt-2">
<div className="flex flex-row gap-2">
<button
onClick={() => handleEmailSendToSigners()}
className="op-btn op-btn-primary font-[500] text-sm shadow"
>
{t("send")}
</button>
{isCustomize && (
<button
onClick={() => setIsCustomize(false)}
className="op-btn op-btn-ghost font-[500] text-sm"
>
{t("close")}
</button>
)}
</div>
{
!isCustomize && (
<span
className="op-link op-link-accent text-sm"
onClick={() => setIsCustomize(!isCustomize)}
>
{t("cutomize-email")}
</span>
)
}
</div>
<div className="flex justify-center items-center mt-3">
<span className="h-[1px] w-[20%] bg-[#ccc]"></span>
<span className="ml-[5px] mr-[5px]">{t("or")}</span>
<span className="h-[1px] w-[20%] bg-[#ccc]"></span>
</div>
<div className="my-3">{props?.handleShareList()}</div>
<p id="copyUrl" ref={copyUrlRef} className="hidden"></p>
</div>
</ModalUi>
)}
</>
);
}
export default CustomizeMail;
@@ -1,5 +1,5 @@
import React from "react";
import { getWidgetType, widgets } from "../../constant/Utils";
import { widgets } from "../../constant/Utils";
import getWidgetType from "./getWidgetType";
function DragElement(item) {
const getWidgets = widgets;
@@ -21,6 +21,7 @@ function DropdownWidgetOption(props) {
const [layout, setLayout] = useState("vertical");
const statusArr = ["required", "optional"];
const layoutArr = ["vertical", "horizontal"];
const isPrefillExist = props?.roleName === "prefill";
const resetState = () => {
setDropdownOptionList(["Option-1", "Option-2"]);
@@ -87,7 +88,7 @@ function DropdownWidgetOption(props) {
const deleteOption = true;
const addOption = false;
const getUpdatedOptions = dropdownOptionList.filter(
(data, index) => index !== ind
(_, index) => index !== ind
);
setDropdownOptionList(getUpdatedOptions);
props.handleSaveWidgetsOptions(
@@ -102,6 +103,14 @@ function DropdownWidgetOption(props) {
};
const handleSaveOption = () => {
if (["checkbox", radioButtonWidget, "dropdown"].includes(props.type)) {
const allUnique =
new Set(dropdownOptionList).size === dropdownOptionList.length;
if (!allUnique) {
alert("Please remove duplicate option");
return;
}
}
const defaultData =
defaultCheckbox && defaultCheckbox.length > 0
? defaultCheckbox
@@ -228,37 +237,40 @@ function DropdownWidgetOption(props) {
></i>
</div>
))}
<i
onClick={handleAddInput}
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
></i>
<div>
<i
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
aria-label="Add option"
onClick={handleAddInput}
></i>
</div>
</div>
{["dropdown", radioButtonWidget].includes(props.type) && (
<>
<label className="text-[13px] font-semibold mt-[5px]">
{t("default-value")}
</label>
<select
onChange={(e) => setDefaultValue(e.target.value)}
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
name="defaultvalue"
value={defaultValue}
placeholder="select default value"
>
<option value="" disabled hidden className="text-[13px]">
{t("select")}...
</option>
{dropdownOptionList.map((data, ind) => {
return (
<option className="text-[13px]" key={ind} value={data}>
{data}
</option>
);
})}
</select>
</>
)}
{props.type !== "checkbox" && (
{["dropdown", radioButtonWidget].includes(props.type) &&
!isPrefillExist && (
<>
<label className="text-[13px] font-semibold mt-[5px]">
{t("default-value")}
</label>
<select
value={defaultValue}
onChange={(e) => setDefaultValue(e.target.value)}
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
name="defaultvalue"
>
<option value="" disabled hidden className="text-[13px]">
{t("select")}...
</option>
{dropdownOptionList.map((data, ind) => {
return (
<option className="text-[13px]" key={ind} value={data}>
{data}
</option>
);
})}
</select>
</>
)}
{props.type !== "checkbox" && !isPrefillExist && (
<div className="flex flex-row gap-[10px] mt-[0.5rem]">
{statusArr.map((data, ind) => (
<div
@@ -332,7 +344,7 @@ function DropdownWidgetOption(props) {
props.type
) && (
<div className="flex flex-row gap-5 my-2 items-center text-center">
{props.isShowAdvanceFeature && (
{props.isShowAdvanceFeature && !isPrefillExist && (
<div className="flex items-center">
<input
id="isreadonly"
@@ -416,7 +428,7 @@ function DropdownWidgetOption(props) {
{props.currWidgetsDetails?.options?.values?.length > 0 && (
<button
type="submit"
className="op-btn op-btn-ghost ml-1"
className="op-btn op-btn-ghost text-base-content ml-1"
onClick={() => {
props.handleClose && props.handleClose();
resetState();
@@ -28,6 +28,7 @@ const EditTemplate = ({
onSuccess,
setPdfArrayBuffer,
setPdfBase64Url,
isAddYourSelfCheckbox,
}) => {
const appName =
"OpenSign™";
@@ -71,7 +72,7 @@ const EditTemplate = ({
handleReplaceFileValdition(file);
// You can handle the file here
} else {
alert("Only pdf files are allowed.");
alert(t("only-pdf-allowed"));
if (inputFileRef.current) inputFileRef.current.value = "";
}
};
@@ -181,7 +182,8 @@ const EditTemplate = ({
localStorage.getItem("TenantId") ||
template?.ExtUserPtr?.TenantId?.objectId;
const buffer = atob(uploadPdf.base64);
SaveFileSize(buffer.length, pdfUrl, tenantId);
const userId = template?.ExtUserPtr?.UserId?.objectId;
SaveFileSize(buffer.length, pdfUrl, tenantId, userId);
}
const isChecked = formData.SendinOrder === "true" ? true : false;
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
@@ -490,10 +492,11 @@ const EditTemplate = ({
helptextZindex={50}
helpText={t("bcc-help")}
isCaptureAllData
isAddYourSelfCheckbox={isAddYourSelfCheckbox}
/>
</div>
<div className="text-xs mt-2">
<label className="block">Redirect Url</label>
<label className="block">{t("redirect-url")}</label>
<input
name="RedirectUrl"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
@@ -18,7 +18,13 @@ export function EmailBody(props) {
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
value={props.requestSubject}
onChange={(e) => props.setRequestSubject(e.target.value)}
onChange={(e) => {
props?.setRequestSubject(e.target.value);
props?.setCustomizeMail((prev) => ({
...prev,
subject: e.target.value
}));
}}
placeholder='${senderName} has requested you to sign "${documentName}"'
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
@@ -0,0 +1,30 @@
const Guidelines = ({ x1, x2, y1, y2 }) => {
return (
<>
{/* Horizontal guidelines */}
{/* top guide */}{" "}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: y1 }}
/>
{/* bottom guide */}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: y2 }}
/>
{/* Vertical guidelines */}
{/* left guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: x1 }}
/>
{/* right guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: x2 }}
/>
</>
);
};
export default Guidelines;
@@ -61,14 +61,14 @@ export default function PageReorderModal({
</span>
<div className="flex gap-1">
<button
className="op-btn op-btn-xs op-btn-ghost"
className="op-btn op-btn-xs op-btn-ghost text-base-content"
disabled={i === 0}
onClick={() => move(i, -1)}
>
<i className="fa-light fa-arrow-up"></i>
</button>
<button
className="op-btn op-btn-xs op-btn-ghost"
className="op-btn op-btn-xs op-btn-ghost text-base-content"
disabled={i === order.length - 1}
onClick={() => move(i, 1)}
>
@@ -84,7 +84,7 @@ export default function PageReorderModal({
<button
onClick={handleClose}
type="button"
className="op-btn op-btn-ghost ml-1"
className="op-btn op-btn-ghost text-base-content ml-1"
>
{t("close")}
</button>
+37 -85
View File
@@ -23,9 +23,6 @@ import { maxFileSize } from "../../constant/const";
function Header(props) {
const { t } = useTranslation();
const filterPrefill =
props?.signerPos &&
props?.signerPos?.filter((data) => data.Role !== "prefill");
const isMobile = window.innerWidth < 767;
const [isDownloading, setIsDownloading] = useState("");
const [isDeletePage, setIsDeletePage] = useState(false);
@@ -71,14 +68,13 @@ function Header(props) {
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) {
alert("Please upload a valid PDF file.");
alert(t("please-select-pdf"));
return;
}
if (!file.type.includes("pdf")) {
alert("Only PDF files are allowed.");
alert(t("only-pdf-allowed"));
return;
}
const mb = Math.round(file?.size / Math.pow(1024, 2));
if (mb > maxFileSize) {
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
@@ -108,18 +104,18 @@ function Header(props) {
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert("Incorrect password or decryption failed.");
alert(t("incorrect-password-or-decryption-failed"));
}
} else {
alert("Please provided Password.");
alert(t("provide-password"));
}
} else {
console.log("Err ", err);
alert("error while uploading pdf.");
alert(t("error-uploading-pdf"));
}
}
} else {
alert("error while uploading pdf.");
alert(t("error-uploading-pdf"));
}
}
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
@@ -162,6 +158,21 @@ function Header(props) {
}
setIsReorderModal(false);
};
const handleDownloadDoc = async () => {
await handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
);
};
const handleDownloadBtn = async () => {
if (props?.isCompleted) {
props?.setIsDownloadModal(true);
} else {
await handleDownloadDoc();
}
};
return (
<div className="flex py-[5px]">
{isMobile && props?.isShowHeader ? (
@@ -201,17 +212,7 @@ function Header(props) {
>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => {
if (props?.isCompleted) {
props?.setIsDownloadModal(true);
} else {
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
);
}
}}
onClick={() => handleDownloadBtn()}
>
<div className="flex flex-row">
<i
@@ -290,7 +291,7 @@ function Header(props) {
<div
onClick={() => {
if (!props?.isMailSend) {
props?.alertSendEmail();
props?.handleSaveDoc();
}
}}
className={`${
@@ -348,13 +349,7 @@ function Header(props) {
)}
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
)
}
onClick={() => handleDownloadDoc()}
>
<div className="flex flex-row">
<i
@@ -474,42 +469,21 @@ function Header(props) {
/>
{props?.isPlaceholder ? (
<>
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none">
{!props?.isMailSend &&
props?.signersdata.length > 0 &&
props?.signersdata.length !== filterPrefill.length && (
<div>
{filterPrefill.length === 0 ? (
<span className="text-[13px] text-[#f5405e]">
{t("add")}{" "}
{props?.signersdata.length - filterPrefill.length}{" "}
{t("recipients")} {t("widgets-name.signature")}
</span>
) : (
<span className="text-[13px] text-[#f5405e]">
{t("add")}{" "}
{props?.signersdata.length - filterPrefill.length}{" "}
{t("more")}
{t("recipients")} {t("widgets-name.signature")}
</span>
)}
</div>
)}
</div>
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none"></div>
<div className="flex">
{props?.setIsEditTemplate && (
<button
onClick={() => props?.setIsEditTemplate(true)}
className="outline-none border-none text-center mr-[3px]"
>
<i className="fa-light fa-gear fa-lg"></i>
<i className="fa-light fa-gear fa-lg text-base-content"></i>
</button>
)}
{enabledBackBtn && (
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
className="op-btn op-btn-ghost text-base-content op-btn-sm mr-[3px]"
>
{t("back")}
</button>
@@ -518,7 +492,7 @@ function Header(props) {
disabled={props?.isMailSend && true}
data-tut="headerArea"
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => props?.alertSendEmail()}
onClick={() => props?.handleSaveDoc()}
>
{props?.completeBtnTitle
? props?.completeBtnTitle
@@ -565,17 +539,7 @@ function Header(props) {
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => {
if (props?.isCompleted) {
props?.setIsDownloadModal(true);
} else {
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
);
}
}}
onClick={() => handleDownloadBtn()}
>
<i
className="fa-light fa-download py-[3px]"
@@ -590,15 +554,9 @@ function Header(props) {
<>
{props?.templateId && (
<button
onClick={() =>
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
)
}
onClick={() => handleDownloadDoc()}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
className="op-btn op-btn-ghost text-base-content op-btn-sm mr-[3px]"
>
<span className="hidden lg:block">{t("download")}</span>
</button>
@@ -614,14 +572,8 @@ function Header(props) {
{!props?.templateId && (
<button
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
onClick={() =>
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
)
}
className="op-btn op-btn-ghost text-base-content op-btn-sm mr-[3px]"
onClick={() => handleDownloadDoc()}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
<span className="hidden lg:block">{t("download")}</span>
@@ -699,7 +651,7 @@ function Header(props) {
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
className="op-btn op-btn-ghost text-base-content op-btn-sm mr-[3px]"
>
{t("back")}
</button>
@@ -744,8 +696,8 @@ function Header(props) {
handleClose={() => setIsDeletePage(false)}
>
<div className="h-[100%] p-[20px]">
<p className="font-medium">{t("delete-alert-2")}</p>
<p className="pt-3">{t("delete-note")}</p>
<p className="font-medium text-base-content">{t("delete-alert-2")}</p>
<p className="pt-3 text-base-content">{t("delete-note")}</p>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<button
onClick={() => handleDetelePage()}
@@ -757,7 +709,7 @@ function Header(props) {
<button
onClick={() => setIsDeletePage(false)}
type="button"
className="op-btn op-btn-ghost"
className="op-btn op-btn-ghost text-base-content"
>
{t("no")}
</button>
@@ -14,7 +14,7 @@ import { PDFDocument } from "pdf-lib";
import { maxFileSize } from "../../constant/const";
import PageReorderModal from "./PageReorderModal";
function PdfZoom(props) {
function PdfTools(props) {
const { t } = useTranslation();
const mergePdfInputRef = useRef(null);
const [isDeletePage, setIsDeletePage] = useState(false);
@@ -57,13 +57,14 @@ function PdfZoom(props) {
};
const handleFileUpload = async (e) => {
props.setIsTour && props.setIsTour(false);
const file = e.target.files[0];
if (!file) {
alert("Please upload a valid PDF file.");
alert(t("please-select-pdf"));
return;
}
if (!file.type.includes("pdf")) {
alert("Only PDF files are allowed.");
alert(t("only-pdf-allowed"));
return;
}
const mb = Math.round(file?.size / Math.pow(1024, 2));
@@ -95,18 +96,18 @@ function PdfZoom(props) {
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert("Incorrect password or decryption failed.");
alert(t("incorrect-password-or-decryption-failed"));
}
} else {
alert("Please provided Password.");
alert(t("provide-password"));
}
} else {
console.log("Err ", err);
alert("error while uploading pdf.");
alert(t("error-uploading-pdf"));
}
}
} else {
alert("error while uploading pdf.");
alert(t("error-uploading-pdf"));
}
}
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
@@ -150,13 +151,42 @@ function PdfZoom(props) {
setIsReorderModal(false);
};
const handleDeletePage = () => {
setIsDeletePage(true);
props.setIsTour && props.setIsTour(false);
};
const handleReorderPages = () => {
setIsReorderModal(true);
props.setIsTour && props.setIsTour(false);
};
const handleZoomIn = () => {
props.clickOnZoomIn();
props.setIsTour && props.setIsTour(false);
};
const handleZoomOut = () => {
props.clickOnZoomOut();
props.setIsTour && props.setIsTour(false);
};
const handleRotate = () => {
props.handleRotationFun(90);
props.setIsTour && props.setIsTour(false);
};
const handleAntiRotate = () => {
props.handleRotationFun(-90);
props.setIsTour && props.setIsTour(false);
};
return (
<>
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[42px]">
<span
data-tut="pdftools"
className="hidden h-max md:flex flex-col gap-1 text-center md:w-[5%] mt-[42px]"
>
{!props.isDisableEditTools && (
<>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={() => mergePdfInputRef.current.click()}
title={t("add-pages")}
>
@@ -170,15 +200,15 @@ function PdfZoom(props) {
<i className="fa-light fa-plus text-gray-500 2xl:text-[25px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={() => setIsDeletePage(true)}
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={handleDeletePage}
title={t("delete-page")}
>
<i className="fa-light fa-trash text-gray-500 2xl:text-[25px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={() => setIsReorderModal(true)}
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={handleReorderPages}
title={t("reorder-pages")}
>
<i className="fa-light fa-list-ol text-gray-500 2xl:text-[25px]"></i>
@@ -187,7 +217,7 @@ function PdfZoom(props) {
)}
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={() => props.clickOnZoomIn()}
onClick={handleZoomIn}
title={t("zoom-in")}
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[25px]"></i>
@@ -197,7 +227,7 @@ function PdfZoom(props) {
<>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={() => props.handleRotationFun(90)}
onClick={handleRotate}
title={t("rotate-right")}
>
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[25px]"></i>
@@ -205,18 +235,15 @@ function PdfZoom(props) {
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
title={t("rotate-left")}
onClick={() => props.handleRotationFun(-90)}
onClick={handleAntiRotate}
>
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[25px]"></i>
</span>
</>
)}
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px]"
onClick={() => props.clickOnZoomOut()}
style={{
cursor: props.zoomPercent > 0 ? "pointer" : "default"
}}
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={handleZoomOut}
title={t("zoom-out")}
>
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
@@ -229,8 +256,8 @@ function PdfZoom(props) {
handleClose={() => setIsDeletePage(false)}
>
<div className="h-[100%] p-[20px]">
<p className="font-medium">{t("delete-alert-2")}</p>
<p className="pt-3">{t("delete-note")}</p>
<p className="font-medium text-base-content">{t("delete-alert-2")}</p>
<p className="pt-3 text-base-content">{t("delete-note")}</p>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<button
onClick={() => handleDetelePage()}
@@ -242,7 +269,7 @@ function PdfZoom(props) {
<button
onClick={() => setIsDeletePage(false)}
type="button"
className="op-btn op-btn-ghost ml-1"
className="op-btn op-btn-ghost text-base-content ml-1"
>
{t("no")}
</button>
@@ -258,4 +285,4 @@ function PdfZoom(props) {
);
}
export default PdfZoom;
export default PdfTools;
+138 -242
View File
@@ -8,23 +8,27 @@ import {
fontsizeArr,
getContainerScale,
handleCopyNextToWidget,
handleCopySignUrl,
handleHeighlightWidget,
onChangeInput,
radioButtonWidget,
textInputWidget,
cellsWidget,
textWidget,
selectFormat
selectFormat,
randomId
} 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 } from "react-redux";
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
import { useDispatch, useSelector } from "react-redux";
import {
setIsShowModal,
setPrefillImg
} from "../../redux/reducers/widgetSlice";
import { themeColor } from "../../constant/const";
import { useGuidelinesContext } from "../../context/GuidelinesContext";
//function to get default format
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
@@ -47,6 +51,8 @@ const getDefaultDate = (dateStr, format) => {
function Placeholder(props) {
const { t } = useTranslation();
const dispatch = useDispatch();
const prefillImg = useSelector((state) => state.widget.prefillImg);
const { showGuidelines } = useGuidelinesContext();
const widgetData =
props.pos?.options?.defaultValue || props.pos?.options?.response;
const [isDateModal, setIsDateModal] = useState(false);
@@ -128,15 +134,18 @@ function Placeholder(props) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectDate]);
//`handleWidgetIdandPopup` is used to set current widget id and open relative popup
const handleWidgetIdandPopup = async () => {
const handleOnClickPlaceholder = async () => {
props?.setIsReqSignTourDisabled && props?.setIsReqSignTourDisabled(true);
//'props.isOpenSignPad' variable is used to check flow to open signature pad for finish document
//request sign, signyourself, selfsign,public-sign, kiosk mode
if (
props.isOpenSignPad &&
!props.isDragging &&
!props.pos.options?.isReadOnly &&
props.pos.type !== textWidget
props?.data?.Role !== "prefill" &&
props.data?.Id === props.uniqueId
) {
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
if (props?.ispublicTemplate) {
props.handleUserDetails();
} else {
@@ -157,113 +166,25 @@ function Placeholder(props) {
}
dispatch(setIsShowModal({ [props.pos.key]: true }));
}
} else if (
props.isPlaceholder &&
!props.isDragging &&
props.pos.type !== textWidget
) {
if (props.pos.key === props?.currWidgetsDetails?.key) {
}
//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);
const checkIndex = props.xyPosition.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
} else if (props?.data?.Role === "prefill") {
dispatch(setIsShowModal({ [props.pos.key]: true }));
props.setUniqueId(props?.data?.Id);
props?.setRoleName("prefill");
}
//handle prefill 'text widget' click then save previos in tem variable after save or close button again assign current selected userId
} else if (props.pos.type === textWidget) {
dispatch(setIsShowModal({ [props.pos.key]: true }));
props.setTempSignerId(props?.uniqueId);
props.setUniqueId(props?.data?.Id);
}
};
const widgetClickHandler = () => {
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
if (props?.data?.signerObjId === props?.signerObjId && !props.isDragging) {
handleWidgetIdandPopup();
} //condition for open contact details popup when sign templet from public signing flow
else if (props?.uniqueId && props.data?.Id === props?.uniqueId) {
handleWidgetIdandPopup();
}
};
const handleOnClickPlaceholder = () => {
//'props.isDragging' variable is used to checking if user take any widget and try to drag then in that case
//onclick event call. so to prevent onclick and do not open unecessary modal open.
//condition only for request signing flow and self signing flow then apply one click copy sign url of previous drawn signature
if (props.isApplyAll) {
props.setRequestSignTour && props.setRequestSignTour(true);
let isCopySignature = false;
let existSignPosition = "";
//condition for 'signature','initials','stamp' widgets only
if (["signature", "initials", "stamp"].includes(props.pos.type)) {
const widgetKey = props.pos.key;
let exitLoop = false;
const placeholders = props.data.placeHolder;
//check onclick current widget signature/stamp/initials signature url exist or not
placeholders?.forEach((holder) => {
holder?.pos?.forEach((posItem) => {
if (posItem.SignUrl && posItem.type === props.pos.type) {
existSignPosition = posItem;
return; // Break out of the current iteration
}
});
if (existSignPosition) {
return;
}
});
//case:-
//1- Open signpad once when if there are no any signature url draw
//2- If multiple signature widgets are present, clicking on the next widget should copy the signature URL from the previously drawn signature
//3- If the user attempts to edit the signature, open the signature pad to allow drawing a new signature upon clicking.
for (const placeholderObj of props.data.placeHolder || []) {
for (const posItem of placeholderObj.pos || []) {
if (posItem.key === widgetKey) {
//This condition is used to open signature pad to draw signature in some case
//case-1 : The condition (!posItem.SignUrl && !existSignPosition) checks if the current widget being clicked does not have a drawn signature and no signature URL exists for any signature widget.
//case-2 : The condition posItem.SignUrl is used to verify if the current widget already has a drawn signature and if the user is attempting to edit it
if ((!posItem.SignUrl && !existSignPosition) || posItem.SignUrl) {
exitLoop = true;
} else {
//else condition is used to copy the signature URL from the previously drawn signature
exitLoop = true;
isCopySignature = true;
break;
}
}
}
if (exitLoop) {
break; // Stop iterating through placeholders if exitLoop true
}
}
//existSignPosition.type === props.pos.type ensures that the current widget's type matches the type of the widget associated with the existing signature URL during the click event
if (
existSignPosition.type === props.pos.type &&
existSignPosition &&
isCopySignature
) {
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
//function to copy the signature URL from the previously drawn signature when clicking on the next widget.
handleCopySignUrl(
props.pos, //currect widget position details
existSignPosition, //exist sign url widget's position details
props.setXyPosition, //used to update Placeholders details
props.xyPosition, //keep Placeholders details
props.pageNumber, //current page number
props.signerObjId //current signer's object id
);
existSignPosition = "";
} else {
widgetClickHandler();
}
} else {
widgetClickHandler();
}
} else {
//The else condition is used to handle the case when the user clicks on a widget and open signature pad to draw sign
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
handleWidgetIdandPopup();
}
};
//`handleOnClickSettingIcon` is used set current widget details and open setting of it
@@ -300,17 +221,17 @@ function Placeholder(props) {
}
//condition for only placeholder and template flow
if (props.data && props?.pos?.type !== textWidget) {
if (props.data && props?.data?.Role !== "prefill") {
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPosition
.filter((data) => data.Role !== "prefill")
.filter((data) => data?.Role !== "prefill")
.findIndex((data) => data.Id === props.data.Id);
props.setIsSelectId && props.setIsSelectId(checkIndex || 0);
}
//condition to handle in placeholder and template flow for text widget signerId for text widgets i have to set uniqueId in tempSignerId because
//it does not have any signer user and any signerobjId
else if (props.data && props.pos.type === textWidget && !props.isSelfSign) {
props.setTempSignerId(props.uniqueId);
} else if (
!props.isSelfSign &&
props.data &&
props.pos.type === "prefill"
) {
props.setUniqueId(props?.data?.Id);
}
props.setCurrWidgetsDetails(props.pos);
@@ -320,21 +241,17 @@ function Placeholder(props) {
e.stopPropagation();
//condition to handle text widget signer obj id and unique id
//when user click on copy icon of text widget in that condition text widget does not have any signerObjId
//in that case i have to save in tempSignerId as a unique id of previous select signer's unique id
//and on save or cancel button of copy all page popup i have set this temp signer Id in unique id
if (props.data && props?.pos?.type !== textWidget) {
if (props.data && props?.data?.Role !== "prefill") {
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPosition
.filter((data) => data.Role !== "prefill")
.filter((data) => data?.Role !== "prefill")
.findIndex((data) => data.Id === props.data.Id);
props.setIsSelectId && props.setIsSelectId(checkIndex || 0);
} else if (
!props.isSelfSign &&
props.data &&
props.pos.type === textWidget &&
!props.isSelfSign
props.pos.type === "prefill"
) {
props.setTempSignerId(props.uniqueId);
props.setUniqueId(props?.data?.Id);
}
@@ -347,15 +264,26 @@ function Placeholder(props) {
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.pos.type,
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
})
);
}
}
};
@@ -380,7 +308,7 @@ function Placeholder(props) {
//`onChangeInput` is used to save data related to date in a placeholder field
onChangeInput(
date,
props.pos.key,
props.pos,
props.xyPosition,
props.index,
props.setXyPosition,
@@ -476,23 +404,14 @@ function Placeholder(props) {
e.stopPropagation();
handleOnClickSettingIcon();
}}
className="fa-light fa-gear icon"
style={{
color: "#188ae2",
right: "29px",
top: "-19px",
cursor: "pointer",
zIndex: 99,
pointerEvents: "auto"
}}
className="fa-light fa-gear icon text-[#188ae2] right-[29px] -top-[19px] z-[99] pointer-events-auto"
></i>
) : (
/* condition to add setting icon for placeholder & template flow for all widgets except signature and date */
((!props?.pos?.type && props.pos.isStamp) ||
(props?.pos?.type &&
!["date"].includes(props.pos.type) &&
!props.isSignYourself &&
!props.isSelfSign)) && (
!props.isSignYourself &&
!props.isSelfSign &&
props?.pos?.type &&
!["date"].includes(props.pos.type) && (
<i
onPointerDown={(e) => {
e.stopPropagation();
@@ -506,25 +425,20 @@ function Placeholder(props) {
e.stopPropagation();
handleOnClickSettingIcon();
}}
className="fa-light fa-gear icon"
className="fa-light fa-gear icon text-[#188ae2] -top-[19px] z-[99] pointer-events-auto"
style={{
color: "#188ae2",
right: props?.pos?.type === textWidget ? "32px" : "51px",
top: "-19px",
cursor: "pointer",
zIndex: 99,
pointerEvents: "auto"
right: props?.data?.Role === "prefill" ? "32px" : "51px"
}}
></i>
)
)}
{/* condition for usericon for all widgets except text widgets and signyour-self flow */}
{props.pos.type !== textWidget &&
!props.isSignYourself &&
!props.isSelfSign && (
{/* condition for usericon to display for all widgets except prefill widgets, signyour-self and self-sign flow */}
{!props.isSignYourself &&
!props.isSelfSign &&
props?.data?.Role !== "prefill" && (
<i
data-tut="assignSigner"
className="fa-light fa-user icon"
className="fa-light fa-user icon text-[#188ae2] right-[32px] -top-[18px]"
onClick={(e) => {
e.stopPropagation();
props.handleLinkUser(props.data.Id);
@@ -543,7 +457,6 @@ function Placeholder(props) {
);
props.setIsSelectId(checkIndex || 0);
}}
style={{ color: "#188ae2", right: "32px", top: "-18px" }}
></i>
)}
</>
@@ -580,36 +493,32 @@ function Placeholder(props) {
}
}}
style={{
zIndex: "99",
top: "-18px",
right: props.isPlaceholder ? "50px" : "30px",
color: "#188ae2",
fontSize: "14px",
cursor: "pointer",
pointerEvents: "auto"
right:
props.isPlaceholder && props?.data?.Role !== "prefill"
? "50px"
: "30px"
}}
className="fa-light fa-gear icon"
className="fa-light fa-gear icon text-[#188ae2] text-[14px] z-[99] -top-[18px] pointer-events-auto"
></i>
)}
{/* copy icon for all widgets */}
<i
className="fa-light fa-copy icon"
className="fa-light fa-copy icon text-[#188ae2] right-[12px] -top-[18px] "
onClick={(e) => handleCopyPlaceholder(e)}
onTouchEnd={(e) => handleCopyPlaceholder(e)}
style={{ color: "#188ae2", right: "12px", top: "-18px" }}
></i>
{/* delete icon for all widgets */}
<i
className="fa-light fa-circle-xmark icon"
className="fa-light fa-circle-xmark icon text-[#188ae2] -right-[8px] -top-[18px] "
onClick={(e) => {
e.stopPropagation();
//condition for template and placeholder flow
if (props.data) {
props.handleDeleteSign(props.pos.key, props.data.Id);
props.handleDeleteWidget(props.pos.key, props.data.Id);
}
//condition for signyour-self flow
else {
props.handleDeleteSign(props.pos.key);
props.handleDeleteWidget(props.pos.key);
}
}}
//for mobile and tablet touch event
@@ -617,14 +526,13 @@ function Placeholder(props) {
e.stopPropagation();
//condition for template and placeholder flow
if (props.data) {
props.handleDeleteSign(props.pos.key, props.data?.Id);
props.handleDeleteWidget(props.pos.key, props.data?.Id);
}
//condition for signyour-self flow
else {
props.handleDeleteSign(props.pos.key);
props.handleDeleteWidget(props.pos.key);
}
}}
style={{ color: "#188ae2", right: "-8px", top: "-18px" }}
></i>
</>
)
@@ -648,11 +556,7 @@ function Placeholder(props) {
return resizePos * pos.scale * containerScale;
}
} else {
// if (pos.scale === containerScale) {
// return resizePos * pos.scale * props.scale;
// } else {
return resizePos * containerScale * props.scale;
// }
}
}
};
@@ -674,15 +578,7 @@ function Placeholder(props) {
} else {
return resizePos * pos.scale * containerScale;
}
}
// else if (pos.scale === containerScale) {
// if (props.scale > 1) {
// return resizePos * pos.scale * props.scale;
// } else {
// return resizePos * pos.scale;
// }
// }
else {
} else {
return resizePos * containerScale * props.scale;
}
}
@@ -733,7 +629,6 @@ function Placeholder(props) {
return "rgba(203, 233, 237, 0.69)";
}
};
const fontSize = calculateFont(props.pos.options?.fontSize);
const fontColor = props.pos.options?.fontColor || "black";
@@ -817,7 +712,7 @@ function Placeholder(props) {
bounds="parent"
className="signYourselfBlock"
style={{
border: "1px solid #007bff",
border: "1.5px solid #007bff",
borderRadius: "2px",
cursor: getCursor(),
zIndex:
@@ -830,18 +725,20 @@ function Placeholder(props) {
: "5",
opacity:
props.isNeedSign && props.data?.Id !== props?.uniqueId && "0.4",
background: handleBackground()
background:
props?.data?.Role === "prefill"
? "transparent"
: handleBackground() //handle block color of widget for
}}
onDrag={(_, d) => {
props.handleTabDrag && props.handleTabDrag(props.pos.key);
props.showGuidelines &&
props.showGuidelines(
true,
d.x,
d.y,
d.node.offsetWidth,
d.node.offsetHeight
);
props?.handleTabDrag?.(props.pos.key);
showGuidelines(
true,
d.x,
d.y,
d.node.offsetWidth,
d.node.offsetHeight
);
}}
size={{
width:
@@ -863,55 +760,49 @@ function Placeholder(props) {
}
maxHeight="auto"
onResizeStart={(e, dir, ref) => {
props.setIsResize && props.setIsResize(true);
props.showGuidelines &&
props.showGuidelines(
true,
xPos(props.pos, props.isSignYourself),
yPos(props.pos, props.isSignYourself),
ref.offsetWidth,
ref.offsetHeight
);
props?.setIsResize?.(true);
showGuidelines(
true,
xPos(props.pos, props.isSignYourself),
yPos(props.pos, props.isSignYourself),
ref.offsetWidth,
ref.offsetHeight
);
}}
onResize={(e, dir, ref, delta, position) => {
props.showGuidelines &&
props.showGuidelines(
true,
position.x,
position.y,
ref.offsetWidth,
ref.offsetHeight
);
showGuidelines(
true,
position.x,
position.y,
ref.offsetWidth,
ref.offsetHeight
);
}}
onResizeStop={(e, direction, ref) => {
setTimeout(() => {
props.setIsResize && props.setIsResize(false);
}, 50);
props.handleSignYourselfImageResize &&
props.handleSignYourselfImageResize(
ref,
props.pos.key,
props.xyPosition,
props.setXyPosition,
props.index,
containerScale,
props.scale,
props.data && props.data.Id,
props.isResize
);
props.showGuidelines && props.showGuidelines(false);
setTimeout(() => props?.setIsResize?.(false), 50);
props?.handleSignYourselfImageResize?.(
ref,
props.pos.key,
props.xyPosition,
props.setXyPosition,
props.index,
containerScale,
props.scale,
props.data && props.data.Id,
props.isResize
);
showGuidelines(false);
}}
onDragStop={(event, dragElement) => {
props.handleStop &&
props.handleStop(
event,
dragElement,
props.data?.Id,
props.pos?.key
);
props.isDragging &&
props.showGuidelines &&
props.showGuidelines(false);
if (props?.isDragging) {
showGuidelines(false);
}
props?.handleStop?.(
event,
dragElement,
props.data?.Id,
props.pos?.key
);
}}
position={{
x: xPos(props.pos, props.isSignYourself),
@@ -948,11 +839,12 @@ function Placeholder(props) {
![radioButtonWidget, "checkbox"].includes(props.pos.type) &&
props.pos.key === props?.currWidgetsDetails?.key && <BorderResize />
)}
{/* 1- Show a border if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
2- If props.isShowBorder is true, display border for all widgets.
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border:
3.1- When isAlllowModify is true, show border.
3.2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
{/* 1- Show a ouline if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
2- If props.isShowBorder is true, display ouline for all widgets.
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show ouline:
3.1- When isAlllowModify is true, show ouline.
3.2- Do not display ouline for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
*/}
{props.pos.key === props?.currWidgetsDetails?.key &&
(props.isShowBorder ||
@@ -992,6 +884,11 @@ function Placeholder(props) {
calculateFontsize={props.calculateFontsize}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
isAllowModify={
props.isShowBorder ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key))
}
setSelectDate={setSelectDate}
selectDate={selectDate}
startDate={startDate}
@@ -1003,7 +900,6 @@ function Placeholder(props) {
</div>
</Rnd>
)}
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
<div className="text-base-content h-[100%] p-[20px]">
<div className="flex flex-col md:flex-row md:items-center gap-y-3">
@@ -1073,7 +969,7 @@ function Placeholder(props) {
></span>
</div>
</div>
{props?.isPlaceholder && (
{props?.isPlaceholder && props?.data?.Role !== "prefill" && (
<div className="flex items-center mt-3">
<input
id="isReadOnly"
@@ -1,10 +1,6 @@
import React, { useState } from "react";
import { useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import {
handleCopyNextToWidget,
randomId,
textWidget
} from "../../constant/Utils";
import { handleCopyNextToWidget, randomId } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
function PlaceholderCopy(props) {
@@ -125,7 +121,13 @@ function PlaceholderCopy(props) {
);
for (let i = 0; i < props.allPages; i++) {
const newId = randomId();
const newPlaceholder = { ...currentPlaceholder, key: newId };
const nameId = randomId(2)
const widgetName = `${currentPlaceholder?.options?.name}${nameId}`;
const newPlaceholder = {
...currentPlaceholder,
key: newId,
options: { ...currentPlaceholder?.options, name: widgetName }
};
//get exist placeholder position for particular page
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
(data) => data.pageNumber === newPageNumber
@@ -213,6 +215,7 @@ function PlaceholderCopy(props) {
//function for getting selected type placeholder copy
const handleApplyCopy = () => {
const newId = randomId();
if (selectCopyType === 4) {
const signerPosition = props.xyPosition;
let currentXYposition;
@@ -230,8 +233,8 @@ function PlaceholderCopy(props) {
);
//function to create new widget next to just widget
handleCopyNextToWidget(
newId,
currentXYposition,
props.widgetType,
props.xyPosition,
props.pageNumber,
props.setXyPosition,
@@ -248,8 +251,8 @@ function PlaceholderCopy(props) {
);
//function to create new widget next to just widget
handleCopyNextToWidget(
newId,
currentXYposition,
props.widgetType,
props.xyPosition,
getIndex,
props.setXyPosition
@@ -260,11 +263,6 @@ function PlaceholderCopy(props) {
}
};
const handleUniqueId = () => {
const signerId = props.signerObjId ? props.signerObjId : props.Id;
if (signerId && props.widgetType === textWidget && props.setTempSignerId) {
props.setUniqueId(props?.tempSignerId);
props.setTempSignerId("");
}
props.setIsPageCopy(false);
setSelectCopyType(1);
};
@@ -306,7 +304,7 @@ function PlaceholderCopy(props) {
</button>
<button
type="button"
className="op-btn op-btn-ghost ml-2"
className="op-btn op-btn-ghost text-base-content ml-2"
onClick={() => handleUniqueId()}
>
{t("cancel")}
@@ -1,4 +1,4 @@
import React, { useEffect, useState, forwardRef } from "react";
import { useEffect, useState, forwardRef } from "react";
import {
getMonth,
getYear,
@@ -9,21 +9,25 @@ import {
months,
years,
selectCheckbox,
checkRegularExpress
checkRegularExpress,
isBase64
} from "../../constant/Utils";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import "../../styles/signature.css";
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] rounded-[2px] border-[1px] border-[#007bff] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap";
const selectWidgetCls =
"w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content";
"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 =
"select-none-cls overflow-hidden w-full h-full text-black flex flex-col justify-center items-center";
function PlaceholderType(props) {
const selectWidgetCls = `w-full h-full absolute left-0 top-0 focus:outline-none text-base-content`;
const { t } = useTranslation();
const prefillImg = useSelector((state) => state.widget.prefillImg);
const prefillImgLoad = useSelector((state) => state.widget.prefillImgLoad);
const type = props?.pos?.type;
const iswidgetEnable =
props.isSignYourself ||
@@ -39,6 +43,7 @@ function PlaceholderType(props) {
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";
const textWidgetStyle = {
@@ -51,7 +56,6 @@ function PlaceholderType(props) {
display: "flex",
height: "100%"
};
useEffect(() => {
if (type !== "date") {
if (type && type === "checkbox") {
@@ -100,6 +104,26 @@ function PlaceholderType(props) {
return false;
}
};
//function is used to get prefill image's signedUrl after expired
useEffect(() => {
const loadImage = async () => {
const isBase64Url = isBase64(props?.pos?.SignUrl);
if (
props.pos.SignUrl &&
props.pos.type === "image" &&
props?.data?.Role === "prefill" &&
!isBase64Url
) {
const getPrefillImg = prefillImg?.find((x) => x.id === props.pos.key);
if (getPrefillImg) {
setImgUrl(getPrefillImg?.base64);
}
} else {
setImgUrl(props.pos.SignUrl);
}
};
loadImage();
}, [props.pos.SignUrl]);
switch (type) {
case "signature":
@@ -154,9 +178,9 @@ function PlaceholderType(props) {
const checkBoxLayout = props.pos.options?.layout || "vertical";
const isMultipleCheckbox =
props.pos.options?.values?.length > 0 ? true : false;
const checkBoxWrapperClass = `flex items-start ${
const checkBoxWrapperClass = `flex items-start whitespace-pre-wrap ${
checkBoxLayout === "horizontal"
? `flex-row flex-wrap ${isMultipleCheckbox ? "gap-x-2" : ""}`
? `flex-row flex-wrap lg:py-[1.6px] ${isMultipleCheckbox ? "gap-x-2" : ""}`
: `flex-col ${isMultipleCheckbox ? "gap-y-[3px]" : ""}`
}`; // Using gap-y-1 for consistency, adjust if needed
@@ -229,7 +253,7 @@ function PlaceholderType(props) {
height={height}
value={cells.join("")}
editable={isEditable}
resizable={isEditable}
resizable={props?.isAllowModify}
fontSize={fontSize}
fontColor={fontColor}
hint={hint}
@@ -338,7 +362,7 @@ function PlaceholderType(props) {
</div>
);
case "date":
return iswidgetEnable ? (
return iswidgetEnable || props?.data?.Role === "prefill" ? (
<DatePicker
renderCustomHeader={({ date, changeYear, changeMonth }) => (
<div className="flex justify-start ml-2 ">
@@ -397,11 +421,15 @@ function PlaceholderType(props) {
</div>
);
case "image":
return props.pos.SignUrl ? (
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>
) : imgUrl ? (
<img
alt="image"
draggable="false"
src={props.pos.SignUrl}
src={imgUrl}
className="w-full h-full select-none-cls object-contain"
/>
) : (
@@ -445,9 +473,9 @@ function PlaceholderType(props) {
case radioButtonWidget:
const radioLayout = props.pos.options?.layout || "vertical";
const isOnlyOneBtn = props.pos.options?.values?.length > 0 ? true : false;
const radioWrapperClass = `flex items-start ${
const radioWrapperClass = `flex items-start whitespace-pre-wrap ${
radioLayout === "horizontal"
? `flex-row flex-wrap ${isOnlyOneBtn ? "gap-x-[10px]" : ""}`
? `flex-row flex-wrap lg:py-[1.6px] ${isOnlyOneBtn ? "gap-x-[10px]" : ""}`
: `flex-col ${isOnlyOneBtn ? "gap-y-[5px]" : ""}`
}`; // Using gap-y-1 for consistency, adjust if needed
return (
@@ -0,0 +1,768 @@
import { forwardRef, useEffect, useMemo, useRef, useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import {
getMonth,
getYear,
radioButtonWidget,
compressedFileSize,
textWidget,
months,
changeDateToMomentFormat,
convertBase64ToFile,
generatePdfName,
isBase64
} from "../../constant/Utils";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import { range } from "pdf-lib";
import { useTranslation } from "react-i18next";
import moment from "moment";
import AsyncSelect from "react-select/async";
import axios from "axios";
import AddContact from "../../primitives/AddContact";
import Loader from "../../primitives/Loader";
import { useDispatch, useSelector } from "react-redux";
import {
resetWidgetState,
setPrefillImg
} from "../../redux/reducers/widgetSlice";
import * as utils from "../../utils";
const ShowTextWidget = ({ position, handleWidgetDetails }) => {
const inputRef = useRef(null);
const [inputValue, setInputValue] = useState(position.options.response || "");
return (
<input
ref={inputRef}
rows={1}
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
handleWidgetDetails(position, e.target.value);
}}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
);
};
function PrefillWidgetModal(props) {
const { t } = useTranslation();
const dispatch = useDispatch();
// Track already loaded image keys so they don't increment multiple times
const loadedSet = useRef(new Set());
const initializedRef = useRef(false); // prevent rerun on state updates
const prefillImg = useSelector((state) => state.widget.prefillImg);
const [uniqueWidget, setUniqueWidget] = useState([]);
const [image, setImage] = useState(null);
const [imageLoaders, setImageLoaders] = useState({});
const [currentWidget, setCurrentWidget] = useState("");
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 memoizedUniqueWidget = useMemo(() => {
//functions to used remove duplicate name values across all pages
if (!props.prefillData) return [];
//This will help us track which name values have already been encountered across all pages.
const uniqueNames = new Set();
//Filter and flatten placeholder widgets while keeping unique names
const filteredArray = props.prefillData?.placeHolder?.map((item) => ({
...item,
pos: item.pos.filter((curr) => {
if (uniqueNames.has(curr?.options?.name)) return false; //Duplicate name found, remove it
uniqueNames.add(curr?.options?.name); //First time seen, add to set
return true;
})
}));
//latten the filtered array and exclude read-only widgets
const flatArray = filteredArray?.flatMap((page) =>
page.pos
.filter((widget) => !widget.options?.isReadOnly)
.map((widget) => ({
widget,
pageNumber: page.pageNumber
}))
);
return flatArray || [];
}, [props.prefillData]);
useEffect(() => {
dispatch(resetWidgetState([]));
}, []);
// Reset loader state when modal closes
useEffect(() => {
if (!props?.isPrefillModal) {
initializedRef.current = false;
setTotalImages(0);
setLoadedImages(0);
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)) {
prefillImg.forEach((img) => dispatch(setPrefillImg(img)));
}
setImageLoaders({});
};
savePrefillImg();
}, [props.xyPosition]);
useEffect(() => {
setUniqueWidget(memoizedUniqueWidget);
}, [memoizedUniqueWidget]);
useEffect(() => {
if (totalImages > 0 && loadedImages === totalImages) {
setLoading(false);
}
}, [loadedImages, totalImages]);
useEffect(() => {
if (image?.src) {
handleWidgetDetails(currentWidget);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [image]);
// Run only once per modal open
useEffect(() => {
if (props?.isPrefillModal && !initializedRef.current) {
const getImgWidgets = uniqueWidget?.filter(
(w) => w.widget?.type === "image" && w.widget?.options?.response
);
if (getImgWidgets?.length > 0) {
const imgCount = getImgWidgets.length;
setTotalImages(imgCount);
setLoadedImages(0);
setLoading(true);
initializedRef.current = true; // mark as initialized
}
}
}, [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" }}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full"
onClick={onClick}
ref={ref}
>
{value}
<i className="fa-light fa-calendar ml-[5px]"></i>
</div>
));
ExampleCustomInput.displayName = "ExampleCustomInput";
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(),
position?.options?.validation?.format
);
return getDate;
};
//function to set date with required date format onchange date
const handleOnDateChange = (date, position) => {
const format = position?.options?.validation?.format || "MM/dd/yyyy";
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)
);
}
handleWidgetDetails(position, newDate);
};
const handleSavePrefillImg = async (widgetDetails) => {
setImageLoaders((prev) => ({ ...prev, [widgetDetails?.key]: true }));
try {
const imageName = generatePdfName(16);
const imageUrl = await convertBase64ToFile(
imageName,
image.src,
image.imgType
);
setImageLoaders({});
if (imageUrl) {
return imageUrl;
}
} catch (e) {
console.log("error in handleSavePrefillImg function ", e);
}
};
//function is used to handle prefill widgets details and check if there are any duplicate widget name field exist then update all duplicate value
const handleWidgetDetails = async (widgetDetails, response) => {
const widgetName = widgetDetails?.options?.name;
const getPrefill = props.xyPosition.find((x) => x?.Role === "prefill");
const getPlaceholder = getPrefill?.placeHolder;
let imgUrl;
if (widgetDetails?.type === "image") {
imgUrl = await handleSavePrefillImg(widgetDetails);
}
const updatedData = getPlaceholder.map((page) => ({
...page,
pos: page.pos.map((item) => {
if (item.options.name === widgetName) {
if (widgetDetails?.type === "image") {
return {
...item,
SignUrl: imgUrl,
ImageType: image.imgType,
options: { ...item.options, response: imgUrl }
};
} else {
return {
...item,
options: { ...item.options, response: response }
};
}
} else {
return item;
}
})
}));
const newUpdateSigner = props.xyPosition.map((obj) => {
if (obj.Role === "prefill") {
return { ...obj, placeHolder: updatedData };
}
return obj;
});
props.setXyPosition(newUpdateSigner);
};
//function for set checked and unchecked value of checkbox
const handleCheckboxValue = (isChecked, ind, position) => {
let updateSelectedCheckbox = [];
updateSelectedCheckbox =
position.options?.defaultValue || position.options?.response || [];
if (isChecked) {
updateSelectedCheckbox.push(ind);
} else {
updateSelectedCheckbox = updateSelectedCheckbox.filter(
(data) => data !== ind
);
}
handleWidgetDetails(position, updateSelectedCheckbox);
};
//function for image upload or update
const onImageChange = (event, position) => {
if (event.target.files && event.target.files[0]) {
const file = event.target.files[0];
compressedFileSize(file, setImage);
setCurrentWidget(position);
}
};
const handleRadioCheck = (data, position) => {
const res = position?.options?.response;
const defaultCheck = position?.options?.defaultValue;
if (res === data || defaultCheck === data) {
return true;
} else {
return false;
}
};
//function for show checked checkbox
const selectCheckbox = (ind, position) => {
const res = position?.options?.response;
const defaultCheck = position?.options?.defaultValue;
if (res && res?.length > 0) {
const isSelectIndex = res.indexOf(ind);
if (isSelectIndex > -1) {
return true;
} else {
return false;
}
} else if (defaultCheck) {
const isSelectIndex = defaultCheck.indexOf(ind);
if (isSelectIndex > -1) {
return true;
} else {
return false;
}
} else {
return false;
}
};
const handleClearImage = (position) => {
let prefillPlaceholder = props?.xyPosition.filter(
(data) => data?.Role === "prefill"
);
const updatedArray = prefillPlaceholder[0]?.placeHolder?.map((page) => ({
...page,
pos: page.pos.map((item) => {
if (item.options.name === position.options.name) {
return {
...item,
SignUrl: "",
options: {
...position.options,
response: ""
}
};
}
return item;
})
}));
const newUpdateSigner = props.xyPosition.map((obj) => {
if (obj.Role === "prefill") {
return { ...obj, placeHolder: updatedArray };
}
return obj;
});
props.setXyPosition(newUpdateSigner);
};
const handleImageLoaded = (key) => {
// Prevent counting the same image multiple times.
// If this image (key) has not already been marked as loaded...
if (!loadedSet.current.has(key)) {
// Mark this image as loaded by adding its key to the Set
loadedSet.current.add(key);
// Increment the loadedImages state by 1
// (tracks how many images have finished loading)
setLoadedImages((prev) => prev + 1);
}
};
const handleWidgetType = (position) => {
switch (position?.type) {
case "checkbox":
return (
<>
<span className={widgetTitle}>{position.options?.name}</span>
<div className="flex flex-col gap-y-1">
{position.options?.values?.map((data, ind) => (
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5"
>
<input
id={`checkbox-${position.key + ind}`}
className="mt-[2px] op-checkbox op-checkbox-xs"
type="checkbox"
checked={selectCheckbox(ind, position)}
onChange={(e) => {
handleCheckboxValue(e.target.checked, ind, position);
}}
/>
{!position.options?.isHideLabel && (
<label
htmlFor={`checkbox-${position.key + ind}`}
className="text-xs mb-0 text-center ml-[3px] cursor-pointer"
>
{data}
</label>
)}
</div>
))}
</div>
</>
);
case textWidget:
return (
<>
<span className={widgetTitle}>{position.options?.name}</span>
<ShowTextWidget
position={position}
handleWidgetDetails={handleWidgetDetails}
/>
</>
);
case "dropdown":
return (
<>
<span className={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);
}}
>
{/* Default/Title option */}
<option value="" disabled hidden>
{t("choose-one")}
</option>
{position?.options?.values?.map((data, ind) => (
<option key={ind} value={data}>
{data}
</option>
))}
</select>
</>
);
case "date":
return (
<>
<span className={widgetTitle}>{position.options?.name}</span>
<DatePicker
portalId="datepicker-portal-root"
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(position)}
onChange={(date) => {
handleOnDateChange(date, position);
}}
customInput={<ExampleCustomInput />}
dateFormat={position?.options?.validation?.format || "MM/dd/yyyy"}
/>
</>
);
case "image":
return (
<ImageComponent
position={position}
docId={props?.docId}
/>
);
case radioButtonWidget:
return (
<>
<span className={widgetTitle}>{position.options?.name}</span>
<div className="flex flex-col gap-y-1">
{position.options?.values.map((data, ind) => (
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5"
>
<input
id={`radio-${position.key + ind}`}
className="mt-[2px] op-radio op-radio-xs"
type="radio"
checked={handleRadioCheck(data, position)}
onChange={() => {
handleWidgetDetails(position, data);
}}
/>
{!position.options?.isHideLabel && (
<label
htmlFor={`radio-${position.key + ind}`}
className="text-xs mb-0 ml-[2px] cursor-pointer"
>
{data}
</label>
)}
</div>
))}
</div>
</>
);
default:
return position?.SignUrl ? (
<div className="pointer-events-none">
<img
alt="image"
draggable="false"
src={position?.SignUrl}
className="w-full h-full"
/>
</div>
) : (
<div className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full">
No widget
</div>
);
}
};
const handleEmbedPrefill = (item) => {
props.handleCreateDocument();
};
//`loadOptions` function to use show all list of signer in dropdown
const loadOptions = async (inputValue) => {
try {
const baseURL = localStorage.getItem("baseUrl");
const url = `${baseURL}functions/getsigners`;
const token = {
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
};
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
...token
};
const search = inputValue;
const axiosRes = await axios.post(url, { search }, { headers });
const contactRes = axiosRes?.data?.result || [];
if (contactRes) {
const res = JSON.parse(JSON.stringify(contactRes));
const result = res;
setUserList(result);
return await result.map((item) => ({
label: `${item.Name}<${item.Email}>`,
value: item.objectId
}));
}
} catch (error) {
console.log("err", error);
}
};
//`handleInputChange` function to get signers list from dropdown
const handleInputChange = (item, id) => {
const signerExist = props.forms.some((x) => x.label === item.label);
if (signerExist) {
alert(t("already-exist-signer"));
} else {
let newForm = [...props.forms];
let signerId = newForm[id].value;
newForm[id].label = item?.label;
// newForm[id].value = item?.value;
props.setForms(newForm);
const getSigner = userList.find((x) => x.objectId === item.value);
props.handleAddUser(getSigner, signerId);
}
};
//show modal to create new contact
const handleCreateNew = (e, id) => {
e.preventDefault();
props.setIsNewContact({ status: true, id: id });
};
const closePopup = () => {
props.setIsNewContact({ status: false, id: "" });
};
return (
<>
<ModalUi
title={uniqueWidget?.length > 0 ? t("prefill-widget") : "Recipients"}
isOpen={true}
handleClose={props.handleClosePrefillModal}
>
<div className="relative">
{loading && (
<div className="absolute inset-0 flex items-center justify-center bg-white/70 z-[9999]">
<Loader />
</div>
)}
{uniqueWidget?.length > 0 && (
<div className="py-3 px-[10px] op-card border-[1px] border-gray-400 m-3 md:m-6 text-base-content flex flex-col relative">
<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)}
</div>
))}
</div>
</div>
)}
<div>
{props.forms.length > 0 && (
<div className="overflow-y-auto m-3">
{uniqueWidget?.length > 0 && (
<h1 className="font-medium text-[15px] mb-2">
{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">
{props.forms?.map((field, id) => {
return (
<div className="flex flex-col" key={field?.value}>
<label>{field?.role}</label>
<div className="flex justify-between items-center gap-1">
<div className="flex-1">
<AsyncSelect
cacheOptions
defaultOptions
value={field}
loadingMessage={() => t("loading")}
noOptionsMessage={() => t("contact-not-found")}
loadOptions={loadOptions}
onChange={(item) => handleInputChange(item, id)}
unstyled
onFocus={() => loadOptions()}
classNames={{
control: () =>
"op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full h-full text-[11px]",
valueContainer: () =>
"flex flex-row gap-x-[2px] gap-y-[2px] md:gap-y-0 w-full my-[2px]",
multiValue: () =>
"op-badge op-badge-primary h-full text-[11px]",
multiValueLabel: () => "mb-[2px]",
menu: () =>
"mt-1 shadow-md rounded-lg bg-base-200 text-base-content absolute z-9999",
menuList: () => "shadow-md rounded-lg ",
option: () =>
"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"
}}
menuPortalTarget={document.getElementById(
"selectSignerModal"
)}
/>
</div>
<button
onClick={(e) => handleCreateNew(e, field.value)}
className="op-btn op-btn-accent op-btn-outline op-btn-sm "
>
<i className="fa-light fa-plus"></i>
</button>
</div>
</div>
);
})}
</div>
</div>
)}
</div>
<div className="flex gap-2 mx-4 mb-3">
<button
disabled={isAnyLoaderActive}
className="op-btn op-btn-primary op-btn-sm w-[80px]"
onClick={() => handleEmbedPrefill(props?.item)}
>
<span>{t("next")}</span>
</button>
<button
className="op-btn op-btn-ghost op-btn-sm"
onClick={() => props.navigatePageToDoc()}
>
<span>{t("edit-draft")}</span>
</button>
</div>
</div>
</ModalUi>
<ModalUi
title={t("add-contact")}
isOpen={props.isNewContact.status}
handleClose={closePopup}
>
<AddContact
isDisableTitle
isAddYourSelfCheckbox
details={props.handleAddUser}
closePopup={closePopup}
newContactId={props.isNewContact.id}
/>
</ModalUi>
</>
);
}
export default PrefillWidgetModal;
@@ -20,7 +20,7 @@ function PrevNext({ pageNumber, allPages, changePage }) {
onClick={previousPage}
>
<span className="block">
<i className="fa-light fa-backward" aria-hidden="true"></i>
<i className="fa-light fa-chevron-up" aria-hidden="true"></i>
</span>
</button>
<span className="text-xs text-base-content font-medium mx-2 2xl:text-[20px]">
@@ -32,7 +32,7 @@ function PrevNext({ pageNumber, allPages, changePage }) {
onClick={nextPage}
>
<span className="block">
<i className="fa-light fa-forward" aria-hidden="true"></i>
<i className="fa-light fa-chevron-down" aria-hidden="true"></i>
</span>
</button>
</div>
@@ -1,4 +1,4 @@
import React, { useRef, useState } from "react";
import { useRef, useState } from "react";
import { useAutoAnimate } from "@formkit/auto-animate/react";
import {
color,
@@ -12,6 +12,7 @@ const cursor =
const RecipientList = (props) => {
const [animationParent] = useAutoAnimate();
const [isHover, setIsHover] = useState();
const [isPrefill, setIsPrefill] = useState(false);
const [isEdit, setIsEdit] = useState(false);
//function for onhover signer name change background color
const inputRef = useRef(null);
@@ -80,9 +81,43 @@ const RecipientList = (props) => {
});
props?.setSignerPos(changeOrderSignerList);
};
const handleSelectRecipient = (e, index, obj, prefill) => {
e.preventDefault();
props?.setIsSelectId(index);
props?.setUniqueId(obj.Id);
props?.setRoleName(obj.Role);
props?.setBlockColor(obj?.blockColor);
props?.handleModal && props?.handleModal();
setIsPrefill(prefill ?? false);
props.setIsTour && props?.setIsTour(false);
};
const isSelected = (ind) => {
const isUserSelected =
(!isMobile && isHover === ind) ||
(!isPrefill && props.isSelectListId === ind);
return isUserSelected;
};
return (
<>
{props.signersdata?.length > 0 &&
{props?.prefillSigner?.length > 0 &&
props?.prefillSigner?.map((obj, ind) => (
<div
key={ind}
data-tut="prefillTour"
className={`${
props.uniqueId === obj.Id
? "op-bg-primary text-white"
: "transparent text-base-content"
} cursor-pointer px-2 py-1 m-1 mb-2 border-[1px] gap-1 rounded-xl flex justify-center items-center op-border-primary text-[12px] font-bold whitespace-nowrap text-ellipsis`}
onClick={(e) => handleSelectRecipient(e, ind, obj, true)}
>
<i
className={`${props.uniqueId === obj.Id ? "bg-white op-text-primary" : "op-bg-primary text-white"} w-[20px] h-[20px] flex justify-center items-center text-[10px] fa-light fa-signature rounded-full`}
></i>
<span>{obj.Name}</span>
</div>
))}
{props.signersdata.length > 0 &&
props.signersdata.map((obj, ind) => {
return (
<div
@@ -118,16 +153,7 @@ const RecipientList = (props) => {
: color[ind % color.length]
: "transparent"
}}
onClick={(e) => {
e.preventDefault();
props.setIsSelectId(ind);
props.setUniqueId(obj.Id);
props.setRoleName(obj.Role);
props.setBlockColor(obj?.blockColor);
if (props.handleModal) {
props.handleModal();
}
}}
onClick={(e) => handleSelectRecipient(e, ind, obj)}
>
<div className="flex flex-row items-center w-full overflow-hidden pr-2">
<div
@@ -142,38 +168,22 @@ const RecipientList = (props) => {
{isWidgetExist(obj.Id) ? (
<i className="fa-light fa-check"></i>
) : (
<>
{obj.Name
? getFirstLetter(obj.Name)
: getFirstLetter(obj.Role)}
</>
<>{getFirstLetter(obj?.Name ?? obj?.Role)}</>
)}
</span>
</div>
<div
className={`${
obj.Name ? "flex-col" : "flex-row"
className={`${obj.Name ? "flex-col" : "flex-row"} ${
isSelected(ind) ? "text-[#424242]" : "text-base-content"
} flex overflow-hidden flex-grow-0`}
>
{obj.Name ? (
<span
className={`${
(!isMobile && isHover === ind) ||
props.isSelectListId === ind
? "text-[#424242]"
: "text-base-content"
} text-[12px] font-bold truncate whitespace-nowrap`}
>
<span className="text-[12px] font-bold truncate whitespace-nowrap">
{obj.Name}
</span>
) : (
<span
className={`${
(!isMobile && isHover === ind) ||
props.isSelectListId === ind
? "text-[#424242]"
: "text-base-content"
} text-[12px] font-bold truncate whitespace-nowrap cursor-pointer`}
className="text-[12px] font-bold truncate whitespace-nowrap cursor-pointer"
onClick={() => {
setIsEdit({ [obj.Id]: true });
props.setRoleName(obj.Role);
@@ -204,14 +214,7 @@ const RecipientList = (props) => {
</span>
)}
{obj.Name && (
<span
className={` ${
(!isMobile && isHover === ind) ||
props.isSelectListId === ind
? "text-[#424242]"
: "text-base-content"
} text-[10px] font-medium truncate whitespace-nowrap`}
>
<span className="text-[10px] font-medium truncate whitespace-nowrap">
{obj?.Role || obj?.Email}
</span>
)}
@@ -247,18 +250,13 @@ const RecipientList = (props) => {
</div>
</div>
)}
{props.handleDeleteUser && (
{props.handleDeleteUser && obj?.Role !== "prefill" && (
<div
onClick={(e) => {
e.stopPropagation();
props.handleDeleteUser(obj.Id);
}}
className={`${
(!isMobile && isHover === ind) ||
props.isSelectListId === ind
? "text-[#424242]"
: "text-base-content"
} cursor-pointer`}
className={`${isSelected(ind) ? "text-[#424242]" : "text-base-content"} cursor-pointer`}
>
<i className="fa-light fa-trash-can 2xl:text-[22px]"></i>
</div>
@@ -17,7 +17,7 @@ function RenderAllPdfPage(props) {
const mergePdfInputRef = useRef(null);
const [signPageNumber, setSignPageNumber] = useState([]);
const [bookmarkColor, setBookmarkColor] = useState("");
const isHeader = useSelector((state) => state.showHeader);
const isSidebar = useSelector((state) => state.sidebar.isOpen);
const [pageWidth, setPageWidth] = useState("");
//set all number of pages after load pdf
@@ -51,7 +51,7 @@ function RenderAllPdfPage(props) {
const timer = setTimeout(updateSize, 100); // match the transition duration
return () => clearTimeout(timer);
}, [isHeader, pageContainer, props?.containerWH]);
}, [isSidebar, pageContainer, props?.containerWH]);
//'function `addSignatureBookmark` is used to display the page where the user's signature is located.
const addSignatureBookmark = (index) => {
const ispageNumber = signPageNumber.includes(index + 1);
@@ -78,11 +78,11 @@ function RenderAllPdfPage(props) {
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) {
alert("Please upload a valid PDF file.");
alert(t("please-select-pdf"));
return;
}
if (!file.type.includes("pdf")) {
alert("Only PDF files are allowed.");
alert(t("only-pdf-allowed"));
return;
}
const mb = Math.round(file?.size / Math.pow(1024, 2));
@@ -114,18 +114,18 @@ function RenderAllPdfPage(props) {
// Upload the file to Parse Server
} catch (err) {
console.error("Incorrect password or decryption failed", err);
alert("Incorrect password or decryption failed.");
alert(t("incorrect-password-or-decryption-failed"));
}
} else {
alert("Please provided Password.");
alert(t("provide-password"));
}
} else {
console.log("Err ", err);
alert("error while uploading pdf.");
alert(t("error-uploading-pdf"));
}
}
} else {
alert("error while uploading pdf.");
alert(t("error-uploading-pdf"));
}
}
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
+137 -57
View File
@@ -1,4 +1,10 @@
import React, { useState, useRef } from "react";
import React, {
useState,
useRef,
useCallback,
useLayoutEffect,
useEffect
} from "react";
import RSC from "react-scrollbars-custom";
import { Document, Page } from "react-pdf";
import {
@@ -12,22 +18,30 @@ import Placeholder from "./Placeholder";
import Alert from "../../primitives/Alert";
import { useTranslation } from "react-i18next";
import usePdfPinchZoom from "../../hook/usePdfPinchZoom";
import { useDispatch, useSelector } from "react-redux";
import Guidelines from "./Guidelines";
import { useGuidelinesContext } from "../../context/GuidelinesContext";
import { toggleSidebar } from "../../redux/reducers/sidebarReducer";
function RenderPdf(props) {
const { t } = useTranslation();
const dispatch = useDispatch();
const [scaledHeight, setScaledHeight] = useState();
const [guideline, setGuideline] = useState({
show: false,
x1: 0,
x2: 0,
y1: 0,
y2: 0
});
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 () => {
dispatch(toggleSidebar(true));
};
}, []);
// enable pinch to zoom only on actual pdf wrapper
usePdfPinchZoom(
pdfContainerRef,
@@ -36,20 +50,6 @@ function RenderPdf(props) {
props.setZoomPercent
);
const handleGuideline = (isShow, x = 0, y = 0, width = 0, height = 0) => {
if (isShow) {
setGuideline({
show: true,
x1: x,
x2: x + width,
y1: y,
y2: y + height
});
} else {
setGuideline({ show: false, x1: 0, x2: 0, y1: 0, y2: 0 });
}
};
// handle signature block width and height according to screen
const posWidth = (pos, signYourself) => {
const containerScale = getContainerScale(
@@ -111,6 +111,98 @@ function RenderPdf(props) {
}
};
// `smoothScrollTo` is used to provide smooth scrolling while focus on widget
const smoothScrollTo = (targetY, duration = 500) => {
const sb = scrollRef.current;
if (!sb) return;
const start = sb.scrollTop;
const change = targetY - start;
const startTime = performance.now();
const animate = (now) => {
const elapsed = now - startTime;
const t = Math.min(1, elapsed / duration);
// easeInOutQuad
const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
sb.scrollTo(sb.scrollLeft, start + change * ease);
if (t < 1) requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
};
// `scrollToTarget` is used to focus on widget and scroll to top
const scrollToTarget = useCallback(() => {
// Get the scrollbar container ref
const sb = scrollRef.current;
// If there's no content element or no widget details, bail out
if (!sb?.contentElement || !props.currWidgetsDetails) return;
// The absolute Y position (relative to the document) where we want to scroll
const yPosition = props?.currWidgetsDetails?.yPosition || 0;
const containerScale = getContainerScale(
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const targetTop = yPosition * containerScale * props.scale;
// The Y offset of the scrollable content container itself
const { offsetTop } = sb.contentElement;
// Account for the header height + a little extra padding
// Different header height if user is a guest signer
const headerOffset = isGuestSigner ? 10 : 79;
// Compute the scroll position inside the container
const positionTop = targetTop - offsetTop - headerOffset;
const pageNumber = props.pageNumber > 0 ? props.pageNumber - 1 : 0;
const ogH = props.pdfOriginalWH[pageNumber]?.height;
// If the modal for this widget is open, we may need to expand the PDF container
if (props.isShowModal[scrollTriggerId]) {
// Original PDF height for this page
if (pdfContainerRef?.current) {
// Increase container height to include the area up to the target
pdfContainerRef.current.style.height = `${ogH + (targetTop - offsetTop)}px`;
}
} else {
// Otherwise, reset any inline height override
if (pdfContainerRef?.current) {
pdfContainerRef.current.style.height = "";
}
}
// Actually perform the scroll: keep the same horizontal scroll, scroll vertically
if (targetTop > ogH * 0.75 && !isGuestSigner && isOpen) {
smoothScrollTo(positionTop - 300);
} else if (targetTop > ogH * 0.75 && !isGuestSigner && !isOpen) {
smoothScrollTo(positionTop - 100);
} else {
smoothScrollTo(positionTop);
}
// Highlight only the target widget; reset others to default border
document.querySelectorAll(".signYourselfBlock").forEach((w) => {
w.style.border =
w.id === String(scrollTriggerId)
? "1.5px solid red" // active widget in red
: "1.5px solid #007bff"; // others in blue
});
}, [
scrollTriggerId,
props.currWidgetsDetails?.yPosition,
props.isShowModal,
props.pdfOriginalWH,
props.pageNumber
]);
useLayoutEffect(() => {
// Whenever scrollTriggerId changes, fire off the scroll in the next rAF
// to ensure the DOM has painted/layout is stable before scrolling
if (scrollTriggerId) {
scrollToTarget();
}
}, [scrollTriggerId, scrollToTarget]);
// function for render placeholder block over pdf document (all signing flow)
const checkSignedSigners = (data) => {
let checkSign = [];
@@ -147,7 +239,6 @@ function RenderPdf(props) {
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
showGuidelines={handleGuideline}
isDragging={props.isDragging}
pdfDetails={props.pdfDetails}
unSignedWidgetId={props.unSignedWidgetId}
@@ -164,7 +255,7 @@ function RenderPdf(props) {
handleStop={props.handleStop}
setUniqueId={props.setUniqueId}
setIsSelectId={props.setIsSelectId}
handleDeleteSign={props.handleDeleteSign}
handleDeleteWidget={props.handleDeleteWidget}
setIsPageCopy={props.setIsPageCopy}
handleTextSettingModal={props.handleTextSettingModal}
handleCellSettingModal={props.handleCellSettingModal}
@@ -172,16 +263,14 @@ function RenderPdf(props) {
isFreeResize={props.isSelfSign ? true : false}
isOpenSignPad={true}
assignedWidgetId={props.assignedWidgetId}
isApplyAll={true}
setCellCount={props.setCellCount}
setFontSize={props.setFontSize}
fontSize={props.fontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
setRequestSignTour={props.setRequestSignTour}
setIsReqSignTourDisabled={props.setIsReqSignTourDisabled}
calculateFontsize={calculateFontsize}
currWidgetsDetails={props?.currWidgetsDetails}
setTempSignerId={props.setTempSignerId}
/>
</React.Fragment>
)
@@ -218,6 +307,7 @@ function RenderPdf(props) {
<Alert type={"success"}>{t("success-email-alert")}</Alert>
)}
<RSC
ref={scrollRef}
style={{
position: "relative",
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
@@ -271,8 +361,8 @@ function RenderPdf(props) {
<Placeholder
pos={pos}
setIsPageCopy={props.setIsPageCopy}
handleDeleteSign={
props.handleDeleteSign
handleDeleteWidget={
props.handleDeleteWidget
}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
@@ -292,7 +382,6 @@ function RenderPdf(props) {
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
showGuidelines={handleGuideline}
isDragging={props.isDragging}
setIsValidate={props.setIsValidate}
setIsRadio={props.setIsRadio}
@@ -301,7 +390,6 @@ function RenderPdf(props) {
props.setCurrWidgetsDetails
}
handleNameModal={props.handleNameModal}
setTempSignerId={props.setTempSignerId}
uniqueId={props.uniqueId}
handleTextSettingModal={
props.handleTextSettingModal
@@ -328,6 +416,11 @@ function RenderPdf(props) {
currWidgetsDetails={
props?.currWidgetsDetails
}
setRoleName={props?.setRoleName}
pdfDetails={props.pdfDetails}
setIsReqSignTourDisabled={
props.setIsReqSignTourDisabled
}
/>
</React.Fragment>
))}
@@ -346,7 +439,9 @@ function RenderPdf(props) {
key={id}
pos={pos}
setIsPageCopy={props.setIsPageCopy}
handleDeleteSign={props.handleDeleteSign}
handleDeleteWidget={
props.handleDeleteWidget
}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
@@ -360,8 +455,6 @@ function RenderPdf(props) {
isSignYourself={true}
posWidth={posWidth}
posHeight={posHeight}
showGuidelines={handleGuideline}
pdfDetails={props.pdfDetails[0]}
isDragging={props.isDragging}
setIsCheckbox={props.setIsCheckbox}
setCurrWidgetsDetails={
@@ -388,6 +481,9 @@ function RenderPdf(props) {
currWidgetsDetails={
props?.currWidgetsDetails
}
setIsReqSignTourDisabled={
props.setIsReqSignTourDisabled
}
/>
)
)}
@@ -426,28 +522,12 @@ function RenderPdf(props) {
/>
</Document>
{guideline.show && (
<>
{/* top guide */}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: guideline.y1 }}
/>
{/* bottom guide */}
<div
className="absolute pointer-events-none z-[1000] left-0 w-full border-t-[1px] border-dashed border-[#3b82f6]"
style={{ top: guideline.y2 }}
/>
{/* left guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: guideline.x1 }}
/>
{/* right guide */}
<div
className="absolute pointer-events-none z-[1000] top-0 h-full border-l-[1px] border-dashed border-[#3b82f6]"
style={{ left: guideline.x2 }}
/>
</>
<Guidelines
x1={guideline.x1}
x2={guideline.x2}
y1={guideline.y1}
y2={guideline.y2}
/>
)}
</div>
</RSC>
@@ -1,16 +1,23 @@
import React from "react";
import RecipientList from "./RecipientList";
import { Tooltip } from "react-tooltip";
// import { Tooltip } from "react-tooltip";
import { useTranslation } from "react-i18next";
function SignerListPlace(props) {
const { t } = useTranslation();
const handleAddRecipient = () => {
props?.setIsAddSigner(true);
props.setIsTour && props.setIsTour(false);
};
return (
<div>
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
<span className="relative">
{props.title ? props.title : "Recipients"}
<span className="absolute text-xs z-[30] mt-1 ml-0.5">
<sup onClick={() => props.setIsTour && props.setIsTour(true)}>
<i className="ml-1 cursor-pointer fa-light fa-question rounded-full border-[1px] border-base-content text-[11px] py-[1px] px-[3px]"></i>
</sup>
{/* <span className="absolute text-xs z-[30] mt-1 ml-0.5">
{props?.title === "Roles" && (
<>
<a data-tooltip-id="my-tooltip">
@@ -30,7 +37,7 @@ function SignerListPlace(props) {
</Tooltip>
</>
)}
</span>
</span> */}
</span>
</div>
<div className="overflow-auto hide-scrollbar max-h-[180px]">
@@ -53,7 +60,7 @@ function SignerListPlace(props) {
data-tut="addRecipient"
className="op-btn op-btn-accent op-btn-outline w-full mt-[14px]"
disabled={props?.isMailSend ? true : false}
onClick={() => props.setIsAddSigner(true)}
onClick={handleAddRecipient}
>
<i className="fa-light fa-plus"></i> {t("add-recipients")}
</div>
@@ -4,7 +4,6 @@ import RecipientList from "./RecipientList";
import { useDrag } from "react-dnd";
import WidgetList from "./WidgetList";
import {
color,
isMobile,
radioButtonWidget,
textInputWidget,
@@ -88,12 +87,12 @@ function WidgetComponent(props) {
signature,
stamp,
initials,
textInput,
name,
jobTitle,
company,
date,
text,
textInput,
cells,
checkbox,
dropdown,
@@ -110,6 +109,7 @@ function WidgetComponent(props) {
// 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(
(data) =>
![
@@ -121,6 +121,7 @@ function WidgetComponent(props) {
"checkbox"
].includes(data.type)
);
//allow only (signature,stamp,initials,text) widget when isAllowModification true and user does not have session token
const unlogedInUserWidgets = widget.filter(
(data) =>
![
@@ -136,25 +137,37 @@ function WidgetComponent(props) {
"company"
].includes(data.type)
);
const filterWidgets = widget.filter(
const selfSignWidgets = widget.filter(
(data) =>
!["dropdown", radioButtonWidget, textInputWidget].includes(
data.type
)
!["dropdown", radioButtonWidget, textInputWidget].includes(data.type)
);
const textWidgetData = widget.filter((data) => data.type !== textWidget);
const updateWidgets = props.isSignYourself
? filterWidgets
: props.isTemplateFlow
? textWidgetData
: props.isAlllowModify
? userInformation
? modifiedWidgets
: unlogedInUserWidgets
: widget;
//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) =>
(props.isPrefillDropdown ? ["dropdown"] : [])
.concat([radioButtonWidget, textWidget, "date", "image", "checkbox"])
.includes(data.type)
);
//function to show widget on the base of conditionAdd commentMore actions
const handleWidgetType = () => {
if (props.isSignYourself) {
return selfSignWidgets;
} else if (props?.roleName === "prefill") {
return prefillAllowWidgets;
} else if (props.isAlllowModify) {
if (userInformation) {
return modifiedWidgets;
} else {
return unlogedInUserWidgets;
}
} else if (props?.roleName !== "prefill") {
return widget.filter((data) => ![textWidget].includes(data.type));
}
};
const handleSelectRecipient = () => {
if (
if (props?.roleName === "prefill") {
return "Prefill by owner";
} else if (
props.signersdata[props.isSelectListId]?.Email ||
props.signersdata[props.isSelectListId]?.Role
) {
@@ -180,9 +193,10 @@ function WidgetComponent(props) {
className="w-full op-select op-select-bordered pointer-events-none"
value={handleSelectRecipient()}
style={{
backgroundColor: props.blockColor
? props.blockColor
: color[0]
backgroundColor:
props.roleName === "prefill"
? "#edf6fc"
: props?.blockColor || "#edf6fc"
}}
>
<option value={handleSelectRecipient()}>
@@ -221,7 +235,7 @@ function WidgetComponent(props) {
>
<div className="flex whitespace-nowrap overflow-x-scroll pt-[10px] pb-[5px] pr-[5px]">
<WidgetList
updateWidgets={updateWidgets}
updateWidgets={handleWidgetType}
handleDivClick={props.handleDivClick}
handleMouseLeave={props.handleMouseLeave}
signRef={signRef}
@@ -240,12 +254,18 @@ function WidgetComponent(props) {
} hidden md:block h-full bg-base-100`}
>
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
<span>{t("fields")}</span>
<span>
{t("widgets")}
{props?.isSignYourself && (
<sup onClick={() => props.setIsTour && props.setIsTour(true)}>
<i className="ml-1 cursor-pointer fa-light fa-question rounded-full border-[1px] border-base-content text-[11px] py-[1px] px-[3px]"></i>
</sup>
)}
</span>
</div>
<div className="p-[15px] flex flex-col pt-4" data-tut="addWidgets">
<WidgetList
updateWidgets={updateWidgets}
updateWidgets={handleWidgetType}
handleDivClick={props.handleDivClick}
handleMouseLeave={props.handleMouseLeave}
signRef={signRef}
@@ -260,7 +280,7 @@ function WidgetComponent(props) {
isOpen={isSignersModal}
handleClose={handleModal}
>
{props.signersdata.length > 0 ? (
{props.signersdata.length > 0 || props.prefillSigner.length > 0 ? (
<div className="max-h-[600px] overflow-auto pb-1">
<RecipientList
signerPos={props.signerPos}
@@ -278,6 +298,7 @@ function WidgetComponent(props) {
setBlockColor={props.setBlockColor}
uniqueId={props.uniqueId}
setSignerPos={props.setSignerPos}
prefillSigner={props.prefillSigner}
/>
</div>
) : (
@@ -1,10 +1,11 @@
import React from "react";
import { getWidgetType, isMobile } from "../../constant/Utils";
import { isMobile } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
import getWidgetType from "./getWidgetType";
function WidgetList(props) {
const { t } = useTranslation();
return props.updateWidgets.map((item, ind) => {
const getWidgetList = props.updateWidgets();
return getWidgetList?.map((item, ind) => {
return (
<div className="2xl:p-1 mb-[5px]" key={ind}>
<div
@@ -31,15 +31,15 @@ const WidgetNameModal = (props) => {
const type = props.defaultdata?.type;
if (type === "signature") {
return "Draw signature";
return t("draw-signature");
} else if (type === "stamp" || type === "image") {
return `Upload ${type}`;
return type === "stamp" ? t("upload-stamp-image") : t("upload-image");
} else if (type === "initials") {
return "Draw initial";
return t("draw-initials");
} else if (type === textInputWidget) {
return "Enter text";
return t("enter-text");
} else {
return `Enter ${type}`;
return t("enter-widgettype", { widgetType: type });
}
};
useEffect(() => {
@@ -81,7 +81,7 @@ const WidgetNameModal = (props) => {
if (enabledSignTypes.length === 0) {
alert(t("at-least-one-signature-type"));
} else if (isDefaultSignTypeOnly) {
alert(t("expect-default-one-more-signature-type"));
alert(t("expect-default-one-signature-type"));
} else {
const data = { ...formdata, signatureType };
props.handleData(data, props.defaultdata?.type);
@@ -231,101 +231,105 @@ const WidgetNameModal = (props) => {
/>
</div>
)}
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) && (
<>
<div className="mb-[0.75rem]">
<label htmlFor="name" className="text-[13px]">
{t("default-value")}
</label>
<input
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="defaultValue"
value={formdata.defaultValue}
onChange={(e) => handledefaultChange(e)}
autoComplete="off"
maxLength={
props.defaultdata?.type === cellsWidget
? formdata.cellCount
: undefined
}
onBlur={() => {
if (isValid === false) {
setFormdata({ ...formdata, defaultValue: "" });
setIsValid(true);
{[textInputWidget, cellsWidget].includes(props.defaultdata?.type) &&
props?.roleName !== "prefill" && (
<>
<div className="mb-[0.75rem]">
<label htmlFor="name" className="text-[13px]">
{t("default-value")}
</label>
<input
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="defaultValue"
value={formdata.defaultValue}
onChange={(e) => handledefaultChange(e)}
autoComplete="off"
maxLength={
props.defaultdata?.type === cellsWidget
? formdata.cellCount
: undefined
}
}}
/>
{isValid === false && (
<div
className="warning defaultvalueWarning"
style={{ fontSize: 12 }}
>
<i
className="fa-light fa-exclamation-circle text-[15px]"
style={{ color: "#fab005" }}
></i>
{t("invalid-default-value")}
</div>
)}
</div>
</>
)}
{!["signature", "initials", textWidget].includes(
props.defaultdata?.type
) && (
<div className="mb-[0.75rem]">
<div className="flex flex-row gap-[10px] mb-[0.5rem]">
{statusArr.map((data, ind) => {
return (
onBlur={() => {
if (isValid === false) {
setFormdata({ ...formdata, defaultValue: "" });
setIsValid(true);
}
}}
/>
{isValid === false && (
<div
key={ind}
className="flex flex-row gap-[5px] items-center"
className="warning defaultvalueWarning"
style={{ fontSize: 12 }}
>
<i
className="fa-light fa-exclamation-circle text-[15px]"
style={{ color: "#fab005" }}
></i>
{t("invalid-default-value")}
</div>
)}
</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">
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
name="status"
id="isReadOnly"
name="isReadOnly"
type="checkbox"
checked={formdata.isReadOnly}
className="op-checkbox op-checkbox-xs"
onChange={() =>
setFormdata({ ...formdata, status: data.toLowerCase() })
}
checked={
formdata.status.toLowerCase() === data.toLowerCase()
setFormdata((prev) => ({
...formdata,
isReadOnly: !prev.isReadOnly
}))
}
/>
<div className="text-[13px] font-medium">
{t(`widget-status.${data}`)}
</div>
<label
className="ml-1.5 mb-0 capitalize text-[13px]"
htmlFor="isreadonly"
>
{t("read-only")}
</label>
</div>
);
})}
)}
</div>
{[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>
)}
)}
{["signature", "initials"].includes(props.defaultdata?.type) && (
<div className="mb-[0.75rem]">
<label htmlFor="signaturetype" className="text-[14px] mb-[0.7rem]">
@@ -354,7 +358,7 @@ const WidgetNameModal = (props) => {
</div>
</div>
)}
{props.defaultdata?.type !== textWidget && (
{props?.roleName !== "prefill" && (
<div className="mb-[0.75rem]">
<label htmlFor="hint" className="text-[13px]">
{t("hint")}
@@ -1,4 +1,4 @@
import React, { forwardRef, useEffect, useRef, useState } from "react";
import { forwardRef, useEffect, useRef, useState } from "react";
import ModalUi from "../../primitives/ModalUi";
import { useTranslation } from "react-i18next";
import { removeBackground } from "@imgly/background-removal";
@@ -19,32 +19,30 @@ import {
textWidget,
years,
convertTextToImg,
convertJpegToPng
convertJpegToPng,
convertBase64ToFile,
generatePdfName
} from "../../constant/Utils";
import CellsWidget from "./CellsWidget";
import DatePicker from "react-datepicker";
import "react-datepicker/dist/react-datepicker.css";
import SignatureCanvas from "react-signature-canvas";
import Parse from "parse";
import {
generateTitleFromFilename,
getBase64FromUrl,
getSecureUrl
} from "../../constant/Utils";
import sanitizeFileName from "../../primitives/sanitizeFileName";
import { SaveFileSize } from "../../constant/saveFileSize";
import moment from "moment";
import {
setSaveSignCheckbox,
setDefaultSignImg,
setIsShowModal,
setMyInitial,
setLastIndex
setLastIndex,
setScrollTriggerId,
setPrefillImg,
setPrefillImgLoad
} from "../../redux/reducers/widgetSlice";
import { useDispatch, useSelector } from "react-redux";
import Loader from "../../primitives/Loader";
import { emailRegex } from "../../constant/const";
import RegexParser from "regex-parser";
import { saveToMySign } from "../../utils/widgetUtils";
//function to get default format
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
@@ -76,9 +74,8 @@ const isTabCls = "bg-[#002864] text-white rounded-[15px] px-[10px] py-[4px]";
function WidgetsValueModal(props) {
const dispatch = useDispatch();
const saveSignCheckbox = useSelector(
(state) => state.widget.saveSignCheckbox
);
const prefillImg = useSelector((state) => state.widget.prefillImg);
const mysign = useSelector((state) => state.widget.saveSignCheckbox);
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
const myInitial = useSelector((state) => state.widget.myInitial);
const lastWidget = useSelector((state) => state.widget.lastIndex);
@@ -92,8 +89,6 @@ function WidgetsValueModal(props) {
currWidgetsDetails,
setXyPosition,
isSave,
setUniqueId,
tempSignerId,
signatureTypes,
setCellCount,
allowCellResize = true
@@ -124,6 +119,8 @@ function WidgetsValueModal(props) {
const senderUser = localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const kiosk_signer =
props?.kiosk_signer || JSON.parse(localStorage.getItem("kiosk_signer"));
const jsonSender = senderUser && JSON.parse(senderUser);
const currentUserName = jsonSender && jsonSender?.name;
const widgetTypeTranslation = t(`widgets-name.${currWidgetsDetails?.type}`);
@@ -140,7 +137,12 @@ function WidgetsValueModal(props) {
"";
return Array.from({ length: count }, (_, i) => val[i] || "");
});
const isSignOrInitials = ["signature", "initials"].includes(
currWidgetsDetails?.type
);
const isImageOrStamp = ["image", "stamp"].includes(currWidgetsDetails?.type);
const cellRefs = useRef([]);
const widgetRef = useRef(null);
// keep track of the first empty cell to automatically focus it after updates
useEffect(() => {
const index = cellsValue.findIndex((v) => !v);
@@ -173,6 +175,18 @@ function WidgetsValueModal(props) {
)
: new Date()
);
useEffect(() => {
dispatch(setScrollTriggerId(currWidgetsDetails?.key));
return () => dispatch(setScrollTriggerId());
}, []);
// below useEffect is used to focus text widgets when user open modal
useEffect(() => {
if (widgetRef?.current) {
const clearFocus = setTimeout(() => widgetRef?.current.focus(), 10);
return () => clearTimeout(clearFocus);
}
}, [widgetRef.current]);
useEffect(() => {
if (
@@ -190,7 +204,7 @@ function WidgetsValueModal(props) {
} else {
setHint(currWidgetsDetails?.type);
}
} else if (["signature", "initials"].includes(currWidgetsDetails?.type)) {
} else if (isSignOrInitials) {
setIsTab(currWidgetsDetails?.signatureType);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -234,7 +248,7 @@ function WidgetsValueModal(props) {
//`onChangeInput` is used to save data related to date in a placeholder field
onChangeInput(
date,
currWidgetsDetails?.key,
currWidgetsDetails,
xyPosition,
props.index,
setXyPosition,
@@ -251,7 +265,7 @@ function WidgetsValueModal(props) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [signatureTypes]);
//function for image upload or update
const onImageChange = (event) => {
const onImageChange = async (event) => {
if (event.target.files && event.target.files[0]) {
const file = event.target.files[0];
setOriginalImage(null); // Reset original image when a new file is selected
@@ -260,11 +274,42 @@ function WidgetsValueModal(props) {
compressedFileSize(file, setImage);
}
};
const handleSavePrefillImg = async () => {
setIsLoader(true);
try {
dispatch(
setPrefillImg({
id: currWidgetsDetails?.key,
base64: image?.src
})
);
const imageName = generatePdfName(16);
const imageUrl = await convertBase64ToFile(
imageName,
image.src,
image.imgType
);
setIsLoader(false);
if (imageUrl) {
return imageUrl;
}
} catch (e) {
console.log("error in handleSavePrefillImg function ", e);
}
};
//function is used to save image,stamp widgets data
const handleSaveImage = (signatureType) => {
const handleSaveImage = async (signatureType) => {
const widgetsType = currWidgetsDetails?.type;
//`isApplyAll` is used when user edit stamp then updated signature apply all existing drawn signatures
const isApplyAll = true;
let imgUrl;
const isPrefill = xyPosition.some(
(x) => x.Id === uniqueId && x?.Role === "prefill"
);
if (isPrefill) {
dispatch(setPrefillImgLoad({ [currWidgetsDetails?.key]: true }));
imgUrl = await handleSavePrefillImg();
}
//check condition signyour-self or other flow
if (uniqueId) {
setXyPosition((prev) =>
@@ -275,7 +320,6 @@ function WidgetsValueModal(props) {
const index = signer.placeHolder.findIndex(
(x) => x.pageNumber === pageNumber
);
// Get updated placeholder list
const updatedPlaceholders = onSaveImage(
signatureType,
@@ -285,7 +329,8 @@ function WidgetsValueModal(props) {
image,
isAutoSign,
widgetsType,
isApplyAll
isApplyAll,
isPrefill && imgUrl
);
return {
@@ -294,6 +339,7 @@ function WidgetsValueModal(props) {
};
})
);
dispatch(setPrefillImgLoad({}));
} else {
const index = props?.xyPosition?.findIndex((object) => {
return object.pageNumber === pageNumber;
@@ -379,7 +425,10 @@ function WidgetsValueModal(props) {
isTypeText,
typedSignature,
false,
widgetsType
widgetsType,
false,
fontSelect,
penColor
);
setXyPosition(getUpdatePosition);
}
@@ -388,47 +437,49 @@ function WidgetsValueModal(props) {
//function to handle allowed tab in signature pad
function handleTab() {
const signtypes = signatureTypes || [];
const defaultIndex = signtypes?.findIndex(
(x) =>
x.name === "default" &&
x.enabled === true &&
defaultSignImg &&
currWidgetsDetails?.type !== "image" &&
currWidgetsDetails?.type !== "stamp"
);
const getIndex =
defaultIndex !== -1 // Check if the default index exists
? defaultIndex // If found, use it
: signtypes?.findIndex((x) => x.enabled === true);
if (["signature", "initials"].includes(currWidgetsDetails.type)) {
const signtypes = signatureTypes || [];
const defaultIndex = signtypes?.findIndex(
(x) =>
x.name === "default" &&
x.enabled === true &&
defaultSignImg &&
currWidgetsDetails?.type !== "image" &&
currWidgetsDetails?.type !== "stamp"
);
const getIndex =
defaultIndex !== -1 // Check if the default index exists
? defaultIndex // If found, use it
: signtypes?.findIndex((x) => x.enabled === true);
if (getIndex !== -1) {
setIsSignTypes(true);
const tab = signatureTypes?.[getIndex].name;
if (currWidgetsDetails?.signatureType === "type") {
setIsTab("type");
} else if (tab === "draw") {
setIsTab("draw");
} else if (tab === "upload") {
setIsImageSelect(true);
setIsTab("uploadImage");
} else if (tab === "typed") {
setIsTab("type");
} else if (tab === "default") {
if (
(currWidgetsDetails?.type === "initials" && myInitial) ||
(currWidgetsDetails?.type !== "initials" && defaultSignImg)
) {
setIsDefaultSign(true);
setIsTab("mysignature");
} else {
if (getIndex !== -1) {
setIsSignTypes(true);
const tab = signatureTypes?.[getIndex].name;
if (currWidgetsDetails?.signatureType === "type") {
setIsTab("type");
} else if (tab === "draw") {
setIsTab("draw");
} else if (tab === "upload") {
setIsImageSelect(true);
setIsTab("uploadImage");
} else if (tab === "typed") {
setIsTab("type");
} else if (tab === "default") {
if (
(currWidgetsDetails?.type === "initials" && myInitial) ||
(currWidgetsDetails?.type !== "initials" && defaultSignImg)
) {
setIsDefaultSign(true);
setIsTab("mysignature");
} else {
setIsTab("draw");
}
} else {
setIsTab(true);
}
} else {
setIsTab(true);
setIsSignTypes(false);
}
} else {
setIsSignTypes(false);
}
}
function isTabEnabled(tabName) {
@@ -463,7 +514,7 @@ function WidgetsValueModal(props) {
setWidgetValue(combined);
onChangeInput(
combined,
currWidgetsDetails?.key,
currWidgetsDetails,
xyPosition,
props.index,
setXyPosition,
@@ -473,7 +524,7 @@ function WidgetsValueModal(props) {
setWidgetValue("");
onChangeInput(
"",
currWidgetsDetails?.key,
currWidgetsDetails,
xyPosition,
props.index,
setXyPosition,
@@ -486,126 +537,40 @@ function WidgetsValueModal(props) {
const handleSignatureChange = (data) => {
setSignature(data);
};
function base64StringtoFile(base64String, filename) {
let arr = base64String.split(","),
// type of uploaded image
mime = arr[0].match(/:(.*?);/)[1],
// decode base64
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
const ext = mime.split("/").pop();
const name = `${filename}.${ext}`;
return new File([u8arr], name, { type: mime });
}
const uploadFile = async (file) => {
try {
const parseFile = new Parse.File(file.name, file);
const response = await parseFile.save();
if (response?.url()) {
const fileRes = await getSecureUrl(response.url());
if (fileRes.url) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(file.size, fileRes.url, tenantId);
return fileRes?.url;
} else {
alert(`${t("something-went-wrong-mssg")}`);
return false;
}
} else {
alert(`${t("something-went-wrong-mssg")}`);
return false;
}
} catch (err) {
console.log("sign upload err", err);
alert(`${err.message}`);
return false;
}
};
// `handlesavesign` is used to save signature, initials, stamp as a default
const handleSaveSign = async () => {
// `handleSaveToMySign` is used to save signature, initials, stamp as a default
const handleSaveToMySign = async () => {
if (signature || image?.src) {
setIsLoader(true);
try {
const User = Parse?.User?.current();
const sanitizename = generateTitleFromFilename(User?.get("name"));
const replaceSpace = sanitizeFileName(sanitizename);
let file;
if (signature) {
file = base64StringtoFile(signature, `${replaceSpace}__sign`);
} else {
file = base64StringtoFile(image?.src, `${replaceSpace}__sign`);
const mySignRes = await saveToMySign({
type: currWidgetsDetails?.type,
base64: signature || image?.src,
defaultSignId: mysign?.signId
});
if (mysign?.signId) {
dispatch(setSaveSignCheckbox({ ...mysign, signId: mySignRes?.id }));
}
const imageUrl = await uploadFile(file);
const userId = {
__type: "Pointer",
className: "_User",
objectId: User?.id
};
if (imageUrl) {
// below code is used to save or update default signature, initials, stamp
try {
const signCls = new Parse.Object("contracts_Signature");
if (saveSignCheckbox?.signId) {
signCls.id = saveSignCheckbox.signId;
}
if (currWidgetsDetails?.type === "initials") {
signCls.set("Initials", imageUrl);
} else if (currWidgetsDetails?.type === "signature") {
signCls.set("ImageURL", imageUrl);
}
signCls.set("UserId", userId);
const signRes = await signCls.save();
if (signRes) {
// props.saveSignCheckbox.signId;
dispatch(
setSaveSignCheckbox({
...saveSignCheckbox,
signId: signRes?.id
})
);
const _signRes = JSON.parse(JSON.stringify(signRes));
if (currWidgetsDetails?.type === "signature") {
const defaultSign = await getBase64FromUrl(
_signRes?.ImageURL,
true
);
dispatch(setDefaultSignImg(defaultSign));
} else if (currWidgetsDetails?.type === "initials") {
const defaultInitials = await getBase64FromUrl(
_signRes?.Initials,
true
);
dispatch(setMyInitial(defaultInitials));
}
alert(t("saved-successfully"));
}
return signRes;
} catch (err) {
console.log(err);
alert(`${err.message}`);
} finally {
setIsLoader(false);
}
if (currWidgetsDetails?.type === "signature") {
dispatch(setDefaultSignImg(mySignRes.base64File));
} else if (currWidgetsDetails?.type === "initials") {
dispatch(setMyInitial(mySignRes.base64File));
}
} catch (err) {
console.log("Err while saving signature", err);
alert(t("saved-successfully"));
} catch (error) {
console.log("error while save to my sign", error?.message);
alert(`${error.message}`);
} finally {
setIsLoader(false);
}
}
};
const handleSaveBtn = async () => {
// if User checked save signature or save initials checkbox
if (accesstoken && isSavedSign) {
await handleSaveSign();
resetToDefault();
} else {
resetToDefault();
await handleSaveToMySign();
}
resetToDefault();
};
const resetToDefault = () => {
props?.setCurrWidgetsDetails({});
@@ -681,8 +646,8 @@ function WidgetsValueModal(props) {
useEffect(() => {
if (currWidgetsDetails?.options?.response) {
const url = currWidgetsDetails?.options?.response;
if (["signature", "initials"].includes(currWidgetsDetails?.type)) {
let url = currWidgetsDetails?.options?.response;
if (isSignOrInitials) {
if (isTab === "draw" && currWidgetsDetails?.signatureType === "draw") {
setSignature(url);
// Load the default signature after the component mounts
@@ -692,7 +657,16 @@ function WidgetsValueModal(props) {
} else if (isTab === "uploadImage" && currWidgetsDetails?.ImageType) {
setImage({ imgType: currWidgetsDetails?.ImageType, src: url });
}
} else if (["image", "stamp"].includes(currWidgetsDetails?.type)) {
} else if (isImageOrStamp) {
const isPrefill = xyPosition.some(
(x) => x.Id === uniqueId && x?.Role === "prefill"
);
if (isPrefill) {
const getPrefillImg = prefillImg?.find(
(x) => x.id === currWidgetsDetails?.key
);
url = getPrefillImg?.base64;
}
setImage({ imgType: currWidgetsDetails?.ImageType, src: url });
}
}
@@ -700,9 +674,15 @@ function WidgetsValueModal(props) {
//trim user name or typed name value to show in initial signature
const trimmedName = typedSignature
? typedSignature?.trim()
: currentUserName?.trim();
: props?.journey === "kiosk-signing" && kiosk_signer
? kiosk_signer[0]?.Name?.trim()
: currentUserName?.trim();
//get full name of user
const fullUserName = typedSignature || currentUserName;
const fullUserName =
typedSignature ||
(props?.journey === "kiosk-signing" && kiosk_signer
? kiosk_signer[0]?.Name
: currentUserName);
const firstCharacter = trimmedName?.charAt(0);
const userName =
currWidgetsDetails?.type === "initials" ? firstCharacter : fullUserName;
@@ -728,15 +708,17 @@ function WidgetsValueModal(props) {
return (
<div className="flex flex-row items-center m-[5px] gap-3">
<span className="text-base-content">{t("options")}</span>
{allColor.map((data, key) => {
{allColor.map((color) => {
const borderColor = penColor === color ? color : "white";
return (
<i
key={key}
key={color}
onClick={() => {
convertToImg(fontSelect, typedSignature, data);
setPenColor(allColor[key]);
convertToImg(fontSelect, typedSignature, color);
setPenColor(color);
}}
className={`border-b-[2px] cursor-pointer ${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`}
style={{ borderBottom: `2px solid ${borderColor}` }}
className={`border-b-[2px] cursor-pointer text-[${color}] text-[16px] fa-light fa-pen-nib`}
></i>
);
})}
@@ -746,11 +728,7 @@ function WidgetsValueModal(props) {
// Effect to store original image and apply initial BG removal if enabled
useEffect(() => {
if (
image?.src &&
!originalImage &&
(isImageSelect || ["image", "stamp"].includes(currWidgetsDetails?.type))
) {
if (image?.src && !originalImage && (isImageSelect || isImageOrStamp)) {
// If there's an image, and we haven't stored an original for it yet,
// and we are in a relevant tab/widget type for uploads
setOriginalImage(image); // Store the current image as original
@@ -761,7 +739,7 @@ function WidgetsValueModal(props) {
.then((blob) => {
const reader = new FileReader();
reader.onloadend = () => {
setImage({ src: reader.result, imgType: blob.type });
// setImage({ src: reader.result, imgType: blob.type });
};
reader.onerror = () => {
console.error(
@@ -791,10 +769,7 @@ function WidgetsValueModal(props) {
// Effect for toggling BG removal ON/OFF
useEffect(() => {
if (
!originalImage?.src ||
!(isImageSelect || ["image", "stamp"].includes(currWidgetsDetails?.type))
) {
if (!originalImage?.src || !(isImageSelect || isImageOrStamp)) {
// No original image to process or not in a relevant upload state
return;
}
@@ -866,7 +841,7 @@ function WidgetsValueModal(props) {
}
onChangeInput(
checkedList ? checkedList : updateSelectedCheckbox,
currWidgetsDetails?.key,
currWidgetsDetails,
xyPosition,
props.index,
setXyPosition,
@@ -884,7 +859,7 @@ function WidgetsValueModal(props) {
setWidgetValue(data);
onChangeInput(
data,
currWidgetsDetails?.key,
currWidgetsDetails,
xyPosition,
props.index,
setXyPosition,
@@ -916,7 +891,7 @@ function WidgetsValueModal(props) {
setWidgetValue(e.target.value);
onChangeInput(
e.target.value,
currWidgetsDetails?.key,
currWidgetsDetails,
xyPosition,
props.index,
setXyPosition,
@@ -934,14 +909,13 @@ function WidgetsValueModal(props) {
);
onChangeInput(
combined,
currWidgetsDetails?.key,
currWidgetsDetails,
xyPosition,
props.index,
setXyPosition,
uniqueId
);
};
const handleCellsInput = (e, idx) => {
setIsShowValidation(false);
const val = e.target.value.slice(0, 1);
@@ -1024,7 +998,7 @@ function WidgetsValueModal(props) {
}}
className={`${
isTab === "mysignature" && `${isTabCls}`
} ml-[2px] cursor-pointer`}
} ml-[2px] cursor-pointer`}
>
{t("my-initials")}
</span>
@@ -1043,7 +1017,7 @@ function WidgetsValueModal(props) {
}}
className={`${
isTab === "draw" && `${isTabCls}`
} ml-[2px] cursor-pointer`}
} ml-[2px] cursor-pointer`}
>
{t("draw")}
</span>
@@ -1055,11 +1029,12 @@ function WidgetsValueModal(props) {
onClick={() => {
setIsDefaultSign(false);
setIsImageSelect(true);
setTypedSignature("");
setIsTab("uploadImage");
}}
className={`${
isTab === "uploadImage" && `${isTabCls}`
} ml-[2px] cursor-pointer`}
} ml-[2px] cursor-pointer`}
>
{t("upload-image")}
</span>
@@ -1193,34 +1168,31 @@ function WidgetsValueModal(props) {
</div>
{/* Shared container for autoSignAll and removeBackground when image is displayed for upload/stamp/image */}
{(uniqueId ||
(image &&
(isImageSelect ||
["image", "stamp"].includes(
currWidgetsDetails?.type
)))) && (
<div className="flex justify-center items-center gap-x-2 my-2">
(image && (isImageSelect || isImageOrStamp))) && (
<div className="flex flex-wrap flex-row sm:justify-center items-center gap-x-3 my-2">
{uniqueId && autoSignAll}
{image &&
(isImageSelect ||
["image", "stamp"].includes(
currWidgetsDetails?.type
)) && (
<label
htmlFor={`removeBgToggleModal-${currWidgetsDetails?.key}`}
className="mb-0 cursor-pointer flex items-center text-sm"
>
<input
type="checkbox"
id={`removeBgToggleModal-${currWidgetsDetails?.key}`} // Unique ID
className="mr-2 op-checkbox op-checkbox-xs md:op-checkbox-sm"
checked={removeBgEnabled}
onChange={() =>
setRemoveBgEnabled(!removeBgEnabled)
}
/>
{t("remove-background")}
</label>
)}
{image && (isImageSelect || isImageOrStamp) && (
<label
htmlFor={`removeBgToggleModal-${currWidgetsDetails?.key}`}
className="mb-0 cursor-pointer flex items-center text-sm"
>
<input
type="checkbox"
id={`removeBgToggleModal-${currWidgetsDetails?.key}`} // Unique ID
className="mr-2 op-checkbox op-checkbox-xs md:op-checkbox-sm"
checked={removeBgEnabled}
onChange={() =>
setRemoveBgEnabled(!removeBgEnabled)
}
/>
{t("remove-background")}
</label>
)}
{accesstoken && isSignOrInitials && (
<div className="flex justify-start my-1">
{mysign?.isVisible && savesigncheckbox}
</div>
)}
</div>
)}
<div className="flex justify-end"></div>
@@ -1250,7 +1222,9 @@ function WidgetsValueModal(props) {
value={typedSignature}
onChange={(e) => {
setTypedSignature(e.target.value);
convertToImg(fontSelect, e.target.value);
if (e.target.value?.trim()?.length > 0) {
convertToImg(fontSelect, e.target.value);
}
}}
/>
</div>
@@ -1296,7 +1270,7 @@ function WidgetsValueModal(props) {
)}
{accesstoken && (
<div className="flex justify-start my-1">
{saveSignCheckbox?.isVisible && savesigncheckbox}
{mysign?.isVisible && savesigncheckbox}
</div>
)}
</div>
@@ -1331,7 +1305,7 @@ function WidgetsValueModal(props) {
)}
{accesstoken && (
<div className="flex justify-start my-1">
{saveSignCheckbox?.isVisible && savesigncheckbox}
{mysign?.isVisible && savesigncheckbox}
</div>
)}
</div>
@@ -1364,8 +1338,8 @@ function WidgetsValueModal(props) {
{currWidgetsDetails?.options?.values?.map((data, ind) => (
<div key={ind} className="text-base-content select-none-cls">
<label
htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`}
className="text-xs flex items-center gap-1"
// htmlFor={`checkbox-${currWidgetsDetails?.key + ind}`}
className="text-xs flex items-center gap-1 cursor-pointer"
>
<input
id={`checkbox-${currWidgetsDetails?.key + ind}`}
@@ -1405,6 +1379,7 @@ function WidgetsValueModal(props) {
case textInputWidget:
return (
<input
ref={widgetRef}
placeholder={hint || t("widgets-name.text")}
value={widgetValue ?? ""} // ensures it's always a string
onBlur={handleInputBlur}
@@ -1432,7 +1407,7 @@ function WidgetsValueModal(props) {
case "dropdown":
return (
<select
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-base-content w-full text-xs"
id="myDropdown"
value={widgetValue}
onChange={(e) => handleOnchangeTextBox(e)}
@@ -1456,6 +1431,7 @@ function WidgetsValueModal(props) {
case "name":
return (
<input
ref={widgetRef}
type="text"
placeholder={hint || widgetTypeTranslation}
value={widgetValue}
@@ -1467,6 +1443,7 @@ function WidgetsValueModal(props) {
case "company":
return (
<input
ref={widgetRef}
placeholder={hint || widgetTypeTranslation}
value={widgetValue}
type="text"
@@ -1478,6 +1455,7 @@ function WidgetsValueModal(props) {
case "job title":
return (
<input
ref={widgetRef}
type="text"
onBlur={handleInputBlur}
placeholder={hint || widgetTypeTranslation}
@@ -1540,6 +1518,7 @@ function WidgetsValueModal(props) {
return (
<>
<input
ref={widgetRef}
type="email"
onBlur={handleInputBlur}
placeholder={hint || widgetTypeTranslation}
@@ -1565,7 +1544,7 @@ function WidgetsValueModal(props) {
{currWidgetsDetails?.options?.values.map((data, ind) => (
<div key={ind} className="text-base-content select-none-cls">
<label
htmlFor={`radio-${currWidgetsDetails?.key + ind}`}
// htmlFor={`radio-${currWidgetsDetails?.key + ind}`}
className="cursor-pointer flex items-center text-sm gap-1"
>
<input
@@ -1587,6 +1566,7 @@ function WidgetsValueModal(props) {
case textWidget:
return (
<input
ref={widgetRef}
type="text"
placeholder={t("widgets-name.text")}
value={widgetValue ?? ""} //use an empty string "" as a default
@@ -1721,7 +1701,8 @@ function WidgetsValueModal(props) {
break;
}
};
// console.log("currwidgetdetails ", currWidgetsDetails )
// focusNextWidget(currWidgetsDetails?.key, xyPosition)
//function too use on click on next/finish button then update modal UI according to current widgets
const handleClickOnNext = (isFinishDoc) => {
if (
@@ -1792,20 +1773,27 @@ function WidgetsValueModal(props) {
currWidgetsDetails?.type
)
) {
if (
((currWidgetsDetails?.type === "stamp" ||
currWidgetsDetails?.type === "image") &&
image) ||
(isTab === "draw" && signature) ||
(isTab === "uploadImage" && (signature || image)) ||
(isTab === "image" && image) ||
(isTab === "mysignature" && defaultSignImg) ||
(isTab === "type" && typedSignature)
) {
return false;
} else {
return true;
const { type } = currWidgetsDetails || {};
//conditions based on widget type or active tab
const conditions = {
stamp: !image, // Stamp requires an image
image: !image, // Image widget also requires an image
draw: !signature, // Draw tab requires a drawn signature
uploadImage: !image, // Upload tab requires an image
mysignature: !defaultSignImg, // My Signature tab requires a saved/default signature
type: !(typedSignature?.trim()?.length > 0) // Type tab requires non-empty typed signature
};
// First check conditions based on widget type
if (type && conditions.hasOwnProperty(type)) {
return conditions[type];
}
//If type doesn't match, check based on active tab
if (isTab && conditions.hasOwnProperty(isTab)) {
return conditions[isTab];
}
return true; // default
} else if (
currWidgetsDetails?.type === "checkbox" &&
selectedCheckbox?.length > 0
@@ -1828,9 +1816,6 @@ function WidgetsValueModal(props) {
}
dispatch(setIsShowModal({}));
dispatch(setLastIndex(""));
if (currWidgetsDetails?.type === textWidget && uniqueId) {
setUniqueId(tempSignerId);
}
};
//function is used to execute the finish button functionality
const handleFinish = () => {
@@ -1894,6 +1879,11 @@ function WidgetsValueModal(props) {
position="bottom"
>
<div className="h-[100%] p-[18px]">
{isLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
{isFinish ? (
<>
<div className="p-1 mt-3">
@@ -1956,7 +1946,7 @@ function WidgetsValueModal(props) {
) : (
<button
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-1 cursor-default"
className="op-btn op-btn-ghost text-base-content op-btn-sm mr-1 cursor-default"
></button>
)}
<div className="flex items-center gap-2">
@@ -0,0 +1,21 @@
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 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">
{widgetName}
</span>
</div>
<div className="text-[20px] op-btn op-btn-primary rounded-none w-[40px] h-full flex justify-center items-center">
<i className={item.icon}></i>
</div>
</div>
</div>
);
};
export default getWidgetType;
@@ -1,5 +1,5 @@
import React, { useState } from "react";
import { formatDateTime } from "../../../constant/Utils";
import { formatDateTime } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
const DateFormatSelector = (props) => {
@@ -17,7 +17,9 @@ const DateFormatSelector = (props) => {
"YYYY-MM-DD",
"MM-DD-YYYY",
"MM.DD.YYYY",
"MMM DD, YYYY"
"MMM DD, YYYY",
"DD.MM.YYYY",
"DD/MM/YYYY"
];
// Handle format change
@@ -0,0 +1,99 @@
import { useState, useEffect, useMemo } from "react";
import Parse from "parse";
import { buildDownloadFilename } from "../../utils";
import { useTranslation } from "react-i18next";
import { Tooltip as ReactTooltip } from "react-tooltip";
/**
* Enum-like list of supported filename format IDs and their labels
* Keep IDs stable; you can freely change labels for UX.
*/
const FILENAME_FORMATS = [
{ id: "DOCNAME", label: "document Name.pdf" },
{ id: "DOCNAME_SIGNED", label: "document Name - Signed.pdf" },
{ id: "DOCNAME_EMAIL", label: "document Name - name@domain.com.pdf" },
{
id: "DOCNAME_EMAIL_DATE",
label: "document Name - name@domain.com - date.pdf"
}
];
const FilenameFormatSelector = ({ fileNameFormat, setFileNameFormat }) => {
const { t } = useTranslation();
const sampleDocName = "Agreement";
const [value, setValue] = useState(fileNameFormat);
const [error, setError] = useState("");
const currentUser = Parse?.User?.current();
const email = currentUser?.get("email") || "user@example.com";
// Load preference from contracts_User
useEffect(() => {
(async () => {
try {
if (!currentUser) return;
const rec = await Parse.Cloud.run("getUserDetails");
if (rec) {
const fmt = rec.get("DownloadFilenameFormat");
if (fmt) setValue(fmt);
}
} catch (e) {
console.error("Load filename pref failed", e);
setError(e?.message || String(e));
}
})();
}, [currentUser]);
const preview = useMemo(() => {
return buildDownloadFilename(value, {
docName: sampleDocName,
email,
isSigned: true // preview with signed true for that option
});
}, [value, email, sampleDocName]);
async function savePreference(nextValue) {
setFileNameFormat(nextValue);
}
return (
<div className="max-w-[400px] pr-[20px]">
<label className="text-[14px] mb-[0.7rem] font-medium">
{t("document-download-filename-format")}
<span className="text-sm">
<a data-tooltip-id="filename-tooltip" className="ml-1" href="/">
<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="filename-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p>{t("download-filename-format-help")}</p>
</div>
</ReactTooltip>
</span>
</label>
<select
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full h-full text-[11px]"
value={value}
onChange={async (e) => {
const v = e.target.value;
setValue(v);
await savePreference(v);
}}
>
{FILENAME_FORMATS.map((opt) => (
<option key={opt.id} value={opt.id}>
{opt.label}
</option>
))}
</select>
<div className="mt-2 text-xs opacity-80">
{t("preview")}
<span className="font-medium">{preview}</span>
</div>
{error && <div className="mt-2 text-xs text-red-600">{error}</div>}
</div>
);
};
export default FilenameFormatSelector;
@@ -197,7 +197,7 @@ const FolderModal = (props) => {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-secondary"
className="w-[1.4rem] h-[1.4rem] fill-current"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
@@ -183,7 +183,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[40px] h-[40px] fill-current op-text-secondary"
className="w-[40px] h-[40px] fill-current"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
@@ -262,7 +262,7 @@ const SelectFolder = ({ required, onSuccess, folderCls, isReset }) => {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
className="w-[1.4rem] h-[1.4rem] fill-current op-text-secondary"
className="w-[1.4rem] h-[1.4rem] fill-current"
>
<path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z" />
</svg>
@@ -63,6 +63,10 @@ const SelectSigners = (props) => {
if (props.closePopup) {
props.closePopup();
}
} else if (selected?.value) {
if (props.closePopup) {
props.closePopup();
}
} else {
setIsError(true);
setTimeout(() => setIsError(false), 1000);
@@ -217,6 +217,7 @@ const SignersInput = (props) => {
{isModal && (
<AddContact
isDisableTitle
isAddYourSelfCheckbox={props?.isAddYourSelfCheckbox}
details={handleNewDetails}
closePopup={handleModalCloseClick}
/>
@@ -5,9 +5,15 @@ import SocialMedia from "../SocialMedia";
import dp from "../../assets/images/dp.png";
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";
const Sidebar = ({ isOpen, closeSidebar }) => {
const Sidebar = () => {
const { width } = useWindowSize();
const navigate = useNavigate();
const dispatch = useDispatch();
const isOpen = useSelector((state) => state.sidebar.isOpen);
const [menuList, setmenuList] = useState([]);
const [submenuOpen, setSubmenuOpen] = useState(false);
const username = localStorage.getItem("username");
@@ -22,6 +28,12 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
}
}, []);
const closeSidebar = () => {
if (width <= 1023) {
dispatch(toggleSidebar(false));
}
};
const menuItem = async () => {
try {
if (localStorage.getItem("defaultmenuid")) {
@@ -59,8 +71,8 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
};
return (
<aside
className={`absolute lg:relative bg-base-100 h-screen overflow-y-auto transition-all z-[500] shadow-lg hide-scrollbar
${isOpen ? "w-full md:w-[300px]" : "w-0"}`}
className={`absolute max-lg:min-h-screen lg:relative bg-base-100 overflow-y-auto transition-all z-[500] shadow-lg hide-scrollbar
${isOpen ? "w-full md:w-64" : "w-0"}`}
>
<div className="flex px-2 py-3 gap-2 items-center shadow-md">
<div
@@ -119,7 +131,7 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
)}
</ul>
</nav>
<footer className="mt-4 flex justify-center items-center text-[25px] text-base-content gap-3">
<footer className="my-3 flex justify-center items-center text-[25px] text-base-content gap-3">
<SocialMedia />
</footer>
</aside>
File diff suppressed because it is too large Load Diff
+10 -4
View File
@@ -4,13 +4,18 @@ const parseAppId = process.env.REACT_APP_APPID
? process.env.REACT_APP_APPID
: "opensign";
const serverUrl = serverUrl_fn();
export const SaveFileSize = async (size, imageUrl, tenantId) => {
export const SaveFileSize = async (size, imageUrl, tenantId, userId) => {
//checking server url and save file's size
const tenantPtr = {
__type: "Pointer",
className: "partners_Tenant",
objectId: tenantId
};
const UserPtr = userId && {
__type: "Pointer",
className: "_User",
objectId: userId
};
const _tenantPtr = JSON.stringify(tenantPtr);
try {
const res = await axios.get(
@@ -53,15 +58,16 @@ export const SaveFileSize = async (size, imageUrl, tenantId) => {
} catch (err) {
console.log("err in save usage", err);
}
saveDataFile(size, imageUrl, tenantPtr);
saveDataFile(size, imageUrl, tenantPtr, UserPtr);
};
//function for save fileUrl and file size in particular client db class partners_DataFiles
const saveDataFile = async (size, imageUrl, tenantPtr) => {
const saveDataFile = async (size, imageUrl, tenantPtr, UserId) => {
const data = {
FileUrl: imageUrl,
FileSize: size,
TenantPtr: tenantPtr
TenantPtr: tenantPtr,
...(UserId ? { UserId: UserId } : {})
};
// console.log("data save",file, data)
@@ -0,0 +1,43 @@
import { createContext, useContext, useState, useCallback } from "react";
const GuidelinesContext = createContext();
export const GuidelinesProvider = ({ children }) => {
const [guideline, setGuideline] = useState({
show: false,
x1: 0,
x2: 0,
y1: 0,
y2: 0
});
const showGuidelines = useCallback((show, x, y, width, height) => {
if (show) {
setGuideline({
show: true,
x1: x,
x2: x + width - 1.5,
y1: y,
y2: y + height - 1
});
} else {
setGuideline((prev) => ({ ...prev, show: false }));
}
}, []);
return (
<GuidelinesContext.Provider value={{ guideline, showGuidelines }}>
{children}
</GuidelinesContext.Provider>
);
};
export const useGuidelinesContext = () => {
const context = useContext(GuidelinesContext);
if (!context) {
throw new Error(
"useGuidelinesContext must be used within a GuidelinesProvider"
);
}
return context;
};
+34
View File
@@ -0,0 +1,34 @@
import { useEffect, useMemo } from "react";
export function useManifestUrl(appName, logo) {
const url = useMemo(() => {
const manifest = {
short_name: appName,
name: appName,
start_url: ".",
display: "standalone",
theme_color: "#000000",
background_color: "#ffffff",
...(logo && {
icons: [
{ src: logo, type: "image/png", sizes: "64x64 32x32 24x24 16x16" },
{ src: logo, type: "image/png", sizes: "192x192" },
{ src: logo, type: "image/png", sizes: "512x512" }
]
})
};
const blob = new Blob([JSON.stringify(manifest)], {
type: "application/json"
});
return URL.createObjectURL(blob);
}, [appName, logo]);
useEffect(() => {
// cleanup when unmounting or when url changes
return () => {
URL.revokeObjectURL(url);
};
}, [url]);
return url;
}
+59 -46
View File
@@ -24,7 +24,7 @@ body {
@media screen and (max-width: 766px) {
.reactour__close {
width: 17px !important;
padding-left: 3px !important;
padding-left: 3px !important;
}
}
@@ -100,49 +100,62 @@ body {
border-radius: 10px;
}
.op-border-hover {
@apply border-gray-300 hover:border-base-content;
}
/* Note: Dark mode styling is now handled via Tailwind utilities in tailwind.config.js */
/* You can use classes like: icon-improved, icon-muted, icon-disabled, op-btn-vscode-disabled */
/* React-tour and ReactTooltip dark mode styling */
[data-theme="opensigndark"] {
.op-border-hover {
@apply border-gray-700 hover:border-base-content;
}
/* for change calender icon of input date type */
::-webkit-calendar-picker-indicator {
filter: invert(1);
}
/* React-tour modal styling */
.reactour__helper {
background-color: #1F2937 !important;
color: #E5E7EB !important;
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 {
color: #E5E7EB !important;
color: #e5e7eb !important;
background-color: #374151 !important;
border: 1px solid #4B5563 !important;
border: 1px solid #4b5563 !important;
}
.reactour__close:hover {
background-color: #4B5563 !important;
color: #FFFFFF !important;
background-color: #4b5563 !important;
color: #ffffff !important;
}
/* React-tour navigation buttons */
.reactour__controls {
background-color: #1F2937 !important;
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;
color: #e5e7eb !important;
border: 1px solid #4b5563 !important;
}
.reactour__controls button:hover {
background-color: #4B5563 !important;
color: #FFFFFF !important;
background-color: #4b5563 !important;
color: #ffffff !important;
}
.reactour__controls button:disabled {
background-color: #3C3C3C !important;
background-color: #3c3c3c !important;
color: #858585 !important;
border-color: #565656 !important;
cursor: not-allowed !important;
@@ -150,28 +163,28 @@ body {
/* ReactTooltip styling */
.react-tooltip {
background-color: #1F2937 !important;
color: #E5E7EB !important;
background-color: #1f2937 !important;
color: #e5e7eb !important;
border: 1px solid #374151 !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
}
.react-tooltip.type-dark {
background-color: #1F2937 !important;
color: #E5E7EB !important;
background-color: #1f2937 !important;
color: #e5e7eb !important;
}
.react-tooltip.place-top:after,
.react-tooltip.place-bottom:after,
.react-tooltip.place-left:after,
.react-tooltip.place-right:after {
border-color: #1F2937 !important;
border-color: #1f2937 !important;
}
/* Tour content improvements */
.reactour__badge {
background-color: #007ACC !important;
color: #FFFFFF !important;
background-color: #007acc !important;
color: #ffffff !important;
}
.reactour__helper h1,
@@ -180,82 +193,82 @@ body {
.reactour__helper h4,
.reactour__helper h5,
.reactour__helper h6 {
color: #FFFFFF !important;
color: #ffffff !important;
}
.reactour__helper p {
color: #E5E7EB !important;
color: #e5e7eb !important;
}
/* VS Code-style tour buttons */
.reactour__controls .op-btn {
background-color: #007ACC !important;
color: #FFFFFF !important;
border: 1px solid #007ACC !important;
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;
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;
color: #e5e7eb !important;
border: 1px solid #4b5563 !important;
}
.reactour__controls .op-btn-secondary:hover {
background-color: #4B5563 !important;
color: #FFFFFF !important;
background-color: #4b5563 !important;
color: #ffffff !important;
}
/* React-datepicker dark mode styling */
.react-datepicker {
background-color: #1F2937 !important;
background-color: #1f2937 !important;
border: 1px solid #374151 !important;
color: #E5E7EB !important;
color: #e5e7eb !important;
}
.react-datepicker__header {
background-color: #374151 !important;
border-bottom: 1px solid #4B5563 !important;
color: #E5E7EB !important;
border-bottom: 1px solid #4b5563 !important;
color: #e5e7eb !important;
}
.react-datepicker__current-month,
.react-datepicker__day-name {
color: #E5E7EB !important;
color: #e5e7eb !important;
}
.react-datepicker__day {
color: #E5E7EB !important;
color: #e5e7eb !important;
}
.react-datepicker__day:hover {
background-color: #4B5563 !important;
color: #FFFFFF !important;
background-color: #4b5563 !important;
color: #ffffff !important;
}
.react-datepicker__day--selected {
background-color: #007ACC !important;
color: #FFFFFF !important;
background-color: #007acc !important;
color: #ffffff !important;
}
.react-datepicker__day--keyboard-selected {
background-color: #374151 !important;
color: #E5E7EB !important;
color: #e5e7eb !important;
}
.react-datepicker__day--outside-month {
color: #6B7280 !important;
color: #6b7280 !important;
}
.react-datepicker__navigation {
color: #E5E7EB !important;
color: #e5e7eb !important;
}
.react-datepicker__navigation:hover {
background-color: #4B5563 !important;
background-color: #4b5563 !important;
}
}
}
+9 -43
View File
@@ -1,19 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import "./styles/dark-theme-improvements.css";
import App from "./App";
import { showUpgradeProgress, hideUpgradeProgress } from "./utils";
import { Provider } from "react-redux";
import { store } from "./redux/store";
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
import {
DndProvider,
TouchTransition,
MouseTransition,
Preview
} from "react-dnd-multi-backend";
import DragElement from "./components/pdf/DragElement.jsx";
import Parse from "parse";
import "./polyfills";
import { serverUrl_fn } from "./constant/appinfo";
@@ -25,47 +16,22 @@ const serverUrl = serverUrl_fn();
Parse.initialize(appId);
Parse.serverURL = serverUrl;
if (localStorage.getItem("showUpgradeProgress")) {
showUpgradeProgress();
}
const savedTheme = localStorage.getItem("theme");
if (savedTheme === "dark") {
document.documentElement.setAttribute("data-theme", "opensigndark");
}
const HTML5toTouch = {
backends: [
{
id: "html5",
backend: HTML5Backend,
transition: MouseTransition
},
{
id: "touch",
backend: TouchBackend,
options: { enableMouseEvents: true },
preview: true,
transition: TouchTransition
}
]
};
const generatePreview = (props) => {
const { item, style } = props;
const newStyle = {
...style
};
return (
<div style={newStyle}>
<DragElement {...item} />
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<DndProvider options={HTML5toTouch}>
<Preview>{generatePreview}</Preview>
<App />
</DndProvider>
<App />
</Provider>
);
hideUpgradeProgress();
localStorage.removeItem("showUpgradeProgress");
+51 -44
View File
@@ -1,9 +1,18 @@
export const extraCols = [
"Note",
"Time to complete (Days)",
"Enable Tour",
"Notify on signatures",
"Redirect url",
"Created Date",
"Updated Date"
];
export default function reportJson(id) {
// console.log("json ", json);
const head = ["Title", "Note", "Folder", "File", "Owner", "Signers"];
const declineHead = ["Title", "Reason", "Folder", "File", "Owner", "Signers"];
const iphead = ["Title", "Note", "Folder", "File", "Signers"];
const contactbook = ["Name", "Email", "Phone"];
const contactbook = ["Name", "Email", "Phone", "Company", "JobTitle"];
const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
const templateReport = ["Title", "File", "Owner", "Signers"];
switch (id) {
@@ -28,26 +37,26 @@ export default function reportJson(id) {
btnIcon: "fa-light fa-trash",
redirectUrl: "",
action: "delete"
},
{
btnId: "22534",
hoverLabel: "option",
btnColor: "",
restrictBtn: true,
textColor: "black",
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "1630",
btnLabel: "Save as template",
hoverLabel: "Save as template",
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
}
]
}
// {
// btnId: "22534",
// hoverLabel: "option",
// btnColor: "",
// restrictBtn: true,
// textColor: "black",
// btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
// action: "option",
// subaction: [
// {
// btnId: "1630",
// btnLabel: "Save as template",
// hoverLabel: "Save as template",
// btnIcon: "fa-light fa-envelope",
// redirectUrl: "",
// action: "saveastemplate"
// }
// ]
// }
],
helpMsg:
"These are documents you have started but have not finalized for sending."
@@ -81,7 +90,7 @@ export default function reportJson(id) {
btnId: "8901",
hoverLabel: "Share",
btnColor: "op-btn-primary",
btnIcon: "fa-light fa-share",
btnIcon: "fa-light fa-copy",
redirectUrl: "",
action: "share"
},
@@ -317,7 +326,7 @@ export default function reportJson(id) {
btnId: "1999",
hoverLabel: "Share",
btnColor: "op-btn-primary",
btnIcon: "fa-light fa-share",
btnIcon: "fa-light fa-copy",
redirectUrl: "",
action: "share"
},
@@ -419,26 +428,26 @@ export default function reportJson(id) {
btnIcon: "fa-light fa-trash",
redirectUrl: "",
action: "delete"
},
{
btnId: "55534",
hoverLabel: "option",
btnColor: "",
restrictBtn: true,
textColor: "black",
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
action: "option",
subaction: [
{
btnId: "6630",
btnLabel: "Save as template",
hoverLabel: "Save as template",
btnIcon: "fa-light fa-envelope",
redirectUrl: "",
action: "saveastemplate"
}
]
}
// {
// btnId: "55534",
// hoverLabel: "option",
// btnColor: "",
// restrictBtn: true,
// textColor: "black",
// btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
// action: "option",
// subaction: [
// {
// btnId: "6630",
// btnLabel: "Save as template",
// hoverLabel: "Save as template",
// btnIcon: "fa-light fa-envelope",
// redirectUrl: "",
// action: "saveastemplate"
// }
// ]
// }
]
};
// contactbook report
@@ -462,8 +471,6 @@ export default function reportJson(id) {
action: "delete"
}
],
import: true,
form: "ContactBook",
helpMsg:
"This is a list of contacts/signers added by you. These will appear as suggestions when you try to add signers to a new document."
};
+47
View File
@@ -0,0 +1,47 @@
import { Trans } from "react-i18next";
export const templateReportTour = [
{
selector: "[data-tut=reactourFirst]",
content: <Trans i18nKey="tour-mssg.report-1" />,
position: "top",
style: { fontSize: "13px" }
},
{
selector: "[data-tut=reactourSecond]",
content: <Trans i18nKey="tour-mssg.redirect" />,
position: "top",
style: { fontSize: "13px" }
},
{
selector: "[data-tut=tourbulksend]",
content: <Trans i18nKey="tour-mssg.bulksend" />,
position: "top",
style: { fontSize: "13px" }
},
{
selector: "[data-tut=reactourThird]",
content: (
<Trans i18nKey="tour-mssg.option">
This menu reveals more options such as Edit, Delete, Rename, Duplicate,
Share, etc.
<a
className="cursor-pointer op-text-primary"
href="https://docs.opensignlabs.com/docs/help/Templates/manage-templates"
target="_blank"
rel="noopener noreferrer"
>
Click here
</a>
to read more about all available options.
<p className="pt-2">
Note: Changes to an existing template will apply to all future
documents created from that template but won't affect documents that
are already sent out.
</p>
</Trans>
),
position: "top",
style: { fontSize: "13px" }
}
];
+52 -55
View File
@@ -1,16 +1,14 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import Header from "../components/Header";
import Footer from "../components/Footer";
import Sidebar from "../components/sidebar/Sidebar";
import { useWindowSize } from "../hook/useWindowSize";
import Tour from "../primitives/Tour";
import axios from "axios";
import { useDispatch, useSelector } from "react-redux";
import { useSelector } from "react-redux";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import { useNavigate, useLocation, Outlet } from "react-router";
import Loader from "../primitives/Loader";
import { showHeader } from "../redux/reducers/showHeader";
import { useTranslation } from "react-i18next";
const HomeLayout = () => {
@@ -19,9 +17,6 @@ const HomeLayout = () => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
const { width } = useWindowSize();
const [isOpen, setIsOpen] = useState(true);
const arr = useSelector((state) => state.TourSteps);
const [isUserValid, setIsUserValid] = useState(true);
const [isLoader, setIsLoader] = useState(true);
@@ -29,7 +24,7 @@ const HomeLayout = () => {
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
const [tourConfigs, setTourConfigs] = useState([]);
const [isLoggingOut, setIsLoggingOut] = useState(false);
const tenantId = localStorage.getItem("TenantId");
useEffect(() => {
@@ -67,15 +62,6 @@ const HomeLayout = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tenantId]);
const showSidebar = () => {
setIsOpen((value) => !value);
dispatch(showHeader(!isOpen));
};
useEffect(() => {
if (width && width <= 768) {
setIsOpen(false);
}
}, [width]);
useEffect(() => {
if (arr && arr.length > 0) {
@@ -87,6 +73,7 @@ const HomeLayout = () => {
}, [arr]);
const handleDynamicSteps = () => {
const github = "https://github.com/OpenSignLabs/OpenSign";
if (arr && arr.length > 0) {
// const resArr = arr;
const resArr = arr.map((obj, index) => {
@@ -105,7 +92,7 @@ const HomeLayout = () => {
{
selector: '[data-tut="nonpresentmask"]',
content: t("tour-mssg.home-layout-1"),
position: "center",
position: "center"
},
{
selector: '[data-tut="tourbutton"]',
@@ -115,8 +102,23 @@ const HomeLayout = () => {
...resArr,
{
selector: '[data-tut="nonpresentmask"]',
content: t("tour-mssg.home-layout-3", { appName }),
position: "center",
content: () => (
<div>
{t("tour-mssg.home-layout-3", { appName })}
<p className="mt-[3px]">
Star us on
<a
href={github}
target="_blank"
rel="noopener noreferrer"
className="underline font-medium pl-1 cursor-pointer"
>
GitHub
</a>
</p>
</div>
),
position: "center"
}
]);
checkTourStatus();
@@ -146,19 +148,11 @@ const HomeLayout = () => {
updatedTourStatus = [{ loginTour: true }];
}
// console.log("updatedTourStatus ", updatedTourStatus);
await axios.put(
serverUrl + "classes/contracts_Users/" + extUserId,
{
TourStatus: updatedTourStatus
},
{
headers: {
"X-Parse-Application-Id": appId
}
}
{ TourStatus: updatedTourStatus },
{ headers: { "X-Parse-Application-Id": appId } }
);
// console.log("updatedRes ", updatedRes);
};
async function checkTourStatus() {
@@ -175,12 +169,6 @@ const HomeLayout = () => {
}
}
const closeSidebar = () => {
if (width <= 1023) {
setIsOpen(false);
}
};
const handleLoginBtn = async () => {
try {
await Parse?.User?.logOut();
@@ -192,12 +180,11 @@ const HomeLayout = () => {
}
};
return (
<div>
<div className="sticky top-0 z-[501]">
{!isLoader && (
<Header showSidebar={showSidebar} setIsMenu={setIsOpen} />
)}
</div>
<div className="flex flex-col h-screen overflow-hidden">
{/* HEADER */}
<header className="z-[501]">
{!isLoader && <Header setIsLoggingOut={setIsLoggingOut} />}
</header>
{isUserValid ? (
<>
{isLoader ? (
@@ -206,27 +193,37 @@ const HomeLayout = () => {
</div>
) : (
<>
<div className="flex md:flex-row flex-col z-50">
<Sidebar isOpen={isOpen} closeSidebar={closeSidebar} />
<div
id="renderList"
className="relative h-screen flex flex-col justify-between w-full overflow-y-auto"
>
<div className="bg-base-200 p-3">{<Outlet />}</div>
<div className="z-30">
<Footer />
</div>
{isLoggingOut && (
<div className="inset-0 bg-black/30 z-[1000] fixed flex justify-center items-center">
<Loader />
</div>
)}
{/* BODY */}
<div className="flex flex-1 overflow-hidden">
{/* SIDEBAR with width animation */}
<Sidebar />
{/* MAIN (includes both content + footer in one scrollable column) */}
<main
id="renderList"
className="flex-1 overflow-auto transition-all duration-300 ease-in-out"
>
<div className="flex flex-col min-h-full">
{/* your page content */}
<div className="p-3">{<Outlet />}</div>
{/* sticky-but-scrollable footer */}
<div className="mt-auto z-30">
<Footer />
</div>
</div>
</main>
</div>
<Tour
onRequestClose={closeTour}
steps={tourConfigs}
isOpen={isTour}
closeWithMask={false}
disableKeyboardNavigation={["esc"]}
// disableInteraction={true}
scrollOffset={-100}
rounded={5}
showCloseButton={isCloseBtn}
/>
</>
@@ -235,7 +232,7 @@ const HomeLayout = () => {
) : (
<ModalUi showHeader={false} isOpen={true} showClose={false}>
<div className="flex flex-col justify-center items-center py-4 md:py-5 gap-5">
<p className="text-xl font-medium">Your session has expired.</p>
<p className="text-xl font-medium">{t("session-expired")}</p>
<button onClick={handleLoginBtn} className="op-btn op-btn-neutral">
{t("login")}
</button>
+4 -4
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import Parse from "parse";
import { appInfo } from "../constant/appinfo";
import { NavLink, useNavigate } from "react-router";
@@ -11,7 +11,6 @@ import {
import { useDispatch } from "react-redux";
import { showTenant } from "../redux/reducers/ShowTenant";
import Loader from "../primitives/Loader";
import Title from "../components/Title";
import { useTranslation } from "react-i18next";
import { emailRegex } from "../constant/const";
@@ -73,6 +72,7 @@ const AddAdmin = () => {
const defaultmenuid = localStorage.getItem("defaultmenuid");
const PageLanding = localStorage.getItem("PageLanding");
const userSettings = localStorage.getItem("userSettings");
const favicon = localStorage.getItem("favicon");
localStorage.clear();
saveLanguageInLocal(i18n);
@@ -84,12 +84,13 @@ const AddAdmin = () => {
localStorage.setItem("userSettings", userSettings);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
localStorage.setItem("favicon", favicon);
};
const handleSubmit = async (event) => {
event.preventDefault();
if (!emailRegex.test(email)) {
alert("Please enter a valid email address.");
alert(t("valid-email-alert"));
} else {
if (lengthValid && caseDigitValid && specialCharValid) {
clearStorage();
@@ -265,7 +266,6 @@ const AddAdmin = () => {
};
return (
<div className="h-screen flex justify-center">
<Title title="Add admin" />
{state.loading ? (
<div className="text-[grey] flex justify-center items-center text-lg md:text-2xl">
<Loader />
+1 -3
View File
@@ -1,7 +1,6 @@
import React, { useState } from "react";
import { useState } from "react";
import Parse from "parse";
import { Navigate } from "react-router";
import Title from "../components/Title";
import { useTranslation } from "react-i18next";
function ChangePassword() {
@@ -56,7 +55,6 @@ function ChangePassword() {
}
return (
<div className="w-full bg-base-100 text-base-content shadow rounded-box p-2">
<Title title="Change Password" />
<div className="text-xl font-bold border-b-[1px] border-gray-300">
{t("change-password")}
</div>
-2
View File
@@ -1,7 +1,6 @@
import React, { useState, useEffect } from "react";
import GetDashboard from "../components/dashboard/GetDashboard";
import { useNavigate, useParams } from "react-router";
import Title from "../components/Title";
import { useDispatch } from "react-redux";
import { saveTourSteps } from "../redux/reducers/TourStepsReducer";
import dashboardJson from "../json/dashboardJson";
@@ -57,7 +56,6 @@ const Dashboard = () => {
return (
<React.Fragment>
<Title title="Dashboard" />
{loading ? (
<div className="h-[300px] w-full bg-white flex justify-center items-center rounded-md">
<Loader />
+4 -4
View File
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import Title from "../components/Title";
import RenderDebugPdf from "../components/RenderDebugPdf";
import { pdfjs } from "react-pdf";
import ModalUi from "../primitives/ModalUi";
import Alert from "../primitives/Alert";
import HandleError from "../primitives/HandleError";
import { useWindowSize } from "../hook/useWindowSize";
import { useTranslation } from "react-i18next";
function processDimensions(x, y, width, height) {
if (width < 0) {
@@ -27,6 +27,7 @@ function processDimensions(x, y, width, height) {
};
}
const DebugPdf = () => {
const {t} = useTranslation()
const { width } = useWindowSize();
const [pdf, setPdf] = useState("");
const [isModal, setIsModal] = useState(true);
@@ -215,8 +216,7 @@ const DebugPdf = () => {
};
return (
<div>
{copied && <Alert type="success">Copied</Alert>}
<Title title={"Debug Pdf"} />
{copied && <Alert type="success">{t("copied")}</Alert>}
{width < 800 ? (
<HandleError handleError={"Debug PDF only availble for PC"} />
) : (
+1 -3
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Confetti from "react-confetti"; // Import the confetti library
import {
@@ -10,7 +10,6 @@ import {
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
import Title from "../components/Title";
import CheckCircle from "../primitives/CheckCircle";
const DocSuccessPage = () => {
@@ -62,7 +61,6 @@ const DocSuccessPage = () => {
return (
<>
<Title title="Success" />
{/* Confetti Effect */}
{showConfetti && (
<Confetti width={window.innerWidth} height={window.innerHeight} />
+2 -4
View File
@@ -1,5 +1,4 @@
import React, { useEffect, useState } from "react";
import Title from "../components/Title";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router";
import login_img from "../assets/images/login_img.svg";
import Parse from "parse";
@@ -40,7 +39,7 @@ function ForgotPassword() {
const handleSubmit = async (event) => {
event.preventDefault();
if (!emailRegex.test(state.email)) {
alert("Please enter a valid email address.");
alert(t("valid-email-alert"));
} else {
setIsLoading(true);
localStorage.setItem("appLogo", appInfo.applogo);
@@ -87,7 +86,6 @@ function ForgotPassword() {
<Loader />
</div>
)}
<Title title="Forgot password" />
{toast?.message && <Alert type={toast.type}>{toast.message}</Alert>}
<div className="md:p-10 lg:p-16">
<div className="md:p-4 lg:p-10 p-4 bg-base-100 text-base-content op-card">
+13 -10
View File
@@ -5,7 +5,6 @@ import Parse from "parse";
import Alert from "../primitives/Alert";
import SelectFolder from "../components/shared/fields/SelectFolder";
import SignersInput from "../components/shared/fields/SignersInput";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
import { SaveFileSize } from "../constant/saveFileSize";
import {
@@ -195,7 +194,9 @@ const Forms = (props) => {
setIsDecrypting(false);
setfileload(false);
removeFile(e);
alert(`Incorrect password for file: ${file.name}`);
alert(
t("incorrect-password-for-file", { file: file.name })
);
}
} else {
console.error("password not provided");
@@ -273,9 +274,8 @@ const Forms = (props) => {
setfileload(false);
removeFile(e);
console.log("err in docx to pdf ", err);
const error = isOpenSignDomain
? `${t("docx-error")} ${t("docx-error-contact")}`
: t("docx-error");
const error =
t("docx-error");
alert(error);
return;
}
@@ -321,7 +321,8 @@ const Forms = (props) => {
}
if (uploadedUrl) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(pdfBytes.byteLength, uploadedUrl, tenantId);
const userId = extUserData?.UserId?.objectId;
SaveFileSize(pdfBytes.byteLength, uploadedUrl, tenantId, userId);
setFileUpload(uploadedUrl);
setfileload(false);
const title = generateTitleFromFilename(filesNameArr?.[0]);
@@ -555,6 +556,8 @@ const Forms = (props) => {
e.preventDefault();
setIsPassword(false);
setfileload(true);
const tenantId = localStorage.getItem("TenantId");
const userId = extUserData?.UserId?.objectId;
try {
const size = formData?.file?.size;
const name = generatePdfName(16);
@@ -579,8 +582,7 @@ const Forms = (props) => {
removeFile();
const title = generateTitleFromFilename(formData?.file?.name);
setFormData((obj) => ({ ...obj, password: "", Name: title }));
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes.url, tenantId);
SaveFileSize(size, fileRes?.url, tenantId, userId);
return fileRes.url;
} else {
removeFile();
@@ -630,7 +632,6 @@ const Forms = (props) => {
return (
<div className="shadow-md rounded-box my-[2px] p-3 bg-base-100 text-base-content">
<Title title={props?.title} />
{isAlert?.message && <Alert type={isAlert.type}>{isAlert.message}</Alert>}
{isSubmit ? (
<div className="h-[300px] flex justify-center items-center">
@@ -791,6 +792,7 @@ const Forms = (props) => {
onChange={handleSigners}
isReset={isReset}
helptextZindex={50}
isAddYourSelfCheckbox
required
/>
)}
@@ -1027,6 +1029,7 @@ const Forms = (props) => {
helptextZindex={50}
helpText={t("bcc-help")}
isCaptureAllData
isAddYourSelfCheckbox
/>
)}
</>
@@ -1221,7 +1224,7 @@ const Forms = (props) => {
{t("next")}
</button>
<div
className="op-btn op-btn-ghost"
className="op-btn op-btn-ghost text-base-content"
onClick={() => handleCancel()}
>
{t("cancel")}
+82 -25
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router";
import axios from "axios";
import {
@@ -14,7 +14,6 @@ import Parse from "parse";
import { useTranslation } from "react-i18next";
import SelectLanguage from "../components/pdf/SelectLanguage";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
import Title from "../components/Title";
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
@@ -36,8 +35,14 @@ function GuestLogin() {
const [documentId, setDocumentId] = useState(id);
const [contactId, setContactId] = useState(contactBookId);
const [sendmail, setSendmail] = useState();
const [contact, setContact] = useState({ name: "", phone: "", email: "" });
const [contact, setContact] = useState({
name: "",
phone: "",
email: "",
jobTitle: "",
company: ""
});
const [isOptionalDetails, setIsOptionalDetails] = useState(false);
const navigateToDoc = async (docId, contactId) => {
try {
@@ -72,8 +77,10 @@ function GuestLogin() {
//function generate serverUrl and parseAppId from url and save it in local storage
const handleServerUrl = async () => {
setAppLogo(logo);
const favicon = localStorage.getItem("favicon");
localStorage.clear(); // Clears everything
localStorage.setItem("favicon", favicon);
localStorage.setItem(
"appname",
"OpenSign™"
@@ -214,7 +221,7 @@ function GuestLogin() {
const handleUserData = async (e) => {
e.preventDefault();
if (!emailRegex.test(contact.email?.toLowerCase()?.replace(/\s/g, ""))) {
alert("Please enter a valid email address.");
alert(t("valid-email-alert"));
} else {
const params = { ...contact, docId: documentId };
try {
@@ -253,7 +260,6 @@ function GuestLogin() {
return (
<div>
<Title title="Request Sign" />
{/* OTP Verification Modal */}
{EnterOTP && (
@@ -356,7 +362,7 @@ function GuestLogin() {
<div className="mb-2">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
className="block text-xs font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
@@ -378,7 +384,7 @@ function GuestLogin() {
<div className="mb-2">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
className="block text-xs font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
@@ -393,23 +399,74 @@ function GuestLogin() {
disabled
/>
</div>
<div className="mt-2.5">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("phone")}
</label>
<input
type="text"
name="phone"
value={contact.phone}
onChange={handleInputChange}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
disabled={loading}
placeholder={t("phone-optional")}
/>
</div>
{isOptionalDetails && (
<>
<div className="mb-2">
<label
htmlFor="phone"
className="block text-xs font-semibold"
>
{t("phone")}
</label>
<input
type="text"
name="phone"
value={contact.phone}
onChange={handleInputChange}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
disabled={loading}
placeholder={t("phone-optional")}
/>
</div>
<div className="mb-2">
<label
htmlFor="company"
className="block text-xs font-semibold"
>
{t("company")}
</label>
<input
type="text"
id="company"
name="company"
value={contact.company}
onChange={handleInputChange}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
disabled={loading}
placeholder={t("phone-optional")}
/>
</div>
<div className="mb-2">
<label
htmlFor="jobTitle"
className="block text-xs font-semibold"
>
{t("job-title")}
</label>
<input
type="text"
id="jobTitle"
name="jobTitle"
value={contact.jobTitle}
onChange={handleInputChange}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
disabled={loading}
placeholder={t("phone-optional")}
/>
</div>
</>
)}
<button
onClick={(e) => {
e.preventDefault();
setIsOptionalDetails(!isOptionalDetails);
}}
className="op-link op-link-secondary max-w-fit text-xs"
>
{isOptionalDetails
? t("hide-optional-details")
: t("optional-details")}
</button>
<div className="mt-2 flex justify-start">
<button
type="submit"
+10 -10
View File
@@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import Parse from "parse";
import { useDispatch } from "react-redux";
import axios from "axios";
import Title from "../components/Title";
import { NavLink, useNavigate, useLocation } from "react-router";
import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize";
@@ -128,14 +127,14 @@ function Login() {
if (error?.code === 1001) {
showToast("danger", t("action-prohibited"));
} else {
showToast("danger", "Invalid username/password or region");
showToast("danger", t("invalid-username-password-region"));
}
}
};
const handleLoginBtn = async (event) => {
event.preventDefault();
if (!emailRegex.test(state.email)) {
alert("Please enter a valid email address.");
alert(t("valid-email-alert"));
return;
}
await handleLogin();
@@ -333,6 +332,7 @@ function Login() {
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
let favicon = localStorage.getItem("favicon");
localStorage.clear();
saveLanguageInLocal(i18n);
@@ -343,6 +343,7 @@ function Login() {
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
localStorage.setItem("favicon", favicon);
};
const continueLoginFlow = async () => {
@@ -407,8 +408,7 @@ function Login() {
{errMsg}
</div>
) : (
<div>
<Title title="Login" />
<>
{state.loading && (
<div
aria-live="assertive"
@@ -543,7 +543,7 @@ function Login() {
<label
htmlFor="Company"
style={{ display: "flex" }}
className="block text-xs text-gray-700 font-semibold"
className="block text-xs font-semibold"
>
{t("company")}{" "}
<span className="text-[red] text-[13px]">*</span>
@@ -570,7 +570,7 @@ function Login() {
<label
htmlFor="JobTitle"
style={{ display: "flex" }}
className="block text-xs text-gray-700 font-semibold"
className="block text-xs font-semibold"
>
{t("job-title")}
<span className="text-[red] text-[13px]">*</span>
@@ -603,7 +603,7 @@ function Login() {
</button>
<button
type="button"
className="op-btn op-btn-ghost"
className="op-btn op-btn-ghost text-base-content"
onClick={logOutUser}
>
{t("cancel")}
@@ -620,7 +620,7 @@ function Login() {
<Loader />
</div>
)}
</div>
</>
);
}
export default Login;
+5 -6
View File
@@ -1,4 +1,4 @@
import React, { useState, useRef, useEffect } from "react";
import { useState, useRef, useEffect } from "react";
import SignatureCanvas from "react-signature-canvas";
import "../styles/managesign.css";
import "../styles/signature.css";
@@ -12,8 +12,7 @@ import { SaveFileSize } from "../constant/saveFileSize";
import Alert from "../primitives/Alert";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import sanitizeFileName from "../primitives/sanitizeFileName";
import Title from "../components/Title";
import { sanitizeFileName } from "../utils";
const ManageSign = () => {
const { t } = useTranslation();
const [penColor, setPenColor] = useState("blue");
@@ -206,7 +205,8 @@ const ManageSign = () => {
const fileRes = await getSecureUrl(response?.url());
if (fileRes?.url) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(file.size, fileRes?.url, tenantId);
const userId = Parse?.User?.current()?.id;
SaveFileSize(file.size, fileRes?.url, tenantId, userId);
return fileRes?.url;
} else {
alert(t("something-went-wrong-mssg"));
@@ -298,7 +298,6 @@ const ManageSign = () => {
};
return (
<div className="relative h-full bg-base-100 text-base-content flex shadow-md rounded-box overflow-auto">
<Title title="My signature" />
{isLoader && (
<div className="absolute bg-black bg-opacity-30 z-50 w-full h-full flex justify-center items-center">
<Loader />
@@ -325,7 +324,7 @@ const ManageSign = () => {
/>
<div className="relative">
{image ? (
<div className="mysignatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden">
<div className="mysignatureCanvas relative border-[2px] border-[#888] rounded-box overflow-hidden">
<img
alt="signature"
src={image}
+68 -43
View File
@@ -1,4 +1,5 @@
import React, { useEffect, useState, useRef } from "react";
import React, { useEffect, useState, useRef, useCallback } from "react";
import { lazyWithRetry } from "../utils";
import "../styles/opensigndrive.css";
import {
getThemeIconColor,
@@ -7,7 +8,6 @@ import {
getDrive
} from "../constant/Utils";
import { useNavigate } from "react-router";
import Title from "../components/Title";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
@@ -16,7 +16,7 @@ import axios from "axios";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
const DriveBody = React.lazy(
const DriveBody = lazyWithRetry(
() => import("../components/opensigndrive/DriveBody")
);
const dropdowncss =
@@ -35,6 +35,8 @@ function Opensigndrive() {
const { t } = useTranslation();
const navigate = useNavigate();
const scrollRef = useRef(null);
// Create a ref for the "sentinel" element at the bottom of the list
const bottomRef = useRef(null);
const [isList, setIsList] = useState(false);
const [selectedSort, setSelectedSort] = useState("Date");
const [sortingOrder, setSortingOrder] = useState("Descending");
@@ -60,7 +62,7 @@ function Opensigndrive() {
const [loading, setLoading] = useState(false);
const sortOrder = ["Ascending", "Descending"];
const sortingValue = ["Name", "Date"];
const [isDontShow, setIsDontShow] = useState(false);
const [isDontShow, setIsDontShow] = useState(true);
const [tourData, setTourData] = useState();
const [showTourFirstTIme, setShowTourFirstTime] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
@@ -221,39 +223,38 @@ function Opensigndrive() {
}
};
//function to fetch drive details list on scroll bottom
const handleScroll = () => {
//get document of render openSign-drive component using id
const documentList = document.getElementById("renderList");
const { scrollTop, clientHeight, scrollHeight } = documentList;
const scrolled = Math.ceil(scrollTop + clientHeight); // ceil return e.g 3.14 => 4
const totalHeight = Math.floor(scrollHeight); // floor return e.g 3.14 => 3
// clientHeight property returns the height of an element's content area, including padding but not including borders, margins, or scrollbars.
// scrollHeight property returns the entire height of an element,including the parts that are not visible due to overflow..
// scrollTop property show height of element, how much the content has been scrolled from the top.
// When the sum of scrollTop and clientHeight is equal to scrollHeight, it means that the user has scrolled to the bottom of the div.
if (scrolled >= totalHeight) {
//disableLoading is used disable initial loader
const disableLoading = true;
// If the fetched data length is less than the limit, it means there's no more data to fetch
if (!loading && pdfData.length % 50 === 0) {
getPdfDocumentList(disableLoading);
}
// Memoize the loader function so it only changes when deps change
const loadMore = useCallback(() => {
// Only fetch if we're not already loading and we've got a full "page" (50 items)
if (!loading && pdfData.length % 50 === 0) {
// Pass `true` to disable the initial loader UI
getPdfDocumentList(true);
}
};
//useEffect is used to call handleScroll function on scrolling event
}, [loading, pdfData.length, getPdfDocumentList]);
useEffect(() => {
const documentList = document.getElementById("renderList");
if (documentList) {
documentList.addEventListener("scroll", handleScroll);
return () => {
documentList.removeEventListener("scroll", handleScroll);
};
}
// eslint-disable-next-line
}, [loading, sortingOrder, selectedSort]); // Add/remove scroll event listener when loading changes
const container = document.getElementById("renderList") || null;
if (!container) return;
// Set up the IntersectionObserver
const observer = new IntersectionObserver(
([entry]) => {
// 5. When the sentinel comes fully into view, trigger loadMore()
if (entry.isIntersecting) loadMore();
},
{
root: container, // Determine the scroll container (null = viewport)
rootMargin: "0px", // no extra margin
threshold: 1.0 // fire when 100% of sentinel is visible
}
);
// Start observing the sentinel DOM node
if (bottomRef.current) observer.observe(bottomRef.current);
// Clean up on unmount or if loadMore changes
return () => {
if (bottomRef.current) observer.unobserve(bottomRef.current);
observer.disconnect();
};
}, [loadMore]);
//function for handle folder name path
const handleRoute = (index, folderData) => {
@@ -554,9 +555,27 @@ function Opensigndrive() {
};
}, []);
const handleHighlightClick = () => {
setIsTour(false);
setShowTourFirstTime(false);
};
const handleFolderOptions = () => {
setIsOptions(!isOptions);
handleHighlightClick();
};
const handleSortOptions = () => {
setIsShowSort(!isShowSort);
handleHighlightClick();
};
const handleViewOption = () => {
setIsList(!isList);
handleHighlightClick();
};
return (
<div className="bg-base-100 text-base-content rounded-box w-full shadow-md">
<Title title={`${drivename} Drive`} drive={true} />
<ModalUi
isOpen={isAlert.isShow}
title={t("alert")}
@@ -614,7 +633,7 @@ function Opensigndrive() {
</button>
<button
type="button"
className="op-btn op-btn-ghost ml-1"
className="op-btn op-btn-ghost text-base-content ml-1"
onClick={oncloseFolder}
>
{t("close")}
@@ -643,14 +662,13 @@ function Opensigndrive() {
onRequestClose={closeTour}
steps={tourData}
isOpen={isTour}
closeWithMask={false}
scrollOffset={-100}
rounded={5}
/>
)}
<div
data-tut="reactourFirst"
onMouseEnter={(e) => handleMouseEnter(e)}
onClick={handleHighlightClick}
ref={scrollRef}
className="w-full whitespace-nowrap cursor-pointer select-none overflow-x-auto"
>
@@ -682,7 +700,7 @@ function Opensigndrive() {
<div
id="folder-menu"
className={`${isOptions ? "dropdown show dropDownStyle" : "dropdown"} hidden md:block cursor-pointer hover:bg-gray-200 p-2 rounded-md`}
onClick={() => setIsOptions(!isOptions)}
onClick={handleFolderOptions}
>
<div data-tut="reactourSecond">
<i
@@ -727,7 +745,7 @@ function Opensigndrive() {
<div
id="menu-container"
className={isShowSort ? "dropdown show" : "dropdown"}
onClick={() => setIsShowSort(!isShowSort)}
onClick={handleSortOptions}
>
<div
data-tut="reactourThird"
@@ -739,7 +757,12 @@ function Opensigndrive() {
aria-hidden="true"
style={{ color: `${getThemeIconColor()}` }}
></i>
<span style={{ fontSize: "15px", color: `${getThemeIconColor()}` }}>
<span
style={{
fontSize: "15px",
color: `${getThemeIconColor()}`
}}
>
{selectedSort}
</span>
</div>
@@ -796,7 +819,7 @@ function Opensigndrive() {
<div
className="cursor-pointer p-2 hover:bg-gray-200 rounded-md flex justify-center items-center"
data-tut="reactourForth"
onClick={() => setIsList(!isList)}
onClick={handleViewOption}
>
<i
className={`${isList ? "fa-light fa-th-large" : "fa-light fa-list"} text-[20px]`}
@@ -891,6 +914,8 @@ function Opensigndrive() {
setSkip={setSkip}
sortingData={sortingData}
/>
{/* sentinel */}
<div ref={bottomRef} className="h-1" />
{loading && (
<div className="text-center pb-[20px]">{t("loading")}</div>
)}
+3 -5
View File
@@ -1,15 +1,13 @@
// NotFound.js
import React from "react";
import Title from "../components/Title";
import { useTranslation } from "react-i18next";
const PageNotFound = ({ prefix }) => {
const { t } = useTranslation();
return (
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
<Title title={"Page Not Found"} />
<div className="text-center">
<h1 className="text-[60px] lg:text-[120px] font-semibold">404</h1>
<p className="text-[30px] lg:text-[50px]">
{prefix ? prefix : "Page"} Not Found
{prefix ? `${prefix} Not Found` : t("page-not-found")}
</p>
</div>
</div>
+308 -233
View File
@@ -3,8 +3,7 @@ import { PDFDocument } from "pdf-lib";
import "../styles/signature.css";
import Parse from "parse";
import axios from "axios";
import { DndProvider, useDrop } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrop } from "react-dnd";
import { useDispatch, useSelector } from "react-redux";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import Tour from "../primitives/Tour";
@@ -14,11 +13,12 @@ import {
setSaveSignCheckbox,
setMyInitial,
setDefaultSignImg,
resetWidgetState
resetWidgetState,
setPrefillImg
} from "../redux/reducers/widgetSlice.js";
import {
contractDocument,
multiSignEmbed,
embedWidgetsToDoc,
embedDocId,
pdfNewWidthFun,
signPdfFun,
@@ -48,13 +48,13 @@ import {
widgetDataValue,
getOriginalWH,
handleCheckResponse,
convertJpegToPng
} from "../constant/Utils";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
import Title from "../components/Title";
import DefaultSignature from "../components/pdf/DefaultSignature";
import SignerListComponent from "../components/pdf/SignerListComponent";
import PdfZoom from "../components/pdf/PdfZoom";
import PdfTools from "../components/pdf/PdfTools";
import { useTranslation } from "react-i18next";
import ModalUi from "../primitives/ModalUi";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
@@ -68,12 +68,14 @@ import AgreementSign from "../components/pdf/AgreementSign";
import WidgetComponent from "../components/pdf/WidgetComponent";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import TextFontSetting from "../components/pdf/TextFontSetting";
import WidgetsValueModal from "../components/pdf/WidgetsValueModal.jsx";
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
import * as utils from "../utils";
function PdfRequestFiles(
) {
const { t } = useTranslation();
const dispatch = useDispatch();
const prefillImg = useSelector((state) => state.widget.prefillImg);
const isShowModal = useSelector((state) => state.widget.isShowModal);
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
const myInitial = useSelector((state) => state.widget.myInitial);
@@ -88,8 +90,9 @@ function PdfRequestFiles(
const [pageNumber, setPageNumber] = useState(1);
const [handleError, setHandleError] = useState();
const [isCelebration, setIsCelebration] = useState(false);
const [requestSignTour, setRequestSignTour] = useState(true);
const [isReqSignTourDisabled, setIsReqSignTourDisabled] = useState(true);
const [tourStatus, setTourStatus] = useState([]);
const [isDontShowCheckbox, setIsDontShowCheckbox] = useState(false);
const [isLoading, setIsLoading] = useState({
isLoad: true,
message: t("loading-mssg")
@@ -107,11 +110,8 @@ function PdfRequestFiles(
const [expiredDate, setExpiredDate] = useState("");
const [isResize, setIsResize] = useState(false);
const [signerUserId, setSignerUserId] = useState();
const [isDontShow, setIsDontShow] = useState(false);
const [isDontShow, setIsDontShow] = useState(true);
const [isDownloading, setIsDownloading] = useState("");
// tempSignerId is used to temporarily store the currently selected signer's unique ID, When editing a text widget, it automatically attaches a prefill user, and since prefill users are not shown in the signer list, the selected signer from before editing would be lost. To handle this, we store the currently selected signer's unique ID in tempSignerId before entering the text widget edit mode. Once the text widget settings are completed,
// we restore the original selected signer by setting tempSignerId back to uniqueId.This ensures that the correct signer remains selected and visible in the UI even after interacting with a prefill-only widget like the text widget.
const [tempSignerId, setTempSignerId] = useState("");
const [defaultSignAlert, setDefaultSignAlert] = useState({
isShow: false,
alertMessage: ""
@@ -135,7 +135,7 @@ function PdfRequestFiles(
const [scale, setScale] = useState(1);
const [uniqueId, setUniqueId] = useState("");
const [documentId, setDocumentId] = useState("");
const isHeader = useSelector((state) => state.showHeader);
const isSidebar = useSelector((state) => state.sidebar.isOpen);
const divRef = useRef(null);
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [signatureType, setSignatureType] = useState([]);
@@ -155,6 +155,11 @@ function PdfRequestFiles(
const [assignedWidgetId, setAssignedWidgetId] = useState([]);
const [showSignPagenumber, setShowSignPagenumber] = useState([]);
const [owner, setOwner] = useState({});
const [tenantMailTemplate, setTenantMailTemplate] = useState({
body: "",
subject: ""
});
const [isOptionalDetails, setIsOptionalDetails] = useState(false);
const [, drop] = useDrop({
accept: "BOX",
drop: (item, monitor) => addPositionOfSignature(item, monitor),
@@ -211,7 +216,7 @@ function PdfRequestFiles(
const timer = setTimeout(updateSize, 100); // match the transition duration
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current, isHeader]);
}, [divRef.current, isSidebar]);
const redirectUrl = pdfDetails?.[0]?.RedirectUrl || "";
useEffect(() => {
if (isredirectCanceled) return; // Stop the redirect timer if canceled
@@ -244,6 +249,12 @@ function PdfRequestFiles(
const filterSignTypes = signatureType?.filter(
(x) => x.enabled === true
);
if (tenantDetails?.RequestBody) {
setTenantMailTemplate({
body: tenantDetails?.RequestBody,
subject: tenantDetails?.RequestSubject
});
}
return filterSignTypes;
}
@@ -258,7 +269,6 @@ function PdfRequestFiles(
isSuccessPage = false,
) => {
try {
let isUpdatedSubscribe;
const senderUser = localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
@@ -274,7 +284,7 @@ function PdfRequestFiles(
//getting document details
const documentData = await contractDocument(docId);
// Filter out 'prefill' roles from the Placeholder array
const filteredPlaceholder = documentData[0].Placeholders.filter(
const filteredPlaceholder = documentData[0]?.Placeholders?.filter(
(data) => data.Role !== "prefill"
);
// Reassign the updated Placeholder back to the documentData array
@@ -283,6 +293,14 @@ function PdfRequestFiles(
Placeholders: filteredPlaceholder
};
if (documentData && documentData.length > 0) {
const prefillImg = await utils?.savePrefillImg(
documentData[0]?.Placeholders
);
if (prefillImg && Array.isArray(prefillImg)) {
prefillImg.forEach((img) => {
dispatch(setPrefillImg(img));
});
}
const userSignatureType =
documentData[0]?.ExtUserPtr?.SignatureType || signatureTypes;
const docSignTypes =
@@ -310,7 +328,7 @@ function PdfRequestFiles(
setOwner(documentData?.[0]?.ExtUserPtr);
const isCompleted =
documentData[0].IsCompleted && documentData[0].IsCompleted;
const expireDate = documentData[0].ExpiryDate.iso;
const expireDate = documentData[0]?.ExpiryDate?.iso;
const declined =
documentData[0].IsDeclined && documentData[0].IsDeclined;
const expireUpdateDate = new Date(expireDate).getTime();
@@ -365,7 +383,7 @@ function PdfRequestFiles(
}
} else {
if (currUserId) {
const checkCurrentUser = documentData[0].Placeholders.find(
const checkCurrentUser = documentData[0]?.Placeholders?.find(
(data) => data?.signerObjId === currUserId
);
if (checkCurrentUser) {
@@ -399,9 +417,11 @@ function PdfRequestFiles(
let signers = [];
let unSignedSigner = [];
const removePrefill = documentData[0]?.Placeholders.filter(
(x) => x.Role !== "prefill"
);
const placeholdersOrSigners = [];
for (const placeholder of documentData[0].Placeholders) {
for (const placeholder of removePrefill) {
//`emailExist` variable to handle condition for quick send flow and show unsigned signers list
const signerIdExist = placeholder?.signerObjId;
if (signerIdExist) {
@@ -454,10 +474,9 @@ function PdfRequestFiles(
checkAlreadySign ||
!currUserId ||
declined ||
currDate > expireUpdateDate ||
!isTourEnabled
currDate > expireUpdateDate
) {
setRequestSignTour(true);
setIsReqSignTourDisabled(true);
} else {
const isEnableOTP = documentData?.[0]?.IsEnableOTP || false;
const sessionToken = localStorage.getItem("accesstoken");
@@ -471,17 +490,8 @@ function PdfRequestFiles(
setContractName("_Users");
currUserId = res[0].objectId;
setSignerUserId(currUserId);
const tourData = res[0].TourStatus && res[0].TourStatus;
if (tourData && tourData.length > 0) {
const checkTourRequest = tourData.filter(
(data) => data?.requestSign
);
setTourStatus(tourData);
setRequestSignTour(checkTourRequest[0]?.requestSign || false);
} else {
setRequestSignTour(false);
}
storeSignerDetails(res?.[0]);
handleTourStatus(isTourEnabled, res?.[0]?.TourStatus);
//function to get default signatur of current user from `contracts_Signature` class
const defaultSignRes = await getDefaultSignature(
jsonSender?.objectId
@@ -507,17 +517,9 @@ function PdfRequestFiles(
} else if (res[0] && res.length) {
setContractName("_Contactbook");
const objectId = res[0].objectId;
storeSignerDetails(res?.[0]);
setSignerUserId(objectId);
const tourData = res[0].TourStatus && res[0].TourStatus;
if (tourData && tourData.length > 0) {
const checkTourRequest = tourData.filter(
(data) => data?.requestSign
);
setTourStatus(tourData);
setRequestSignTour(checkTourRequest[0]?.requestSign || false);
} else {
setRequestSignTour(false);
}
handleTourStatus(isTourEnabled, res?.[0]?.TourStatus);
} else if (res.length === 0) {
setHandleError(t("user-not-exist"));
}
@@ -535,25 +537,10 @@ function PdfRequestFiles(
}
);
const contact = resContact?.data?.result;
localStorage.setItem(
"signer",
JSON.stringify({
Name: contact?.Name,
Email: contact?.Email,
UserId: contact?.UserId?.objectId
})
);
storeSignerDetails(contact);
setContractName("_Contactbook");
setSignerUserId(contact?.objectId);
const tourData = contact?.TourStatus && contact?.TourStatus;
if (tourData && tourData.length > 0) {
const checkTourRequest =
tourData?.some((data) => data?.requestSign) || false;
setTourStatus(tourData);
setRequestSignTour(checkTourRequest);
} else {
setRequestSignTour(false);
}
handleTourStatus(isTourEnabled, contact?.TourStatus);
} catch (err) {
console.log("err while getting tourstatus", err);
}
@@ -587,6 +574,38 @@ function PdfRequestFiles(
setIsLoading({ isLoad: false });
}
};
// `storeSignerDetails` is used to stor signer details to set in text type widgets
const storeSignerDetails = (signer) => {
localStorage.setItem(
"signer",
JSON.stringify({
Name: signer?.Name,
Email: signer?.Email,
UserId: signer?.UserId?.objectId,
JobTitle: signer?.JobTitle,
Company: signer?.Company
})
);
};
// `handleTourStatus` is used to disable/enable tour
const handleTourStatus = (isTourEnabled, tourData) => {
if (isTourEnabled) {
if (tourData && tourData.length > 0) {
const checkTourRequest =
tourData?.some((data) => data?.requestSign) || false;
setTourStatus(tourData);
setIsDontShowCheckbox(!checkTourRequest);
setIsReqSignTourDisabled(checkTourRequest);
} else {
setIsReqSignTourDisabled(false);
}
} else {
setIsReqSignTourDisabled(true);
}
};
//function for embed signature or image url in pdf
async function embedWidgetsData(
) {
@@ -645,6 +664,7 @@ function PdfRequestFiles(
// `widgets` is Used to return widgets details with page number of current user
const widgets = checkUser?.[0]?.placeHolder;
let pdfArrBuffer;
const isSignYourSelfFlow = false;
//`contractDocument` function used to get updated SignedUrl
// to resolve issue of widgets get remove automatically when more than 1 signers try to sign doc at a time
const documentData = await contractDocument(docId);
@@ -657,6 +677,23 @@ function PdfRequestFiles(
} else {
pdfArrBuffer = arrayBuffer;
}
const prefillDetails = signerPos.find(
(x) => x.Role === "prefill"
);
//condition to embed prefill widgets details in pdf in public signing flow
if (prefillDetails && props.templateId) {
const prefillwidgets = prefillDetails?.placeHolder;
const pdfDoc = await PDFDocument.load(pdfArrBuffer);
const pdfbase64 = await embedWidgetsToDoc(
prefillwidgets,
pdfDoc,
isSignYourSelfFlow,
scale,
prefillImg
);
//convert base64 to arraybuffer is used in pdf-lib
pdfArrBuffer = base64ToArrayBuffer(pdfbase64);
}
} else if (
documentData === "Error: Something went wrong!" ||
(documentData.result && documentData.result.error)
@@ -669,11 +706,8 @@ function PdfRequestFiles(
} else {
setHandleError("Document not Found!");
}
// Load a PDFDocument from the existing PDF bytes
const existingPdfBytes = pdfArrBuffer;
try {
const pdfDoc = await PDFDocument.load(existingPdfBytes);
const isSignYourSelfFlow = false;
const pdfDoc = await PDFDocument.load(pdfArrBuffer);
const extUserPtr = pdfDetails[0].ExtUserPtr;
const HeaderDocId = extUserPtr?.HeaderDocId;
//embed document's object id to all pages in pdf document
@@ -684,7 +718,7 @@ function PdfRequestFiles(
}
}
//embed all widgets in document
const pdfBytes = await multiSignEmbed(
const pdfBytes = await embedWidgetsToDoc(
widgets,
pdfDoc,
isSignYourSelfFlow,
@@ -719,11 +753,13 @@ function PdfRequestFiles(
updatedDoc.updatedPdfDetails?.[0]?.Signers.findIndex(
(x) => x.objectId === contactId
);
const removePrefill =
updatedDoc.updatedPdfDetails?.[0]?.Placeholders?.filter(
(x) => x.Role !== "prefill"
);
const newIndex = index + 1;
const usermail = {
Email:
updatedDoc.updatedPdfDetails?.[0]?.Placeholders[newIndex]
?.email || ""
Email: removePrefill[newIndex]?.email || ""
};
const user = usermail?.Email
? usermail
@@ -733,9 +769,11 @@ function PdfRequestFiles(
sendInOrder
) {
const requestBody =
updatedDoc.updatedPdfDetails?.[0]?.RequestBody;
updatedDoc.updatedPdfDetails?.[0]?.RequestBody ||
tenantMailTemplate?.body;
const requestSubject =
updatedDoc.updatedPdfDetails?.[0]?.RequestSubject;
updatedDoc.updatedPdfDetails?.[0]?.RequestSubject ||
tenantMailTemplate?.subject;
if (user) {
const expireDate = expiry;
const newDate = new Date(expireDate);
@@ -923,8 +961,8 @@ function PdfRequestFiles(
let filterSignerPos = [];
if (signerObjId) {
//get current signerObjId placeholder details
filterSignerPos = updateSignPos?.filter(
(data) => data.Id === signerObjId
filterSignerPos = updateSignPos.filter(
(data) => data.Id === signerObjId && data.Role !== "prefill"
);
}
@@ -957,7 +995,7 @@ function PdfRequestFiles(
//update new placeholder of current signer
const newUpdateSigner = updateSignPos.map((obj) => {
if (signerObjId) {
if (obj.Id === signerObjId) {
if (obj.Id === uniqueId && obj.Role !== "prefill") {
return { ...obj, placeHolder: newUpdateSignPos };
}
}
@@ -1090,19 +1128,18 @@ function PdfRequestFiles(
});
};
//function to add default signature for all requested placeholder of sign
const addDefaultSignature = () => {
const addDefaultSignature = async () => {
const type = defaultSignAlert?.type;
//get current signers placeholder position data
const currentSignerPosition = signerPos?.filter(
(data) => data.signerObjId === signerObjectId
);
const defaultSign = type === "signature" ? defaultSignImg : myInitial;
const placeHolder = currentSignerPosition[0].placeHolder;
const filename = type === "signature" ? "mysign" : "myinitials";
const signImg = await convertJpegToPng(defaultSign, filename);
//function for save default signature url for all placeholder position
const updatePlace = addDefaultSignatureImg(
currentSignerPosition[0].placeHolder,
defaultSign,
type
);
const updatePlace = addDefaultSignatureImg(placeHolder, signImg, type);
const updatesignerPos = signerPos.map((x) =>
x.signerObjId === signerObjectId ? { ...x, placeHolder: updatePlace } : x
@@ -1115,7 +1152,7 @@ function PdfRequestFiles(
};
//function to close tour and save tour status
const closeRequestSignTour = async () => {
setRequestSignTour(true);
setIsReqSignTourDisabled(true);
if (isDontShow) {
const isEnableOTP = pdfDetails?.[0]?.IsEnableOTP || false;
const sessionToken = localStorage.getItem("accesstoken");
@@ -1186,6 +1223,7 @@ function PdfRequestFiles(
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-6", { pagenumbers })}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
@@ -1197,6 +1235,7 @@ function PdfRequestFiles(
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-1")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
@@ -1208,6 +1247,19 @@ function PdfRequestFiles(
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-2")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
},
{
selector: '[data-tut="pdftools"]',
content: () => (
<TourContentWithBtn
message={t("pdf-tools-tour")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
@@ -1219,6 +1271,7 @@ function PdfRequestFiles(
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-3")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
@@ -1231,6 +1284,7 @@ function PdfRequestFiles(
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-4")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
@@ -1249,6 +1303,7 @@ function PdfRequestFiles(
content: () => (
<TourContentWithBtn
message={t("tour-mssg.pdf-request-file-5")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
@@ -1261,6 +1316,7 @@ function PdfRequestFiles(
content: () => (
<TourContentWithBtn
message={t("tour-mssg.allowModify-widgets")}
isDontShowCheckbox={isDontShowCheckbox}
isChecked={handleDontShow}
/>
),
@@ -1297,12 +1353,14 @@ function PdfRequestFiles(
onRequestClose={closeRequestSignTour}
steps={isMobile ? mobileTour : defaultSignTour}
isOpen={true}
closeWithMask={false}
rounded={5}
/>
);
};
const handleCloseTour = () => {
closeTour();
setIsReqSignTourDisabled(true);
};
const clickOnZoomIn = () => {
onClickZoomIn(scale, zoomPercent, setScale, setZoomPercent);
@@ -1312,11 +1370,17 @@ function PdfRequestFiles(
};
const handleDownloadBtn = async () => {
const url = pdfDetails?.[0]?.SignedUrl || pdfDetails?.[0]?.URL;
const isCompleted = pdfDetails?.[0]?.IsCompleted || false;
const name =
pdfDetails?.[0]?.Name?.length > 100
? pdfDetails?.[0]?.Name?.slice(0, 100)
: pdfDetails?.[0]?.Name || "Document";
await fetchUrl(url, name);
const formatId = pdfDetails?.[0]?.ExtUserPtr?.DownloadFilenameFormat;
const docName = utils?.buildDownloadFilename(formatId, {
docName: name,
isSigned: isCompleted
});
await fetchUrl(url, docName);
};
const handleDeclineMssg = () => {
const user = pdfDetails[0]?.DeclineBy?.email;
@@ -1400,80 +1464,95 @@ function PdfRequestFiles(
getSignerPos(item, monitor);
};
const getSignerPos = (item, monitor) => {
const posZIndex = zIndex + 1;
setZIndex(posZIndex);
const key = randomId();
const containerScale = getContainerScale(
pdfOriginalWH,
pageNumber,
containerWH
);
let dropData = [],
dropObj;
let placeHolder;
const dragTypeValue = item?.text ? item.text : monitor.type;
const widgetWidth =
defaultWidthHeight(dragTypeValue).width * containerScale;
const widgetHeight =
defaultWidthHeight(dragTypeValue).height * containerScale;
const extUser = localStorage.getItem("Extand_Class");
const parseUser = extUser && JSON.parse(extUser)[0];
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key,
scale: containerScale,
zIndex: posZIndex,
type: dragTypeValue,
options: addWidgetOptions(dragTypeValue, owner, widgetValue),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
dropData.push(dropObj);
placeHolder = { pageNumber: pageNumber, pos: dropData };
} else {
const offset = monitor.getClientOffset();
//This method returns the offset of the current pointer (mouse) position relative to the client viewport.
const containerRect = document
.getElementById("container")
.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;
dropObj = {
xPosition: getXPosition / (containerScale * scale),
yPosition: getYPosition / (containerScale * scale),
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key,
scale: containerScale,
zIndex: posZIndex,
type: dragTypeValue,
options: addWidgetOptions(dragTypeValue, owner, widgetValue),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
dropData.push(dropObj);
placeHolder = { pageNumber: pageNumber, pos: dropData };
}
if (uniqueId) {
let filterSignerPos, currentPagePosition;
const posZIndex = zIndex + 1;
setZIndex(posZIndex);
const key = randomId();
const containerScale = getContainerScale(
pdfOriginalWH,
pageNumber,
containerWH
);
let filterSignerPos,
currentPagePosition,
dropData = [],
dropObj,
placeHolder;
filterSignerPos = signerPos?.find((data) => data.Id === uniqueId);
const dragTypeValue = item?.text ? item.text : monitor.type;
const widgetWidth =
defaultWidthHeight(dragTypeValue).width * containerScale;
const widgetHeight =
defaultWidthHeight(dragTypeValue).height * containerScale;
const extUser = localStorage.getItem("Extand_Class");
const parseUser = extUser && JSON.parse(extUser)[0];
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
//adding and updating drop position in array when user drop signature button in div
if (item === "onclick") {
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
const divWidth = divRef.current.getBoundingClientRect().width;
const divHeight = divRef.current.getBoundingClientRect().height;
// Compute the pixelspace center within the PDF viewport:
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
const xPosition_Final = centerX_Pixels / (containerScale * scale);
dropObj = {
//onclick put placeholder center on pdf
xPosition: xPosition_Final,
yPosition: widgetHeight + divHeight / 2,
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key,
scale: containerScale,
zIndex: posZIndex,
type: dragTypeValue,
options: addWidgetOptions(
dragTypeValue,
owner,
filterSignerPos?.placeHolder,
widgetValue
),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
dropData.push(dropObj);
placeHolder = { pageNumber: pageNumber, pos: dropData };
} else {
const offset = monitor.getClientOffset();
//This method returns the offset of the current pointer (mouse) position relative to the client viewport.
const containerRect = document
.getElementById("container")
.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;
dropObj = {
xPosition: getXPosition / (containerScale * scale),
yPosition: getYPosition / (containerScale * scale),
isStamp:
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
key: key,
scale: containerScale,
zIndex: posZIndex,
type: dragTypeValue,
options: addWidgetOptions(
dragTypeValue,
owner,
filterSignerPos?.placeHolder,
widgetValue
),
Width: widgetWidth / (containerScale * scale),
Height: widgetHeight / (containerScale * scale)
};
dropData.push(dropObj);
placeHolder = { pageNumber: pageNumber, pos: dropData };
}
const getPlaceHolder = filterSignerPos?.placeHolder;
if (getPlaceHolder) {
//checking exist placeholder on same page
@@ -1518,7 +1597,7 @@ function PdfRequestFiles(
};
//function for delete signature block
const handleDeleteSign = (key, Id) => {
const handleDeleteWidget = (key, Id) => {
const updateData = [];
const filterSignerPos = signerPos?.filter((data) => data.Id === Id);
if (filterSignerPos.length > 0) {
@@ -1580,9 +1659,9 @@ function PdfRequestFiles(
}
}
};
//function to get first widget id and page number to assign correct signer and show tour message
//function to get first widget and page number to assign currect signer and tour message
const showFirstWidget = () => {
if (!requestSignTour) {
if (!isReqSignTourDisabled) {
const getCurrentUserPlaceholder = signerPos?.find(
(x) => x.Id === uniqueId
);
@@ -1604,13 +1683,39 @@ function PdfRequestFiles(
setShowSignPagenumber(sortedPagenumber);
}
};
const RedirectNotice = () => {
return !isredirectCanceled && redirectUrl ? (
<div
className="flex flex-row gap-1 items-center justify-center mb-3"
aria-live="polite"
>
<p>{t("redirecting-you-in", { redirectTimeLeft })}</p>
<button
onClick={handleRedirectCancel}
className="underline cursor-pointer op-text-primary focus:outline-none ml-2"
>
{t("cancel")}
</button>
</div>
) : (
<></>
);
};
const handleTourHelp = () => {
setIsReqSignTourDisabled(false);
};
const getSignerEmail = () => {
const isSignerAssigned = unsignedSigners[0]?.objectId;
let email = formData[0]?.email;
if (isSignerAssigned) {
email = unsignedSigners[0]?.Email;
}
return email;
};
return (
<DndProvider backend={HTML5Backend}>
<Title
title={
"Request Sign"
}
/>
<>
{isLoading.isLoad ? (
<LoaderWithMsg isLoading={isLoading} />
) : handleError ? (
@@ -1628,14 +1733,6 @@ function PdfRequestFiles(
showFirstWidget={showFirstWidget}
/>
)}
{isUiLoading && (
<div className="absolute h-[100vh] w-full flex flex-col justify-center items-center z-[999] bg-[#e6f2f2] bg-opacity-80">
<Loader />
<span className="text-[13px] text-base-content">
{t("loading-mssg")}
</span>
</div>
)}
{isCelebration && (
<div className="relative z-[1000]">
<Confetti
@@ -1660,7 +1757,13 @@ function PdfRequestFiles(
: "op-card"
} relative overflow-hidden flex flex-col md:flex-row justify-between bg-base-300`}
>
{!requestSignTour &&
{isUiLoading && (
<div className="absolute h-full w-full flex flex-col justify-center items-center z-[999] bg-[#e6f2f2]/80">
<Loader />
<span className="text-[13px]">{t("loading-mssg")}</span>
</div>
)}
{!isReqSignTourDisabled &&
isAgree &&
signerObjectId &&
!alreadySign &&
@@ -1672,8 +1775,6 @@ function PdfRequestFiles(
onRequestClose={closeTour}
steps={tourConfig}
isOpen={widgetsTour}
rounded={5}
closeWithMask={false}
/>
{/* this modal is used to show decline alert */}
@@ -1759,14 +1860,15 @@ function PdfRequestFiles(
signedUrl={pdfDetails?.[0]?.SignedUrl || ""}
/>
{/* pdf render view */}
<div className=" w-full md:w-[57%] flex mr-4">
<PdfZoom
<div className="w-full md:w-[57%] flex mr-4">
<PdfTools
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
isDisableEditTools={true}
allPages={allPages}
setAllPages={setAllPages}
setPageNumber={setPageNumber}
setIsTour={() => setIsReqSignTourDisabled(true)}
/>
<PlaceholderCopy
isPageCopy={isPageCopy}
@@ -1779,8 +1881,6 @@ function PdfRequestFiles(
Id={uniqueId}
widgetType={currWidgetsDetails?.type}
setUniqueId={setUniqueId}
tempSignerId={tempSignerId}
setTempSignerId={setTempSignerId}
/>
<div className=" w-full md:w-[95%] ">
{/* this modal is used show this document is already sign */}
@@ -1798,20 +1898,8 @@ function PdfRequestFiles(
<div className="h-full p-[20px] text-base-content">
{isCompleted?.message ? (
<>
<p>{isCompleted?.message}</p>
{!isredirectCanceled && redirectUrl && (
<div className="flex flex-row gap-1 items-center justify-center mb-3 mt-2">
<p>
Redirecting you in {redirectTimeLeft} sec...
</p>
<button
onClick={handleRedirectCancel}
className="underline cursor-pointer op-text-primary focus:outline-none ml-2"
>
Cancel
</button>
</div>
)}
<p className="mb-2">{isCompleted?.message}</p>
<RedirectNotice />
</>
) : (
<div className="px-[15px]">
@@ -1820,19 +1908,7 @@ function PdfRequestFiles(
)}
{!isCompleted?.message && (
<div className="flex flex-col mt-3 gap-1 px-[10px] justify-center items-center">
{!isredirectCanceled && redirectUrl && (
<div className="flex flex-row gap-1 items-center justify-center mb-3">
<p>
Redirecting you in {redirectTimeLeft} sec...
</p>
<button
onClick={handleRedirectCancel}
className="underline cursor-pointer op-text-primary focus:outline-none ml-2"
>
Cancel
</button>
</div>
)}
<RedirectNotice />
<div className={`${!redirectUrl ? "m-2" : ""}`}>
<button
onClick={(e) =>
@@ -1945,11 +2021,11 @@ function PdfRequestFiles(
pdfBase64={pdfBase64Url}
isGuestSignFlow={isGuestSignFlow}
/>
<div
ref={divRef}
data-tut="pdfArea"
className="h-full md:h-[95%]"
onClick={() => setIsReqSignTourDisabled(true)}
>
{containerWH && (
<RenderPdf
@@ -1980,17 +2056,18 @@ function PdfRequestFiles(
scale={scale}
uniqueId={uniqueId}
pdfBase64Url={pdfBase64Url}
isAgree={isAgree}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
isDragging={isDragging}
isAlllowModify={pdfDetails[0]?.AllowModifications}
setUniqueId={setUniqueId}
handleDeleteSign={handleDeleteSign}
handleDeleteWidget={handleDeleteWidget}
handleTextSettingModal={handleTextSettingModal}
assignedWidgetId={assignedWidgetId}
setRequestSignTour={setRequestSignTour}
setIsReqSignTourDisabled={handleCloseTour}
currWidgetsDetails={currWidgetsDetails}
setTempSignerId={setTempSignerId}
isShowModal={isShowModal}
/>
)}
</div>
@@ -2015,7 +2092,6 @@ function PdfRequestFiles(
<SignerListComponent
ind={ind}
obj={obj}
isMenu={isHeader}
signerPos={signerPos}
/>
</div>
@@ -2027,25 +2103,24 @@ function PdfRequestFiles(
{unsignedSigners.length > 0 && (
<>
<div
data-tut="reactourFirst"
className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300"
>
<span>{t("yet-to-sign")}</span>
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
<span>
{t("yet-to-sign")}
<sup onClick={handleTourHelp}>
<i className="ml-1 cursor-pointer fa-light fa-question rounded-full border-[1px] border-base-content text-[11px] py-[1px] px-[3px]"></i>
</sup>
</span>
</div>
<div className="mt-[5px]">
{unsignedSigners.map((obj, ind) => {
return (
<div key={ind}>
<SignerListComponent
ind={ind}
obj={obj}
isMenu={isHeader}
signerPos={signerPos}
/>
</div>
);
})}
<div data-tut="reactourFirst" className="mt-[5px]">
{unsignedSigners.map((obj, ind) => (
<div key={ind}>
<SignerListComponent
ind={ind}
obj={obj}
signerPos={signerPos}
/>
</div>
))}
</div>
</>
)}
@@ -2062,6 +2137,7 @@ function PdfRequestFiles(
signatureType?.find((x) => x.name === "default")
?.enabled || false
}
isAgree={isAgree}
/>
)}
</div>
@@ -2097,9 +2173,8 @@ function PdfRequestFiles(
currWidgetsDetails={currWidgetsDetails}
index={pageNumber}
setUniqueId={setUniqueId}
tempSignerId={tempSignerId}
signatureTypes={signatureType}
allowCellResize={pdfDetails[0]?.AllowModifications ?? false}
allowCellResize={false}
/>
)}
<DownloadPdfZip
@@ -2135,7 +2210,7 @@ function PdfRequestFiles(
handleSaveFontSize={handleSaveFontSize}
currWidgetsDetails={currWidgetsDetails}
/>
</DndProvider>
</>
);
}
export default PdfRequestFiles;
File diff suppressed because it is too large Load Diff
+23 -6
View File
@@ -1,5 +1,4 @@
import React, { useEffect, useState, useRef } from "react";
import Title from "../components/Title";
import Alert from "../primitives/Alert";
import { useTranslation } from "react-i18next";
import Loader from "../primitives/Loader";
@@ -12,7 +11,6 @@ import {
} from "../constant/Utils";
import Parse from "parse";
import { Tooltip as ReactTooltip } from "react-tooltip";
import TimezoneSelector from "../components/shared/fields/TimezoneSelector";
import ReactQuill from "react-quill-new";
import "../styles/quill.css";
import EditorToolbar, {
@@ -20,7 +18,9 @@ import EditorToolbar, {
module2,
formats
} from "../components/pdf/EditorToolbar";
import DateFormatSelector from "../components/shared/fields/DateFormatSelector";
import TimezoneSelector from "../components/preferences/TimezoneSelector";
import DateFormatSelector from "../components/preferences/DateFormatSelector";
import FilenameFormatSelector from "../components/preferences/FilenameFormatSelector";
const Preferences = () => {
const appName =
@@ -60,6 +60,7 @@ const Preferences = () => {
requestMail: false,
completionMail: false
});
const [fileNameFormat, setFileNameFormat] = useState("DOCNAME");
useEffect(() => {
fetchSignType();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -125,6 +126,9 @@ const Preferences = () => {
const isLTVEnabled =
_getUser?.IsLTVEnabled !== undefined ? _getUser?.IsLTVEnabled : false;
setIsLTVEnabled(isLTVEnabled);
const downloadFilenameFormat =
_getUser?.DownloadFilenameFormat || "DOCNAME";
setFileNameFormat(downloadFilenameFormat);
}
} catch (err) {
console.log("err while getting user details", err);
@@ -164,11 +168,17 @@ const Preferences = () => {
type: "danger",
msg: t("at-least-one-signature-type")
});
setIsLoader(false);
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
return;
} else if (isDefaultSignTypeOnly) {
setIsAlert({
type: "danger",
msg: t("expect-default-one-more-signature-type")
msg: t("expect-default-one-signature-type")
});
setIsLoader(false);
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
return;
} else {
params = { ...params, SignatureType: signatureType };
}
@@ -183,7 +193,8 @@ const Preferences = () => {
IsTourEnabled: isTourEnabled,
DateFormat: dateFormat,
Is12HourTime: is12HourTime,
IsLTVEnabled: isLTVEnabled
IsLTVEnabled: isLTVEnabled,
DownloadFilenameFormat: fileNameFormat
};
const updateRes = await Parse.Cloud.run("updatepreferences", params);
if (updateRes) {
@@ -197,6 +208,7 @@ const Preferences = () => {
extUser.IsTourEnabled = isTourEnabled;
extUser.DateFormat = dateFormat;
extUser.Is12HourTime = is12HourTime;
extUser.DownloadFilenameFormat = fileNameFormat;
const _extUser = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
}
@@ -389,7 +401,6 @@ const Preferences = () => {
};
return (
<React.Fragment>
<Title title={t("Preferences")} />
{isalert.msg && <Alert type={isalert.type}>{isalert.msg}</Alert>}
{isTopLoader ? (
<div className="flex justify-center items-center h-screen">
@@ -761,6 +772,12 @@ const Preferences = () => {
setDateFormat={setDateFormat}
/>
</div>
<div className="mb-6">
<FilenameFormatSelector
fileNameFormat={fileNameFormat}
setFileNameFormat={setFileNameFormat}
/>
</div>
</div>
{/* Save Button - Full Width */}
<div className="md:col-span-12 flex justify-start mt-2">
+85 -73
View File
@@ -1,19 +1,20 @@
import React, { useEffect, useState, useRef } from "react";
import ReportTable from "../primitives/GetReportDisplay";
import { useEffect, useState, useRef } from "react";
import Parse from "parse";
import axios from "axios";
import reportJson from "../json/ReportJson";
import reportJson, { extraCols } from "../json/ReportJson";
import { useParams } from "react-router";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import Contactbook from "../reports/contact/Contactbook";
import ColumnSelector from "../components/ColumnSelector";
import TemplatesReport from "../reports/template/TemplatesReport";
import DocumentsReport from "../reports/document/DocumentsReport";
import { templateReportTour } from "../json/ReportTour";
const Report = () => {
const { t } = useTranslation();
const { id } = useParams();
const [List, setList] = useState([]);
const abortController = new AbortController();
const [list, setList] = useState([]);
const [isLoader, setIsLoader] = useState(true);
const [reportName, setReportName] = useState("");
const [reporthelp, setReportHelp] = useState("");
@@ -21,16 +22,17 @@ const Report = () => {
const [heading, setHeading] = useState([]);
const [isNextRecord, setIsNextRecord] = useState(false);
const [isMoreDocs, setIsMoreDocs] = useState(true);
const [form, setForm] = useState("");
const [tourData, setTourData] = useState([]);
const [isDontShow, setIsDontShow] = useState(false);
const [isImport, setIsImport] = useState(false);
const abortController = new AbortController();
const docPerPage = 10;
const [searchTerm, setSearchTerm] = useState("");
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
const [isSearchResult, setIsSearchResult] = useState(false);
const [allColumns, setAllColumns] = useState([]);
const [visibleColumns, setVisibleColumns] = useState([]);
const [columnLabels, setColumnLabels] = useState({});
const [defaultColumns, setDefaultColumns] = useState([]);
const [isColumnModal, setIsColumnModal] = useState(false);
const debounceTimer = useRef(null);
const docPerPage = 20;
// below useEffect is call when id param change
useEffect(() => {
@@ -38,6 +40,14 @@ const Report = () => {
setList([]);
setSearchTerm("");
setMobileSearchOpen(false);
const saved = JSON.parse(localStorage.getItem("reportColumns") || "{}");
if (saved[id]) {
setVisibleColumns(saved[id].visible || saved[id]);
setColumnLabels(saved[id].labels || {});
} else {
setVisibleColumns([]);
setColumnLabels({});
}
getReportData(0, docPerPage, "");
// Function returned from useEffect is called on unmount
@@ -54,15 +64,11 @@ const Report = () => {
// below useEffect call when isNextRecord state is true and fetch next record
useEffect(() => {
if (isNextRecord) {
getReportData(List.length, 20, searchTerm);
getReportData(list.length, 20, searchTerm);
}
// eslint-disable-next-line
}, [isNextRecord]);
const handleDontShow = (isChecked) => {
setIsDontShow(isChecked);
};
const handleSearchChange = async (e) => {
const term = e.target.value.toLowerCase();
setSearchTerm(term);
@@ -118,11 +124,21 @@ const Report = () => {
const json = reportJson(id);
if (json) {
setActions(json.actions);
setHeading(json.heading);
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);
setForm(json.form);
setReportHelp(json?.helpMsg);
setIsImport(json?.import || false);
const currentUser = Parse.User.current().id;
const headers = {
@@ -142,39 +158,11 @@ const Report = () => {
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") {
const tourConfig = [
{
selector: "[data-tut=reactourFirst]",
content: () => (
<TourContentWithBtn
message={t("tour-mssg.report-1")}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
}
];
if (res.data.result && res.data.result?.length > 0) {
json.actions.map((data) => {
const newConfig = {
selector: `[data-tut="${data?.selector}"]`,
content: () => (
<TourContentWithBtn
message={t(`tour-mssg.${data?.action}`)}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
};
tourConfig.push(newConfig);
});
}
setTourData(tourConfig);
setTourData(templateReportTour);
}
if (id === "4Hhwbp482K") {
const listData = res.data?.result.filter((x) => x.Signers.length > 0);
@@ -234,42 +222,66 @@ const Report = () => {
setIsLoader(false);
}
};
const commonProps = {
ReportName: reportName,
List: list,
setList,
actions: actions,
heading: heading,
setIsNextRecord,
isMoreDocs,
docPerPage,
mobileSearchOpen,
setMobileSearchOpen,
searchTerm,
handleSearchChange,
handleSearchPaste,
isSearchResult,
columnLabels,
openColumnModal: () => setIsColumnModal(true)
};
return (
<>
<Title title={reportName} />
{isLoader ? (
<div className="h-[100vh] flex justify-center items-center">
<Loader />
</div>
) : (
<>
{reportName ? (
<ReportTable
ReportName={reportName}
List={List}
setList={setList}
actions={actions}
heading={heading}
setIsNextRecord={setIsNextRecord}
isMoreDocs={isMoreDocs}
docPerPage={docPerPage}
form={form}
{id === "contacts" ? (
<Contactbook {...commonProps} />
) : id === "6TeaPr321t" ? (
<TemplatesReport
{...commonProps}
report_help={reporthelp}
tourData={tourData}
isDontShow={isDontShow}
mobileSearchOpen={mobileSearchOpen}
setMobileSearchOpen={setMobileSearchOpen}
searchTerm={searchTerm}
handleSearchChange={handleSearchChange}
handleSearchPaste={handleSearchPaste}
isSearchResult={isSearchResult}
isImport={isImport}
/>
) : reportName ? (
<DocumentsReport {...commonProps} report_help={reporthelp} />
) : (
<PageNotFound prefix={"Report"} />
)}
</>
)}
<ColumnSelector
isOpen={isColumnModal}
allColumns={allColumns}
visibleColumns={visibleColumns}
columnLabels={columnLabels}
defaultColumns={defaultColumns}
onApply={(cols, labels) => {
setVisibleColumns(cols);
setHeading(cols);
setColumnLabels(labels);
const saved = JSON.parse(
localStorage.getItem("reportColumns") || "{}"
);
saved[id] = { visible: cols, labels };
localStorage.setItem("reportColumns", JSON.stringify(saved));
}}
onClose={() => setIsColumnModal(false)}
/>
</>
);
};
+79 -78
View File
@@ -5,8 +5,6 @@ import Parse from "parse";
import Confetti from "react-confetti";
import axios from "axios";
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { useDrop } from "react-dnd";
import EmailComponent from "../components/pdf/EmailComponent";
import WidgetComponent from "../components/pdf/WidgetComponent";
@@ -14,7 +12,7 @@ import {
getTenantDetails,
contractDocument,
embedDocId,
multiSignEmbed,
embedWidgetsToDoc,
calculateInitialWidthHeight,
defaultWidthHeight,
contractUsers,
@@ -47,12 +45,11 @@ import Signedby from "../components/pdf/Signedby";
import Header from "../components/pdf/PdfHeader";
import RenderPdf from "../components/pdf/RenderPdf";
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
import Title from "../components/Title";
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
import { useDispatch, useSelector } from "react-redux";
import TextFontSetting from "../components/pdf/TextFontSetting";
import VerifyEmail from "../components/pdf/VerifyEmail";
import PdfZoom from "../components/pdf/PdfZoom";
import PdfTools from "../components/pdf/PdfTools";
import { useTranslation } from "react-i18next";
import RotateAlert from "../components/RotateAlert";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
@@ -124,7 +121,7 @@ function SignYourSelf() {
const openCellsSettingModal = () => setIsCellsSetting(true);
const [pdfLoad, setPdfLoad] = useState(false);
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isDontShow, setIsDontShow] = useState(false);
const [isDontShow, setIsDontShow] = useState(true);
const [isCompleted, setIsCompleted] = useState(false);
const [isCelebration, setIsCelebration] = useState(false);
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
@@ -132,7 +129,7 @@ function SignYourSelf() {
const [isVerifyModal, setIsVerifyModal] = useState(false);
const [otp, setOtp] = useState("");
const [zoomPercent, setZoomPercent] = useState(0);
const isHeader = useSelector((state) => state.showHeader);
const isSidebar = useSelector((state) => state.sidebar.isOpen);
const [scale, setScale] = useState(1);
const [pdfBase64Url, setPdfBase64Url] = useState("");
const [showRotateAlert, setShowRotateAlert] = useState({
@@ -195,7 +192,7 @@ function SignYourSelf() {
const timer = setTimeout(updateSize, 100); // match the transition duration
return () => clearTimeout(timer);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [divRef.current, isHeader]);
}, [divRef.current, isSidebar]);
//function for get document details for perticular signer with signer'object id
const getDocumentDetails = async (showComplete) => {
@@ -281,18 +278,14 @@ function SignYourSelf() {
} else if (contractUsersRes[0] && contractUsersRes.length > 0) {
setContractName("_Users");
setSignerUserId(contractUsersRes[0].objectId);
const tourstatuss =
contractUsersRes[0].TourStatus && contractUsersRes[0].TourStatus;
if (tourstatuss && tourstatuss.length > 0 && !isCompleted) {
setTourStatus(tourstatuss);
const checkTourRecipients = tourstatuss.filter(
(data) => data.signyourself
);
if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].signyourself);
}
const tourstatus = contractUsersRes?.[0]?.TourStatus || [];
if (tourstatus && tourstatus.length > 0 && !isCompleted) {
setTourStatus(tourstatus);
const tour = tourstatus?.some((data) => data.signyourself) || false;
setSignTour(!tour);
setCheckTourStatus(tour);
} else {
setCheckTourStatus(false);
setSignTour(true);
}
setIsLoading({ isLoad: false });
} else if (contractUsersRes.length === 0) {
@@ -300,28 +293,19 @@ function SignYourSelf() {
if (contractContactBook && contractContactBook.length > 0) {
setContractName("_Contactbook");
setSignerUserId(contractContactBook[0].objectId);
const tourstatuss =
contractContactBook[0].TourStatus &&
contractContactBook[0].TourStatus;
if (tourstatuss && tourstatuss.length > 0 && !isCompleted) {
setTourStatus(tourstatuss);
const checkTourRecipients = tourstatuss.filter(
(data) => data.signyourself
);
if (checkTourRecipients && checkTourRecipients.length > 0) {
setCheckTourStatus(checkTourRecipients[0].signyourself);
}
const tourstatus = contractContactBook?.[0]?.TourStatus || [];
if (tourstatus && tourstatus.length > 0 && !isCompleted) {
setTourStatus(tourstatus);
const tour = tourstatus?.some((data) => data.signyourself) || false;
setSignTour(!tour);
setCheckTourStatus(tour);
} else {
setCheckTourStatus(true);
setSignTour(true);
}
} else {
setHandleError(t("no-data-avaliable"));
}
const loadObj = {
isLoad: false
};
setIsLoading(loadObj);
setIsLoading({ isLoad: false });
}
} catch (err) {
console.log("Error: error in getDocumentDetails", err);
@@ -347,7 +331,6 @@ function SignYourSelf() {
return "";
}
};
//function for setting position after drop signature button over pdf
const addPositionOfSignature = (item, monitor) => {
setCurrWidgetsDetails({});
@@ -394,7 +377,12 @@ function SignYourSelf() {
scale: containerScale,
Width: getWidth,
Height: getHeight,
options: addWidgetSelfsignOptions(dragTypeValue, getWidgetValue, owner)
options: addWidgetSelfsignOptions(
dragTypeValue,
getWidgetValue,
owner,
xyPosition
)
};
dropData.push(dropObj);
} else {
@@ -423,7 +411,12 @@ function SignYourSelf() {
type: dragTypeValue,
Width: getWidth / (containerScale * scale),
Height: getHeight / (containerScale * scale),
options: addWidgetSelfsignOptions(dragTypeValue, getWidgetValue, owner),
options: addWidgetSelfsignOptions(
dragTypeValue,
getWidgetValue,
owner,
xyPosition
),
scale: containerScale
};
dropData.push(dropObj);
@@ -502,7 +495,10 @@ function SignYourSelf() {
};
useEffect(() => {
const timer = setTimeout(() => {
if (!pdfDetails?.[0]?.IsCompleted) {
if (
!pdfDetails?.[0]?.IsCompleted &&
pdfDetails?.[0]?.CreatedBy?.objectId === jsonSender?.objectId
) {
autosavedetails();
}
}, 2000);
@@ -643,7 +639,7 @@ function SignYourSelf() {
await embedDocId(pdfOriginalWH, pdfDoc, documentId);
}
//embed all widgets in document
const pdfBytes = await multiSignEmbed(
const pdfBytes = await embedWidgetsToDoc(
xyPosition,
pdfDoc,
isSignYourSelfFlow,
@@ -835,7 +831,7 @@ function SignYourSelf() {
setSignBtnPosition([xySignature]);
};
//function for delete signature block
const handleDeleteSign = (key) => {
const handleDeleteWidget = (key) => {
setCurrWidgetsDetails({});
const updateResizeData = [];
let filterData = xyPosition[index].pos.filter((data) => data.key !== key);
@@ -872,6 +868,7 @@ function SignYourSelf() {
content: () => (
<TourContentWithBtn
message={t("tour-mssg.signyour-self-1")}
isDontShowCheckbox={!checkTourStatus}
isChecked={handleDontShow}
/>
),
@@ -883,6 +880,19 @@ function SignYourSelf() {
content: () => (
<TourContentWithBtn
message={t("tour-mssg.signyour-self-2")}
isDontShowCheckbox={!checkTourStatus}
isChecked={handleDontShow}
/>
),
position: "top",
style: { fontSize: "13px" }
},
{
selector: '[data-tut="pdftools"]',
content: () => (
<TourContentWithBtn
message={t("pdf-tools-tour")}
isDontShowCheckbox={!checkTourStatus}
isChecked={handleDontShow}
/>
),
@@ -894,7 +904,8 @@ function SignYourSelf() {
//function for update TourStatus
const closeTour = async () => {
setSignTour(false);
if (isDontShow) {
setIsDontShow(true);
if (!checkTourStatus && isDontShow) {
let updatedTourStatus = [];
if (tourStatus.length > 0) {
updatedTourStatus = [...tourStatus];
@@ -1184,22 +1195,13 @@ function SignYourSelf() {
setShowRotateAlert({ status: false, degree: 0 });
};
return (
<DndProvider backend={HTML5Backend}>
<Title title={"Signyourself"} />
<>
{isLoading.isLoad ? (
<LoaderWithMsg isLoading={isLoading} />
) : handleError ? (
<HandleError handleError={handleError} />
) : (
<div>
{isUiLoading && (
<div className="absolute h-[100vh] w-full z-[999] flex flex-col justify-center items-center bg-[#e6f2f2] bg-opacity-80">
<Loader />
<span style={{ fontSize: "13px", fontWeight: "bold" }}>
{t("loader")}
</span>
</div>
)}
{isCelebration && (
<div className="relative z-[1000]">
<Confetti
@@ -1211,6 +1213,12 @@ function SignYourSelf() {
</div>
)}
<div className="relative op-card overflow-hidden flex flex-col md:flex-row justify-between bg-base-300">
{isUiLoading && (
<div className="absolute h-full w-full z-[999] flex flex-col justify-center items-center bg-[#e6f2f2]/80">
<Loader />
<span className="text-[13px]">{t("loader")}</span>
</div>
)}
{!isEmailVerified && (
<VerifyEmail
isVerifyModal={isVerifyModal}
@@ -1224,18 +1232,14 @@ function SignYourSelf() {
/>
)}
{/* this component used for UI interaction and show their functionality */}
{pdfLoad && !checkTourStatus && !isCompleted && (
{pdfLoad && !isCompleted && (
<Tour
onRequestClose={closeTour}
steps={tourConfig}
isOpen={signTour}
rounded={5}
closeWithMask={false}
/>
)}
{/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage
allPages={allPages}
setAllPages={setAllPages}
@@ -1251,8 +1255,8 @@ function SignYourSelf() {
pdfArrayBuffer={pdfArrayBuffer}
isMergePdfBtn={!pdfDetails?.[0]?.IsCompleted}
/>
<div className=" w-full md:w-[57%] flex mr-4">
<PdfZoom
<div className="w-full md:w-[57%] flex mr-4">
<PdfTools
clickOnZoomIn={clickOnZoomIn}
clickOnZoomOut={clickOnZoomOut}
handleRotationFun={handleRotationFun}
@@ -1280,7 +1284,6 @@ function SignYourSelf() {
<p>{isAlert.alertMessage}</p>
</div>
</ModalUi>
{/* this modal is used show this document is already sign */}
<ModalUi
isOpen={showAlreadySignDoc.status}
@@ -1291,7 +1294,7 @@ function SignYourSelf() {
<p>{showAlreadySignDoc.mssg}</p>
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<button
className="op-btn op-btn-ghost shadow-md"
className="op-btn op-btn-ghost text-base-content shadow-md"
onClick={() => setShowAlreadySignDoc({ status: false })}
>
{t("close")}
@@ -1370,7 +1373,7 @@ function SignYourSelf() {
handleTabDrag={handleTabDrag}
handleStop={handleStop}
isDragging={isDragging}
handleDeleteSign={handleDeleteSign}
handleDeleteWidget={handleDeleteWidget}
pdfDetails={pdfDetails}
setIsDragging={setIsDragging}
xyPosition={xyPosition}
@@ -1401,6 +1404,7 @@ function SignYourSelf() {
setIsResize={setIsResize}
divRef={divRef}
currWidgetsDetails={currWidgetsDetails}
isShowModal={isShowModal}
/>
)}
</div>
@@ -1409,21 +1413,18 @@ function SignYourSelf() {
<div className="w-full md:w-[23%] bg-base-100 overflow-y-auto hide-scrollbar">
<div className={`max-h-screen`}>
{!isCompleted ? (
<div>
<WidgetComponent
pdfUrl={pdfUrl}
handleDivClick={handleDivClick}
handleMouseLeave={handleMouseLeave}
xyPosition={xyPosition}
isSignYourself={true}
addPositionOfSignature={addPositionOfSignature}
isMailSend={false}
/>
</div>
<WidgetComponent
pdfUrl={pdfUrl}
handleDivClick={handleDivClick}
handleMouseLeave={handleMouseLeave}
xyPosition={xyPosition}
isSignYourself={true}
addPositionOfSignature={addPositionOfSignature}
isMailSend={false}
setIsTour={setSignTour}
/>
) : (
<div>
<Signedby pdfDetails={pdfDetails[0]} />
</div>
<Signedby pdfDetails={pdfDetails[0]} />
)}
</div>
</div>
@@ -1484,7 +1485,7 @@ function SignYourSelf() {
handleSaveFontSize={handleSaveFontSize}
currWidgetsDetails={currWidgetsDetails}
/>
</DndProvider>
</>
);
}
File diff suppressed because it is too large Load Diff
@@ -1,9 +1,8 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import Loader from "../primitives/Loader";
import Parse from "parse";
import { NavLink, useNavigate } from "react-router";
import Alert from "../primitives/Alert";
import Title from "../components/Title";
import { useTranslation } from "react-i18next";
import { emailRegex } from "../constant/const";
const UpdateExistUserAdmin = () => {
@@ -39,7 +38,7 @@ const UpdateExistUserAdmin = () => {
e.preventDefault();
e.stopPropagation();
if (!emailRegex.test(formdata.email)) {
alert("Please enter a valid email address.");
alert(t("valid-email-alert"));
} else {
setIsSubmitLoading(true);
try {
@@ -73,7 +72,6 @@ const UpdateExistUserAdmin = () => {
};
return (
<div className="h-screen flex justify-center">
<Title title={"Add Admin"} />
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
{loader ? (
<div className="text-[grey] flex justify-center items-center text-lg md:text-2xl">
+128 -2
View File
@@ -7,10 +7,23 @@ import ModalUi from "../primitives/ModalUi";
import pad from "../assets/images/pad.svg";
import Tooltip from "../primitives/Tooltip";
import AddUser from "../components/AddUser";
import Title from "../components/Title";
import {
useTranslation
} from "react-i18next";
import DeleteUserModal from "../primitives/DeleteUserModal";
import axios from "axios";
const actions = [
{
btnId: "4741",
hoverLabel: "Delete",
btnColor: "op-btn-secondary",
btnIcon: "fa-light fa-trash",
redirectUrl: "",
action: "delete",
restrictAdmin: true
},
];
const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"];
const UserList = () => {
const { t } = useTranslation();
@@ -30,6 +43,11 @@ const UserList = () => {
const [isActLoader, setIsActLoader] = useState({});
const [isAdmin, setIsAdmin] = useState(false);
const [formHeader, setFormHeader] = useState(t("add-user"));
const [deleteUserRes, setDeleteUserRes] = useState("");
const [deleting, setDeleting] = useState(false);
const [isActModal, setIsActModal] = useState({});
const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class);
const recordperPage = 10;
const startIndex = (currentPage - 1) * recordperPage; // user per page
@@ -194,9 +212,59 @@ const UserList = () => {
setIsAlert({ type, msg });
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
};
const handleDeleteAccount = async (item) => {
setDeleting(true);
if (item?.UserId?.objectId) {
const url = localStorage.getItem("baseUrl")?.replace(/\/app\/?$/, "/");
const deleteUrl = `${url}deleteuser/${item.UserId.objectId}`;
try {
await axios.post(deleteUrl, null, {
headers: { sessiontoken: localStorage.getItem("accesstoken") }
});
setUserList((prev) =>
prev.filter((user) => user.objectId !== item.objectId)
);
showAlert("success", t("user-deleted-successfully"));
} catch (err) {
const message = err?.response?.data?.message || err?.message;
setDeleteUserRes(message);
showAlert("danger", message);
console.log("Err in deleteuser acc", err);
} finally {
setDeleting(false);
}
} else {
showAlert("danger", t("something-went-wrong-mssg"));
setDeleteUserRes(t("something-went-wrong-mssg"));
setDeleting(false);
}
};
const handleCloseModal = () => {
setIsActModal({});
setDeleteUserRes("");
setDeleting(false);
};
const handleActionBtn = async (act, item) => {
if (act.action === "delete") {
setIsActModal({ [`delete_${item.objectId}`]: true });
}
};
const handleBtnVisibility = (act, item) => {
if (act.restrictAdmin) {
return item?.objectId !== extClass?.[0]?.objectId;
} else if (
act.restrictBtn === true &&
item?.objectId === extClass?.[0]?.objectId
) {
return true;
} else {
return true;
}
};
return (
<div className="relative">
<Title title={isAdmin ? "Users" : "Page not found"} />
{isLoader && (
<div className="absolute w-full h-[300px] md:h-[400px] flex justify-center items-center z-30 rounded-box">
<Loader />
@@ -241,6 +309,11 @@ const UserList = () => {
{t(`report-heading.${item}`)}
</th>
))}
{actions?.length > 0 && (
<th className="p-2 text-transparent pointer-events-none">
{t("action")}
</th>
)}
</tr>
</thead>
{userList?.length > 0 && (
@@ -315,6 +388,59 @@ const UserList = () => {
) : (
<td className="px-4 py-2 font-semibold"></td>
)}
{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) => (
<React.Fragment key={index}>
{handleBtnVisibility(act, item) && (
<div
role="button"
data-tut={act?.selector}
onClick={() =>
handleActionBtn(act, item)
}
title={t(
`btnLabel.${act.hoverLabel}`
)}
className={
act.action !== "option"
? `${act?.btnColor || ""} op-btn op-btn-sm mr-1 `
: "text-base-content focus:outline-none text-lg mr-2 relative"
}
>
<i className={act.btnIcon}></i>
{act.btnLabel && (
<span className="uppercase font-medium">
{t(
`btnLabel.${act.btnLabel}`
)}
</span>
)}
</div>
)}
<DeleteUserModal
title={t("delete-account")}
deleting={deleting}
userEmail={item?.Email}
isOpen={
isActModal[
"delete_" + item.objectId
]
}
onConfirm={() =>
handleDeleteAccount(item)
}
deleteRes={deleteUserRes}
handleClose={handleCloseModal}
/>
</React.Fragment>
))}
</div>
</td>
)}
</tr>
))}
</tbody>
+93 -11
View File
@@ -6,8 +6,7 @@ import { Navigate, useNavigate } from "react-router";
import Parse from "parse";
import { SaveFileSize } from "../constant/saveFileSize";
import dp from "../assets/images/dp.png";
import Title from "../components/Title";
import sanitizeFileName from "../primitives/sanitizeFileName";
import { sanitizeFileName } from "../utils";
import axios from "axios";
import Tooltip from "../primitives/Tooltip";
import {
@@ -22,8 +21,12 @@ import SelectLanguage from "../components/pdf/SelectLanguage";
function UserProfile() {
const navigate = useNavigate();
const { t } = useTranslation();
let UserProfile = JSON.parse(localStorage.getItem("UserInformation"));
let extendUser = JSON.parse(localStorage.getItem("Extand_Class"));
let UserProfile =
localStorage.getItem("UserInformation") &&
JSON.parse(localStorage.getItem("UserInformation"));
let extendUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"));
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [editmode, setEditMode] = useState(false);
@@ -42,6 +45,9 @@ function UserProfile() {
const [otp, setOtp] = useState("");
const [otpLoader, setOtpLoader] = useState(false);
const [isEmailVerified, setIsEmailVerified] = useState(false);
const [isdeleteModal, setIsdeleteModal] = useState(false);
const [deleteUserRes, setDeleteUserRes] = useState("");
const [isDelLoader, setIsDelLoader] = useState(false);
useEffect(() => {
getUserDetail();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -183,7 +189,8 @@ function UserProfile() {
setImage(fileRes?.url);
setpercentage(0);
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(size, fileRes?.url, tenantId);
const userId = extendUser?.[0]?.UserId?.objectId;
SaveFileSize(size, fileRes?.url, tenantId, userId);
return fileRes?.url;
}
}
@@ -249,9 +256,37 @@ function UserProfile() {
setJobTitle(extendUser?.[0]?.JobTitle);
};
const handleDeleteAccountBtn = () => {
const isAdmin = extendUser?.[0]?.UserRole === "contracts_Admin";
if (!isAdmin) {
setDeleteUserRes(t("delete-action-prohibited"));
}
setIsdeleteModal(true);
};
const handleDeleteAccount = async (e) => {
e.preventDefault();
setIsDelLoader(true);
try {
await Parse.Cloud.run("senddeleterequest", {
userId: Parse.User.current().id
});
setDeleteUserRes(t("account-deletion-request-sent-via-mail"));
} catch (err) {
setDeleteUserRes(err.message);
console.log("Err in deleteuser acc", err);
} finally {
setIsDelLoader(false);
}
};
const handleCloseDeleteModal = () => {
setIsdeleteModal(false);
setDeleteUserRes("");
};
return (
<React.Fragment>
<Title title={"Profile"} />
{isLoader ? (
<div className="h-[100vh] flex justify-center items-center">
<Loader />
@@ -383,15 +418,15 @@ function UserProfile() {
<span className="font-semibold">{t("is-email-verified")}:</span>{" "}
<span>
{isEmailVerified ? (
"Verified"
t("verified")
) : (
<span>
Not verified(
{t("not-verified")} (
<span
onClick={() => handleVerifyBtn()}
className="hover:underline text-blue-600 cursor-pointer"
>
verify
{t("verify")}
</span>
)
</span>
@@ -410,13 +445,13 @@ function UserProfile() {
/>
</li>
</ul>
<div className="flex justify-center gap-2 pt-2 pb-3 md:pt-3 md:pb-4">
<div className="flex flex-col md:flex-row justify-center gap-2 pt-2 pb-3 md:pt-3 md:pb-4 mx-2 md:mx-0">
<button
type="button"
onClick={(e) => {
editmode ? handleSubmit(e) : setEditMode(true);
}}
className="op-btn op-btn-primary w-[100px]"
className="op-btn op-btn-primary md:w-[100px]"
>
{editmode ? t("save") : t("edit")}
</button>
@@ -431,8 +466,55 @@ function UserProfile() {
>
{editmode ? t("cancel") : t("change-password")}
</button>
<button
onClick={() => handleDeleteAccountBtn()}
className="op-link op-link-accent text-sm mx-2"
>
{t("delete-account")}
</button>
</div>
</div>
{isdeleteModal && (
<ModalUi
isOpen
title={t("delete-account")}
handleClose={handleCloseDeleteModal}
>
{isDelLoader ? (
<div className="h-[100px] flex justify-center items-center">
<Loader />
</div>
) : (
<>
{deleteUserRes ? (
<div className="h-[100px] p-[20px] flex justify-center items-center text-sm md:text-base">
{deleteUserRes}
</div>
) : (
<form onSubmit={(e) => handleDeleteAccount(e)}>
<div className="px-6 py-3 text-base-content text-sm md:text-base">
{t("delete-account-que")}
</div>
<div className="px-6 mb-3">
<button
type="submit"
className="op-btn op-btn-primary w-[100px]"
>
{t("yes")}
</button>
<button
className="op-btn op-btn-secondary ml-2 w-[100px]"
onClick={handleCloseDeleteModal}
>
{t("cancel")}
</button>
</div>
</form>
)}
</>
)}
</ModalUi>
)}
{isVerifyModal && (
<ModalUi
isOpen
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More