feat: create duplicate template functionality
feat: add support of csv and xlsx for bulk contacts import
feat: add info text for forms and dashboard button
feat: implement functionality to delete a PDF page while editing the document
feat: show completed documents in which signers included but not owner in completed reports
feat: add delete folder button in opensign drive
feat: introduce Bcc email support for sending completed documents
feat: allow merging multiple pdf while drafting document and template
feat: add 'My Initials' tab for auto-signing in request-sign flow
feat: add support for redirect URL to navigate after document completion
feat: introduce privacy policy and digital signature terms before signing documents.
feat: add feature to allow adding new pages to existing document
feat: add save signature, initials, stamp functionality in sign pad for logged in user
feat: add preferences menu
feat: add save custom email template, notifyonsignature, set timezone, allow signature types in preferences
feat: secure local url
feat: implement Italian language translation
feat: implement German language translation
feat: update menu name from report to documents and shift contactbook in main menu
feat: provide edit contact functionality

fix: unable to delete folder when all its documents are deleted
fix: fields.push is not function
fix: document loading issue in opensign drive
fix: adjust the guest signature flow to display the document in full screen, eliminating any blank space which is displayed below the place holder in mobile view
fix: resolve issue of instance of pdfdict or pdfstream but got undefined

build(deps): update dependencies

refactor: change note text from add contact form
This commit is contained in:
prafull-opensignlabs
2025-02-10 14:41:16 +00:00
parent b55aff20d3
commit e299891174
260 changed files with 22438 additions and 25112 deletions
Executable → Regular
View File
+4 -3
View File
@@ -14,14 +14,15 @@ RUN npm install
COPY apps/OpenSign/ .
COPY apps/OpenSign/.husky .
# Define environment variables if needed
ENV NODE_ENV=production
ENV REACT_APP_DEPLOYMENT=free_selfhost
# build
RUN npm run build
# Make port 3000 available to the world outside this container
EXPOSE 3000
# Define environment variables if needed
# ENV NODE_ENV production
# Run the application
CMD ["npm", "start"]
+1526 -902
View File
File diff suppressed because it is too large Load Diff
+39 -39
View File
@@ -4,16 +4,16 @@
"private": true,
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@lottiefiles/dotlottie-react": "^0.10.0",
"@lottiefiles/dotlottie-react": "^0.12.3",
"@pdf-lib/fontkit": "^1.1.1",
"@radix-ui/themes": "^3.1.6",
"@react-pdf/renderer": "^4.1.4",
"@reduxjs/toolkit": "^2.3.0",
"axios": "^1.7.7",
"@react-pdf/renderer": "^4.1.6",
"@reduxjs/toolkit": "^2.5.1",
"axios": "^1.7.9",
"file-saver": "^2.0.5",
"i18next": "^23.16.5",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.2",
"i18next": "^23.16.8",
"i18next-browser-languagedetector": "^8.0.2",
"i18next-http-backend": "^3.0.1",
"jszip": "^3.10.1",
"jwt-decode": "^4.0.0",
"moment": "^2.30.1",
@@ -23,29 +23,30 @@
"print-js": "^1.6.0",
"radix-ui": "^1.0.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.5",
"react-confetti": "^6.1.0",
"react-bootstrap": "^2.10.9",
"react-confetti": "^6.2.2",
"react-cookie": "^7.2.2",
"react-datepicker": "^7.5.0",
"react-datepicker": "^7.6.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-multi-backend": "^8.0.3",
"react-dnd-multi-backend": "^9.0.0",
"react-dnd-touch-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-gtm-module": "^2.0.11",
"react-helmet": "^6.1.0",
"react-i18next": "^15.1.1",
"react-i18next": "^15.4.0",
"react-konva": "^18.2.10",
"react-pdf": "^9.1.1",
"react-pdf": "^9.2.1",
"react-quill-new": "^3.3.3",
"react-redux": "^9.1.2",
"react-rnd": "^10.4.13",
"react-router-dom": "^6.28.0",
"react-redux": "^9.2.0",
"react-rnd": "^10.4.14",
"react-router": "^7.1.5",
"react-scripts": "5.0.1",
"react-scrollbars-custom": "^4.1.1",
"react-select": "^5.8.3",
"react-signature-canvas": "^1.0.6",
"react-select": "^5.10.0",
"react-signature-canvas": "^1.0.7",
"react-syntax-highlighter": "^15.6.1",
"react-timezone-select": "^3.2.8",
"react-tooltip": "^5.28.0",
"react-web-share": "^2.0.2",
"reactour": "^1.19.4",
@@ -55,17 +56,16 @@
"serve": "^14.2.4",
"styled-components": "^5.3.0",
"web-vitals": "^4.2.4",
"ws": "^8.18.0"
"ws": "^8.18.0",
"xlsx": "^0.18.5"
},
"scripts": {
"build-webpack": "webpack --config webpack.config.js",
"build": "npm run version && npm run build-webpack && react-scripts build",
"build-watch": "webpack --config webpack.config.js --watch",
"build": "npm run version && react-scripts build",
"start-dev": "react-scripts start",
"start": "serve -s build",
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
"build-win": "npm run version-win && npm run build-webpack && react-scripts build",
"build-win": "npm run version-win && react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"release": "standard-version",
@@ -95,29 +95,29 @@
}
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.25.9",
"@babel/runtime-corejs2": "^7.26.0",
"@babel/core": "^7.26.8",
"@babel/preset-env": "^7.26.8",
"@babel/preset-react": "^7.26.3",
"@babel/runtime-corejs2": "^7.26.7",
"autoprefixer": "^10.4.20",
"babel-loader": "^9.2.1",
"clean-webpack-plugin": "^4.0.0",
"commitizen": "^4.3.1",
"concurrently": "^9.1.0",
"concurrently": "^9.1.2",
"css-loader": "^7.1.2",
"daisyui": "^4.12.14",
"dotenv": "^16.4.5",
"daisyui": "^4.12.23",
"dotenv": "^16.4.7",
"dotenv-webpack": "^8.1.0",
"eslint": "^9.14.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-react": "^7.37.2",
"lint-staged": "^15.2.10",
"eslint": "^9.20.0",
"eslint-plugin-prettier": "^5.2.3",
"eslint-plugin-react": "^7.37.4",
"lint-staged": "^15.4.3",
"mini-css-extract-plugin": "^2.9.2",
"postcss": "^8.4.49",
"prettier": "^3.3.3",
"postcss": "^8.5.1",
"prettier": "^3.5.0",
"pretty-quick": "^4.0.0",
"tailwindcss": "^3.4.15",
"terser-webpack-plugin": "^5.3.10",
"tailwindcss": "^3.4.17",
"terser-webpack-plugin": "^5.3.11",
"webpack-cli": "^5.1.4"
},
"overrides": {
@@ -127,4 +127,4 @@
"engines": {
"node": "18 || 20 || 22"
}
}
}
Executable → Regular
View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+2 -2
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<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="%PUBLIC_URL%/logo192.png" /> -->
@@ -15,6 +15,6 @@
<title>OpenSign™</title>
</head>
<body>
<div id="root"></div>
<div id="root" style="touch-action:pan-x pan-y;"></div>
</body>
</html>
@@ -0,0 +1,837 @@
{
"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",
"create-account": "Konto erstellen",
"login": "Anmelden",
"language": "Sprache",
"name": "Name",
"phone": "Telefon",
"phone-optional": "optional",
"email": "E-Mail",
"company": "Unternehmen",
"job-title": "Berufsbezeichnung",
"profile": "Profil",
"log-out": "Abmelden",
"is-email-verified": "Ist E-Mail verifiziert",
"public-profile": "Öffentliches Profil",
"tagline": "Slogan",
"has-requested-you-to-sign": "hat Sie gebeten, zu unterschreiben",
"has-requested-you-to-review-and-sign": "hat Sie gebeten, zu prüfen und zu unterschreiben",
"input-required": "Bitte füllen Sie dieses Feld aus.",
"sort-order": {
"Ascending": "Aufsteigend",
"Descending": "Absteigend",
"Name": "Name",
"Date": "Datum"
},
"context-menu": {
"Download": "Herunterladen",
"Rename": "Umbenennen",
"Move": "Verschieben",
"Delete": "Löschen"
},
"action": "Aktion",
"add": "Hinzufügen",
"add-new-folder": "Neuen Ordner hinzufügen",
"disable-documentId": "DocumentId deaktivieren",
"edit": "Bearbeiten",
"change-password": "Passwort ändern",
"save": "Speichern",
"cancel": "Abbrechen",
"upgrade-now": "Jetzt upgraden",
"upgrade-to": "Upgrade zu",
"plan": "Plan",
"subscribe-card-teamplan": "Entfesseln Sie die volle Kraft der Zusammenarbeit! Erstellen Sie unbegrenzt Organisationen, Teams und Hierarchien. Teilen Sie Vorlagen nahtlos zwischen Teams und weisen Sie benutzerdefinierte Benutzerrollen zu. Optimieren Sie Ihren Workflow noch heute!",
"subscribe-card-plan": "Entsperren Sie Premium-Funktionen ab nur {{premiumPrice}}/Monat. Genießen Sie eine verbesserte Leistung und zahlen Sie nur {{addonPrice}} pro zusätzlichem Credit nach den enthaltenen Premium-Credits.",
"user-name-limit-char": "Um einen Benutzernamen mit weniger als 8 Zeichen zu haben, abonnieren Sie bitte.",
"tour-content": "Nicht erneut anzeigen",
"pro": "PRO",
"team": "TEAM",
"user": "Benutzer",
"docs": "Dokumente",
"session-expired": "Ihre Sitzung ist abgelaufen.",
"public-profile-help": "Dies ist Ihre öffentliche URL. Kopieren oder teilen Sie sie mit dem Unterzeichner, und Sie können alle öffentlich eingestellten Vorlagen sehen.",
"tagline-help": "Dieser Slogan wird in Ihrem öffentlichen OpenSign-Profil angezeigt, z. B. https://opensign.me/alex",
"welcome": "Willkommen zurück!",
"Login-to-your-account": "Melden Sie sich bei Ihrem Konto an",
"password": "Passwort",
"forgot-password": "Passwort vergessen?",
"loading": "Wird geladen...",
"of": "von",
"sign-SSO": "Mit SSO anmelden",
"login-page": "Anmeldeseite",
"or": "ODER",
"signup-page": "Registrierungsseite",
"password-length": "Das Passwort sollte 8 Zeichen lang sein",
"password-case": "Das Passwort sollte einen Großbuchstaben, einen Kleinbuchstaben und eine Zahl enthalten",
"password-special-char": "Das Passwort sollte ein Sonderzeichen enthalten",
"agreee": "Ich stimme den",
"term": "Nutzungsbedingungen zu",
"subscribe-to-opensign": "Abonnieren Sie den OpenSign-Newsletter",
"register": "Registrieren",
"sidebar": {
"Dashboard": "Armaturenbrett",
"Sign yourself": "Selbst unterschreiben",
"Request signatures": "Signaturen anfordern",
"Templates": "Vorlagen",
"Templates-Children": {
"Create template": "Vorlage erstellen",
"Manage templates": "Vorlagen verwalten",
"Embedding": "Einbetten"
},
"OpenSign™ Drive": "OpenSign™ drive",
"Documents": "Dokumente",
"Reports": "Berichte",
"Documents-Children": {
"Need your sign": "Benötigt Ihre Unterschrift",
"In Progress": "In Bearbeitung",
"Completed": "Abgeschlossen",
"Drafts": "Entwürfe",
"Declined": "Abgelehnt",
"Expired": "Abgelaufen"
},
"Contactbook": "Kontaktbuch",
"Settings": "Einstellungen",
"Settings-Children": {
"My Signature": "Meine Unterschrift",
"API Token": "API-Token",
"Webhook": "Webhook",
"Preferences": "Einstellungen",
"Teams": "Teams",
"Team-update-successfully": "Team erfolgreich aktualisiert.",
"Users": "Benutzer"
}
},
"dashboard-card": {
"Need your Signature": "Benötigt Ihre Unterschrift",
"Out for signatures": "Ausstehende Signaturen"
},
"report-name": {
"Draft Documents": "Entwurfsdokumente",
"Need your sign": "Benötigt Ihre Unterschrift",
"In-progress documents": "Dokumente in Bearbeitung",
"Completed Documents": "Abgeschlossene Dokumente",
"Declined Documents": "Abgelehnte Dokumente",
"Expired Documents": "Abgelaufene Dokumente",
"Recently sent for signatures": "Kürzlich für Signaturen versendet",
"Recent signature requests": "Kürzliche Signaturanfragen",
"Drafts": "Entwürfe",
"Contactbook": "Kontaktbuch",
"Templates": "Vorlagen",
"Teams": "Teams",
"Users": "Benutzer"
},
"btnLabel": {
"sign": "unterschreiben",
"Sign": "UNTERSCHREIBEN",
"Resend": "Erneut senden",
"Rename": "Umbenennen",
"Revoke": "Widerrufen",
"Delete": "Löschen",
"Use": "Verwenden",
"Quick send": "Schnell senden",
"Edit": "Bearbeiten",
"Share with team": "Mit Team teilen",
"Share": "Teilen",
"View": "Ansehen",
"option": "Option",
"Embed": "Einbetten",
"Copy TemplateId": "Vorlagen-ID kopieren",
"Copy Public URL": "Öffentliche URL kopieren",
"extend-expiry-date": "Ablaufdatum verlängern",
"Duplicate Template": "Vorlage duplizieren",
"Duplicate": "Duplikat"
},
"report-heading": {
"Sr.No": "Nr.",
"Title": "Titel",
"Email": "E-Mail",
"Phone": "Telefon",
"File": "Datei",
"Owner": "Besitzer",
"Signers": "Unterzeichner",
"Note": "Notiz",
"Folder": "Ordner",
"Reason": "Grund",
"Parent Team": "Übergeordnetes Team",
"Active": "Aktiv",
"Role": "Rolle",
"Team": "Team",
"Name": "Name",
"Status": "Status",
"created-date": "Erstellungsdatum",
"Type": "Typ",
"Logs": "Protokolle",
"Expiry-date": "Ablaufdatum"
},
"report-help": {
"Draft Documents": "Dies sind Dokumente, die Sie begonnen, aber noch nicht zum Versenden fertiggestellt haben.",
"Need your sign": "Dies ist eine Liste von Dokumenten, die auf Ihre Unterschrift warten.",
"In-progress documents": "Dies ist eine Liste von Dokumenten, die Sie anderen Parteien zur Unterschrift gesendet haben.",
"Completed Documents": "Dies ist eine Liste von Dokumenten, die von allen Parteien unterzeichnet wurden.",
"Declined Documents": "Dies ist eine Liste von Dokumenten, die von einem der Unterzeichner abgelehnt wurden.",
"Expired Documents": "Dies ist eine Liste von Dokumenten, deren Ablaufdatum erreicht wurde.",
"Contactbook": "Dies ist eine Liste von Kontakten/Unterzeichnern, die Sie hinzugefügt haben. Diese erscheinen als Vorschläge, wenn Sie neue Unterzeichner hinzufügen möchten.",
"Templates": "Dies ist eine Liste von Vorlagen, die Ihnen zur Verfügung stehen, um Dokumente zu erstellen. Sie können die Schaltfläche 'Verwenden' anklicken, um ein neues Dokument mit einer Vorlage zu erstellen, das Dokument zu ändern und Unterzeichner im nächsten Schritt hinzuzufügen."
},
"form-name": {
"Sign Yourself": "Selbst unterschreiben",
"Request Signatures": "Signaturen anfordern",
"New Template": "Neue Vorlage"
},
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "Datei ausgewählt",
"template-title": "Vorlagentitel",
"document-title": "Dokumenttitel",
"description": "Beschreibung",
"time-to-complete": "Bearbeitungszeit (Tage)",
"send-in-order": "In der Reihenfolge senden",
"send-in-order-help": {
"p1": "Wählen Sie aus, wie Sie die Unterzeichnungsanfragen an die Dokumentenunterzeichner senden möchten:",
"p2": "Wenn Sie diese Option auswählen, wird die Unterzeichnungsanfrage zunächst an den ersten Unterzeichner gesendet. Sobald dieser seine Aufgabe abgeschlossen hat, erhält der nächste Unterzeichner in der Reihenfolge die Anfrage. Dieser Prozess setzt sich fort, bis alle Unterzeichner das Dokument unterschrieben haben. Diese Methode stellt sicher, dass das Dokument in einer bestimmten Reihenfolge unterzeichnet wird.",
"p3": "Wenn Sie diese Option auswählen, werden die Signaturlinks gleichzeitig an alle Unterzeichner gesendet. Jeder Unterzeichner kann das Dokument nach Belieben unterzeichnen, unabhängig davon, ob andere Unterzeichner ihre Unterschriften abgeschlossen haben. Diese Methode ist schneller, erzwingt jedoch keine Reihenfolge unter den Teilnehmern.",
"p4": "Wählen Sie die Option, die am besten zu den Anforderungen Ihrer Dokumentenverarbeitung passt."
},
"no": "Nein",
"auto-reminder": "Automatische Erinnerung",
"remind-once": "Einmal erinnern alle (Tage)",
"next": "Weiter",
"select-folder": "Ordner auswählen",
"OpenSign-drive": "OpenSign™ Drive",
"drive-document-status": {
"Completed": "Abgeschlossen",
"Declined": "Abgelehnt",
"Draft": "Entwurf",
"Expired": "Abgelaufen",
"In Progress": "In Bearbeitung"
},
"select-folder-help": "Wenn Sie keinen Ordner auswählen, wird Ihr unterzeichnetes Dokument im Hauptordner des OpenSign-Drives gespeichert.",
"no-data": "Keine Daten gefunden",
"save-here": "Hier speichern",
"back": "Zurück",
"add-folder": "Ordner hinzufügen",
"create-folder": "Ordner erstellen",
"parent-folder": "Übergeordneter Ordner",
"create": "Erstellen",
"signers": "Unterzeichner",
"signers-help": "Beginnen Sie mit der Eingabe eines Kontaktnamens, um vorgeschlagene Unterzeichner aus Ihren gespeicherten Kontakten zu sehen, oder fügen Sie neue hinzu. Ordnen Sie die Reihenfolge der Unterzeichner, indem Sie sie in der gewünschten Reihenfolge hinzufügen. Verwenden Sie die '+'-Schaltfläche, um Unterzeichner hinzuzufügen, und 'x', um sie zu entfernen. Jeder Unterzeichner erhält eine E-Mail-Aufforderung, das Dokument in der aufgelisteten Reihenfolge zu unterzeichnen.",
"bcc-help": "Beginnen Sie mit der Eingabe des Namens eines Kontakts, um Vorschläge aus Ihren gespeicherten Kontakten anzuzeigen oder neue hinzuzufügen. Verwenden Sie die Schaltfläche '+', um einen Benutzer hinzuzufügen, und das 'x', um ihn zu entfernen. Die E-Mail des ausgewählten Benutzers wird als Bcc (Blindkopie) hinzugefügt. Jeder Benutzer erhält eine E-Mail-Benachrichtigung, sobald das Dokument fertig ist.",
"add-signer": "Unterzeichner hinzufügen",
"contact-not-found": "Kontakt nicht gefunden",
"add-yourself": "Sich selbst hinzufügen",
"submit": "Absenden",
"reset": "Zurücksetzen",
"my-signature": "Meine Unterschrift",
"signature": "Unterschrift",
"upload-image": "Bild hochladen",
"clear": "Löschen",
"upload-signature/Image": "Bitte Unterschrift/Bild hochladen",
"initials": "Initialen",
"API": "API",
"api-token": "API-Token",
"regenerate-token": "Live-Token neu generieren",
"generate-token": "Live-Token generieren",
"view-docs": "Dokumentation ansehen",
"generate-token-alert": "Sind Sie sicher, dass Sie das Token neu generieren möchten? Das alte Token wird ablaufen.",
"yes": "Ja",
"copied": "Kopiert",
"something-went-wrong-mssg": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später erneut.",
"token-generated": "Token erfolgreich generiert.",
"webhook": "Webhook",
"update-webhook": "Webhook aktualisieren",
"add-webhook": "Webhook hinzufügen",
"webhook-added": "Webhook erfolgreich hinzugefügt.",
"team-disabled": "Team deaktiviert",
"Team enabled": "Team aktiviert",
"are-you-sure": "Sind Sie sicher, dass Sie",
"disable": "deaktivieren",
"enable": "aktivieren",
"this-team": "dieses Team möchten?",
"edit-team": "Team bearbeiten",
"name-of-team": "Name des Teams",
"prev": "Zurück",
"no-data-avaliable": "Keine Daten verfügbar",
"add-team": "Team hinzufügen",
"page-not-found": "Seite nicht gefunden",
"users-from-teams": "Benutzer aus Teams",
"user-status": "Benutzerstatus",
"activate": "aktivieren",
"deactivate": "deaktivieren",
"this-user": "diesen Benutzer",
"delete-user": "Benutzer 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",
"user-deactivated": "Benutzer deaktiviert.",
"user-activated": "Benutzer aktiviert.",
"public": "Öffentlich",
"download": "Herunterladen",
"view": "Ansehen",
"make-template-public": "Vorlage öffentlich machen",
"make-template-private": "Vorlage privat machen",
"make-template-public-alert": "Sind Sie sicher, dass Sie diese Vorlage öffentlich machen möchten?",
"make-template-private-alert": "Sind Sie sicher, dass Sie diese Vorlage privat machen möchten? Dadurch wird sie aus Ihrem öffentlichen Profil entfernt.",
"public-role": "Öffentliche Rolle",
"public-url": "Öffentliches Profil",
"embed-template": "Vorlage einbetten",
"public-url-copy": "Hier ist Ihre öffentliche URL: ",
"public-url-copy-mssg": "Kopieren oder teilen Sie diese URL, und Sie können alle öffentlich eingestellten Vorlagen sehen.",
"add-public-url-alert": "Sie können Ihr öffentliches OpenSign-Profil generieren",
"share-with-alert": "Sie können eine Vorlage nicht teilen, wenn Rollen bereits Kontakte zugewiesen haben. Entfernen Sie bitte alle Kontaktzuweisungen von den Rollen, bevor Sie die Vorlage teilen.",
"share-with": "Teilen mit",
"team-not-found": "Team nicht gefunden",
"roles": "Rollen",
"delete-document": "Dokument löschen",
"delete-document-alert": "Sind Sie sicher, dass Sie dieses Dokument löschen möchten?",
"quick-send": "Schnell senden",
"add-new": "Neu hinzufügen",
"send": "Senden",
"quick-send-alert-1": "Alle Rollen in diesem Dokument sind derzeit mit Kontakten verknüpft. Um Kopien dieser Vorlage schnell an mehrere Unterzeichner zu senden, stellen Sie sicher, dass mindestens eine Rolle keinem Kontakt zugeordnet ist.",
"quick-send-alert-2": "Bitte stellen Sie sicher, dass für alle Empfänger mindestens ein Signatur-Widget hinzugefügt wurde.",
"quick-send-alert-3": "Bitte fügen Sie mindestens eine Rolle zu dieser Vorlage hinzu, um Kopien davon schnell an mehrere Unterzeichner zu senden.",
"quick-send-alert-4": "Das Limit für Schnellsendungen wurde erreicht.",
"copy-link": "Link kopieren",
"copy": "Kopieren",
"revoke-document": "Dokument widerrufen",
"revoke-document-alert": "Sind Sie sicher, dass Sie dieses Dokument widerrufen möchten?",
"resend-mail": "E-Mail erneut senden",
"resend-mail-help": "Sie können folgende Variablen verwenden, die durch ihre tatsächlichen Werte ersetzt werden: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"subject": "Betreff",
"body": "Inhalt",
"add-contact": "Kontakt hinzufügen",
"edit-contact": "Kontakt bearbeiten",
"add-signer-alert": "Kontakt existiert bereits! Bitte wählen Sie ihn aus dem Dropdown-Menü 'Unterzeichner' aus.",
"record-delete-alert": "Datensatz erfolgreich gelöscht!",
"record-revoke-alert": "Datensatz erfolgreich widerrufen!",
"mail-sent-alert": "E-Mail erfolgreich gesendet.",
"document-sent-alert": "Dokument erfolgreich gesendet!",
"template-share-alert": "Vorlage erfolgreich geteilt.",
"template-public-alert-1": "Sie haben die Vorlage erfolgreich öffentlich gemacht.",
"template-public-alert-2": "Sie haben die Vorlage erfolgreich privat gemacht.",
"template-public-alert-3": "Sie müssen eine Rolle für die öffentlichen Unterzeichner auswählen.",
"template-public-alert-4": "Bitte stellen Sie sicher, dass für alle Unterzeichner mindestens ein Signatur-Widget hinzugefügt wurde.",
"template-public-alert-5": "Da für diese Vorlage das Senden in Reihenfolge aktiviert ist, muss die öffentliche Rolle oben positioniert sein.",
"template-public-alert-6": "Bitte verknüpfen Sie Unterzeichner mit allen Rollen, außer mit der öffentlichen Rolle.",
"template-public-alert-7": "Bitte weisen Sie mindestens eine öffentliche Rolle zu, um diese Vorlage öffentlich zu machen.",
"loader": "Das könnte etwas Zeit in Anspruch nehmen",
"pages": "Seiten",
"document-signed": "Dokument unterzeichnet",
"close": "Schließen",
"validation-alert": "Validierungswarnung",
"validate-alert-mssg": "Die Eingabe erfüllt nicht die Kriterien des regulären Ausdrucks.",
"otp-verification": "OTP-Verifizierung",
"enter-otp": "OTP eingeben",
"verify": "Verifizieren",
"verify-email-1": "E-Mail verifizieren",
"resend": "Erneut senden",
"contact-details": "Kontaktdetails",
"verify-email": "Bitte verifizieren Sie Ihre E-Mail!",
"send-otp": "OTP senden",
"otp-placeholder": "Verifizierungscode aus der E-Mail eingeben",
"loading-doc": "Dokument wird geladen...",
"widgets-name": {
"signature": "Unterschrift",
"stamp": "Stempel",
"initials": "Initialen",
"name": "Name",
"job title": "Berufsbezeichnung",
"company": "Unternehmen",
"date": "Datum",
"text": "Text",
"text input": "Texteingabe",
"checkbox": "Checkbox",
"dropdown": "Dropdown",
"radio button": "Radiobutton",
"image": "Bild",
"email": "E-Mail"
},
"fields": "Felder",
"recipients": "Empfänger",
"please-add": "Bitte fügen Sie hinzu",
"signed-by": "Unterzeichnet von",
"more": "mehr",
"sent": "gesendet",
"print": "Drucken",
"certificate": "Zertifikat",
"decline": "Ablehnen",
"finish": "Fertigstellen",
"mail": "E-Mail",
"sign-now": "Jetzt unterzeichnen",
"successfully-signed": "Erfolgreich unterzeichnet!",
"email-mssg": "Empfänger, die hier hinzugefügt werden, erhalten eine Kopie des unterzeichneten Dokuments.",
"email-error-1": "Bitte geben Sie eine korrekte E-Mail-Adresse an.",
"email-error-2": "Sie können nur an maximal zehn Empfänger gleichzeitig senden.",
"Email-verified-alert-1": "E-Mail wurde verifiziert.",
"Email-verified-alert-2": "E-Mail wurde bereits verifiziert.",
"upload-stamp-image": "Stempelbild hochladen",
"draw": "Zeichnen",
"type": "Schreiben",
"color-type": {
"red": "rot",
"blue": "blau",
"black": "schwarz",
"yellow": "gelb"
},
"my-initials": "Meine Initialen",
"upload": "Hochladen",
"initial-teb": "Initialen",
"signature-tab": "Unterschrift",
"initial-alert": "Meine Initiale 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.",
"reset-password-alert-2": "Bitte richten Sie den E-Mail-Adapter ein.",
"reset-password-alert-3": "Setzen Sie Ihr Passwort zurück",
"faild-animation": "Animation konnte nicht geladen werden",
"apply": "Anwenden",
"copy-type": {
"All pages": "Alle Seiten",
"All pages but last": "Alle Seiten außer der letzten",
"All pages but first": "Alle Seiten außer der ersten",
"Next to current widget": "Neben dem aktuellen Widget"
},
"options": "Optionen",
"minimun-check": "Minimale Anzahl",
"maximum-check": "Maximale Anzahl",
"default-value": "Standardwert",
"select": "Auswählen",
"read-only": "Nur lesen",
"hide-labels": "Labels ausblenden",
"checkbox": "Checkbox",
"alert": "Warnung",
"zoom-in": "Vergrößern",
"zoom-out": "Verkleinern",
"document-signed-alert": "Herzlichen Glückwunsch! 🎉 Dieses Dokument wurde erfolgreich von Ihnen unterzeichnet!",
"otp-sent-alert": "OTP wurde an Ihre E-Mail gesendet",
"fields-required": "Erforderliche Felder",
"signature-widget-alert-1": "Bitte stellen Sie sicher, dass mindestens ein Signatur-Widget hinzugefügt wurde",
"signature-widget-alert-2": "Bitte stellen Sie sicher, dass alle Felder korrekt ausgefüllt sind und die Anforderungen erfüllen.",
"encrypted-pdf-alert": "Derzeit werden verschlüsselte PDF-Dateien nicht unterstützt.",
"encrypted-pdf-alert-1": "Das Passwort der PFX-Datei ist ungültig.",
"user-not-exist": "Benutzer existiert nicht",
"template-role-alert": "Bitte fügen Sie zuerst Rollen hinzu",
"create-document": "Dokument erstellen",
"template-created-alert": "Möchten Sie ein Dokument mit der gerade erstellten Vorlage erstellen?",
"signers-alert": "Unterzeichner-Warnung",
"template-creation-alert-1": "Bitte wählen Sie einen Unterzeichner aus, um Platzhalter hinzuzufügen!",
"ok": "OK",
"radio-group": "Radiogruppe",
"dropdown-options": "Dropdown-Optionen",
"add-role": "Rolle hinzufügen",
"role-ex": "z. B.: Kunde, Personalabteilung, Direktor, Manager, Student, etc.",
"add/choose-signer": "Unterzeichner hinzufügen/auswählen",
"choose-from-contacts": "Aus Kontakten auswählen",
"select-signer": "Bitte Unterzeichner auswählen",
"edit-template": "Vorlage bearbeiten",
"widget-info": "Widget-Info",
"validation": "Validierung",
"invalid-default-value": "Ungültiger Standardwert",
"hint": "Hinweis",
"color": "Farbe",
"role-help": {
"p1": "Was sind Vorlagenrollen?",
"p2": "Beginnen Sie damit, jede Rolle zu definieren, die für die Fertigstellung des Dokuments erforderlich ist. Überlegen Sie, welche Parteien in den Unterschriftsprozess einbezogen sind und welche Aufgaben sie haben. Häufige Rollen sind z. B. Personalabteilung für interne Dokumente, Kunde für Vereinbarungen oder Anbieter für Geschäftsvereinbarungen.",
"p3": "Warum Benutzer an bestimmte Rollen vorab anhängen?",
"p4": "Für Rollen, die regelmäßig dieselbe Person betreffen (z. B. die Unterschrift des CEOs bei Mitarbeiterangebotsschreiben), können Sie einen Benutzer in der Vorlage vorab an eine Rolle anhängen. Dieser Schritt ist optional, aber empfohlen für Effizienz und Konsistenz.",
"p5": "Wann lege ich den Benutzer für jede Rolle fest?",
"p6": "Wenn Sie ein Dokument aus Ihrer Vorlage erstellen, werden Sie aufgefordert, Benutzer an jede definierte Rolle anzuhängen. Wenn eine Rolle bereits einen Benutzer hat, wird diese vorausgefüllt, kann aber vor dem Versand des Dokuments geändert werden."
},
"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",
"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?",
"placeholder-alert-4": "Sie haben erfolgreich E-Mails an alle Empfänger gesendet!",
"placeholder-mail-alert": "Sie haben erfolgreich eine E-Mail an {{name}} gesendet. Die nachfolgenden Unterzeichner erhalten eine E-Mail, sobald {{name}} das Dokument unterzeichnet.",
"placeholder-alert-5": "Möchten Sie die Dokumente jetzt unterschreiben?",
"placeholder-alert-6": "Bitte richten Sie den E-Mail-Adapter ein, um E-Mails zu senden!",
"placeholder-alert-7": "Bitte wählen Sie einen Unterzeichner aus, um Platzhalter hinzuzufügen!",
"email-subject": "E-Mail-Betreff",
"email-body": "E-Mail-Inhalt",
"email-placeholder": "E-Mail-Text eingeben",
"reset-to-default": "Auf Standard zurücksetzen",
"cutomize-email": "E-Mail anpassen",
"upgrade-to-customize-email": "Upgrade, um E-Mails anzupassen",
"sign-url": "Signatur-URL",
"mails-sent": "E-Mails gesendet",
"mail-not-delivered": "E-Mail nicht zugestellt",
"document-alert": "Dokument-Warnung",
"owner-subscription-expired": "Das Abonnement des Besitzers ist abgelaufen.",
"subscription-expired": "Abonnement abgelaufen",
"alert-message": "Warnmeldung",
"document-decline": "Dokument ablehnen",
"decline-alert-1": "Sind Sie sicher, dass Sie dieses Dokument ablehnen möchten?",
"decline-alert-2": "Sie haben dieses Dokument abgelehnt!",
"decline-alert-3": "Sie können dieses Dokument nicht unterschreiben, da es abgelehnt/widerrufen wurde.",
"sign here": "Hier unterschreiben",
"guest-email-alert": "Ein Verifizierungscode wurde an Ihre E-Mail gesendet",
"get-otp-alert": "Sie erhalten einen Verifizierungscode per E-Mail",
"user-already-exist": "Benutzer existiert bereits.",
"server-error": "Interner Serverfehler!",
"fill-required-details!": "Bitte füllen Sie die erforderlichen Felder aus!",
"sign-up-form": "Registrierungsformular",
"sign-up": "Registrieren",
"folder-already-exist!": "Ordner existiert bereits!",
"please-select-position!": "Bitte Position auswählen!",
"default-sign-alert": "Möchten Sie wirklich alle angeforderten {{widgetsType}}-Widgets automatisch anmelden?",
"Your-Signature": "Ihre Unterschrift",
"auto-sign-all": "Alles automatisch unterschreiben",
"generating-certificate": "Zertifikat wird generiert",
"pdf-download": "PDF herunterladen",
"generate-certificate-alert": "Ihr Abschlusszertifikat wird gerade generiert. Bitte warten Sie einen Moment. Wenn der Download nicht bald beginnt, klicken Sie erneut auf die Schaltfläche.",
"generate-certificate-err": "Wir haben derzeit ein Problem mit der Zertifikatserstellung. Bitte versuchen Sie es später erneut",
"folder-already-exist": "Ordner existiert bereits!",
"folder-created-successfully": "Ordner erfolgreich erstellt!",
"fill-folder-name": "Bitte Ordnernamen eingeben",
"already-exists-this-username": "Ein Benutzer mit diesem Benutzernamen existiert bereits!",
"verification-code-sent": "Verifizierungs-Mail wurde an Ihre E-Mail gesendet!",
"registered-user-successfully": "Benutzer erfolgreich registriert.",
"role-not-found": "Rolle nicht gefunden.",
"do-not-access": "Sie haben keinen Zugriff. Bitte kontaktieren Sie den Administrator.",
"add-admin": "Admin hinzufügen",
"opensign-setup": "OpenSign-Einrichtung",
"join-discord": "Treten Sie unserem Discord-Server bei",
"admin-already-exist": "Admin existiert bereits.",
"password-update-alert-1": "Passwort erfolgreich aktualisiert.",
"password-update-alert-2": "Ihr aktuelles Passwort fehlt oder ist falsch.",
"password-update-alert-3": "Ihr aktuelles Passwort fehlt oder ist falsch.",
"password-update-alert-4": "Ihr Passwort und die Bestätigung stimmen nicht überein.",
"current-password": "Aktuelles Passwort",
"new-password": "Neues Passwort",
"confirm-password": "Passwort bestätigen",
"file-alert-1": "Die ausgewählte Datei ist zu groß. Bitte wählen Sie eine Datei, die kleiner als",
"file-alert-2": "Bitte Datei auswählen.",
"file-alert-3": "Bitte warten, während das Dokument hochgeladen wird.",
"enter-pdf-password": "PDF-Passwort eingeben",
"correct-password": "Bitte korrektes Passwort angeben",
"decrypting-pdf": "PDF wird entschlüsselt, bitte warten...",
"invalid-otp": "Ungültiger OTP",
"user-not-found": "Benutzer nicht gefunden!",
"enter-otp-alert": "Bitte OTP eingeben!",
"get-verification-code": "Verifizierungscode erhalten",
"get-verification-code-2": "Sie erhalten einen Verifizierungscode per E-Mail",
"enter-verification-code": "Verifizierungscode eingeben",
"provide-your-details": "Bitte geben Sie Ihre Daten an",
"provide-email": "Bitte E-Mail angeben.",
"additional-info": "Zusätzliche Informationen",
"signature-saved-alert": "Unterschrift erfolgreich gespeichert.",
"saved-successfully": "Erfolgreich gespeichert.",
"fill-field": "Bitte dieses Feld ausfüllen",
"error-template": "Fehler: Vorlage nicht gefunden!",
"error": "Fehler",
"invalid-document": "Fehler: ungültiges Dokument!",
"document-not-found": "Dokument nicht gefunden!",
"document-signed-alert-1": "Sie haben das Dokument erfolgreich unterzeichnet. Sie können eine Kopie des teilweise unterzeichneten Dokuments herunterladen oder drucken. Eine Kopie des digital signierten Dokuments und des Abschlusszertifikats wird per E-Mail an den Besitzer und alle Unterzeichner gesendet, sobald alle unterzeichnet haben.",
"encrypted-pdf-not-support": "Derzeit werden verschlüsselte PDF-Dateien nicht unterstützt.",
"document-signed-alert-4": "Herzlichen Glückwunsch! 🎉 Dieses Dokument wurde erfolgreich von allen Teilnehmern unterzeichnet!",
"document-signed-alert-5": "Dieses Dokument wurde von allen Unterzeichnern unterzeichnet.",
"document-signed-alert-6": "Dieses Dokument wurde von einem oder mehreren Empfängern abgelehnt.",
"document-signed-alert-7": "Dieses Dokument ist nicht mehr verfügbar.",
"document-signed-alert-8": "Das Dokument wurde bereits zum Unterzeichnen versendet.",
"yet-to-sign": "Noch zu unterzeichnen",
"validation-alert-1": "Die Eingabe erfüllt nicht die Kriterien des regulären Ausdrucks.",
"account-already-exists": "Konto existiert bereits!",
"choose-password": "Passwort wählen",
"enter-password": "Passwort eingeben",
"sure": "Sicher",
"subscribed-alert": "Sie haben diesen Plan bereits abonniert!",
"monthly": "Monatlich",
"yearly-upto": "Jährlich (bis zu 66 % Rabatt)",
"billed-yearly": "Jährlich abgerechnet",
"billed-monthly": "Monatlich abgerechnet",
"plansubscription-1": "Hosten Sie es selbst kostenlos",
"visit-github": "Besuchen Sie Github",
"user-already-exist-name": "Ein Benutzer mit diesem Benutzernamen existiert bereits!",
"do-not-access-contact-admin": "Sie haben keinen Zugriff, bitte kontaktieren Sie den Administrator.",
"filed-required-correctly": "Bitte füllen Sie die erforderlichen Details korrekt aus.",
"admin-created": "Admin erstellt",
"invalid-masterkey": "Ungültiger Hauptschlüssel",
"opensign-Setup": "OpenSign-Einrichtung",
"master-key": "Hauptschlüssel",
"profile-update-alert": "Profil erfolgreich aktualisiert.",
"date": "Datum",
"report-not-found": "Bericht nicht gefunden",
"please-wait": "Bitte warten",
"select-foler": "Ordner auswählen",
"generated-on": "Erstellt am",
"text-field": "Textfeld",
"font-size": "Schriftgröße",
"widget-status": {
"Required": "Erforderlich",
"Optional": "Optional"
},
"all-right": "Alle Rechte vorbehalten",
"version": "version",
"social-media": {
"github": "Github",
"linked-in": "LinkedIn",
"twitter": "Twitter",
"discord": "Discord"
},
"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, OpenSign zu verwenden! Wenn Sie Unterstützung benötigen, kontaktieren Sie uns gerne.",
"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.",
"opensign-drive-3": "Sortieren Sie Ihre Dokumente nach Datum oder Name über dieses Menü.",
"opensign-drive-4": "Klicken Sie auf dieses Menü, um die Dokumente in der Listenansicht anzuzeigen.",
"opensign-drive-5": "Die Dokumentenliste wird entsprechend der ausgewählten Sortieroption angezeigt. Symbole neben jedem Dokument zeigen den aktuellen Status an.",
"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-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-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-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.",
"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.",
"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.",
"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.",
"Recent signature requests": "Dies ist eine Liste von Dokumenten, die auf Ihre Unterschrift warten.",
"Recently sent for signatures": "Dies ist eine Liste von Dokumenten, die Sie an andere Parteien zur Unterschrift gesendet haben.",
"Drafts": "Dies sind Dokumente, die Sie begonnen, aber noch nicht zum Versenden fertiggestellt haben.",
"public-template": "Dieses Video zeigt Ihnen, wie Sie Ihr personalisiertes öffentliches Profil einrichten, z. B. 'https://opensign.me/Ihr-Benutzername'. Sie lernen auch, wie Sie Ihren Slogan anpassen und Ihre Vorlagen für öffentliche Signaturen verfügbar machen.",
"allowModify-widgets": "Sie können jedes dieser Felder per Drag & Drop in das Dokument ziehen, zusätzlich zu den Feldern, die der Dokumentersteller bereits für Sie festgelegt hat."
},
"enter-email-plaholder": "E-Mail-Adresse hinzufügen und Enter drücken",
"success-email-alert": "E-Mail erfolgreich gesendet!",
"expired-doc-title": "Abgelaufenes Dokument",
"expired-on-mssg": "Dieses Dokument ist am {{expiredDate}} abgelaufen und nicht mehr verfügbar zum Unterzeichnen.",
"signature-validate-alert": "Bitte bestätigen Sie, dass Sie mindestens {{minRequiredCount}} Kontrollkästchen ausgewählt haben.",
"signature-validate-alert-2": "Stellen Sie sicher, dass dieses Feld korrekt ausgefüllt ist und alle Anforderungen erfüllt.",
"remaing-users": "Verbleibende Benutzer",
"Role": "Rolle",
"additional-users": "Bitte kaufen Sie zusätzliche Benutzer hinzu.",
"Quantity-of-users": "Anzahl der Benutzer",
"Price": "Preis",
"Total-price-for-next-time": "Gesamtpreis für das nächste Mal",
"Proceed": "Fortfahren",
"Select": "Auswählen",
"Add-seats": "Plätze hinzufügen",
"user-name-exist": "Benutzername existiert bereits",
"format": "Format",
"select-date-format": "Wählen Sie ein Datumsformat",
"quantity-of-credits": "Anzahl der Premium-Credits",
"remaining-credits": "Verfügbare Premium-Credits:",
"remaining-credits-help": "Verwenden Sie Premium-Credits für API-Dokumentensignaturen, Massenversand oder die Einbettung der OpenSign-Integration auf Ihrer Website. Sie haben {{allowedcredits}} enthaltene Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig.",
"additional-credits": "Bitte kaufen Sie Premium-Credits hinzu",
"quota-err-quicksend": "Kontingent erreicht, Sie haben nicht genügend Credits.",
"buy-credits": "Premium-Credits kaufen",
"rotate-right": "Nach rechts drehen",
"rotate-left": "Nach links drehen",
"rotate-alert-mssg": "Alle Widgets auf dieser Seite gehen verloren. Sind Sie sicher, dass Sie fortfahren möchten?",
"templateid": "Vorlagen-ID",
"bulk-send-subcription-alert": "Bitte upgraden Sie auf den Professional- oder Team-Plan, um den Massenversand zu nutzen.",
"generate-test-token": "Test-Token generieren",
"regenerate-test-token": "Test-Token neu generieren",
"help-test-token": "Dieses Token kann verwendet werden, um die APIs unter https://sandbox.opensignlabs.com/api/v1 zu testen und unbegrenzt Dokumente zu signieren. Beachten Sie, dass die Sandbox-API Ihre Dokumente mit selbstsignierten Zertifikaten signiert, die möglicherweise von Adobe nicht als gültig erkannt werden. Nach Abschluss Ihrer Tests können Sie auf einen unserer kostenpflichtigen Pläne upgraden, um ein Produktionstoken zu generieren.",
"help-api-token": "Dieses Token kann verwendet werden, um auf die Produktions-APIs unter {{origin}}/api/v1 zuzugreifen. Es kann nur auf einem unserer kostenpflichtigen Pläne generiert werden.",
"reason": "Grund",
"decline-by": "Abgelehnt/widerrufen von",
"document-declined": "Dokument abgelehnt",
"public-template-mssg-1": "Um OpenSign in Ihr React- oder Next.js-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
"public-template-mssg-2": "Stellen Sie sicher, dass npm oder yarn in Ihrem Projekt eingerichtet ist. Wenn Sie Yarn verwenden, können Sie npm install durch yarn add @opensign/react ersetzen.",
"public-template-mssg-3": "Benötigen Sie weitere Details oder Beispiele?",
"public-template-mssg-4": "Besuchen Sie die",
"public-template-mssg-5": " npm für die neuesten Updates, detaillierte Dokumentationen und Versionshistorie.",
"public-template-mssg-6": "Bevor Sie diesen Code-Schnipsel verwenden können, müssen Sie diese Vorlage öffentlich machen.",
"public-template-mssg-7": "Bevor Sie einen öffentlichen Link generieren können, müssen Sie diese Vorlage öffentlich machen.",
"here": "Hier",
"copy-code": "KOPIEREN",
"copied-code": "KOPIERT",
"Installation": "Installation",
"Usage": "Nutzung",
"insufficient-credits": "Nicht genügend Signatur-Credits",
"insufficient-credits-mssg": "Der Besitzer dieses Dokuments verfügt derzeit nicht über die notwendigen OpenSign-Credits, damit Sie unterzeichnen können. Bitte wenden Sie sich an den Besitzer, wenn Sie weitere Informationen benötigen.",
"angular-npm-mssg-1": "Um OpenSign in Ihr Angular-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
"quota-mail-info-head": "Monatliches Limit für Signaturanfragen per E-Mail",
"quota-mail-info": "Sie können bis zu 15 Signaturanfragen per E-Mail pro Monat senden. Jetzt upgraden, um unbegrenzt Signaturanfragen direkt zu senden.",
"quota-mail-reset": "Ihre Credits für Signaturanfragen per E-Mail werden zurückgesetzt am",
"quota-mail": "Sie haben Ihr monatliches Limit von 15 Signaturanfragen per E-Mail erreicht. Jetzt upgraden, um weiter E-Mails direkt zu senden.",
"quota-mail-tip": "Tipp: Sie können weiterhin <1>unbegrenzt Dokumente</1> signieren, indem Sie die Signaturanfragelinks manuell teilen.",
"quota-mail-head": "Kontingent erreicht",
"unauthorized-modal": "Sie haben keine Berechtigung, diese Aktion auszuführen. Bitte kontaktieren Sie {{adminEmail}}.",
"sent-this-month": "Diesen Monat gesendet",
"available-seats": "Verfügbare Plätze",
"buy-users": "Weitere Benutzer kaufen",
"isenable-otp": "OTP-Verifizierung aktivieren",
"isenable-otp-help": {
"p1": "Möchten Sie den Verifizierungsprozess mit einem einmaligen Passwort (OTP) aktivieren?",
"p2": "Wenn Sie diese Option auswählen, wird die OTP-Verifizierung aktiviert. Benutzer erhalten einen Verifizierungscode per E-Mail, den sie eingeben müssen, um das Dokument zu unterzeichnen.",
"p3": "Wenn Sie diese Option deaktivieren, können Benutzer das Dokument direkt ohne zusätzliche Schritte unterzeichnen.",
"p4": "Bitte wählen Sie die Option, die am besten zu Ihren Anforderungen für die Dokumentenunterzeichnung passt."
},
"advanced-options": "Erweiterte Optionen",
"hide-advanced-options": "Erweiterte Optionen ausblenden",
"download-files": "Dateien herunterladen",
"download-pdf": "PDF herunterladen",
"pdf-certificate": "PDF + Zertifikat herunterladen",
"document-logs": "Dokumentenprotokolle",
"server-down": "Verbindung zum OpenSign-Server nicht möglich. Wenn Sie OpenSign selbst hosten, stellen Sie bitte sicher, dass alle Schritte in der Dokumentation korrekt befolgt wurden. Wenn Sie OpenSign lokal ausführen, verwenden Sie möglicherweise eine falsche Portnummer.",
"admin-exists": "Admin existiert bereits. Bitte melden Sie sich mit den Admin-Anmeldedaten an, um Benutzer zu verwalten.",
"enable-tour": "Tour aktivieren",
"istourenabled-help": {
"p1": "Dies aktiviert eine geführte Tour für Unterzeichner, die während des Unterzeichnungsprozesses Anweisungen gibt.",
"p2": "Dies deaktiviert die geführte Tour, wodurch ein schneller, ununterbrochener Unterzeichnungsprozess gewährleistet wird.",
"p3": "Tipp: Wenn Ihre Unterzeichner mit OpenSign vertraut sind und es häufig verwenden, können Sie die geführte Tour deaktivieren, um ein nahtloses Erlebnis zu gewährleisten. Wählen Sie die Option, die am besten zu Ihren Anforderungen passt."
},
"form-title-1": "Dokumentenfluss-Einstellungen",
"form-title-2": "Sicherheitseinstellungen",
"public-tour-message": "Die Vorlage muss öffentlich sein, bevor Sie einen teilbaren Link generieren können.",
"add-user-template": "Sie müssen eine Rolle hinzufügen, bevor Sie Felder dafür hinzufügen können.",
"pdf-uncompatible": "Dieses PDF ist nicht kompatibel mit OpenSign. Bitte kontaktieren Sie <support@opensignlabs.com>",
"text-field-tour": "Felder vom Typ 'Text' müssen im Voraus ausgefüllt werden, bevor das Dokument versendet wird. Wenn Sie möchten, dass die Unterzeichner Eingaben machen, verwenden Sie das Feld 'Texteingabe'.",
"attach-signer-tour": "Sie müssen einen Unterzeichner jeder Rolle zuweisen. Dies können Sie durch Klicken auf dieses Symbol tun. Sobald Sie einen Unterzeichner auswählen, wird er allen Feldern der zugehörigen Rolle zugewiesen, die in derselben Farbe erscheinen.",
"allowed-signature-types": "Erlaubte Signaturtypen",
"at-least-one-signature-type": "Mindestens ein Signaturtyp sollte aktiviert sein.",
"expect-default-one-signature-type": "Bitte aktivieren Sie einen zusätzlichen Signaturtyp neben dem Standard.",
"add-signer-note": "Hinweis - Dieser Kontakt wird zu Ihrer Kontaktliste hinzugefügt",
"allowed-signature-types-help": {
"p1": "Diese Signaturpräferenz steuert, welche Signaturoptionen Ihren Unterzeichnern zur Verfügung stehen. Nur die Signaturtypen, die Sie auswählen, werden im Signatur-Widget angezeigt, wenn ein Dokument unterzeichnet wird. Wenn Sie beispielsweise nur die Option 'Zeichnen' in den Einstellungen auswählen, sieht Ihr Unterzeichner nur die Option 'Zeichnen' im Signatur-Widget, während die anderen drei Optionen nicht verfügbar sind.",
"l1": "Erstellen Sie eine handschriftliche Signatur direkt auf dem Bildschirm mit Ihrem Finger oder einem Stift.",
"l2": "Geben Sie Ihren Namen ein und wählen Sie aus verschiedenen Schriftarten, um eine Signatur zu erstellen.",
"l3": "Laden Sie eine vorhandene digitale Version Ihrer Signatur hoch.",
"l4": "Verwenden Sie einen voreingestellten Signaturstil, der unter 'Meine Signatur' in Ihren Einstellungen gespeichert ist."
},
"notify-on-signatures": "Benachrichtigen bei Signaturen",
"notify-on-signatures-help": {
"p1": "Der Ersteller des Dokuments erhält eine E-Mail-Benachrichtigung, sobald ein Unterzeichner das Dokument unterschreibt.",
"note": "Hinweis: Unabhängig von dieser Einstellung wird immer eine Abschluss-E-Mail mit dem signierten Dokument und dem Abschlusszertifikat im Anhang an alle Unterzeichner und den Dokumentbesitzer gesendet."
},
"expiry-date": "Ablaufdatum",
"expiry-date-updated": "Das Ablaufdatum wurde erfolgreich auf {{newexpirydate}} verlängert.",
"expiry-date-error": "Bitte geben Sie ein neues Ablaufdatum an, das nach dem aktuellen liegt.",
"duplicate-template-alert": "Sind Sie sicher, dass Sie eine Kopie dieser Vorlage erstellen möchten?",
"duplicate-template-created": "Kopie der Vorlage erfolgreich erstellt.",
"delete-page": "Seite löschen",
"merge-pdf": "PDFs zusammenführen",
"add-pages": "Seiten hinzufügen",
"delete-alert": "Einzelne Seite kann nicht gelöscht werden.",
"delete-alert-2": "Sind Sie sicher, dass Sie diese Seite löschen möchten?",
"delete-note": "Hinweis: Sobald Sie diese Seite löschen, kann dies nicht rückgängig gemacht werden.",
"Rotation-alert": "Seite drehen",
"bulk-import": "Massenimport",
"contacts-file": "Kontaktdatei (xlsx, csv)",
"100-records-only": "Derzeit können Sie nur bis zu 100 Datensätze importieren.",
"csv-excel-support-only": "Laden Sie eine Datei in einem der folgenden Formate hoch: CSV, XLSX oder XLS.",
"contact-imported": "{{imported}} Kontakte wurden importiert. {{failed}} Kontakte konnten nicht importiert werden.",
"invalid-data": "Ungültige Daten",
"delete-folder-alert": "Sind Sie sicher, dass Sie diesen Ordner löschen möchten?",
"delete-folder-alert-1": "Löschen nicht möglich: Der Ordner muss leer sein.",
"defaultSign-alert": "{{widgetsType}} Widgets sind nicht für die automatische Signatur verfügbar.",
"embed-heading": "OpenSign-Vorlagen einbetten",
"public-template-edit-title": "Öffentliche Vorlage bearbeiten",
"public-template-edit-mssg": "Eine öffentliche Vorlage können Sie nicht direkt bearbeiten. Um Änderungen vorzunehmen, wird die Vorlage auf privat gesetzt und Sie müssen sie anschließend wieder öffentlich machen. Möchten Sie fortfahren?",
"agree-p1": "Ich bestätige, dass ich die ",
"agree-p2": "Offenlegung elektronischer Aufzeichnungen und Signaturen",
"agree-p3": "gelesen und verstanden habe und der Verwendung elektronischer Aufzeichnungen und Signaturen zustimme.",
"agrre-button": "Zustimmen & Fortfahren",
"term-cond-title": "Allgemeine Geschäftsbedingungen",
"term-cond-h": "OFFENLEGUNG ELEKTRONISCHER AUFZEICHNUNGEN UND SIGNATUREN",
"term-cond-p1": "Diese Offenlegung elektronischer Aufzeichnungen und Signaturen ('Offenlegung') ist eine Vereinbarung zwischen dem Dokumentersteller ('Sender') und dem Unterzeichner ('Ihnen'), bereitgestellt über die OpenSign-Plattform ('Plattform'). Durch das Signieren von Dokumenten über OpenSign stimmen Sie den in dieser Offenlegung beschriebenen Bedingungen zu. Bitte lesen Sie sie sorgfältig durch, bevor Sie fortfahren.",
"term-cond-h1": "1. Zweck und Geltungsbereich",
"term-cond-p2": "Diese Offenlegung regelt die Nutzung elektronischer Aufzeichnungen und Signaturen für Dokumente, die vom Sender bereitgestellt und von Ihnen über die OpenSign-Plattform unterzeichnet werden. Mit Ihrer Zustimmung erkennen Sie an, dass:",
"term-cond-p3": "Sie Dokumente elektronisch über OpenSign empfangen und unterzeichnen werden.",
"term-cond-p4": "Ihre elektronische Signatur rechtlich bindend und einer handschriftlichen Signatur gleichgestellt ist.",
"term-cond-h2": "2. Zustimmung zur Nutzung elektronischer Aufzeichnungen und Signaturen",
"term-cond-p5": "Durch Ihre Zustimmung zu dieser Offenlegung:",
"term-cond-p6": "Erklären Sie sich einverstanden, elektronisch mit dem Sender über OpenSign zu kommunizieren, und verstehen, dass diese Zustimmung gültig ist, bis sie widerrufen wird.",
"term-cond-p7": "Stimmen Sie zu, Dokumente über OpenSign elektronisch zu prüfen, zu unterzeichnen und zurückzusenden.",
"term-cond-p8": "Diese Zustimmung gilt nur für die spezifischen Dokumente, die vom Sender bereitgestellt werden.",
"term-cond-h3": "3. Recht auf Widerruf der Zustimmung",
"term-cond-p9": "Sie haben das Recht, Ihre Zustimmung zur Nutzung elektronischer Aufzeichnungen und Unterschriften zu widerrufen, indem Sie:",
"term-cond-p10": "Den Absender direkt über die in seiner Kommunikation angegebenen Kontaktdaten kontaktieren.",
"term-cond-p11": "Den Absender vor Abschluss des Unterschriftsprozesses benachrichtigen.",
"term-cond-h4": "4. Hardware- und Softwareanforderungen",
"term-cond-p12": "Um Dokumente mit OpenSign zu unterzeichnen, benötigen Sie:",
"term-cond-p13": "Ein Gerät mit Internetzugang.",
"term-cond-p14": "Einen kompatiblen Webbrowser mit aktiviertem JavaScript.",
"term-cond-p15": "Ein E-Mail-Konto, um Benachrichtigungen und unterzeichnete Dokumente zu erhalten.",
"term-cond-p16": "Wenn sich diese Anforderungen ändern, werden Sie vom Absender oder OpenSign über die Aktualisierungen informiert.",
"term-cond-h5": "5. Aufbewahrung von Unterlagen und Anforderung von Papierkopien",
"term-cond-p17": "Sie sind dafür verantwortlich, sicherzustellen, dass Sie auf die elektronisch unterzeichneten Dokumente zugreifen und diese aufbewahren können. OpenSign stellt Tools zur Verfügung, mit denen Sie das unterzeichnete Dokument unmittelbar nach Abschluss des Unterzeichnungsprozesses herunterladen oder ausdrucken können.",
"term-cond-p18": "Wenn Sie eine Papierkopie eines elektronisch unterzeichneten Dokuments bevorzugen, können Sie diese direkt beim Absender anfordern. Der Absender kann eine angemessene Gebühr für die Bereitstellung von Papierkopien erheben, sofern dies nicht durch geltendes Recht verboten ist.",
"term-cond-h6": "6. Anerkennung und Zustimmung",
"term-cond-p19": "Durch das Fortfahren bestätigen Sie:",
"term-cond-p20": "Dass Sie in der Lage sind, elektronische Dokumente wie in dieser Offenlegung beschrieben zu öffnen und zu überprüfen.",
"term-cond-p21": "Der Nutzung elektronischer Aufzeichnungen und Unterschriften für die angegebenen Transaktionen mit dem Absender zuzustimmen.",
"term-cond-p22": "Zu verstehen, dass OpenSign eine Plattform zur Unterstützung der Transaktion ist und keine Partei der Vereinbarung ist.",
"term-cond-h7": "7. Rechtliche Wirkung",
"term-cond-p23": "Ihre durch OpenSign vermittelte elektronische Unterschrift:",
"term-cond-p24": "Entspricht den geltenden Gesetzen über elektronische Unterschriften, einschließlich, aber nicht beschränkt auf den E-SIGN Act in den Vereinigten Staaten, die EU-eIDAS-Verordnung und das indische Information Technology Act.",
"term-cond-p25": "Ist rechtlich bindend zwischen Ihnen und dem Absender für die unterzeichneten Dokumente.",
"term-cond-h8": "8. Plattformrolle und Haftungsbeschränkung",
"term-cond-p26": "OpenSign dient als Plattform zur Unterstützung elektronischer Transaktionen. Es ist nicht verantwortlich für den Inhalt, die Gültigkeit oder die Durchsetzbarkeit der vom Absender gesendeten Dokumente. Alle Streitigkeiten oder Probleme im Zusammenhang mit dem Dokument oder seiner Unterzeichnung müssen direkt zwischen Ihnen und dem Absender gelöst werden.",
"term-cond-h9": "9. Beendigung der Vereinbarung",
"term-cond-p27": "Ihre Zustimmung gilt nur für die aktuelle Transaktion, es sei denn, der Absender gibt etwas anderes an. OpenSign behält sich das Recht vor, den Zugriff auf seine Plattform bei Verstößen gegen die Nutzungsbedingungen oder Richtlinien zu beenden.",
"term-cond-p28": "Durch Auswahl von 'Ich stimme zu' bestätigen Sie, dass Sie die Bedingungen dieser Offenlegung über elektronische Aufzeichnungen und Unterschriften verstanden haben und diesen zustimmen sowie der elektronischen Unterzeichnung der vom Absender über OpenSign bereitgestellten Dokumente zustimmen.",
"term-cond-p29": "Wenn Sie Fragen zu dieser Offenlegung haben, kontaktieren Sie den Absender direkt. Für technischen Support mit OpenSign besuchen Sie",
"term-cond-p30": "oder senden Sie eine E-Mail an",
"js-snippet-msg": "Um OpenSign-Vorlagen in Ihre HTML-Websites oder Landingpages zu integrieren, können Sie den folgenden Code verwenden:",
"js-snippet-msg-1": "Die TemplateId erhalten Sie von der",
"js-snippet-msg-2": "Vorlagen verwalten",
"js-snippet-msg-3": "Seite.",
"agrrement-alert": "Um fortzufahren, müssen Sie der Offenlegung über elektronische Aufzeichnungen und Unterschriften zustimmen.",
"webhook-already-exists": "Die URL existiert bereits! Bitte versuchen Sie eine andere.",
"webhook-must-be-secure": "Die Webhook-URL muss sicher sein und https:// verwenden.",
"provide-test-token": "Bitte generieren Sie einen API-Test-Token.",
"invalid-redirect-url": "Bitte geben Sie eine gültige Weiterleitungs-URL an.",
"auto-sign-mssg": "Alles automatisch unterzeichnen",
"already-exist-signer": "Der ausgewählte Unterzeichner ist bereits in der Empfängerliste vorhanden.",
"change-signer": "Unterzeichner ändern",
"signyour-self-button": "Verwenden Sie diese Option, um das Dokument selbst zu unterschreiben, ohne andere hinzuzufügen.",
"requestsign-button": "Verwenden Sie diese Option, um Unterschriften von anderen und sich selbst anzufordern.",
"signyour-self-description": "Verwenden Sie dieses Formular, um das Dokument selbst zu unterschreiben, ohne andere hinzuzufügen.",
"requestsign-description": "Verwenden Sie dieses Formular, um Unterschriften von anderen und sich selbst anzufordern.",
"template-form-description": "Verwenden Sie dieses Formular, um wiederverwendbare Vorlagen für Ihre Dokumente zu erstellen.",
"allow-modify": "Änderungen erlauben",
"allow-modify-help": {
"p1": "Möchten Sie während des Unterzeichnungsprozesses Änderungen am Dokument erlauben?",
"p2": "Durch Auswahl dieser Option können Unterzeichner zusätzliche Elemente wie Unterschriften, Initialen, Stempel oder Texte zu den vorhandenen Widgets hinzufügen.",
"p3": "Durch Auswahl dieser Option wird verhindert, dass Unterzeichner während des Unterzeichnungsprozesses Ergänzungen zum Dokument vornehmen."
},
"signature-setting": "Signatur-Einstellungen",
"activation-key": "Aktivierungsschlüssel",
"paid-selfhost-help": "Bitte kontaktieren Sie OpenSign für weitere Informationen unter ",
"remaining-credits-selfhost-help": "Verwenden Sie Premium-Credits für API-Dokumentensignaturen, den Massenversand und die OpenSign-Integration auf Ihrer Website. Sie haben {{allowedcredits}} enthaltene Credits und {{addoncredits}} zusätzlich gekaufte Credits übrig.",
"signature-type": "Ihre Signatur",
"initial-type": "Ihre Initialen",
"redirect-url": "Weiterleitungs-URL",
"bulk-send": "Massenversand",
"select-timezone": "Wählen Sie Ihre Zeitzone",
"current-time": "Aktuelle Uhrzeit",
"email-help": "Aus Sicherheitsgründen dürfen Sie die E-Mail-Adresse nicht ändern. Bitte erstellen Sie ein weiteres kostenloses Konto mit der neuen E-Mail-Adresse.",
"doc-sent": "Dokument erfolgreich gesendet.",
"no-signer": "Entfernen",
"and": "und",
"make-public": "Öffentlich machen",
"general": "Allgemein",
"request-email": "Anfrage-E-Mail",
"completion-email": "Abschluss-E-Mail",
"variables-use": "Zu verwendende Variablen",
"send-to-sign": "hat Sie gebeten zu unterschreiben",
"use-template": "Vorlage verwenden",
"contact-already-exists": "Der Kontakt existiert bereits."
}
+284 -159
View File
@@ -1,7 +1,7 @@
{
"header-news": "New feature: Teams plan users can now integrate their own AWS S3 buckets for file storage",
"header-news-btn": "Setup Now",
"create-account": "Create Account",
"header-news-btn": "Setup now",
"create-account": "Create account",
"login": "Login",
"language": "Language",
"name": "Name",
@@ -12,7 +12,7 @@
"job-title": "Job title",
"profile": "Profile",
"log-out": "Log Out",
"is-email-verified": "Is Email verified",
"is-email-verified": "Is email verified",
"public-profile": "Public profile",
"tagline": "Tagline ",
"has-requested-you-to-sign": "has requested you to sign",
@@ -33,15 +33,15 @@
"action": "Action",
"add": "Add",
"add-new-folder": "Add a new folder",
"disable-documentId": "Disable DocumentId",
"disable-documentId": "Disable documentId",
"edit": "Edit",
"change-password": "Change Password",
"change-password": "Change password",
"save": "Save",
"cancel": "Cancel",
"upgrade-now": "Upgrade now",
"upgrade-to": "Upgrade to",
"plan": "Plan",
"subscribe-card-teamplan":"Unlock the full power of collaboration! Create unlimited organizations, teams, and hierarchies. Share templates seamlessly across teams and assign custom user roles. Elevate your workflow today!",
"subscribe-card-teamplan": "Unlock the full power of collaboration! Create unlimited organizations, teams, and hierarchies. Share templates seamlessly across teams and assign custom user roles. Elevate your workflow today!",
"subscribe-card-plan": "Unlock premium features starting at just {{premiumPrice}}/month. Enjoy enhanced performance and only {{addonPrice}} per additional credit after your included premium credits.",
"user-name-limit-char": "To have a username less than 8 character please subscribe",
"tour-content": "Don't show this again",
@@ -51,15 +51,15 @@
"docs": "Docs",
"session-expired": "Your session has expired.",
"public-profile-help": "This is your public URL. Copy or share it with the signer, and you will be able to see all your publicly set templates.",
"tagline-help": "This Tagline will appear on your OpenSign public profile for ex. https://opensign.me/alex",
"welcome": "Welcome Back!",
"tagline-help": "This tagline will appear on your OpenSign public profile for ex. https://opensign.me/alex",
"welcome": "Welcome back!",
"Login-to-your-account": "Login to your account",
"password": "Password",
"forgot-password": "Forgot Password?",
"forgot-password": "Forgot password?",
"loading": "Loading...",
"of": "of",
"sign-SSO": "Sign in with SSO",
"login-page": "Login Page",
"login-page": "Login P+age",
"or": "OR",
"signup-page": "Signup page",
"password-length": "Password should be 8 characters long",
@@ -76,23 +76,25 @@
"Templates": "Templates",
"Templates-Children": {
"Create template": "Create template",
"Manage templates": "Manage templates"
"Manage templates": "Manage templates",
"Embedding": "Embedding"
},
"OpenSign™ Drive": "OpenSign™ Drive",
"OpenSign™ Drive": "OpenSign™ drive",
"Reports": "Reports",
"Reports-Children": {
"Documents": "Documents",
"Documents-Children": {
"Need your sign": "Need your sign",
"In Progress": "In Progress",
"In Progress": "In progress",
"Completed": "Completed",
"Drafts": "Drafts",
"Declined": "Declined",
"Expired": "Expired",
"Contactbook": "Contactbook"
"Expired": "Expired"
},
"Contactbook": "Contactbook",
"Settings": "Settings",
"Settings-Children": {
"My Signature": "My Signature",
"API Token": "API Token",
"My Signature": "My signature",
"API Token": "API token",
"Webhook": "Webhook",
"Preferences": "Preferences",
"Teams": "Teams",
@@ -101,16 +103,16 @@
}
},
"dashboard-card": {
"Need your Signature": "Need your Signature",
"Out for signatures": "Out for Signatures"
"Need your Signature": "Need your signature",
"Out for signatures": "Out for signatures"
},
"report-name": {
"Draft Documents": "Draft Documents",
"Draft Documents": "Draft documents",
"Need your sign": "Need your sign",
"In-progress documents": "In-progress documents",
"Completed Documents": "Completed Documents",
"Declined Documents": "Declined Documents",
"Expired Documents": "Expired Documents",
"Completed Documents": "Completed documents",
"Declined Documents": "Declined documents",
"Expired Documents": "Expired documents",
"Recently sent for signatures": "Recently sent for signatures",
"Recent signature requests": "Recent signature requests",
"Drafts": "Drafts",
@@ -123,6 +125,7 @@
"sign": "sign",
"Sign": "SIGN",
"Resend": "Resend",
"Rename": "Rename",
"Revoke": "Revoke",
"Delete": "Delete",
"Use": "Use",
@@ -133,9 +136,11 @@
"View": "View",
"option": "Option",
"Embed": "Embed",
"Copy TemplateId":"Copy TemplateId",
"Copy Public URL":"Copy Public URL",
"extend-expiry-date": "Extend expiry date"
"Copy TemplateId": "Copy TemplateId",
"Copy Public URL": "Copy public URL",
"extend-expiry-date": "Extend expiry date",
"Duplicate Template": "Duplicate template",
"Duplicate": "Duplicate"
},
"report-heading": {
"Sr.No": "Sr.No",
@@ -148,16 +153,16 @@
"Note": "Note",
"Folder": "Folder",
"Reason": "Reason",
"Parent Team": "Parent Team",
"Parent Team": "Parent team",
"Active": "Active",
"Role": "Role",
"Team": "Team",
"Name": "Name",
"Status": "Status",
"created-date": "Created Date",
"created-date": "Created date",
"Type": "Type",
"Logs": "Logs",
"Expiry-date": "Expiry Date"
"Expiry-date": "Expiry date"
},
"report-help": {
"Draft Documents": "These are documents you have started but have not finalized for sending.",
@@ -170,18 +175,18 @@
"Templates": "This is a list of templates that are available to you for creating documents. You can click the 'use' button to create a new document using a template, modify the document & add signers in the next step."
},
"form-name": {
"Sign Yourself": "Sign Yourself",
"Request Signatures": "Request Signatures",
"New Template": "New Template"
"Sign Yourself": "Sign yourself",
"Request Signatures": "Request signatures",
"New Template": "New template"
},
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "file selected",
"template-title": "Template Title",
"document-title": "Document Title",
"template-title": "Template title",
"document-title": "Document title",
"description": "Description",
"time-to-complete": "Time To Complete (Days)",
"send-in-order": "Send In Order",
"time-to-complete": "Time to complete (Days)",
"send-in-order": "Send in order",
"send-in-order-help": {
"p1": "Choose how you want the signing requests to be sent to the document signers:",
"p2": "Selecting this option will send the signing request to the first signer initially. Once the first signer completes their part, the next signer in the sequence will receive the request. This process continues until all signers have signed the document. This method ensures that the document is signed in a specific order.",
@@ -192,49 +197,50 @@
"auto-reminder": "Auto reminder",
"remind-once": " Remind once in every (Days)",
"next": "Next",
"select-folder": "Select Folder",
"OpenSign-drive": "OpenSign™ Drive",
"select-folder": "Select folder",
"OpenSign-drive": "OpenSign™ drive",
"drive-document-status": {
"Completed": "Completed",
"Declined": "Declined",
"Draft": "Draft",
"Expired": "Expired",
"In Progress": "In Progress"
"In Progress": "In progress"
},
"select-folder-help": "If you do not select a folder, your signed document will be saved in the Main OpenSign drive folder.",
"no-data": "No data found",
"save-here": "Save Here",
"save-here": "Save here",
"back": "Back",
"add-folder": "Add folder",
"create-folder": "Create Folder",
"parent-folder": "Parent Folder",
"create-folder": "Create folder",
"parent-folder": "Parent folder",
"create": "Create",
"signers": "Signers",
"signers-help": "Begin typing a contact's name to see suggested signers from your saved contacts or add new ones. Arrange the signing order by adding signers in the desired sequence. Use the '+' button to include signers and the 'x' to remove them. Each signer will receive an email prompt to sign the document in the order listed.",
"bcc-help": "Start typing a contact's name to see suggestions from your saved contacts or add new ones. Use the '+' button to add a user and the 'x' to remove them. The selected user's email will be added as a Bcc (blind carbon copy). Each user will receive an email notification once the document is completed.",
"add-signer": "Add Signer",
"contact-not-found": "Contact not found",
"add-yourself": "Add Yourself",
"add-yourself": "Add yourself",
"submit": "Submit",
"reset": "Reset",
"my-signature": "My Signature",
"my-signature": "My signature",
"signature": "Signature",
"upload-image": " Upload image",
"clear": "Clear",
"upload-signature/Image": "Please upload signature/Image",
"initials": "Initials",
"API": "API",
"api-token": "API Token",
"regenerate-token": "Regenerate Live Token",
"generate-token": "Generate Live Token",
"view-docs": "View Docs",
"api-token": "API token",
"regenerate-token": "Regenerate live token",
"generate-token": "Generate live token",
"view-docs": "View docs",
"generate-token-alert": "Are you sure you want to regenerate token it will expire old token?",
"yes": "Yes",
"copied": "Copied",
"something-went-wrong-mssg": "Something went wrong, please try again later.",
"token-generated": "Token generated successfully.",
"webhook": "Webhook",
"update-webhook": "Update Webhook",
"add-webhook": "Add Webhook",
"update-webhook": "Update webhook",
"add-webhook": "Add webhook",
"webhook-added": "Webhook added successfully.",
"team-disabled": "Team disabled",
"Team enabled": "Team enabled",
@@ -242,20 +248,20 @@
"disable": "disable",
"enable": "enable",
"this-team": " this team?",
"edit-team": "Edit Team",
"name-of-team": "Name of Team",
"edit-team": "Edit team",
"name-of-team": "Name of team",
"prev": "Prev",
"no-data-avaliable": "No Data Available",
"add-team": "Add Team",
"page-not-found": "Page Not Found",
"no-data-avaliable": "No data available",
"add-team": "Add team",
"page-not-found": "Page not found",
"users-from-teams": "users from Teams",
"user-status": "User status",
"activate": "activate",
"deactivate": "deactivate",
"this-user": " this user",
"delete-user": "Delete User",
"delete-user": "Delete user",
"delete": "delete",
"add-user": "Add User",
"add-user": "Add user",
"password-generateed": "Password will only be generated once; make sure to copy it.",
"Team status": "Team status",
"user-deactivated": "User deactivated.",
@@ -269,15 +275,15 @@
"make-template-private-alert": "Are you sure you want to make this template private? This will remove it from your public profile.",
"public-role": "Public role",
"public-url": "Public profile",
"embed-template": "Embed Template",
"embed-template": "Embed template",
"public-url-copy": "Heres your public URL: ",
"public-url-copy-mssg":"Copy it or share it with the signer, and you will be able to see all your publicly set templates.",
"add-public-url-alert": "Please complete your OpenSign public profile setup. Once done, youll be able to make templates public. This process only needs to be completed once.",
"public-url-copy-mssg": "Copy it or share it with the signer, and you will be able to see all your publicly set templates.",
"add-public-url-alert": "You can generate your OpenSign public profile",
"share-with-alert": "You cannot share a template if any roles already have contacts assigned. Please remove all contact assignments from the roles before sharing the template.",
"share-with": "Share with",
"team-not-found": "Team not found",
"roles": "Roles",
"delete-document": "Delete Document",
"delete-document": "Delete document",
"delete-document-alert": "Are you sure you want to delete this document?",
"quick-send": "Quick send",
"add-new": "Add new",
@@ -290,11 +296,12 @@
"copy": "Copy",
"revoke-document": "Revoke document",
"revoke-document-alert": "Are you sure you want to revoke this document?",
"resend-mail": "Resend Mail",
"resend-mail": "Resend mail",
"resend-mail-help": "You can use following variables which will get replaced with their actual values:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"subject": "Subject",
"body": "Body",
"add-contact": "Add Contact",
"add-contact": "Add contact",
"edit-contact": "Edit contact",
"add-signer-alert": "Contact already exist! Please select it from Signers dropdown",
"record-delete-alert": "Record deleted successfully!",
"record-revoke-alert": "Record revoked successfully!",
@@ -319,11 +326,11 @@
"verify": "Verify",
"verify-email-1": "Verify email",
"resend": "Resend",
"contact-details": "Contact Details",
"contact-details": "Contact details",
"verify-email": "Please verify your email !",
"send-otp": " Send OTP",
"otp-placeholder": "Enter verification code received over email",
"loading-doc": "Loading Document..",
"loading-doc": "Loading document..",
"widgets-name": {
"signature": "signature",
"stamp": "stamp",
@@ -351,7 +358,7 @@
"decline": "Decline",
"finish": "Finish",
"mail": "Mail",
"sign-now": "Sign Now",
"sign-now": "Sign now",
"successfully-signed": "Successfully signed!",
"email-mssg": " Recipients added here will get a copy of the signed document.",
"email-error-1": " please provide correct email address",
@@ -367,11 +374,11 @@
"black": "black",
"yellow": "yellow"
},
"my-initials": "My Initials",
"my-initials": "My initials",
"upload": "Upload",
"initial-teb": "Initials",
"signature-tab": "Signature",
"your-signature": "Your signature",
"initial-alert": "My initial not found",
"copy-title": "Copy widget to",
"contact-delete-alert": "Are you sure you want to delete this contact?",
"reset-password-alert-1": "Reset password link has been sent to your email id",
@@ -404,21 +411,20 @@
"encrypted-pdf-alert": "Currently encrypted pdf files are not supported.",
"encrypted-pdf-alert-1": "PFX file password is invalid.",
"user-not-exist": "User does not exist",
"template-signature-field": "Please add at least one signature field for all roles.",
"template-role-alert": "Please add roles first",
"create-document": "Create Document",
"template-created-alert": "Do you want to create a document using the template you just created ?",
"create-document": "Create document",
"template-created-alert": "What would you like to do with the template you just created? ",
"signers-alert": "signers alert",
"template-creation-alert-1": "Please select signer for add placeholder!",
"ok": "Ok",
"radio-group": "Radio group",
"dropdown-options": "Dropdown options",
"add-role": "Add Role",
"add-role": "Add role",
"role-ex": " e.g: Customer, Hr, Director, Manager, Student, etc",
"add/choose-signer": "Add/Choose Signer",
"add/choose-signer": "Add/Choose signer",
"choose-from-contacts": "Choose from contacts",
"select-signer": " Please select signer",
"edit-template": "Edit Template",
"edit-template": "Edit template",
"widget-info": "Widget info",
"validation": "Validation",
"invalid-default-value": "invalid default value",
@@ -435,23 +441,25 @@
"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}}",
"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?",
"placeholder-alert-4": "You have successfully sent mails to all recipients!",
"placeholder-alert-5": "Do you want to sign documents right now ?",
"placeholder-mail-alert": "You have successfully sent email to {{name}}. Subsequent signers will get email(s) once {{name}} signs the document",
"placeholder-alert-5": "Do you want to sign the document right now?",
"placeholder-alert-6": "Please setup mail adapter to send mail!",
"placeholder-alert-7": "Please select signer for add placeholder!",
"email-subject": "email subject",
"email-body": "email body",
"email-placeholder": "add body of email",
"reset-to-default": "Reset to default",
"cutomize-email": "Cutomize Email",
"cutomize-email": "Cutomize email",
"upgrade-to-customize-email": "Upgrade to customize Email",
"sign-url": "Sign url",
"mails-sent": "Mails sent",
"mails-sent": "Mails Sent",
"mail-not-delivered": "Mail not delivered",
"document-alert": "Document Alert",
"document-alert": "Document alert",
"owner-subscription-expired": "Owner's subscription has expired.",
"subscription-expired": "Subscription Expired",
"alert-message": "Alert message",
@@ -469,12 +477,13 @@
"sign-up": "Sign up",
"folder-already-exist!": "folder already exist!",
"please-select-position!": "please select position!",
"default-sign-alert": "Are you sure you want to auto sign at requested all locations?",
"Your-Signature": "Your Signature",
"auto-sign-all": "Auto Sign All",
"default-sign-alert": "Are you sure you want to auto-sign all requested {{widgetsType}} widgets?",
"Your-Signature": "Your signature",
"auto-sign-all": "Auto sign all",
"generating-certificate": "Generating certificate",
"pdf-download": "PDF Download",
"pdf-download": "PDF download",
"generate-certificate-alert": " Your completion certificate is being generated. Please wait momentarily. If the download doesn't start shortly, click the button again.",
"generate-certificate-err": "We are currently experiencing an issue with certificate generation. Please try again later.",
"folder-already-exist": "Folder already exist!",
"folder-created-successfully": "Folder created successfully!",
"fill-folder-name": "Please fill folder name",
@@ -484,32 +493,33 @@
"role-not-found": "Role not found.",
"do-not-access": "You don't have access, please contact the admin.",
"add-admin": "Add admin",
"opensign-setup": "OpenSign Setup",
"opensign-setup": "OpenSign setup",
"join-discord": "Join our discord server",
"admin-already-exist": "Admin already exist.",
"password-update-alert-1": "Password updated successfully.",
"password-update-alert-2": "Your current password is missing or incorrect.",
"password-update-alert-3": "Your current password is missing or incorrect.",
"password-update-alert-4": "Your password and confirmation password do not match.",
"current-password": "Current Password",
"new-password": "New Password",
"confirm-password": "Confirm Password",
"current-password": "Current password",
"new-password": "New password",
"confirm-password": "Confirm password",
"file-alert-1": "The selected file size is too large. Please select a file less than",
"file-alert-2": "Please select file.",
"file-alert-3": "Please wait while the document is being uploaded.",
"enter-pdf-password": "Enter Pdf Password",
"enter-pdf-password": "Enter Pdf password",
"correct-password": "Please provide correct password",
"decrypting-pdf": " Decrypting pdf please wait...",
"invalid-otp": "Invalid Otp",
"invalid-otp": "Invalid otp",
"user-not-found": "User not found!",
"enter-otp-alert": "Please Enter OTP!",
"enter-otp-alert": "Please enter OTP!",
"get-verification-code": "Get verification code",
"get-verification-code-2": "You will get a verification code via Email",
"enter-verification-code": "Enter Verification Code",
"enter-verification-code": "Enter verification code",
"provide-your-details": "Provide your details",
"provide-email": "Please provide email.",
"additional-info": "Additional Info",
"additional-info": "Additional info",
"signature-saved-alert": "Signature saved successfully.",
"saved-successfully": "Saved successfully.",
"fill-field": "Please fill out this field",
"error-template": "Error: Template not found!",
"error": "Error",
@@ -525,8 +535,8 @@
"yet-to-sign": "Yet to sign",
"validation-alert-1": "The input does not meet the criteria set by the regular expression.",
"account-already-exists": "Account already exists!",
"choose-password": "Choose Password",
"enter-password": "Enter Password",
"choose-password": "Choose password",
"enter-password": "Enter password",
"sure": "sure",
"subscribed-alert": "You have already subscribed to plan!",
"monthly": "Monthly",
@@ -540,21 +550,21 @@
"filed-required-correctly": "Please fill required details correctly.",
"admin-created": "Admin created",
"invalid-masterkey": "Invalid masterkey",
"opensign-Setup": "OpenSign Setup",
"opensign-Setup": "OpenSign setup",
"master-key": " Master key",
"profile-update-alert": "Profile updated successfully.",
"date": "Date",
"report-not-found": "Report Not Found",
"report-not-found": "Report not found",
"please-wait": "please wait",
"select-foler": "Select Folder",
"generated-on": "Generated On",
"select-foler": "Select folder",
"generated-on": "Generated on",
"text-field": "Text field",
"font-size": "Font size",
"widget-status": {
"Required": "Required",
"Optional": "Optional"
},
"all-right": "All Rights Reserved",
"all-right": "All rights reserved",
"version": "version",
"social-media": {
"github": "Github",
@@ -576,18 +586,19 @@
"opensign-drive-7": "Right-click on any folder to see options. Choose Rename to change the folders 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 the Back, Decline, or Finish buttons to navigate your document. Use the ellipsis menu for additional options, including the Download button .",
"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-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-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-6": "Clicking 'Send' 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.",
"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 create and send multiple documents using an existing template, click the 'Quick Send' button.",
"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 wont 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.",
"signyour-self-2": "Drag and drop anywhere in this area. You can resize and move it later.",
@@ -602,11 +613,12 @@
"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.",
"public-template": "This video demonstrates how to set up your personalized public profile, such as https://opensign.me/your-username. Youll also learn how to customize your tagline and make your templates available for public signing."
},
"public-template": "This video demonstrates how to set up your personalized public profile, such as https://opensign.me/your-username. Youll 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."
},
"enter-email-plaholder": "Add an email address and hit enter",
"success-email-alert": "Email sent successfully!",
"expired-doc-title": "Expired Document",
"expired-doc-title": "Expired document",
"expired-on-mssg": "This document expired on {{expiredDate}} and is no longer available to sign.",
"signature-validate-alert": "Please confirm that you have selected at least {{minRequiredCount}} checkboxes.",
"signature-validate-alert-2": "Ensure this field is accurately filled and meets all requirements.",
@@ -618,51 +630,53 @@
"Total-price-for-next-time": "Total price for next time",
"Proceed": "Proceed",
"Select": "Select",
"Add-seats": "Add Seats",
"Add-seats": "Add seats",
"user-name-exist": "user name already exist",
"format": "format",
"select-date-format": "Select a date format",
"quantity-of-credits": "Quantity of premium credits",
"remaining-credits": "Premium credits available:",
"remaining-credits-help":"Use premium credits for API document signing, bulk sending, or embedding OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
"remaining-credits-help": "Use premium credits for API document signing, bulk sending, or embedding OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
"additional-credits": "Please purchase premium credits",
"quota-err-quicksend": "Quota Reached, You don't have enough credits.",
"buy-credits": "Buy Premium Credits",
"rotate-right":"Rotate right",
"rotate-left":"Rotate left",
"rotate-alert-mssg":"All widgets on this page will be lost. Are you sure you want to proceed?",
"templateid":"Template-Id",
"bulk-send-subcription-alert":"Please upgrade to Professional or Team plan to use bulk send.",
"generate-test-token":"Generate Test Token",
"regenerate-test-token":"Regenerate Test Token",
"help-test-token":"This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once youve completed your testing, you can upgrade to one of our paid plans to generate a production token.",
"help-api-token":"This token can be used to access the production APIs at the {{origin}}/api/v1 endpoint. It can only be generated on one of our paid plans.",
"reason":"Reason",
"decline-by":"Declined/revoked by",
"quota-err-quicksend": "Quota reached, you don't have enough credits.",
"buy-credits": "Buy premium credits",
"rotate-right": "Rotate right",
"rotate-left": "Rotate left",
"rotate-alert-mssg": "All widgets on this page will be lost. Are you sure you want to proceed?",
"templateid": "Template-Id",
"bulk-send-subcription-alert": "Please upgrade to Professional or Team plan to use bulk send.",
"generate-test-token": "Generate test token",
"regenerate-test-token": "Regenerate test token",
"help-test-token": "This token can be used to test the APIs at the https://sandbox.opensignlabs.com/api/v1 endpoint, allowing you to conduct unlimited document signatures. Please note that the sandbox API will sign your documents with self-signed certificates, which may not be recognized as valid by Adobe. Once youve completed your testing, you can upgrade to one of our paid plans to generate a production token.",
"help-api-token": "This token can be used to access the production APIs at the {{origin}}/api/v1 endpoint. It can only be generated on one of our paid plans.",
"reason": "Reason",
"decline-by": "Declined/revoked by",
"document-declined": "Document declined",
"public-template-mssg-1":"To integrate OpenSign into your React or Next.js project, simply run the following command:",
"public-template-mssg-2" :"Ensure you have npm or yarn set up in your project. If youre using Yarn, you can replace npm install with yarn add @opensign/react.",
"public-template-mssg-3" :"Need more details or examples?",
"public-template-mssg-1": "To integrate OpenSign into your React or Next.js project, simply run the following command:",
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If youre using Yarn, you can replace npm install with yarn add @opensign/react.",
"public-template-mssg-3": "Need more details or examples?",
"public-template-mssg-4": "Visit the",
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
"public-template-mssg-6" :"Before you can use this code snippet, you must make this template public.",
"copy-code":"COPY",
"copied-code":"COPIED",
"Installation":"Installation",
"Usage" :"Usage",
"insufficient-credits":"Insufficient Signing Credits",
"insufficient-credits-mssg":"The owner of this document currently lacks the necessary OpenSign credits for you to sign. Please reach out to the owner if you require further details.",
"public-template-mssg-6": "Before you can use this code snippet, you must make this template public.",
"public-template-mssg-7": "Before you can generate a public link you must make this template public.",
"here": "here",
"copy-code": "COPY",
"copied-code": "COPIED",
"Installation": "Installation",
"Usage": "Usage",
"insufficient-credits": "Insufficient signing credits",
"insufficient-credits-mssg": "The owner of this document currently lacks the necessary OpenSign credits for you to sign. Please reach out to the owner if you require further details.",
"angular-npm-mssg-1": "To integrate OpenSign into your Angular project, simply run the following command:",
"quota-mail-info-head":"Monthly request signatures email limit",
"quota-mail-info-head": "Monthly request signatures email limit",
"quota-mail-info": "You can send upto 15 signature request emails every month. Upgrade now to send unlimited signing requests directly.",
"quota-mail-reset": "Your signature request email credits will be reset on",
"quota-mail": "You've reached your limit of 15 signature request emails for this month. Upgrade now to continue sending emails directly.",
"quota-mail-tip":"Tip: You can still sign <1>unlimited documents</1> by manually sharing the signing request links.",
"quota-mail-head":"Quota Reached",
"unauthorized-modal":"You don't have permission to perform this action, please contact {{adminEmail}}.",
"sent-this-month":"Sent this month",
"available-seats":"Available seats",
"buy-users":"Buy more users",
"quota-mail-tip": "Tip: You can still sign <1>unlimited documents</1> by manually sharing the signing request links.",
"quota-mail-head": "Quota reached",
"unauthorized-modal": "You don't have permission to perform this action, please contact {{adminEmail}}.",
"sent-this-month": "Sent this month",
"available-seats": "Available seats",
"buy-users": "Buy more users",
"isenable-otp": "Enable OTP verification",
"isenable-otp-help": {
"p1": "Would you like to enable the verification process using a one-time password (OTP)?",
@@ -670,31 +684,31 @@
"p3": "Selecting this option will disable OTP verification, allowing users to sign the document directly without additional steps.",
"p4": "Please choose the option that best suits your document signing requirements."
},
"advanced-options":"Advanced options",
"hide-advanced-options":"Hide Advanced options",
"download-files":"Download files",
"download-pdf":"Download Pdf",
"pdf-certificate":"Download Pdf + Certificate",
"document-logs":"Document logs",
"advanced-options": "Advanced options",
"hide-advanced-options": "Hide advanced options",
"download-files": "Download files",
"download-pdf": "Download pdf",
"pdf-certificate": "Download pdf + Certificate",
"document-logs": "Document logs",
"server-down": "Unable to connect to the OpenSign server. If you are self-hosting OpenSign, please ensure that all the steps in the documentation have been followed correctly. If you're running OpenSign locally, you might be accessing it through an incorrect port number.",
"admin-exists": "Admin already exists. Please login to the application using admin credentials in order to manage users.",
"enable-tour":"Enable Tour",
"istourenabled-help":{
"p1":"This will enable a guided tour for signers, providing instructions during the signing process.",
"p2":"This will disable the guided tour, ensuring a faster, uninterrupted signing experience.",
"p3":"Tip: If your signers are familiar with OpenSign and frequently use it, you may want to disable the guided tour for a more seamless experience. Choose the option that best fits your document signing needs."
},
"form-title-1":"Document Flow Setting",
"form-title-2":"Security Settings",
"public-tour-message":"The template needs to be public before you can generate a shareable link.",
"enable-tour": "Enable tour",
"istourenabled-help": {
"p1": "This will enable a guided tour for signers, providing instructions during the signing process.",
"p2": "This will disable the guided tour, ensuring a faster, uninterrupted signing experience.",
"p3": "Tip: If your signers are familiar with OpenSign and frequently use it, you may want to disable the guided tour for a more seamless experience. Choose the option that best fits your document signing needs."
},
"form-title-1": "Document flow setting",
"form-title-2": "Security settings",
"public-tour-message": "The template needs to be public before you can generate a shareable link.",
"add-user-template": "You need to add a role before you can add fields for it.",
"pdf-uncompatible": "This pdf is not compatible with opensign, please contact <support@opensignlabs.com>",
"text-field-tour":"Fields of type 'Text' must be filled in advance before the document is sent. If you need the signers to provide input, use the 'Text Input' field 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.",
"text-field-tour": "Fields of type 'Text' must be filled in advance before the document is sent. If you need the signers to provide input, use the 'Text Input' field 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.",
"expect-default-one-more-signature-type": "Please enable one additional signature type apart from the default.",
"add-signer-note": "Note - This contact will be added to your contact list",
"expect-default-one-signature-type": "Please enable one additional signature type apart from the default.",
"add-signer-note": "Note - This contact will be saved to your Contacts",
"allowed-signature-types-help": {
"p1": "This signature preference controls which signing options are available to your signers. Only the signature types you select will appear in the signature widget when a document is signed. For example, if you choose only the 'Draw' option in preferences, your signer will see only the 'Draw' option in the signature widget, while the other three options will not be available.",
"l1": "Create a handwritten signature directly on the screen using your finger or a stylus.",
@@ -705,9 +719,120 @@
"notify-on-signatures": "Notify on signatures",
"notify-on-signatures-help": {
"p1": "The document creator will receive an email notification whenever a signer signs the document.",
"note": "Note: Notification email will not be sent for the last signer, as the document creator will receive a completion email instead."
"note": "Note: Regardless of this setting, a completion email with the signed document and completion certificate attached is always sent to all signers and the document owner."
},
"expiry-date": "Expiry Date",
"expiry-date": "Expiry date",
"expiry-date-updated": "The expiry date has been successfully extended to {{newexpirydate}}",
"expiry-date-error": "Please provide a new expiry date that is later than the current one"
"expiry-date-error": "Please provide a new expiry date that is later than the current one",
"duplicate-template-alert": "Are you sure you want to create a duplicate of this template?",
"duplicate-template-created": "Duplicate template created successfully.",
"delete-page": "Delete page",
"merge-pdf": "Merge pdf",
"add-pages": "Add pages",
"delete-alert": "Can not delete single page",
"delete-alert-2": "Are you sure you want to delete this page?",
"delete-note": "Note: Once you delete this page, you cannot undo.",
"Rotation-alert": "Rotate page",
"bulk-import": "Bulk import",
"contacts-file": "Contacts file (xlsx, csv)",
"100-records-only": "Currently you can only import up to 100 records.",
"csv-excel-support-only": "Upload a file in one of the following formats: CSV, XLSX or XLS.",
"contact-imported": "{{imported}} contacts were imported. {{failed}} contacts failed to import.",
"invalid-data": "Invalid data",
"delete-folder-alert": "Are you sure you want to delete this Folder ?",
"delete-folder-alert-1": "Unable to delete: The folder must be empty.",
"defaultSign-alert": "{{widgetsType}} widgets are not available for auto sign.",
"embed-heading": "Embed OpenSign Templates",
"public-template-edit-title": "Edit Public Template",
"public-template-edit-mssg": "You cannot edit a public template directly. To make changes, the template will be set to private, and you'll need to make it public again afterward. Do you want to continue?",
"agree-p1": "I confirm that I have read and understood the ",
"agree-p2": "Electronic Record and Signature Disclosure",
"agree-p3": "and consent to use electronic records and signatures.",
"agrre-button": " Agree & Continue",
"term-cond-title": "Terms and conditions",
"term-cond-h": "ELECTRONIC RECORD AND SIGNATURE DISCLOSURE",
"term-cond-p1": "This Electronic Record and Signature Disclosure ('Disclosure') is an agreement between the Document Creator ('Sender') and the Signer ('You'), facilitated through the OpenSign platform ('Platform'). By signing documents via OpenSign, you agree to the terms outlined in this Disclosure. Please read it carefully before proceeding.",
"term-cond-h1": "1. Purpose and Scope",
"term-cond-p2": "This Disclosure governs the use of electronic records and signatures for documents sent by the Sender and signed by You through the OpenSign platform. By agreeing, You acknowledge that:",
"term-cond-p3": "You will receive and sign documents electronically via OpenSign.",
"term-cond-p4": "Your electronic signature is legally binding and equivalent to a handwritten signature.",
"term-cond-h2": "2. Consent to Use Electronic Records and Signatures",
"term-cond-p5": "By agreeing to this Disclosure:",
"term-cond-p6": "You consent to transact electronically with the Sender using OpenSign and understand that this consent is valid until withdrawn.",
"term-cond-p7": "You agree to review, sign, and return documents electronically using OpenSign.",
"term-cond-p8": "This consent applies only to the specific document(s) provided by the Sender.",
"term-cond-h3": "3. Right to Withdraw Consent",
"term-cond-p9": "You have the right to withdraw your consent to use electronic records and signatures by:",
"term-cond-p10": "Contacting the Sender directly using the contact information provided in their communication.",
"term-cond-p11": "Notifying the Sender before completing the signing process.",
"term-cond-h4": "4. Hardware and Software Requirements",
"term-cond-p12": "To sign documents using OpenSign, You must have:",
"term-cond-p13": "A device with internet access.",
"term-cond-p14": "A compatible web browser with JavaScript enabled.",
"term-cond-p15": "An email account to receive notifications and signed documents.",
"term-cond-p16": "If these requirements change, the Sender or OpenSign will notify You of the updates.",
"term-cond-h5": "5. Retention of Records and Request for Paper Copies",
"term-cond-p17": "You are responsible for ensuring You can access and retain the electronically signed document(s). OpenSign provides tools to download or print the signed document immediately after the signing process is complete.",
"term-cond-p18": "If You prefer a paper copy of any electronically signed document, You may request it directly from the Sender. The Sender may charge a reasonable fee for providing paper copies unless prohibited by applicable law.",
"term-cond-h6": "6. Acknowledgment and Agreement",
"term-cond-p19": "By proceeding, You:",
"term-cond-p20": "Confirm that You can access and review electronic documents as described in this Disclosure.",
"term-cond-p21": "Agree to use electronic records and signatures for the specified transaction(s) with the Sender.",
"term-cond-p22": "Understand that OpenSign is a platform facilitating the transaction and is not a party to the agreement.",
"term-cond-h7": "7. Legal Effect",
"term-cond-p23": "Your electronic signature facilitated through OpenSign:",
"term-cond-p24": "Complies with applicable electronic signature laws, including but not limited to the E-SIGN Act in the United States, the EU eIDAS Regulation, and Indias Information Technology Act.",
"term-cond-p25": "Is legally binding between You and the Sender for the signed document(s).",
"term-cond-h8": "8. Platform Role and Limitation of Liability",
"term-cond-p26": "OpenSign serves as a platform to facilitate electronic transactions. It is not responsible for the content, validity, or enforceability of the documents sent by the Sender. Any disputes or issues related to the document or its signing must be resolved directly between You and the Sender.",
"term-cond-h9": "9. Termination of Agreement",
"term-cond-p27": "Your consent applies only to the current transaction unless otherwise specified by the Sender. OpenSign reserves the right to terminate access to its platform for violations of its Terms of Service or policies.",
"term-cond-p28": "By selecting 'I Agree,' You confirm that You understand and agree to the terms of this Electronic Record and Signature Disclosure and consent to electronically sign the document(s) provided by the Sender through OpenSign.",
"term-cond-p29": "If You have questions about this Disclosure, contact the Sender directly. For technical support with OpenSign, visit",
"term-cond-p30": "or email",
"js-snippet-msg": "To integrate OpenSign Templates into your HTML websites or landing pages, you can use below code:",
"js-snippet-msg-1": "You can get the TemplateId from the",
"js-snippet-msg-2": "Manage Templates",
"js-snippet-msg-3": "page.",
"agrrement-alert": "To proceed, you must consent to the electronic record and signature disclosure.",
"webhook-already-exists": "The URL already exists! Please try a different one.",
"webhook-must-be-secure": "The webhook URL must be secure and use https://",
"provide-test-token": "Please generate api test token",
"invalid-redirect-url": "Please provide valid redirect url.",
"auto-sign-mssg": "Auto sign all",
"already-exist-signer": "The selected signer is already present",
"change-signer": "Change signer",
"signyour-self-button": "Use this option to sign the document yourself without adding others",
"requestsign-button": "Use this option to request signatures from others and yourself together.",
"signyour-self-description": "Use this form to sign the document yourself without adding others",
"requestsign-description": "Use this form to request signatures from others and yourself together.",
"template-form-description": "Use this form to create reusable templates for your documents.",
"allow-modify": " Allow modifications",
"allow-modify-help": {
"p1": "Do you want to allow modifications to the document during the signing process?",
"p2": "Selecting this option permits signers to add additional elements, such as signatures, initials, stamps, or text, on top of the existing widgets.",
"p3": "Selecting this option restricts signers from making any additions to the document during the signing process."
},
"signature-setting": "Signature settings",
"activation-key": "Activation key",
"paid-selfhost-help": "Please contact to opensign for more info at ",
"remaining-credits-selfhost-help": "Use premium credits for API document signing, bulk sending OpenSign integration on your website. You have {{allowedcredits}} included credits and {{addoncredits}} additional purchased credits remaining.",
"signature-type": "Your signature",
"initial-type": "Your initials",
"redirect-url": "Redirect url",
"bulk-send": "Bulk send",
"select-timezone": "Select your Timezone",
"current-time": "Current time",
"email-help": "You are not allowed to change email address due to security reasons. Please create another free account using the new email address.",
"doc-sent": "Document sent successfully.",
"no-signer": "Remove",
"and": "and",
"make-public": "Make public",
"general": "General",
"request-email": "Request Email",
"completion-email": "Completion Email",
"variables-use": "Variables to use",
"send-to-sign": "has requested you to sign",
"use-template": "Use Template",
"contact-already-exists": "contact already exists."
}
+145 -20
View File
@@ -76,19 +76,21 @@
"Templates": "Plantillas",
"Templates-Children": {
"Create template": "Crear plantilla",
"Manage templates": "Administrar plantillas"
"Manage templates": "Administrar plantillas",
"Embedding": "Incrustar"
},
"OpenSign™ Drive": "OpenSign™ Drive",
"Reports": "Reportes",
"Reports-Children": {
"Documents": "Documentos",
"Documents-Children": {
"Need your sign": "Necesitan tu firma",
"In Progress": "En progreso",
"Completed": "Completado",
"Drafts": "Borradores",
"Declined": "Rechazados",
"Expired": "Expirados",
"Contactbook": "Agenda de contactos"
"Expired": "Expirados"
},
"Contactbook": "Agenda de contactos",
"Settings": "Ajustes",
"Settings-Children": {
"My Signature": "Mi firma",
@@ -123,6 +125,7 @@
"sign": "firmar",
"Sign": "FIRMAR",
"Resend": "Reenviar",
"Rename": "Renombrar",
"Revoke": "Revocar",
"Delete": "Eliminar",
"Use": "Usar",
@@ -135,7 +138,9 @@
"Embed": "Incrustar",
"Copy TemplateId": "Copiar ID de plantilla",
"Copy Public URL": "Copiar URL pública",
"extend-expiry-date": "Ampliar fecha de caducidad"
"extend-expiry-date": "Date d'expiration",
"Duplicate Template": "Plantilla duplicada",
"Duplicate": "Duplicada"
},
"report-heading": {
"Sr.No": "Nº",
@@ -211,12 +216,14 @@
"create": "Crear",
"signers": "Firmantes",
"signers-help": "Empieza a escribir el nombre de un contacto para ver los firmantes sugeridos entre tus contactos guardados o añade otros nuevos. Organiza el orden de firma añadiendo firmantes en la secuencia deseada. Utiliza el botón «+» para añadir nuevos firmantes y la «X» para quitar todos. Cada firmante recibirá una solicitud por correo electrónico para que firme el documento según el orden indicado.",
"bcc-help": "Empieza a escribir el nombre de un contacto para ver sugerencias de tus contactos guardados o agregar nuevos. Usa el botón '+' para añadir un usuario y el botón 'x' para eliminarlo. El correo electrónico del usuario seleccionado se añadirá como Bcc (copia oculta). Cada usuario recibirá una notificación por correo electrónico una vez que el documento esté completado.",
"add-signer": "Añadir firmante",
"contact-not-found": "Contacto no encontrado",
"add-yourself": "Añadirte a ti mismo",
"submit": "Enviar",
"reset": "Volver a empezar",
"my-signature": "Mi firma",
"initial-alert": "Mi inicial no encontrada",
"signature": "Firma",
"upload-image": " Subir imagen",
"clear": "Limpiar",
@@ -272,7 +279,7 @@
"embed-template": "Insertar plantilla",
"public-url-copy": "Aquí está tu URL pública: ",
"public-url-copy-mssg": "Cópiala o compártela con el firmante y podrás ver todas tus plantillas públicas.",
"add-public-url-alert": "Por favor, completa la configuración de tu perfil público OpenSign. Una vez terminado, podrás crear plantillas públicas. Este proceso solo necesita ser completado una vez.",
"add-public-url-alert": "Puedes generar tu perfil público OpenSign",
"share-with-alert": "No puedes compartir una plantilla si alguno de los roles ya tiene contactos asignados. Elimina todas las asignaciones de contactos de los roles antes de compartir la plantilla.",
"share-with": "Compartir con",
"team-not-found": "Equipo no encontrado",
@@ -295,6 +302,7 @@
"subject": "Asunto",
"body": "Cuerpo",
"add-contact": "Agregar contacto",
"edit-contact": "Editar contacto",
"add-signer-alert": "¡El contacto ya existe! Selecciónelo en el menú desplegable 'Firmantes'",
"record-delete-alert": "¡Entrada eliminada exitosamente!",
"record-revoke-alert": "¡Entrada revocada exitosamente!",
@@ -404,10 +412,9 @@
"encrypted-pdf-alert": "Actualmente los archivos PDF encriptados no están soportados.",
"encrypted-pdf-alert-1": "La contraseña del archivo PFX es inválida.",
"user-not-exist": "El usuario no existe",
"template-signature-field": "Por favor, agrega al menos un campo de firma para todos los roles.",
"template-role-alert": "Por favor, primero agrega roles",
"create-document": "Crear documento",
"template-created-alert": "¿Quieres crear un documento usando la plantilla que acabas de crear?",
"template-created-alert": "¿Qué te gustaría hacer con la plantilla que acabas de crear?",
"signers-alert": "alerta de firmantes",
"template-creation-alert-1": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
"ok": "OK",
@@ -439,6 +446,7 @@
"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?",
"placeholder-alert-4": "¡Has enviado exitosamente correos a todos los destinatarios!",
"placeholder-mail-alert": "Has enviado un correo electrónico con éxito a {{name}}. Los siguientes firmantes recibirán un correo electrónico una vez que {{name}} firme el documento.",
"placeholder-alert-5": "¿Quieres firmar documentos ahora mismo?",
"placeholder-alert-6": "¡Por favor, configura el adaptador de correo para enviar correos!",
"placeholder-alert-7": "¡Por favor, selecciona un firmante para agregar un marcador de posición!",
@@ -469,12 +477,13 @@
"sign-up": "Registrarse",
"folder-already-exist!": "¡la carpeta ya existe!",
"please-select-position!": "¡por favor, selecciona una posición!",
"default-sign-alert": "¿En definitiva quieres firmar automáticamente en todos los lugares solicitados?",
"default-sign-alert": "¿Está seguro de que desea iniciar sesión automáticamente en todos los {{widgetsType}} widgets solicitados?",
"Your-Signature": "Tu firma",
"auto-sign-all": "Firmar todo automáticamente",
"generating-certificate": "Generando certificado",
"pdf-download": "Descargar PDF",
"generate-certificate-alert": " Tu certificado de finalización se está generando. Por favor, espera un momento. Si la descarga no inicia pronto, presiona el botón otra vez.",
"generate-certificate-err": "Estamos experimentando un problema al generar el certificado. Por favor, inténtelo de nuevo más tarde.",
"folder-already-exist": "¡La carpeta ya existe!",
"folder-created-successfully": "¡Carpeta creada exitosamente!",
"fill-folder-name": "Por favor, rellena el nombre de la carpeta",
@@ -510,6 +519,7 @@
"provide-email": "Por favor, proporciona un correo.",
"additional-info": "Información adicional",
"signature-saved-alert": "Firma guardada exitosamente.",
"saved-successfully": "Guardado con éxito.",
"fill-field": "Por favor, rellena este campo",
"error-template": "Error: ¡Plantilla no encontrada!",
"error": "Error",
@@ -576,18 +586,19 @@
"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": "Haz clic en los botones «Atrás», «Rechazar» o «Finalizar» para navegar por el documento. Utiliza el menú de tres puntos para ver otras opciones, como el botón «Descargar».",
"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-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-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-6": "Hacer clic en «Enviar» guardará el documento. En el siguiente paso podrás personalizar los correos que serán enviados a los destinatarios o copiar los enlaces para firmar y compartirlos con los destinatarios por tu cuenta.",
"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 crear y enviar rápidamente múltiples documentos usando una plantilla existente, haz clic en el botón «Envío rápido».",
"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.",
"signyour-self-2": "Arrastra y suelta en cualquier lugar de esta área. Después podrás cambiar el tamaño y moverlo.",
@@ -602,7 +613,8 @@
"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.",
"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."
"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"
},
"enter-email-plaholder": "Agrega una dirección de correo y presiona «Enter»",
"success-email-alert": "¡Correo enviado exitosamente!",
@@ -646,6 +658,8 @@
"public-template-mssg-4": "Visita la",
"public-template-mssg-5": " «npm» para las últimas actualizaciones, documentación detallada e historial de versiones.",
"public-template-mssg-6": "Antes de que puedas usar este fragmento de código, debes convertir esta plantilla en pública.",
"public-template-mssg-7": "Antes de poder generar un enlace público, debes hacer que esta plantilla sea pública.",
"here": "aquí",
"copy-code": "COPIAR",
"copied-code": "COPIADO",
"Installation": "Instalación",
@@ -682,19 +696,19 @@
"add-user-template": "Es necesario agregar un rol antes de poder agregarle campos.",
"pdf-uncompatible": "Este pdf no es compatible con opensign, comuníquese con <support@opensignlabs.com>",
"enable-tour": "Habilitar recorrido",
"istourenabled-help":{
"istourenabled-help": {
"p1": "Esto permitirá realizar una visita guiada para los firmantes, brindando instrucciones durante el proceso de firma.",
"p2": "Esto desactivará la visita guiada, lo que garantizará una experiencia de firma más rápida e ininterrumpida.",
"p3": "Consejo: si sus firmantes están familiarizados con OpenSign y lo utilizan con frecuencia, es posible que desee desactivar la visita guiada para disfrutar de una experiencia más fluida. Elija la opción que mejor se adapte a sus necesidades de firma de documentos"
},
},
"form-title-1": "Configuración del flujo de documentos",
"form-title-2": "Configuración de seguridad",
"text-field-tour":"Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
"attach-signer-tour" :"Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
"text-field-tour": "Los campos de tipo 'Texto' deben completarse con anticipación antes de enviar el documento. Si necesita que los firmantes proporcionen información, utilice el campo 'Entrada de texto'",
"attach-signer-tour": "Debe adjuntar un firmante a cada función. Puede hacerlo haciendo clic en este icono. Una vez que seleccione un Firmante, se adjuntará a todos los campos asociados con ese rol que aparecen en el mismo color.",
"allowed-signature-types": "Tipos de firma permitidos",
"at-least-one-signature-type": "Se debe habilitar al menos un tipo de firma.",
"expect-default-one-signature-type": "Habilite un tipo de firma adicional aparte del predeterminado.",
"add-signer-note": "Nota - Este contacto se agregará a su lista de contactos",
"add-signer-note": "Nota - Este contacto se guardará en sus Contactos",
"allowed-signature-types-help": {
"p1": "Esta preferencia de firma controla qué opciones de firma están disponibles para sus firmantes. Solo los tipos de firma que seleccione aparecerán en el widget de firma cuando se firme un documento. Por ejemplo, si elige sólo la opción 'Draw' en las preferencias, su firmante verá sólo la opción 'Draw' en el widget de firma, mientras que las otras tres opciones no estarán disponibles.",
"l1": "Cree una firma manuscrita directamente en la pantalla usando su dedo o un lápiz.",
@@ -705,9 +719,120 @@
"notify-on-signatures": "Notificar sobre firmas",
"notify-on-signatures-help": {
"p1": "El creador del documento recibirá una notificación por correo electrónico cada vez que un firmante firme el documento.",
"note": "Nota: No se enviará un correo electrónico de notificación al último firmante, ya que el creador del documento recibirá un correo electrónico de finalización."
"note": "Nota: Independientemente de esta configuración, siempre se envía un correo electrónico de finalización con el documento firmado y el certificado de finalización adjunto a todos los firmantes y al propietario del documento."
},
"expiry-date": "Date d'expiration",
"expiry-date-updated": "La fecha de vencimiento se ha extendido con éxito hasta el {{newexpirydate}}",
"expiry-date-error": "Proporcione una nueva fecha de vencimiento que sea posterior a la actual"
"expiry-date-error": "Proporcione una nueva fecha de vencimiento que sea posterior a la actual",
"duplicate-template-alert": "¿Está seguro de que desea crear un duplicado de esta plantilla?",
"duplicate-template-created": "Plantilla duplicada creada correctamente.",
"delete-page": "eliminar página",
"merge-pdf": "fusionar pdf",
"add-pages": "Agregar páginas",
"delete-alert": "No se puede eliminar una sola página",
"delete-alert-2": "¿Está seguro de que desea eliminar esta página?",
"delete-note": "Nota: una vez que elimines esta página, no podrás deshacerla",
"Rotation-alert": "Girar página",
"bulk-import": "Importación masiva",
"contacts-file": "Archivo de contactos (xlsx, csv)",
"100-records-only": "Currently you can only import up to 100 records.",
"csv-excel-support-only": "Cargue un archivo en uno de los siguientes formatos: CSV, XLSX o XLS.",
"contact-imported": "Se importaron {{imported}} contactos. {{failed}} contactos no pudieron importarse.",
"invalid-data": "Datos no válidos",
"delete-folder-alert": "¿Está seguro de que desea eliminar este Carpeta?",
"delete-folder-alert-1": "No se puede eliminar: la carpeta debe estar vacía",
"defaultSign-alert": "Los widgets {{widgetsType}} no están disponibles para la firma automática",
"embed-heading": "Incrustar plantillas de OpenSign",
"public-template-edit-title": "Editar plantilla pública",
"public-template-edit-mssg": "No puede editar una plantilla pública directamente. Para realizar cambios, la plantilla se configurará como privada y luego tendrás que volver a hacerla pública. ¿Quieres continuar?",
"agree-p1": "Confirmo que he leído y comprendido el ",
"agree-p2": "Registro Electrónico y Divulgación de Firma",
"agree-p3": "y consentimiento para utilizar registros y firmas electrónicas.",
"agrre-button": "Aceptar y continuar",
"term-cond-title": "Términos y condiciones",
"term-cond-h": "DIVULGACIÓN DE REGISTRO ELECTRÓNICO Y FIRMA",
"term-cond-p1": "Esta Divulgación de Firma y Registro Electrónico ('Divulgación') es un acuerdo entre el Creador del Documento ('Remitente') y el Firmante ('Usted'), facilitado a través de la plataforma OpenSign ('Plataforma'). Al firmar documentos a través de OpenSign, usted acepta los términos descritos en esta Divulgación. Léala detenidamente antes de continuar.",
"term-cond-h1": "1. Objet et portée",
"term-cond-p2": "Esta Divulgación rige el uso de registros y firmas electrónicas para los documentos enviados por el Remitente y firmados por Usted a través de la plataforma OpenSign. Al aceptar, Usted reconoce que:",
"term-cond-p3": "Recibirá y firmará documentos electrónicamente a través de OpenSign.",
"term-cond-p4": "Su firma electrónica es jurídicamente vinculante y equivalente a una firma manuscrita.",
"term-cond-h2": "2. Consentimiento para el uso de registros y firmas electrónicas",
"js-snippet-msg-2": "Administrar plantillas",
"js-snippet-msg-3": "página.",
"term-cond-p5": "Al aceptar esta Divulgación:",
"term-cond-p6": "Usted acepta realizar transacciones electrónicas con el Remitente utilizando OpenSign y comprende que este consentimiento es válido hasta que se retire.",
"term-cond-p7": "Usted acepta revisar, firmar y devolver documentos electrónicamente utilizando OpenSign.",
"term-cond-p8": "Este consentimiento se aplica únicamente a los documentos específicos proporcionados por el Remitente.",
"term-cond-h3": "3. Derecho a retirar el consentimiento",
"term-cond-p9": "Tiene derecho a retirar su consentimiento para el uso de registros y firmas electrónicas mediante:",
"term-cond-p10": "Contactar directamente con el Remitente utilizando la información de contacto proporcionada en su comunicación.",
"term-cond-p11": "Notificar al remitente antes de completar el proceso de firma.",
"term-cond-h4": "4. Requisitos de hardware y software",
"term-cond-p12": "Para firmar documentos usando OpenSign, debe tener:",
"term-cond-p13": "Un dispositivo con acceso a Internet.",
"term-cond-p14": "Un navegador web compatible con JavaScript habilitado.",
"term-cond-p15": "Una cuenta de correo electrónico para recibir notificaciones y documentos firmados.",
"term-cond-p16": "Si estos requisitos cambian, el Remitente u OpenSign le notificarán las actualizaciones.",
"term-cond-h5": "5. Conservación de registros y solicitud de copias en papel",
"term-cond-p17": "Usted es responsable de garantizar que pueda acceder y conservar los documentos firmados electrónicamente. OpenSign proporciona herramientas para descargar o imprimir el documento firmado inmediatamente después de que se complete el proceso de firma.",
"term-cond-p18": "Si prefiere una copia en papel de cualquier documento firmado electrónicamente, puede solicitarla directamente al Remitente. El Remitente puede cobrar una tarifa razonable por proporcionar copias en papel, a menos que lo prohíba la ley aplicable.",
"term-cond-h6": "6. Reconocimiento y Acuerdo",
"term-cond-p19": "Al continuar, usted:",
"term-cond-p20": "Confirme que puede acceder y revisar documentos electrónicos como se describe en esta Divulgación.",
"term-cond-p21": "Acepta utilizar registros y firmas electrónicas para las transacciones especificadas con el Remitente.",
"term-cond-p22": "Comprenda que OpenSign es una plataforma que facilita la transacción y no es parte del acuerdo.",
"term-cond-h7": "7. Efecto jurídico",
"term-cond-p23": "Tu firma electrónica facilitada a través de OpenSign:",
"term-cond-p24": "Cumple con las leyes de firma electrónica aplicables, incluidas, entre otras, la Ley E-SIGN de los Estados Unidos, el Reglamento eIDAS de la UE y la Ley de tecnología de la información de la India",
"term-cond-p25": "Es legalmente vinculante entre Usted y el Remitente para los documentos firmados",
"term-cond-h8": "8. Platform Role and Limitation of Liability",
"term-cond-p26": "OpenSign sirve como plataforma para facilitar transacciones electrónicas. No es responsable del contenido, validez o aplicabilidad de los documentos enviados por el Remitente. Cualquier disputa o problema relacionado con el documento o su firma debe resolverse directamente entre Usted y el Remitente.",
"term-cond-h9": "9. Terminación del Acuerdo",
"term-cond-p27": "Su consentimiento se aplica únicamente a la transacción actual a menos que el Remitente especifique lo contrario. OpenSign se reserva el derecho de cancelar el acceso a su plataforma por violaciones de sus Términos de Servicio o políticas.",
"term-cond-p28": "Al seleccionar 'Acepto', usted confirma que comprende y acepta los términos de este Registro electrónico y Divulgación de firma y acepta firmar electrónicamente los documentos proporcionados por el Remitente a través de OpenSign.",
"term-cond-p29": "Si tiene preguntas sobre esta Divulgación, comuníquese directamente con el Remitente. Para obtener soporte técnico con OpenSign, visite",
"term-cond-p30": "o correo electrónico",
"js-snippet-msg": "Para integrar plantillas OpenSign en sus sitios web HTML o páginas de destino, puede utilizar el siguiente código:",
"js-snippet-msg-1": "Puede obtener el TemplateId en la página Administrar plantillas",
"agrrement-alert": "Para continuar, debe consentir la divulgación de registros y firmas electrónicas.",
"webhook-already-exists": "¡La URL ya existe! Pruebe con uno diferente.",
"webhook-must-be-secure": "La URL del webhook debe ser segura y utilizar https://",
"provide-test-token": "Por favor genere un token de prueba de API",
"invalid-redirect-url": "Proporcione una URL de redireccionamiento válida.",
"auto-sign-mssg": "firmar automáticamente todo",
"already-exist-signer": "El firmante seleccionado ya está presente en la lista de destinatarios",
"allow-modify": "Permitir modificaciones",
"allow-modify-help": {
"p1": "¿Desea permitir modificaciones al documento durante el proceso de firma?",
"p2": "Seleccionar esta opción permite a los firmantes agregar elementos adicionales, como firmas, iniciales, sellos o texto, sobre los widgets existentes.",
"p3": "Seleccionar esta opción restringe a los firmantes de realizar cualquier adición al documento durante el proceso de firma."
},
"signature-setting": "Configuración de firmas",
"signyour-self-button": "Utilice este formulario para crear plantillas reutilizables para sus documentos.",
"requestsign-button": "Utilice esta opción para solicitar firmas de otras personas y de usted mismo en conjunto.",
"signyour-self-description": "Utilice este formulario para firmar el documento usted mismo sin agregar otros.",
"requestsign-description": "Utilice este formulario para solicitar firmas de otras personas y de usted mismo en conjunto.",
"template-form-description": "Utilice este formulario para crear plantillas reutilizables para sus documentos.",
"change-signer": "Cambiar firmante",
"activation-key": "Clave de activación",
"paid-selfhost-help": "Por favor, póngase en contacto con OpenSign para más información en ",
"remaining-credits-selfhost-help": "Use créditos premium para firmar documentos mediante la API, enviar en masa e integrar OpenSign en su sitio web. Tiene {{allowedcredits}} créditos incluidos y {{addoncredits}} créditos adicionales comprados restantes.",
"signature-type": "Tu firma",
"initial-type": "Tus iniciales",
"redirect-url": "URL de redireccionamiento",
"bulk-send": "envío masivo",
"select-timezone": "Seleccione su zona horaria",
"current-time": "Hora actual",
"email-help": "No se permite cambiar la dirección de correo electrónico por razones de seguridad. Por favor, cree otra cuenta gratuita utilizando la nueva dirección de correo electrónico.",
"doc-sent": "Documento enviado con éxito.",
"no-signer": "Eliminar",
"and": "y",
"make-public": "Hacer público",
"general": "General",
"request-email": "Correo de solicitud",
"completion-email": "Correo de finalización",
"variables-use": "Variables para usar",
"send-to-sign": "le ha solicitado que firme",
"use-template": "Usar plantilla",
"contact-already-exists": "El contacto ya existe."
}
+193 -68
View File
@@ -42,7 +42,7 @@
"upgrade-to": "Mettre à niveau vers",
"pro": "PRO",
"plan": "Offre",
"subscribe-card-teamplan":"Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !",
"subscribe-card-teamplan": "Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !",
"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",
@@ -76,19 +76,21 @@
"Templates": "Modèles",
"Templates-Children": {
"Create template": "Créer un modèle",
"Manage templates": "Gérer les modèles"
"Manage templates": "Gérer les modèles",
"Embedding": "Intégration"
},
"OpenSign™ Drive": "Lecteur OpenSign™",
"Reports": "Rapports",
"Reports-Children": {
"Documents": "Documents",
"Documents-Children": {
"Need your sign": "Besoin de votre signature",
"In Progress": "En cours",
"Completed": "Complété",
"Drafts": "Brouillons",
"Declined": "Signature refusé",
"Expired": "Expiré",
"Contactbook": "Carnet de contacts"
"Expired": "Expiré"
},
"Contactbook": "Carnet de contacts",
"Settings": "Paramètres",
"Settings-Children": {
"My Signature": "Ma signature",
@@ -144,6 +146,7 @@
"btnLabel": {
"sign": "Signer",
"Resend": "Relancer",
"Rename": "Renommer",
"Revoke": "Révoquer",
"Delete": "Supprimer",
"Use": "Utiliser",
@@ -155,8 +158,10 @@
"option": "Option",
"Embed": "Intégrer",
"Copy TemplateId": "Copier l'ID du modèle",
"Copy Public URL":"Copier l'URL publique",
"extend-expiry-date": "Prolonger la date d'expiration"
"Copy Public URL": "Copier l'URL publique",
"extend-expiry-date": "Prolonger la date d'expiration",
"Duplicate Template": "dupliquer le modèle",
"Duplicate": "Double"
},
"report-help": {
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
@@ -210,12 +215,14 @@
"create": "Créer",
"signers": "Signataires",
"signers-help": "Commencez à saisir le nom d'un contact pour voir les signataires suggérés par vos contacts enregistrés ou en ajouter de nouveaux. Organisez l'ordre de signature en ajoutant des signataires dans l'ordre souhaité. Utilisez le bouton « + » pour inclure les signataires et le « x » pour les supprimer. Chaque signataire recevra un e-mail invité à signer le document dans l'ordre indiqué.",
"bcc-help": "Commencez à taper le nom dun contact pour voir les suggestions parmi vos contacts enregistrés ou en ajouter de nouveaux. Utilisez le bouton '+' pour ajouter un utilisateur et le bouton 'x' pour le supprimer. Ladresse e-mail de lutilisateur sélectionné sera ajoutée en Bcc (copie carbone invisible). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
"add-signer": "Ajouter un signataire",
"contact-not-found": "Contact introuvable",
"add-yourself": "Ajoutez-vous",
"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",
@@ -260,7 +267,7 @@
"user-deactivated": "Utilisateur désactivé.",
"user-activated": "Activé par l'utilisateur.",
"public": "Public",
"download": "Téléchargement",
"download": "Télécharger",
"view": "voir",
"make-template-public": "Rendre le modèle public",
"make-template-private": "Rendre le modèle privé",
@@ -271,7 +278,7 @@
"embed-template": "Intégrer le modèle",
"public-url-copy": "Voici votre URL publique : ",
"public-url-copy-mssg": "copiez-la ou partagez-le avec le signataire et vous pouvoir voir tous vos paramètres publics modèles.",
"add-public-url-alert": "Veuillez terminer la configuration de votre profil public OpenSign. Une fois cela fait, vous pourrez rendre les modèles publics. Ce processus ne doit être effectué quune seule fois.",
"add-public-url-alert": "Vous pouvez générer votre profil public OpenSign",
"share-with-alert": "Vous ne pouvez pas partager un modèle si des contacts sont déjà attribués à des rôles. Veuillez supprimer toutes les attributions de contacts des rôles avant de partager le modèle.",
"share-with": "Partager avec",
"team-not-found": "Équipe introuvable",
@@ -294,6 +301,7 @@
"subject": "Sujet",
"body": "Corps",
"add-contact": "Ajouter le contact",
"edit-contact": "Modifier le contact",
"add-signer-alert": "Le contact existe déjà ! Veuillez le sélectionner dans la liste déroulante 'Signataires'",
"record-delete-alert": "Enregistrement supprimé avec succès !",
"record-revoke-alert": " Enregistrement révoqué avec succès ! ",
@@ -403,10 +411,9 @@
"encrypted-pdf-alert": "Les fichiers PDF actuellement cryptés ne sont pas pris en charge.",
"encrypted-pdf-alert-1": "Le mot de passe du fichier PFX n'est pas valide.",
"user-not-exist": "L'utilisateur n'existe pas",
"template-signature-field": "Veuillez ajouter au moins un champ de signature pour tous les rôles.",
"template-role-alert": "Veuillez d'abord ajouter des rôles",
"create-document": "Créer un document",
"template-created-alert": "Voulez-vous créer un document en utilisant le modèle que vous vient de créer ?",
"template-created-alert": "Que voulez-vous faire avec le modèle que vous venez de créer ?",
"signers-alert": "alerte des signataires",
"template-creation-alert-1": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
"ok": "D'accord",
@@ -438,6 +445,7 @@
"placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.",
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
"placeholder-mail-alert": "Vous avez envoyé un e-mail avec succès à {{name}}. Les signataires suivants recevront un e-mail une fois que {{name}} aura signé le document.",
"placeholder-alert-5": "Voulez-vous signer des documents maintenant ?",
"placeholder-alert-6": "Veuillez configurer l'adaptateur de messagerie pour envoyer du courrier !",
"placeholder-alert-7": "Veuillez sélectionner le signataire pour ajouter un espace réservé !",
@@ -468,12 +476,13 @@
"sign-up": "S'inscrire",
"folder-already-exist!": "le dossier existe déjà !",
"please-select-position!": "Veuillez sélectionner un poste !",
"default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les emplacements demandés ? ",
"default-sign-alert": "Êtes-vous sûr de vouloir vous connecter automatiquement à tous les widgets {{widgetsType}} ?",
"Your-Signature": "Votre signature",
"auto-sign-all": "Tout signer automatiquement",
"generating-certificate": "Génération du certificat",
"pdf-download": "Téléchargement PDF",
"pdf-download": "Télécharger PDF",
"generate-certificate-alert": " Votre certificat d'achèvement est en cours de génération. Veuillez patienter quelques instants. Si le téléchargement ne démarre pas sous peu, cliquez à nouveau sur le bouton. ",
"generate-certificate-err": "Nous rencontrons actuellement un problème lors de la génération du certificat. Veuillez réessayer plus tard.",
"folder-already-exist": "Le dossier existe déjà !",
"folder-created-successfully": "Dossier créé avec succès !",
"fill-folder-name": "Veuillez indiquer le nom du dossier",
@@ -509,6 +518,7 @@
"provide-email": "Veuillez fournir un e-mail.",
"additional-info": "Information additionnelle",
"signature-saved-alert": "Signature enregistrée avec succès.",
"saved-successfully": "Enregistré avec succès.",
"fill-field": "Veuillez remplir ce champ",
"error-template": "Erreur : modèle introuvable ! ",
"error": "Erreur",
@@ -575,18 +585,19 @@
"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 Précédent, 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-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-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-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-6": "Cliquer sur 'Envoyer' enregistrera le document. À l'étape suivante, vous pourrez personnaliser les e-mails à envoyer aux destinataires ou copier les liens de signature et les partager vous-même avec les destinataires.",
"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 créer et envoyer rapidement plusieurs documents à l'aide d'un modèle existant, cliquez sur le bouton 'Envoi rapide'.",
"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.",
"signyour-self-2": "Faites glisser et déposer n'importe où dans cette zone. Vous pourrez la redimensionner et la déplacer plus tard.",
@@ -601,7 +612,8 @@
"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.",
"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."
"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."
},
"enter-email-plaholder": "Ajoutez une adresse email et appuyez sur Entrée",
"success-email-alert": "E-mail envoyé avec succès!",
@@ -621,47 +633,49 @@
"Add-seats": "Ajouter des sièges",
"format": "format",
"select-date-format": "Sélectionnez un format de date",
"quantity-of-credits":"Quantité de crédits de prime",
"remaining-credits":"Crédits premium disponibles :",
"additional-credits":"Veuillez acheter des crédits premium",
"remaining-credits-help":"Utilisez des crédits premium pour la signature de documents API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits achetés supplémentaires.",
"quantity-of-credits": "Quantité de crédits de prime",
"remaining-credits": "Crédits premium disponibles :",
"additional-credits": "Veuillez acheter des crédits premium",
"remaining-credits-help": "Utilisez des crédits premium pour la signature de documents API, l'envoi groupé ou l'intégration d'OpenSign sur votre site Web. Il vous reste {{allowedcredits}} crédits inclus et {{addoncredits}} crédits achetés supplémentaires.",
"quota-err-quicksend": "Quota atteint, vous n'avez pas assez de crédits.",
"buy-credits": "Acheter des crédits premium",
"rotate-right" :"Faire pivoter à droite",
"rotate-left" :"Faire pivoter à gauche",
"rotate-alert-mssg" :"Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?",
"templateid":"ID de modèle",
"bulk-send-subcription-alert":"Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
"rotate-right": "Faire pivoter à droite",
"rotate-left": "Faire pivoter à gauche",
"rotate-alert-mssg": "Tous les widgets de cette page seront perdus. Êtes-vous sûr de vouloir continuer ?",
"templateid": "ID de modèle",
"bulk-send-subcription-alert": "Veuillez passer au forfait Professionnel ou Équipe pour utiliser Quicksend.",
"generate-test-token": "Générer jeton de test",
"regenerate-test-token":"Régénérer le jeton de test",
"help-test-token":"Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à lun de nos forfaits payants pour générer un jeton de production.",
"help-api-token":"Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
"reason" :"Raison",
"decline-by" :"Refusé/révoqué par",
"document-declined":"document refusé",
"public-template-mssg-1" :"Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
"public-template-mssg-2" :"Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
"public-template-mssg-3" :"Besoin de plus de détails ou d'exemples ?",
"regenerate-test-token": "Régénérer le jeton de test",
"help-test-token": "Ce jeton peut être utilisé pour tester les API au niveau du point de terminaison https://sandbox.opensignlabs.com/api/v1, vous permettant ainsi d'effectuer un nombre illimité de signatures de documents. Veuillez noter que l'API sandbox signera vos documents avec des certificats auto-signés, qui peuvent ne pas être reconnus comme valides par Adobe. Une fois vos tests terminés, vous pouvez passer à lun de nos forfaits payants pour générer un jeton de production.",
"help-api-token": "Ce jeton peut être utilisé pour accéder aux API de production au point de terminaison {{origin}}/api/v1. Il ne peut être généré que sur l'un de nos forfaits payants.",
"reason": "Raison",
"decline-by": "Refusé/révoqué par",
"document-declined": "document refusé",
"public-template-mssg-1": "Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
"public-template-mssg-2": "Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
"public-template-mssg-3": "Besoin de plus de détails ou d'exemples ?",
"public-template-mssg-4": "Visitez le",
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
"public-template-mssg-6":"Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
"copy-code":"COPIE",
"copied-code":"COPIÉ",
"Installation":"Installation",
"Usage" :"Usage",
"insufficient-credits":"Crédits de signature insuffisants",
"insufficient-credits-mssg" :"Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
"angular-npm-mssg-1" :"Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
"quota-mail-info-head":"Limite mensuelle d'e-mails de signatures de demandes",
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
"public-template-mssg-6": "Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
"public-template-mssg-7": "Avant de pouvoir générer un lien public, vous devez rendre ce modèle public.",
"here": "ici",
"copy-code": "COPIE",
"copied-code": "COPIÉ",
"Installation": "Installation",
"Usage": "Usage",
"insufficient-credits": "Crédits de signature insuffisants",
"insufficient-credits-mssg": "Le propriétaire de ce document ne dispose actuellement pas des crédits OpenSign nécessaires pour que vous puissiez le signer. Veuillez contacter le propriétaire si vous avez besoin de plus de détails.",
"angular-npm-mssg-1": "Pour intégrer OpenSign dans votre projet Angular, exécutez simplement la commande suivante :",
"quota-mail-info-head": "Limite mensuelle d'e-mails de signatures de demandes",
"quota-mail-info": "Vous pouvez envoyer jusqu'à 15 e-mails de demande de signature chaque mois. Mettez à niveau maintenant pour envoyer directement des demandes de signature illimitées.",
"quota-mail-reset": "Les crédits de votre email de demande de signature seront réinitialisés le",
"quota-mail": "Vous avez atteint votre limite de 15 e-mails de demande de signature pour ce mois. Mettez à niveau maintenant pour continuer à envoyer des e-mails directement.",
"quota-mail-tip-tip":"Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
"quota-mail-head":"Quota atteint",
"unauthorized-modal":"Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
"sent-this-month":"envoyé ce mois-ci",
"available-seats":"Disponible sièges",
"buy-users":"Acheter plus d'utilisateurs",
"quota-mail-tip-tip": "Astuce : Vous pouvez toujours signer un nombre <1>illimité de documents</1> en partageant manuellement les liens de demande de signature.",
"quota-mail-head": "Quota atteint",
"unauthorized-modal": "Vous n'êtes pas autorisé à effectuer cette action, veuillez contacter {{adminEmail}}.",
"sent-this-month": "envoyé ce mois-ci",
"available-seats": "Disponible sièges",
"buy-users": "Acheter plus d'utilisateurs",
"isenable-otp": "Activer la vérification OTP",
"isenable-otp-help": {
"p1": "Souhaitez-vous activer le processus de vérification à l'aide d'un mot de passe à usage unique (OTP)?",
@@ -669,31 +683,31 @@
"p3": "La sélection de cette option désactivera la vérification OTP, permettant aux utilisateurs de signer le document directement sans étapes supplémentaires.",
"p4": "Veuillez choisir l'option qui correspond le mieux à vos exigences en matière de signature de documents."
},
"advanced-options":"Options avancées",
"advanced-options": "Options avancées",
"hide-advanced-options": "Masquer les options avancées",
"download-files" :"Télécharger des fichiers",
"download-pdf" :"Télécharger le PDF",
"pdf-certificate" :"Télécharger Pdf + Certificat",
"document-logs":"Journaux de documents",
"download-files": "Télécharger des fichiers",
"download-pdf": "Télécharger le PDF",
"pdf-certificate": "Télécharger Pdf + Certificat",
"document-logs": "Journaux de documents",
"server-down": "Impossible de se connecter au serveur OpenSign. Si vous hébergez vous-même OpenSign, veuillez vous assurer que toutes les étapes de la documentation ont été correctement suivies. Si vous exécutez OpenSign localement, vous y accédez peut-être via un numéro de port incorrect.",
"admin-exists": "L'administrateur existe déjà. Veuillez vous connecter à l'application en utilisant les informations d'identification de l'administrateur afin de gérer les utilisateurs.",
"enable-tour": "Activer la visite",
"istourenabled-otp-help":{
"istourenabled-otp-help": {
"p1": "Cela permettra une visite guidée pour les signataires, fournissant des instructions pendant le processus de signature.",
"p2": "Cela désactivera la visite guidée, garantissant ainsi une expérience de signature plus rapide et ininterrompue.",
"p3": "Conseil : si vos signataires connaissent OpenSign et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
"p3": "Conseil : si vos signataires connaissent OpenSign et l'utilisent fréquemment, vous souhaiterez peut-être désactiver la visite guidée pour une expérience plus fluide. Choisissez l'option qui correspond le mieux à vos besoins en matière de signature de documents."
},
"form-title-1": "Paramètre du flux de documents",
"form-title-2": "Paramètre de sécurité",
"form-title-1": "Paramètre du flux de documents",
"form-title-2": "Paramètre de sécurité",
"public-tour-message": "Le modèle doit être public avant que vous puissiez générer un lien partageable.",
"add-user-template":"Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
"pdf-uncompatible":"Ce pdf n'est pas compatible avec opensign, veuillez contacter <support@opensignlabs.com>",
"text-field-tour" :"Les champs de type 'Texte' doivent être remplis à l'avance avant l'envoi du document. Si vous avez besoin que les signataires fournissent des informations, utilisez plutôt le champ 'Saisie de texte'.",
"attach-signer-tour" :"Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur.",
"add-user-template": "Vous devez ajouter un rôle avant de pouvoir lui ajouter des champs. ",
"pdf-uncompatible": "Ce pdf n'est pas compatible avec opensign, veuillez contacter <support@opensignlabs.com>",
"text-field-tour": "Les champs de type 'Texte' doivent être remplis à l'avance avant l'envoi du document. Si vous avez besoin que les signataires fournissent des informations, utilisez plutôt le champ 'Saisie de texte'.",
"attach-signer-tour": "Vous devez associer un signataire à chaque rôle. Vous pouvez le faire en cliquant sur cette icône. Une fois que vous avez sélectionné un signataire, il sera attaché à tous les champs associés à ce rôle qui apparaissent dans la même couleur.",
"allowed-signature-types": "Types de signature autorisés",
"at-least-one-signature-type": "Au moins un type de signature doit être activé.",
"expect-default-one-signature-type": "Veuillez activer un type de signature supplémentaire en plus de celui par défaut.",
"add-signer-note": "Remarque - Ce contact sera ajouté à votre liste de contacts",
"add-signer-note": "Remarque - Ce contact sera enregistré dans vos contacts",
"allowed-signature-types-help": {
"p1": "Cette préférence de signature contrôle les options de signature disponibles pour vos signataires. Seuls les types de signature que vous sélectionnez apparaîtront dans le widget de signature lorsqu'un document est signé. Par exemple, si vous choisissez uniquement l'option 'Draw' dans les préférences, votre signataire ne verra que l'option 'Draw' dans le widget de signature, tandis que les trois autres options ne seront pas disponibles.",
"l1": "Créez une signature manuscrite directement sur l'écran à l'aide de votre doigt ou d'un stylet.",
@@ -704,9 +718,120 @@
"notify-on-signatures": "Notifier les signatures",
"notify-on-signatures-help": {
"p1": "Le créateur du document recevra une notification par e-mail chaque fois qu'un signataire signera le document.",
"note": "Remarque : L'e-mail de notification ne sera pas envoyé pour le dernier signataire, car le créateur du document recevra à la place un e-mail de fin."
"note": "Remarque: Quel que soit ce paramètre, un e-mail d'achèvement contenant le document signé et le certificat d'achèvement en pièce jointe est toujours envoyé à tous les signataires et au propriétaire du document."
},
"expiry-date": "Fecha de caducidad",
"expiry-date-updated": "La fecha de vencimiento se ha extendido con éxito hasta el {{newexpirydate}}",
"expiry-date-error": "Veuillez fournir une nouvelle date d'expiration postérieure à la date actuelle"
"expiry-date-updated": "La date d'expiration a été prolongée avec succès jusqu'au {{newexpirydate}}",
"expiry-date-error": "Veuillez fournir une nouvelle date d'expiration postérieure à la date actuelle",
"duplicate-template-alert": "Êtes-vous sûr de vouloir créer une copie de ce modèle ?",
"duplicate-template-created": "Modèle en double créé avec succès.",
"delete-page": "supprimer la page",
"merge-pdf": "Fusionner le pdf",
"add-pages": "Ajouter des pages",
"delete-alert": "Impossible de supprimer une seule page",
"delete-alert-2": "Etes-vous sûr de vouloir supprimer cette page ?",
"delete-note": "Remarque : une fois cette page supprimée, vous ne pouvez plus l'annuler",
"Rotation-alert": "Faire pivoter la page",
"bulk-import": "Importation en masse",
"contacts-file": "Fichier de contacts (xlsx, csv)",
"100-records-only": "Actuellement, vous ne pouvez importer que 100 enregistrements.",
"csv-excel-support-only": "Veuillez télécharger un fichier dans l'un des formats suivants : CSV, XLSX ou XLS.",
"contact-imported": "{{imported}} contacts ont été importés. {{failed}} contacts n'ont pas pu être importés.",
"invalid-data": "Données invalides",
"delete-folder-alert": "Êtes-vous sûr de vouloir supprimer ce Dossier ?",
"delete-folder-alert-1": "Impossible de supprimer : le dossier doit être vide.",
"defaultSign-alert": "Les widgets {{widgetsType}} ne sont pas disponibles pour la signature automatique.",
"embed-heading": "Intégrer des modèles OpenSign",
"public-template-edit-title": "Modifier le modèle public",
"public-template-edit-mssg": "Vous ne pouvez pas modifier directement un modèle public. Pour apporter des modifications, le modèle sera défini comme privé et vous devrez ensuite le rendre à nouveau public. Voulez-vous continuer ?",
"agree-p1": "Je confirme avoir lu et compris les ",
"agree-p2": "Divulgation des enregistrements électroniques et des signatures",
"agree-p3": "et consentez à l'utilisation d'enregistrements et de signatures électroniques.",
"agrre-button": " Accepter et continuer ",
"term-cond-title": "Termes et conditions",
"term-cond-h": "DIVULGATION D'ENREGISTREMENT ÉLECTRONIQUE ET DE SIGNATURE",
"term-cond-p1": "Cette divulgation d'enregistrement électronique et de signature (' Divulgation ') est un accord entre le créateur du document (' Expéditeur ') et le signataire (' Vous '), facilité via la plateforme OpenSign (' Plateforme '). En signant des documents via OpenSign, vous acceptez les conditions décrites dans cette divulgation. Veuillez la lire attentivement avant de continuer.",
"term-cond-h1": "1. Objet et portée",
"term-cond-p2": "Cette divulgation régit l'utilisation d'enregistrements et de signatures électroniques pour les documents envoyés par l'expéditeur et signés par vous via la plateforme OpenSign. En acceptant, vous reconnaissez que :",
"term-cond-p3": "Vous recevrez et signerez des documents électroniquement via OpenSign.",
"term-cond-p4": "Votre signature électronique est juridiquement contraignante et équivaut à une signature manuscrite.",
"js-snippet-msg-2": "Gérer les modèles",
"js-snippet-msg-3": "page.",
"term-cond-h2": "2. Consentement à l'utilisation d'enregistrements et de signatures électroniques",
"term-cond-p5": "En acceptant cette divulgation :",
"term-cond-p6": "Vous consentez à effectuer des transactions électroniques avec l'expéditeur en utilisant OpenSign et comprenez que ce consentement est valable jusqu'à son retrait.",
"term-cond-p7": "Vous acceptez de consulter, de signer et de renvoyer des documents par voie électronique à l'aide d'OpenSign.",
"term-cond-p8": "Ce consentement s'applique uniquement au(x) document(s) spécifique(s) fourni(s) par l'Expéditeur.",
"term-cond-h3": "3. Droit de retirer le consentement",
"term-cond-p9": "Vous avez le droit de retirer votre consentement à l'utilisation d'enregistrements et de signatures électroniques en :",
"term-cond-p10": "Contacter directement l'expéditeur en utilisant les coordonnées fournies dans sa communication.",
"term-cond-p11": "Notifier l'expéditeur avant de terminer le processus de signature.",
"term-cond-h4": "4. Configuration matérielle et logicielle requise",
"term-cond-p12": "Pour signer des documents avec OpenSign, vous devez avoir :",
"term-cond-p13": "Un appareil avec accès à Internet.",
"term-cond-p14": "Un navigateur Web compatible avec JavaScript activé.",
"term-cond-p15": "Un compte email pour recevoir des notifications et des documents signés.",
"term-cond-p16": "Si ces exigences changent, l'expéditeur ou OpenSign vous informera des mises à jour.",
"term-cond-h5": "5. Conservation des dossiers et demande de copies papier",
"term-cond-p17": "Vous êtes responsable de vous assurer que vous pouvez accéder et conserver le(s) document(s) signé(s) électroniquement. OpenSign fournit des outils pour télécharger ou imprimer le document signé immédiatement une fois le processus de signature terminé.",
"term-cond-p18": "Si vous préférez une copie papier de tout document signé électroniquement, vous pouvez la demander directement à l'expéditeur. L'expéditeur peut facturer des frais raisonnables pour la fourniture de copies papier, sauf interdiction par la loi applicable.",
"term-cond-h6": "6. Reconnaissance et accord",
"term-cond-p19": "En procédant, Vous :",
"term-cond-p20": "Confirmez que vous pouvez accéder et consulter les documents électroniques comme décrit dans cette divulgation.",
"term-cond-p21": "Accepter d'utiliser des enregistrements et des signatures électroniques pour la ou les transactions spécifiées avec l'expéditeur.",
"term-cond-p22": "Comprenez qu'OpenSign est une plateforme facilitant la transaction et n'est pas partie à l'accord.",
"term-cond-h7": "7. Effet juridique",
"term-cond-p23": "Votre signature électronique facilitée grâce à OpenSign :",
"term-cond-p24": "Conforme aux lois applicables sur les signatures électroniques, y compris, mais sans s'y limiter, la loi E-SIGN aux États-Unis, le règlement eIDAS de l'UE et la loi indienne sur les technologies de l'information.",
"term-cond-p25": "Est juridiquement contraignant entre vous et l'expéditeur pour le(s) document(s) signé(s).",
"term-cond-h8": "8. Rôle de la plateforme et limitation de responsabilité",
"term-cond-p26": "OpenSign sert de plateforme pour faciliter les transactions électroniques. Il n'est pas responsable du contenu, de la validité ou du caractère exécutoire des documents envoyés par l'expéditeur. Tout litige ou problème lié au document ou à sa signature doit être résolu directement entre vous et l'expéditeur. Expéditeur.",
"term-cond-h9": "9. Résiliation du Contrat",
"term-cond-p27": "Votre consentement s'applique uniquement à la transaction en cours, sauf indication contraire de la part de l'expéditeur. OpenSign se réserve le droit de mettre fin à l'accès à sa plateforme en cas de violation de ses conditions d'utilisation ou de ses politiques.",
"term-cond-p28": "En sélectionnant J'accepte, vous confirmez que vous comprenez et acceptez les termes de cette divulgation d'enregistrement électronique et de signature et consentez à signer électroniquement le(s) document(s) fourni(s) par l'expéditeur via OpenSign.",
"term-cond-p29": "Si vous avez des questions sur cette divulgation, contactez directement l'expéditeur. Pour obtenir une assistance technique avec OpenSign, visitez",
"term-cond-p30": "ou par email",
"js-snippet-msg": "Pour intégrer des modèles OpenSign dans vos sites Web ou pages de destination HTML, vous pouvez utiliser le code ci-dessous :",
"js-snippet-msg-1": "Vous pouvez obtenir le TemplateId à partir de la page Gérer les modèles.",
"agrrement-alert": "Pour procéder, vous devez consentir à la divulgation des enregistrements et signatures électroniques.",
"webhook-already-exists": "L'URL existe déjà ! Veuillez en essayer un autre.",
"webhook-must-be-secure": "L'URL du webhook doit être sécurisée et utiliser https://",
"provide-test-token": "Veuillez générer un jeton de test API",
"invalid-redirect-url": "Veuillez fournir une URL de redirection valide.",
"auto-sign-mssg": "Tout signer automatiquement",
"already-exist-signer": "Le signataire sélectionné est déjà présent dans la liste des destinataires",
"allow-modify": "Autoriser les modifications",
"allow-modify-help": {
"p1": "Souhaitez-vous autoriser les modifications du document pendant le processus de signature ?",
"p2": "Sélectionner cette option permet aux signataires d'ajouter des éléments supplémentaires, tels que des signatures, des initiales, des tampons ou du texte, par-dessus les widgets existants.",
"p3": "Sélectionner cette option empêche les signataires d'apporter des ajouts au document pendant le processus de signature."
},
"signature-setting": "Paramètres de signature",
"change-signer": "Changer de signataire",
"signyour-self-button": "Utilisez cette option pour signer le document vous-même sans en ajouter d'autres.",
"requestsign-button": "Utilisez cette option pour demander les signatures des autres et de vous-même.",
"signyour-self-description": "Utilisez ce formulaire pour signer le document vous-même sans en ajouter d'autres.",
"requestsign-description": "Utilisez ce formulaire pour demander les signatures des autres et de vous-même.",
"template-form-description": "Utilisez ce formulaire pour créer des modèles réutilisables pour vos documents.",
"activation-key": "Clé d'activation",
"paid-selfhost-help": "Veuillez contacter OpenSign pour plus d'informations à l'adresse ",
"remaining-credits-selfhost-help": "Utilisez des crédits premium pour la signature de documents via l'API, l'envoi en masse et l'intégration d'OpenSign sur votre site web. Vous avez {{allowedcredits}} crédits inclus et {{addoncredits}} crédits supplémentaires achetés restants.",
"signature-type": "Votre signature",
"initial-type": "Vos initiales",
"redirect-url": "URL de redirection",
"bulk-send": "Envoi groupé",
"select-timezone": "Sélectionnez votre fuseau horaire",
"current-time": "Heure actuelle",
"email-help": "Vous n'êtes pas autorisé à changer d'adresse e-mail pour des raisons de sécurité. Veuillez créer un autre compte gratuit en utilisant la nouvelle adresse e-mail.",
"doc-sent": "Document envoyé avec succès.",
"no-signer": "Supprimer",
"and": "et",
"make-public": "Rendre public",
"general": "Général",
"request-email": "E-mail de demande",
"completion-email": "E-mail de finalisation",
"variables-use": "Variables à utiliser",
"send-to-sign": "vous a demandé de signer",
"use-template": "Utiliser le modèle",
"contact-already-exists": "Le contact existe déjà."
}
@@ -0,0 +1,837 @@
{
"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",
"create-account": "Crea Account",
"login": "Accedi",
"language": "Lingua",
"name": "Nome",
"phone": "Telefono",
"phone-optional": "facoltativo",
"email": "Email",
"company": "Azienda",
"job-title": "Titolo professionale",
"profile": "Profilo",
"log-out": "Disconnetti",
"is-email-verified": "Email verificata",
"public-profile": "Profilo pubblico",
"tagline": "Slogan",
"has-requested-you-to-sign": "ha richiesto la tua firma",
"has-requested-you-to-review-and-sign": "ha richiesto la tua revisione e firma",
"input-required": "Per favore, compila questo campo.",
"sort-order": {
"Ascending": "Crescente",
"Descending": "Decrescente",
"Name": "Nome",
"Date": "Data"
},
"context-menu": {
"Download": "Scarica",
"Rename": "Rinomina",
"Move": "Sposta",
"Delete": "Elimina"
},
"action": "Azione",
"add": "Aggiungi",
"add-new-folder": "Aggiungi una nuova cartella",
"disable-documentId": "Disabilita DocumentId",
"edit": "Modifica",
"change-password": "Cambia Password",
"save": "Salva",
"cancel": "Annulla",
"upgrade-now": "Aggiorna ora",
"upgrade-to": "Aggiorna a",
"plan": "Piano",
"subscribe-card-teamplan": "Sblocca tutto il potenziale della collaborazione! Crea organizzazioni, team e gerarchie illimitati. Condividi modelli senza problemi tra i team e assegna ruoli personalizzati agli utenti. Migliora il tuo flusso di lavoro oggi stesso!",
"subscribe-card-plan": "Sblocca le funzionalità premium a partire da soli {{premiumPrice}}/mese. Approfitta di prestazioni migliorate e paga solo {{addonPrice}} per ogni credito aggiuntivo dopo quelli inclusi.",
"user-name-limit-char": "Per un nome utente con meno di 8 caratteri, abbonati",
"tour-content": "Non mostrare più",
"pro": "PRO",
"team": "TEAM",
"user": "Utente",
"docs": "Documenti",
"session-expired": "La tua sessione è scaduta.",
"public-profile-help": "Questo è il tuo URL pubblico. Copialo o condividilo con il firmatario, e potrai visualizzare tutti i tuoi modelli pubblici.",
"tagline-help": "Questo slogan apparirà nel tuo profilo pubblico OpenSign, es. https://opensign.me/alex",
"welcome": "Bentornato!",
"Login-to-your-account": "Accedi al tuo account",
"password": "Password",
"forgot-password": "Password dimenticata?",
"loading": "Caricamento...",
"of": "di",
"sign-SSO": "Accedi con SSO",
"login-page": "Pagina di accesso",
"or": "O",
"signup-page": "Pagina di registrazione",
"password-length": "La password deve contenere almeno 8 caratteri",
"password-case": "La password deve contenere una lettera maiuscola, una minuscola e un numero",
"password-special-char": "La password deve contenere un carattere speciale",
"agreee": "Accetto i",
"term": "Termini di servizio",
"subscribe-to-opensign": "Iscriviti alla newsletter di OpenSign",
"register": "Registrati",
"sidebar": {
"Dashboard": "Dashboard",
"Sign yourself": "Firma tu stesso",
"Request signatures": "Richiedi firme",
"Templates": "Modelli",
"Templates-Children": {
"Create template": "Crea modello",
"Manage templates": "Gestisci modelli",
"Embedding": "Incorporazione"
},
"OpenSign™ Drive": "OpenSign™ Drive",
"Reports": "Report",
"Documents": "Documenti",
"Documents-Children": {
"Need your sign": "Necessita della tua firma",
"In Progress": "In corso",
"Completed": "Completati",
"Drafts": "Bozze",
"Declined": "Rifiutati",
"Expired": "Scaduti"
},
"Contactbook": "Rubrica",
"Settings": "Impostazioni",
"Settings-Children": {
"My Signature": "La mia firma",
"API Token": "Token API",
"Webhook": "Webhook",
"Preferences": "Preferenze",
"Teams": "Team",
"Team-update-successfully": "Team aggiornato con successo.",
"Users": "Utenti"
}
},
"dashboard-card": {
"Need your Signature": "Necessita della tua firma",
"Out for signatures": "In attesa di firme"
},
"report-name": {
"Draft Documents": "Documenti in bozza",
"Need your sign": "Necessita della tua firma",
"In-progress documents": "Documenti in corso",
"Completed Documents": "Documenti completati",
"Declined Documents": "Documenti rifiutati",
"Expired Documents": "Documenti scaduti",
"Recently sent for signatures": "Inviati recentemente per firme",
"Recent signature requests": "Richieste di firma recenti",
"Drafts": "Bozze",
"Contactbook": "Rubrica",
"Templates": "Modelli",
"Teams": "Team",
"Users": "Utenti"
},
"btnLabel": {
"sign": "firma",
"Sign": "FIRMA",
"Resend": "Reinvia",
"Rename": "Rinominare",
"Revoke": "Revoca",
"Delete": "Elimina",
"Use": "Usa",
"Quick send": "Invio rapido",
"Edit": "Modifica",
"Share with team": "Condividi con il team",
"Share": "Condividi",
"View": "Visualizza",
"option": "Opzione",
"Embed": "Incorpora",
"Copy TemplateId": "Copia TemplateId",
"Copy Public URL": "Copia URL pubblico",
"extend-expiry-date": "Estendi data di scadenza",
"Duplicate Template": "Duplica modello",
"Duplicate": "Duplica"
},
"report-heading": {
"Sr.No": "Nr.",
"Title": "Titolo",
"Email": "Email",
"Phone": "Telefono",
"File": "File",
"Owner": "Proprietario",
"Signers": "Firmatari",
"Note": "Nota",
"Folder": "Cartella",
"Reason": "Motivo",
"Parent Team": "Team genitore",
"Active": "Attivo",
"Role": "Ruolo",
"Team": "Team",
"Name": "Nome",
"Status": "Stato",
"created-date": "Data di creazione",
"Type": "Tipo",
"Logs": "Log",
"Expiry-date": "Data di scadenza"
},
"report-help": {
"Draft Documents": "Questi sono documenti che hai iniziato ma non hai ancora finalizzato per l'invio.",
"Need your sign": "Questa è una lista di documenti in attesa della tua firma.",
"In-progress documents": "Questa è una lista di documenti che hai inviato ad altre parti per la firma.",
"Completed Documents": "Questa è una lista di documenti firmati da tutte le parti.",
"Declined Documents": "Questa è una lista di documenti rifiutati da uno dei firmatari.",
"Expired Documents": "Questa è una lista di documenti che hanno raggiunto la data di scadenza.",
"Contactbook": "Questa è una lista di contatti/firmatari aggiunti da te. Appariranno come suggerimenti quando provi ad aggiungere firmatari a un nuovo documento.",
"Templates": "Questa è una lista di modelli disponibili per creare documenti. Puoi fare clic sul pulsante 'Usa' per creare un nuovo documento utilizzando un modello, modificare il documento e aggiungere firmatari nel passaggio successivo."
},
"form-name": {
"Sign Yourself": "Firma tu stesso",
"Request Signatures": "Richiedi firme",
"New Template": "Nuovo modello"
},
"file-type": "pdf, png, jpg, jpeg",
"docx": "docx",
"file-selected": "file selezionato",
"template-title": "Titolo del modello",
"document-title": "Titolo del documento",
"description": "Descrizione",
"time-to-complete": "Tempo per completare (giorni)",
"send-in-order": "Invia in ordine",
"send-in-order-help": {
"p1": "Scegli come desideri inviare le richieste di firma ai firmatari del documento:",
"p2": "Selezionando questa opzione, la richiesta di firma verrà inviata inizialmente al primo firmatario. Una volta che il primo firmatario avrà completato la propria parte, il successivo nella sequenza riceverà la richiesta. Questo processo continua fino a quando tutti i firmatari hanno firmato il documento. Questo metodo garantisce che il documento venga firmato in un ordine specifico.",
"p3": "Selezionando questa opzione, i link di firma verranno inviati a tutti i firmatari contemporaneamente. Ogni firmatario può firmare il documento a sua discrezione, indipendentemente dal fatto che altri firmatari abbiano completato le firme. Questo metodo è più veloce ma non impone un ordine di firma tra i partecipanti.",
"p4": "Seleziona l'opzione che meglio si adatta alle esigenze del tuo processo documentale."
},
"no": "No",
"auto-reminder": "Promemoria automatico",
"remind-once": "Ricorda una volta ogni (giorni)",
"next": "Avanti",
"select-folder": "Seleziona cartella",
"OpenSign-drive": "OpenSign™ Drive",
"drive-document-status": {
"Completed": "Completato",
"Declined": "Rifiutato",
"Draft": "Bozza",
"Expired": "Scaduto",
"In Progress": "In corso"
},
"select-folder-help": "Se non selezioni una cartella, il tuo documento firmato verrà salvato nella cartella principale di OpenSign drive.",
"no-data": "Nessun dato trovato",
"save-here": "Salva qui",
"back": "Indietro",
"add-folder": "Aggiungi cartella",
"create-folder": "Crea cartella",
"parent-folder": "Cartella principale",
"create": "Crea",
"signers": "Firmatari",
"signers-help": "Inizia a digitare il nome di un contatto per vedere i firmatari suggeriti dai tuoi contatti salvati o aggiungerne di nuovi. Organizza l'ordine di firma aggiungendo firmatari nella sequenza desiderata. Usa il pulsante '+' per includere firmatari e 'x' per rimuoverli. Ogni firmatario riceverà una notifica via email per firmare il documento nell'ordine elencato.",
"bcc-help": "it-Start it-typing it-a it-contact's it-name it-to it-see it-suggestions it-from it-your it-saved it-contacts it-or it-add it-new it-ones. it-Use it-the '+' it-button it-to it-add it-a it-user it-and it-the 'x' it-to it-remove it-them. it-The it-selected it-user's it-email it-will it-be it-added it-as it-a it-Bcc (it-blind it-carbon it-copy). it-Each it-user it-will it-receive it-an it-email it-notification it-once it-the it-document it-is it-completed.",
"add-signer": "Aggiungi firmatario",
"contact-not-found": "Contatto non trovato",
"add-yourself": "Aggiungi te stesso",
"submit": "Invia",
"reset": "Reimposta",
"my-signature": "La mia firma",
"signature": "Firma",
"upload-image": "Carica immagine",
"clear": "Chiara",
"upload-signature/Image": "Carica firma/immagine",
"initials": "Iniziali",
"API": "API",
"api-token": "Token API",
"regenerate-token": "Rigenera token live",
"generate-token": "Genera token live",
"view-docs": "Visualizza documenti",
"generate-token-alert": "Sei sicuro di voler rigenerare il token? Questo invaliderà il vecchio token.",
"yes": "Sì",
"copied": "Copiato",
"something-went-wrong-mssg": "Qualcosa è andato storto, riprova più tardi.",
"token-generated": "Token generato con successo.",
"webhook": "Webhook",
"update-webhook": "Aggiorna Webhook",
"add-webhook": "Aggiungi Webhook",
"webhook-added": "Webhook aggiunto con successo.",
"team-disabled": "Team disabilitato",
"Team enabled": "Team abilitato",
"are-you-sure": "Sei sicuro di voler",
"disable": "disabilitare",
"enable": "abilitare",
"this-team": "questo team?",
"edit-team": "Modifica Team",
"name-of-team": "Nome del Team",
"prev": "Precedente",
"no-data-avaliable": "Nessun dato disponibile",
"add-team": "Aggiungi Team",
"page-not-found": "Pagina non trovata",
"users-from-teams": "utenti dai Team",
"user-status": "Stato utente",
"activate": "attiva",
"deactivate": "disattiva",
"this-user": "questo utente",
"delete-user": "Elimina utente",
"delete": "elimina",
"add-user": "Aggiungi utente",
"password-generateed": "La password verrà generata solo una volta; assicurati di copiarla.",
"Team status": "Stato del Team",
"user-deactivated": "Utente disattivato.",
"user-activated": "Utente attivato.",
"public": "Pubblico",
"download": "Scarica",
"view": "Visualizza",
"make-template-public": "Rendi il modello pubblico",
"make-template-private": "Rendi il modello privato",
"make-template-public-alert": "Sei sicuro di voler rendere pubblico questo modello?",
"make-template-private-alert": "Sei sicuro di voler rendere privato questo modello? Questo lo rimuoverà dal tuo profilo pubblico.",
"public-role": "Ruolo pubblico",
"public-url": "Profilo pubblico",
"embed-template": "Incorpora Modello",
"public-url-copy": "Ecco il tuo URL pubblico: ",
"public-url-copy-mssg": "Copialo o condividilo con il firmatario; potrai vedere tutti i tuoi modelli impostati come pubblici.",
"add-public-url-alert": "Completa la configurazione del tuo profilo pubblico OpenSign. Dopo, potrai rendere i modelli pubblici. Questo processo è necessario solo una volta.",
"share-with-alert": "Non puoi condividere un modello se alcuni ruoli hanno già contatti assegnati. Rimuovi tutte le assegnazioni di contatti dai ruoli prima di condividere il modello.",
"share-with": "Condividi con",
"team-not-found": "Team non trovato",
"roles": "Ruoli",
"delete-document": "Elimina documento",
"delete-document-alert": "Sei sicuro di voler eliminare questo documento?",
"quick-send": "Invio rapido",
"add-new": "Aggiungi nuovo",
"send": "Invia",
"quick-send-alert-1": "Tutti i ruoli in questo documento sono attualmente collegati a contatti. Per inviare rapidamente copie di questo modello a più firmatari, assicurati che almeno un ruolo non sia collegato a nessun contatto.",
"quick-send-alert-2": "Assicurati che ci sia almeno un widget firma aggiunto per tutti i destinatari.",
"quick-send-alert-3": "Aggiungi almeno un ruolo a questo modello per 'invio rapido' a più firmatari.",
"quick-send-alert-4": "Limite di invio rapido raggiunto.",
"copy-link": "Copia link",
"copy": "Copia",
"revoke-document": "Revoca documento",
"revoke-document-alert": "Sei sicuro di voler revocare questo documento?",
"resend-mail": "Reinvia email",
"resend-mail-help": "Puoi usare le seguenti variabili che verranno sostituite con i loro valori effettivi: {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
"subject": "Oggetto",
"body": "Corpo del messaggio",
"add-contact": "Aggiungi contatto",
"edit-contact": "Modifica contatto",
"add-signer-alert": "Il contatto esiste già! Selezionalo dal menu a tendina Firmatari.",
"record-delete-alert": "Record eliminato con successo!",
"record-revoke-alert": "Record revocato con successo!",
"mail-sent-alert": "Email inviata con successo.",
"document-sent-alert": "Documento inviato con successo!",
"template-share-alert": "Modello condiviso con successo.",
"template-public-alert-1": "Hai reso pubblico il modello con successo.",
"template-public-alert-2": "Hai reso privato il modello con successo.",
"template-public-alert-3": "Devi selezionare un ruolo per i firmatari pubblici.",
"template-public-alert-4": "Assicurati che ci sia almeno un widget firma aggiunto per tutti i firmatari.",
"template-public-alert-5": "Poiché questo modello ha l'invio in ordine abilitato, il ruolo pubblico deve essere posizionato in cima.",
"template-public-alert-6": "Assegna firmatari a tutti i ruoli tranne che per l'utente ruolo pubblico.",
"template-public-alert-7": "Assegna almeno un ruolo pubblico per rendere questo modello pubblico.",
"loader": "Questo potrebbe richiedere del tempo.",
"pages": "Pagine",
"document-signed": "Documento firmato",
"close": "Chiudi",
"validation-alert": "Avviso di validazione",
"validate-alert-mssg": "L'input non soddisfa i criteri impostati dall'espressione regolare.",
"otp-verification": "Verifica OTP",
"enter-otp": "Inserisci OTP",
"verify": "Verifica",
"verify-email-1": "Verifica email",
"resend": "Reinvia",
"contact-details": "Dettagli contatto",
"verify-email": "Verifica la tua email!",
"send-otp": "Invia OTP",
"otp-placeholder": "Inserisci il codice di verifica ricevuto via email",
"loading-doc": "Caricamento del documento...",
"widgets-name": {
"signature": "firma",
"stamp": "timbro",
"initials": "iniziali",
"name": "nome",
"job title": "titolo lavorativo",
"company": "azienda",
"date": "data",
"text": "testo",
"text input": "campo di testo",
"checkbox": "casella di controllo",
"dropdown": "menu a tendina",
"radio button": "pulsante di opzione",
"image": "immagine",
"email": "email"
},
"fields": "Campi",
"recipients": "Destinatari",
"please-add": "Aggiungi un",
"signed-by": "Firmato da",
"more": "altro",
"sent": "inviato",
"print": "Stampa",
"certificate": "Certificato",
"decline": "Rifiuta",
"finish": "Completa",
"mail": "Email",
"sign-now": "Firma ora",
"successfully-signed": "Firmato con successo!",
"email-mssg": "I destinatari aggiunti qui riceveranno una copia del documento firmato.",
"email-error-1": "fornisci un indirizzo email corretto",
"email-error-2": "Puoi inviare solo a dieci destinatari alla volta.",
"Email-verified-alert-1": "Email verificata.",
"Email-verified-alert-2": "Email già verificata.",
"upload-stamp-image": "Carica immagine timbro",
"draw": "Disegno",
"type": "Digitata",
"color-type": {
"red": "rosso",
"blue": "blu",
"black": "nero",
"yellow": "giallo"
},
"my-initials": "Le mie iniziali",
"upload": "Caricamento",
"initial-teb": "Iniziali",
"signature-tab": "Firma",
"your-signature": "La tua firma",
"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",
"reset-password-alert-2": "Si prega di configurare l'adattatore email",
"reset-password-alert-3": "Reimposta la tua password",
"faild-animation": "Caricamento animazione non riuscito",
"apply": "Applica",
"copy-type": {
"All pages": "Tutte le pagine",
"All pages but last": "Tutte le pagine tranne l'ultima",
"All pages but first": "Tutte le pagine tranne la prima",
"Next to current widget": "Accanto al widget corrente"
},
"options": "Opzioni",
"minimun-check": "Controllo minimo",
"maximum-check": "Controllo massimo",
"default-value": "Valore predefinita",
"select": "Seleziona",
"read-only": "È solo lettura",
"hide-labels": "Nascondi etichette",
"checkbox": "Casella di controllo",
"alert": "Avviso",
"zoom-in": "Ingrandisci",
"zoom-out": "Riduci",
"document-signed-alert": "Congratulazioni! 🎉 Questo documento è stato firmato con successo da te!",
"otp-sent-alert": "OTP inviato alla tua email",
"fields-required": "Campi richiesti",
"signature-widget-alert-1": "Assicurati che sia stato aggiunto almeno un widget per la firma",
"signature-widget-alert-2": "Assicurati che tutti i campi siano compilati correttamente e soddisfino tutti i requisiti.",
"encrypted-pdf-alert": "Attualmente i file PDF crittografati non sono supportati.",
"encrypted-pdf-alert-1": "La password del file PFX non è valida.",
"user-not-exist": "L'utente non esiste",
"template-role-alert": "Aggiungi prima i ruoli",
"create-document": "Crea Documento",
"template-created-alert": "Cosa vorresti fare con il modello che hai appena creato?",
"signers-alert": "Avviso firmatari",
"template-creation-alert-1": "Seleziona il firmatario per aggiungere un segnaposto!",
"ok": "Ok",
"radio-group": "Gruppo radio",
"dropdown-options": "Opzioni a discesa",
"add-role": "Aggiungi Ruolo",
"role-ex": "es: Cliente, HR, Direttore, Manager, Studente, ecc.",
"add/choose-signer": "Aggiungi/Scegli Firmatario",
"choose-from-contacts": "Scegli dai contatti",
"select-signer": "Seleziona il firmatario",
"edit-template": "Modifica Modello",
"widget-info": "Informazioni Widget",
"validation": "Validazione",
"invalid-default-value": "Valore predefinita non valido",
"hint": "Suggerimento",
"color": "Colore",
"role-help": {
"p1": "Cosa sono i ruoli del modello?",
"p2": "Inizia specificando ogni ruolo necessario per completare il documento. Pensa alle parti coinvolte nel processo di firma e alle loro responsabilità. I ruoli comuni includono HR per documenti interni, Cliente per accordi o Fornitore per accordi commerciali.",
"p3": "Perché pre-assegnare utenti a determinati ruoli?",
"p4": "Per ruoli che coinvolgono sempre la stessa persona (es. la firma del CEO sulle lettere di offerta ai dipendenti), puoi pre-assegnare un utente a un ruolo nel modello. Questo passaggio è facoltativo ma consigliato per efficienza e coerenza nei documenti.",
"p5": "Quando specifico l'utente associato a ciascun ruolo?",
"p6": "Quando crei un documento dal tuo modello, ti verrà chiesto di assegnare utenti a ciascun ruolo definito. Se un ruolo ha già un utente associato, sarà precompilato, ma puoi modificarlo se necessario prima di inviare il documento."
},
"add-recipients": "Aggiungi destinatari",
"loading-mssg": "Questo potrebbe richiedere del tempo",
"send-mail": "Invia Mail",
"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?",
"placeholder-alert-4": "Hai inviato con successo le mail a tutti i destinatari!",
"placeholder-mail-alert": "Hai inviato con successo un'e-mail a {{name}}. I firmatari successivi riceveranno un'e-mail una volta che {{name}} avrà firmato il documento.",
"placeholder-alert-5": "Vuoi firmare i documenti ora?",
"placeholder-alert-6": "Configura l'adattatore email per inviare mail!",
"placeholder-alert-7": "Seleziona il firmatario per aggiungere un segnaposto!",
"email-subject": "Oggetto email",
"email-body": "Corpo email",
"email-placeholder": "aggiungi corpo dell'email",
"reset-to-default": "Reimposta predefinita",
"cutomize-email": "Personalizza Email",
"upgrade-to-customize-email": "Aggiorna per personalizzare l'Email",
"sign-url": "URL Firma",
"mails-sent": "Mail inviate",
"mail-not-delivered": "Mail non consegnata",
"document-alert": "Avviso Documento",
"owner-subscription-expired": "L'abbonamento del proprietario è scaduto.",
"subscription-expired": "Abbonamento Scaduto",
"alert-message": "Messaggio di avviso",
"document-decline": "Documento rifiutato",
"decline-alert-1": "Sei sicuro di voler rifiutare questo documento?",
"decline-alert-2": "Hai rifiutato questo documento!",
"decline-alert-3": "Non puoi firmare questo documento poiché è stato rifiutato/revocato.",
"sign here": "Firma qui",
"guest-email-alert": "Il codice di verifica è stato inviato alla tua email",
"get-otp-alert": "Riceverai un codice di verifica tramite Email",
"user-already-exist": "L'utente esiste già.",
"server-error": "Errore interno del server!",
"fill-required-details!": "Compila i dettagli richiesti!",
"sign-up-form": "Modulo di registrazione",
"sign-up": "Registrati",
"folder-already-exist!": "La cartella esiste già!",
"please-select-position!": "Seleziona la posizione!",
"default-sign-alert": "Sei sicuro di voler firmare automaticamente in tutte le posizioni richieste?",
"Your-Signature": "La tua Firma",
"auto-sign-all": "Firma tutto automaticamente",
"generating-certificate": "Generazione certificato",
"pdf-download": "Scarica PDF",
"generate-certificate-alert": "Il certificato di completamento è in fase di generazione. Attendi momentaneamente. Se il download non inizia a breve, fai clic nuovamente sul pulsante.",
"generate-certificate-err": "Stiamo attualmente riscontrando un problema con la generazione del certificato. Per favore riprova più tardi.",
"folder-already-exist": "La cartella esiste già!",
"folder-created-successfully": "Cartella creata con successo!",
"fill-folder-name": "Compila il nome della cartella",
"already-exists-this-username": "Un utente con questo nome utente esiste già!",
"verification-code-sent": "Una mail di verifica è stata inviata al tuo indirizzo email!",
"registered-user-successfully": "Utente registrato con successo.",
"role-not-found": "Ruolo non trovato.",
"do-not-access": "Non hai accesso, contatta l'amministratore.",
"add-admin": "Aggiungi amministratore",
"opensign-setup": "Configurazione OpenSign",
"join-discord": "Unisciti al nostro server Discord",
"admin-already-exist": "L'amministratore esiste già.",
"password-update-alert-1": "Password aggiornata con successo.",
"password-update-alert-2": "La tua password attuale manca o è errata.",
"password-update-alert-3": "La tua password attuale è mancante o errata.",
"password-update-alert-4": "La tua password e la password di conferma non corrispondono.",
"current-password": "Password attuale",
"new-password": "Nuova Password",
"confirm-password": "Conferma Password",
"file-alert-1": "La dimensione del file selezionato è troppo grande. Seleziona un file inferiore a",
"file-alert-2": "Seleziona un file.",
"file-alert-3": "Attendi mentre il documento viene caricato.",
"enter-pdf-password": "Inserisci la password del PDF",
"correct-password": "Fornisci la password corretta",
"decrypting-pdf": "Decrittazione PDF, attendi...",
"invalid-otp": "OTP non valido",
"user-not-found": "Utente non trovato!",
"enter-otp-alert": "Inserisci l'OTP!",
"get-verification-code": "Ottieni codice di verifica",
"get-verification-code-2": "Riceverai un codice di verifica tramite Email",
"enter-verification-code": "Inserisci il codice di verifica",
"provide-your-details": "Fornisci i tuoi dettagli",
"provide-email": "Fornisci l'email.",
"additional-info": "Informazioni aggiuntive",
"signature-saved-alert": "Firma salvata con successo.",
"saved-successfully": "Salvato con successo.",
"fill-field": "Compila questo campo",
"error-template": "Errore: Modello non trovato!",
"error": "Errore",
"invalid-document": "Errore: documento non valido!",
"document-not-found": "Documento non trovato!",
"document-signed-alert-1": "Hai firmato con successo il documento. Puoi scaricare o stampare una copia del documento parzialmente firmato. Una copia del documento firmato digitalmente e del certificato di completamento verrà inviata al proprietario e a tutti i firmatari via email una volta che tutti avranno firmato.",
"encrypted-pdf-not-support": "Attualmente i file PDF crittografati non sono supportati.",
"document-signed-alert-4": "Congratulazioni! 🎉 Questo documento è stato firmato con successo da tutti i partecipanti!",
"document-signed-alert-5": "Questo documento è stato firmato da tutti i firmatari.",
"document-signed-alert-6": "Questo documento è stato rifiutato da uno o più destinatari.",
"document-signed-alert-7": "Questo documento non è più disponibile.",
"document-signed-alert-8": "Il documento è già stato inviato per la firma.",
"yet-to-sign": "Ancora da firmare",
"validation-alert-1": "L'input non soddisfa i criteri impostati dall'espressione regolare.",
"account-already-exists": "L'account esiste già!",
"choose-password": "Scegli Password",
"enter-password": "Inserisci Password",
"sure": "sicuro",
"subscribed-alert": "Hai già sottoscritto il piano!",
"monthly": "Mensile",
"yearly-upto": "Annuale (fino al 66% di sconto)",
"billed-yearly": "Fatturato annualmente",
"billed-monthly": "Fatturato mensilmente",
"plansubscription-1": "Ospitalo tu stesso gratuitamente",
"visit-github": "Visita Github",
"user-already-exist-name": "Esiste già un utente con questo nome utente!",
"do-not-access-contact-admin": "Non hai accesso, contatta l'amministratore.",
"filed-required-correctly": "Compila i dettagli richiesti correttamente.",
"admin-created": "Amministratore creato",
"invalid-masterkey": "Masterkey non valida",
"opensign-Setup": "Configurazione OpenSign",
"master-key": "Chiave principale",
"profile-update-alert": "Profilo aggiornato con successo.",
"date": "Data",
"report-not-found": "Report non trovato",
"please-wait": "attendere prego",
"select-foler": "Seleziona cartella",
"generated-on": "Generato il",
"text-field": "Campo di testo",
"font-size": "Dimensione carattere",
"widget-status": {
"Required": "Richiesto",
"Optional": "Opzionale"
},
"all-right": "Tutti i diritti riservati",
"version": "versione",
"social-media": {
"github": "Github",
"linked-in": "LinkedIn",
"twitter": "Twitter",
"discord": "Discord"
},
"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 OpenSign! Se hai bisogno di supporto, contattaci pure.",
"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.",
"opensign-drive-3": "Ordina i tuoi documenti per data o nome utilizzando questo menu.",
"opensign-drive-4": "Fai clic su questo menu per visualizzare i documenti in modalità elenco.",
"opensign-drive-5": "L'elenco dei documenti è visualizzato in base all'opzione di ordinamento selezionata. Le icone accanto a ciascun documento indicano il suo stato attuale.",
"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-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-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-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.",
"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.",
"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.",
"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."
},
"enter-email-plaholder": "Aggiungi un indirizzo email e premi invio",
"success-email-alert": "Email inviata con successo!",
"expired-doc-title": "Documento Scaduto",
"expired-on-mssg": "Questo documento è scaduto il {{expiredDate}} e non è più disponibile per la firma.",
"signature-validate-alert": "Conferma di aver selezionato almeno {{minRequiredCount}} caselle di controllo.",
"signature-validate-alert-2": "Assicurati che questo campo sia compilato correttamente e soddisfi tutti i requisiti.",
"remaing-users": "Utenti rimanenti",
"Role": "Ruolo",
"additional-users": "Acquista utenti aggiuntivi.",
"Quantity-of-users": "Quantità di utenti",
"Price": "Prezzo",
"Total-price-for-next-time": "Prezzo totale per la prossima volta",
"Proceed": "Procedi",
"Select": "Seleziona",
"Add-seats": "Aggiungi Posti",
"user-name-exist": "Il nome utente esiste già",
"format": "formato",
"select-date-format": "Seleziona un formato data",
"quantity-of-credits": "Quantità di crediti premium",
"remaining-credits": "Crediti premium disponibili:",
"remaining-credits-help": "Usa i crediti premium per firmare documenti API, invii in massa o per integrare OpenSign nel tuo sito web. Hai a disposizione {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti acquistati aggiuntivi rimanenti.",
"additional-credits": "Acquista crediti premium",
"quota-err-quicksend": "Quota raggiunta, non hai abbastanza crediti.",
"buy-credits": "Acquista Crediti Premium",
"rotate-right": "Ruota a destra",
"rotate-left": "Ruota a sinistra",
"rotate-alert-mssg": "Tutti i widget su questa pagina andranno persi. Sei sicuro di voler procedere?",
"templateid": "ID Modello",
"bulk-send-subcription-alert": "Esegui l'upgrade al piano Professionale o Team per utilizzare l'invio in massa.",
"generate-test-token": "Genera Token di Test",
"regenerate-test-token": "Rigenera Token di Test",
"help-test-token": "Questo token può essere utilizzato per testare le API all'endpoint https://sandbox.opensignlabs.com/api/v1, consentendoti di firmare documenti illimitati. Nota che le API sandbox firmeranno i tuoi documenti con certificati autofirmati, che potrebbero non essere riconosciuti come validi da Adobe. Una volta completati i test, puoi eseguire l'upgrade a uno dei nostri piani a pagamento per generare un token di produzione.",
"help-api-token": "Questo token può essere utilizzato per accedere alle API di produzione all'endpoint {{origin}}/api/v1. Può essere generato solo su uno dei nostri piani a pagamento.",
"reason": "Motivo",
"decline-by": "Rifiutato/revocato da",
"document-declined": "Documento rifiutato",
"public-template-mssg-1": "Per integrare OpenSign nel tuo progetto React o Next.js, esegui semplicemente il seguente comando:",
"public-template-mssg-2": "Assicurati di avere npm o yarn configurato nel tuo progetto. Se usi Yarn, puoi sostituire npm install con yarn add @opensign/react.",
"public-template-mssg-3": "Hai bisogno di maggiori dettagli o esempi?",
"public-template-mssg-4": "Visita il",
"public-template-mssg-5": " npm per gli aggiornamenti più recenti, documentazione dettagliata e cronologia delle versioni.",
"public-template-mssg-6": "Prima di poter utilizzare questo frammento di codice, devi rendere questo modello pubblico.",
"public-template-mssg-7": "Prima di poter generare un link pubblico, devi rendere questo modello pubblico.",
"here": "Qui",
"copy-code": "COPIA",
"copied-code": "COPIATO",
"Installation": "Installazione",
"Usage": "Utilizzo",
"insufficient-credits": "Crediti di firma insufficienti",
"insufficient-credits-mssg": "Il proprietario di questo documento non dispone attualmente dei crediti OpenSign necessari affinché tu possa firmare. Contatta il proprietario per ulteriori dettagli.",
"angular-npm-mssg-1": "Per integrare OpenSign nel tuo progetto Angular, esegui semplicemente il seguente comando:",
"quota-mail-info-head": "Limite mensile email per richieste di firma",
"quota-mail-info": "Puoi inviare fino a 15 email di richiesta di firma ogni mese. Passa al piano superiore per inviare richieste di firma illimitate direttamente.",
"quota-mail-reset": "I tuoi crediti email per richieste di firma saranno ripristinati il",
"quota-mail": "Hai raggiunto il limite di 15 email di richiesta di firma per questo mese. Passa al piano superiore per continuare a inviare email direttamente.",
"quota-mail-tip": "Suggerimento: Puoi comunque firmare <1>documenti illimitati</1> condividendo manualmente i link per le richieste di firma.",
"quota-mail-head": "Quota Raggiunta",
"unauthorized-modal": "Non hai l'autorizzazione per eseguire questa azione, contatta {{adminEmail}}.",
"sent-this-month": "Inviati questo mese",
"available-seats": "Posti disponibili",
"buy-users": "Acquista più utenti",
"isenable-otp": "Abilita verifica OTP",
"isenable-otp-help": {
"p1": "Vuoi abilitare il processo di verifica tramite password monouso (OTP)?",
"p2": "Selezionando questa opzione, abiliterai la verifica OTP. Gli utenti riceveranno un codice di verifica via email che dovranno inserire per firmare il documento.",
"p3": "Selezionando questa opzione, disabiliterai la verifica OTP, consentendo agli utenti di firmare il documento direttamente senza passaggi aggiuntivi.",
"p4": "Scegli l'opzione che meglio si adatta alle tue esigenze di firma dei documenti."
},
"advanced-options": "Opzioni avanzate",
"hide-advanced-options": "Nascondi opzioni avanzate",
"download-files": "Scarica file",
"download-pdf": "Scarica Pdf",
"pdf-certificate": "Scarica Pdf + Certificato",
"document-logs": "Log del documento",
"server-down": "Impossibile connettersi al server OpenSign. Se stai ospitando OpenSign autonomamente, assicurati che tutti i passaggi nella documentazione siano stati seguiti correttamente. Se stai eseguendo OpenSign localmente, potresti accedervi tramite un numero di porta errato.",
"admin-exists": "L'amministratore esiste già. Accedi all'applicazione utilizzando le credenziali di amministratore per gestire gli utenti.",
"enable-tour": "Abilita tour",
"istourenabled-help": {
"p1": "Questo abiliterà un tour guidato per i firmatari, fornendo istruzioni durante il processo di firma.",
"p2": "Questo disabiliterà il tour guidato, garantendo un'esperienza di firma più veloce e senza interruzioni.",
"p3": "Suggerimento: Se i tuoi firmatari sono familiari con OpenSign e lo usano frequentemente, potresti voler disabilitare il tour guidato per un'esperienza più fluida. Scegli l'opzione che meglio si adatta alle tue esigenze di firma dei documenti."
},
"form-title-1": "Impostazioni del flusso del documento",
"form-title-2": "Impostazioni di sicurezza",
"public-tour-message": "Il modello deve essere pubblico prima di poter generare un link condivisibile.",
"add-user-template": "Devi aggiungere un ruolo prima di poter aggiungere i campi per esso.",
"pdf-uncompatible": "Questo PDF non è compatibile con OpenSign, contatta <support@opensignlabs.com>",
"text-field-tour": "I campi di tipo 'Testo' devono essere compilati in anticipo prima che il documento venga inviato. Se hai bisogno che i firmatari forniscano un input, usa invece il campo 'Testo di input'.",
"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.",
"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.",
"l1": "Crea una firma scritta a mano direttamente sullo schermo utilizzando il dito o una penna digitale.",
"l2": "Inserisci il tuo nome e scegli tra vari stili di font per creare una firma.",
"l3": "Carica una versione digitale esistente della tua firma.",
"l4": "Usa uno stile di firma preimpostato salvato sotto 'La mia firma' nelle tue impostazioni."
},
"notify-on-signatures": "Notifica sulle firme",
"notify-on-signatures-help": {
"p1": "Il creatore del documento riceverà una notifica via email ogni volta che un firmatario firma il documento.",
"note": "Nota: L'email di notifica non verrà inviata per l'ultimo firmatario, poiché il creatore del documento riceverà invece un'email di completamento."
},
"expiry-date": "Data di scadenza",
"expiry-date-updated": "La data di scadenza è stata estesa con successo a {{newexpirydate}}",
"expiry-date-error": "Fornisci una nuova data di scadenza successiva a quella attuale",
"duplicate-template-alert": "Sei sicuro di voler creare un duplicato di questo modello?",
"duplicate-template-created": "Modello duplicato creato con successo.",
"delete-page": "Elimina pagina",
"merge-pdf": "Unisci PDF",
"add-pages": "Aggiungi pagine",
"delete-alert": "Non è possibile eliminare una singola pagina",
"delete-alert-2": "Sei sicuro di voler eliminare questa pagina?",
"delete-note": "Nota: Una volta eliminata questa pagina, non potrai annullare l'operazione.",
"Rotation-alert": "Ruota pagina",
"bulk-import": "Importazione massiva",
"contacts-file": "File contatti (xlsx, csv)",
"100-records-only": "Attualmente puoi importare solo fino a 100 record.",
"csv-excel-support-only": "Carica un file nei seguenti formati: CSV, XLSX o XLS.",
"contact-imported": "{{imported}} contatti importati. {{failed}} contatti non sono stati importati.",
"invalid-data": "Dati non validi",
"delete-folder-alert": "Sei sicuro di voler eliminare questa cartella?",
"delete-folder-alert-1": "Impossibile eliminare: la cartella deve essere vuota.",
"defaultSign-alert": "I widget {{widgetsType}} non sono disponibili per la firma automatica.",
"embed-heading": "Incorpora modelli OpenSign",
"public-template-edit-title": "Modifica Modello Pubblico",
"public-template-edit-mssg": "Non puoi modificare direttamente un modello pubblico. Per apportare modifiche, il modello sarà impostato come privato e dovrai renderlo pubblico di nuovo successivamente. Vuoi continuare?",
"agree-p1": "Confermo di aver letto e compreso il",
"agree-p2": "Divulgazione sulle registrazioni e firme elettroniche",
"agree-p3": "e acconsento all'uso di registrazioni e firme elettroniche.",
"agrre-button": "Accetta e Continua",
"term-cond-title": "Termini e Condizioni",
"term-cond-h": "DIVULGAZIONE SULLE REGISTRAZIONI E FIRME ELETTRONICHE",
"term-cond-p1": "Questa Divulgazione sulle Registrazioni e Firme Elettroniche ('Divulgazione') è un accordo tra il Creatore del Documento ('Mittente') e il Firmatario ('Tu'), facilitato tramite la piattaforma OpenSign ('Piattaforma'). Firmando documenti tramite OpenSign, accetti i termini descritti in questa Divulgazione. Ti preghiamo di leggerla attentamente prima di procedere.",
"term-cond-h1": "1. Scopo e ambito",
"term-cond-p2": "Questa Divulgazione regola l'uso di registrazioni e firme elettroniche per i documenti inviati dal Mittente e firmati da Te tramite la piattaforma OpenSign. Accettando, riconosci che:",
"term-cond-p3": "Riceverai e firmerai documenti elettronicamente tramite OpenSign.",
"term-cond-p4": "La tua firma elettronica è legalmente vincolante ed equivalente a una firma manoscritta.",
"term-cond-h2": "2. Consenso all'uso di registrazioni e firme elettroniche",
"term-cond-p5": "Accettando questa Divulgazione:",
"term-cond-p6": "Consenti di effettuare transazioni elettroniche con il Mittente utilizzando OpenSign e comprendi che questo consenso è valido fino a revoca.",
"term-cond-p7": "Accetti di rivedere, firmare e restituire documenti elettronicamente utilizzando OpenSign.",
"term-cond-p8": "Questo consenso si applica solo ai documenti specifici forniti dal Mittente.",
"term-cond-h3": "3. Diritto di revoca del consenso",
"term-cond-p9": "Hai il diritto di ritirare il tuo consenso all'uso di registrazioni elettroniche e firme contattando direttamente il mittente tramite le informazioni di contatto fornite nella loro comunicazione.",
"term-cond-p10": "Contattando il mittente direttamente utilizzando le informazioni di contatto fornite nella loro comunicazione.",
"term-cond-p11": "Notificando il mittente prima di completare il processo di firma.",
"term-cond-h4": "4. Requisiti hardware e software",
"term-cond-p12": "Per firmare documenti utilizzando OpenSign, devi avere:",
"term-cond-p13": "Un dispositivo con accesso a Internet.",
"term-cond-p14": "Un browser web compatibile con JavaScript abilitato.",
"term-cond-p15": "Un account email per ricevere notifiche e documenti firmati.",
"term-cond-p16": "Se questi requisiti cambiano, il mittente o OpenSign ti notificheranno gli aggiornamenti.",
"term-cond-h5": "5. Conservazione dei documenti e richiesta di copie cartacee",
"term-cond-p17": "Sei responsabile di assicurarti di poter accedere e conservare il documento(i) firmato(i) elettronicamente. OpenSign fornisce strumenti per scaricare o stampare il documento firmato subito dopo che il processo di firma è completo.",
"term-cond-p18": "Se preferisci una copia cartacea di qualsiasi documento firmato elettronicamente, puoi richiederla direttamente al mittente. Il mittente può addebitare una tariffa ragionevole per fornire copie cartacee, salvo quanto vietato dalla legge applicabile.",
"term-cond-h6": "6. Riconoscimento e Accordo",
"term-cond-p19": "Procedendo, confermi che:",
"term-cond-p20": "Confermi di poter accedere e rivedere i documenti elettronici come descritto in questa divulgazione.",
"term-cond-p21": "Accetti di utilizzare registrazioni elettroniche e firme per le transazioni specificate con il mittente.",
"term-cond-p22": "Comprendi che OpenSign è una piattaforma che facilita la transazione e non è parte dell'accordo.",
"term-cond-h7": "7. Effetto Legale",
"term-cond-p23": "La tua firma elettronica facilitata tramite OpenSign:",
"term-cond-p24": "Rispetta le leggi applicabili sulle firme elettroniche, inclusa, ma non limitata alla Legge E-SIGN negli Stati Uniti, al Regolamento eIDAS dell'UE e alla Legge sull'Information Technology in India.",
"term-cond-p25": "È legalmente vincolante tra te e il mittente per il/i documento(i) firmato(i).",
"term-cond-h8": "8. Ruolo della piattaforma e limitazione di responsabilità",
"term-cond-p26": "OpenSign funge da piattaforma per facilitare le transazioni elettroniche. Non è responsabile del contenuto, della validità o dell'applicabilità dei documenti inviati dal mittente. Eventuali controversie o problemi relativi al documento o alla sua firma devono essere risolti direttamente tra te e il mittente.",
"term-cond-h9": "9. Risoluzione dell'accordo",
"term-cond-p27": "Il tuo consenso si applica solo alla transazione corrente, salvo diverso accordo da parte del mittente. OpenSign si riserva il diritto di terminare l'accesso alla sua piattaforma per violazioni dei suoi Termini di servizio o delle politiche.",
"term-cond-p28": "Selezionando 'Accetto', confermi di aver compreso e accettato i termini di questa divulgazione delle registrazioni elettroniche e delle firme e acconsenti a firmare elettronicamente il/i documento(i) fornito(i) dal mittente tramite OpenSign.",
"term-cond-p29": "Se hai domande su questa divulgazione, contatta direttamente il mittente. Per supporto tecnico con OpenSign, visita",
"term-cond-p30": "o invia una email a",
"js-snippet-msg": "Per integrare i modelli OpenSign nel tuo sito web HTML o nelle tue pagine di destinazione, puoi usare il codice qui sotto:",
"js-snippet-msg-1": "Puoi ottenere il TemplateId dalla",
"js-snippet-msg-2": "pagina di gestione modelli",
"js-snippet-msg-3": "pagina.",
"agrrement-alert": "Per procedere, devi acconsentire alla divulgazione delle registrazioni elettroniche e delle firme.",
"webhook-already-exists": "L'URL esiste già! Prova con uno diverso.",
"webhook-must-be-secure": "L'URL del webhook deve essere sicuro e utilizzare https://",
"provide-test-token": "Si prega di generare il token di test API",
"invalid-redirect-url": "Si prega di fornire un URL di reindirizzamento valido.",
"auto-sign-mssg": "Firma automatica per tutti",
"already-exist-signer": "Il firmatario selezionato è già presente nell'elenco dei destinatari",
"change-signer": "Cambia firmatario",
"signyour-self-button": "Usa questa opzione per firmare il documento da solo senza aggiungere altri.",
"requestsign-button": "Usa questa opzione per richiedere firme da altri e da te stesso insieme.",
"signyour-self-description": "Usa questo modulo per firmare il documento da solo senza aggiungere altri.",
"requestsign-description": "Usa questo modulo per richiedere firme da altri e da te stesso insieme.",
"template-form-description": "Usa questo modulo per creare modelli riutilizzabili per i tuoi documenti.",
"allow-modify": "Consenti Modifiche",
"allow-modify-help": {
"p1": "Vuoi consentire modifiche al documento durante il processo di firma?",
"p2": "Selezionando questa opzione, i firmatari possono aggiungere elementi aggiuntivi, come firme, iniziali, timbri o testo, sopra i widget esistenti.",
"p3": "Selezionando questa opzione, i firmatari non potranno apportare alcuna aggiunta al documento durante il processo di firma."
},
"signature-setting": "Impostazioni Firma",
"activation-key": "Chiave di attivazione",
"paid-selfhost-help": "Si prega di contattare OpenSign per ulteriori informazioni all'indirizzo ",
"remaining-credits-selfhost-help": "Utilizza i crediti premium per la firma di documenti tramite API, l'invio in massa e l'integrazione di OpenSign sul tuo sito web. Hai {{allowedcredits}} crediti inclusi e {{addoncredits}} crediti aggiuntivi acquistati rimanenti.",
"signature-type": "La tua firma",
"initial-type": "Le tue iniziali",
"redirect-url": "URL di reindirizzamento",
"bulk-send": "Invio in blocco",
"select-timezone": "Seleziona il tuo fuso orario",
"current-time": "Ora corrente",
"email-help": "Non ti è permesso cambiare l'indirizzo email per motivi di sicurezza. Ti preghiamo di creare un altro account gratuito utilizzando il nuovo indirizzo email.",
"doc-sent": "Documento inviato con successo.",
"no-signer": "Rimuovi",
"and": "e",
"make-public": "Rendere pubblico",
"general": "Generale",
"request-email": "Email di richiesta",
"completion-email": "Email di completamento",
"variables-use": "Variabili da utilizzare",
"send-to-sign": "ha richiesto la tua firma",
"use-template": "Usa modello",
"contact-already-exists": "Il contatto esiste già."
}
Executable → Regular
View File

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Executable → Regular
View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.
+3 -44
View File
@@ -1,5 +1,5 @@
import React, { useState, useEffect, lazy } from "react";
import { Routes, Route, BrowserRouter } from "react-router-dom";
import { Routes, Route, BrowserRouter } from "react-router";
import { pdfjs } from "react-pdf";
import Login from "./pages/Login";
import Form from "./pages/Form";
@@ -15,28 +15,20 @@ import DraftDocument from "./components/pdf/DraftDocument";
import PlaceHolderSign from "./pages/PlaceHolderSign";
import PdfRequestFiles from "./pages/PdfRequestFiles";
import LazyPage from "./primitives/LazyPage";
import { isEnableSubscription } from "./constant/const";
import SSOVerify from "./pages/SSOVerify";
import Loader from "./primitives/Loader";
import TeamList from "./pages/TeamList";
import UserList from "./pages/UserList";
import { serverUrl_fn } from "./constant/appinfo";
import DocSuccessPage from "./pages/DocSuccessPage";
const DebugPdf = lazy(() => import("./pages/DebugPdf"));
const ForgetPassword = lazy(() => import("./pages/ForgetPassword"));
const GuestLogin = lazy(() => import("./pages/GuestLogin"));
const Pgsignup = lazy(() => import("./pages/Pgsignup"));
const Subscriptions = lazy(() => import("./pages/PlanSubscriptions"));
const ChangePassword = lazy(() => import("./pages/ChangePassword"));
const UserProfile = lazy(() => import("./pages/UserProfile"));
const Signup = lazy(() => import("./pages/Signup"));
const Opensigndrive = lazy(() => import("./pages/Opensigndrive"));
const ManageSign = lazy(() => import("./pages/Managesign"));
const GenerateToken = lazy(() => import("./pages/GenerateToken"));
const Webhook = lazy(() => import("./pages/Webhook"));
const AddAdmin = lazy(() => import("./pages/AddAdmin"));
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
const Preferences = lazy(() => import("./pages/Preferences"));
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
const AppLoader = () => {
return (
@@ -74,11 +66,6 @@ function App() {
<Routes>
<Route element={<ValidateRoute />}>
<Route exact path="/" element={<Login />} />
{isEnableSubscription && (
<Route path="/signup" element={<LazyPage Page={Signup} />} />
)}
{!isEnableSubscription && (
<>
<Route
path="/addadmin"
element={<LazyPage Page={AddAdmin} />}
@@ -87,8 +74,6 @@ function App() {
path="/upgrade-2.1"
element={<LazyPage Page={UpdateExistUserAdmin} />}
/>
</>
)}
</Route>
<Route element={<Validate />}>
<Route
@@ -123,18 +108,6 @@ function App() {
path="/forgetpassword"
element={<LazyPage Page={ForgetPassword} />}
/>
{isEnableSubscription && (
<>
<Route
path="/pgsignup"
element={<LazyPage Page={Pgsignup} />}
/>
<Route
path="/subscription"
element={<LazyPage Page={Subscriptions} />}
/>
</>
)}
<Route element={<HomeLayout />}>
<Route
path="/changepassword"
@@ -175,7 +148,6 @@ function App() {
path="/pdfRequestFiles/:docId"
element={<PdfRequestFiles />}
/>
{/* recipient signature route with no rowlevel data using docId from url */}
<Route
path="/recipientSignPdf/:docId/:contactBookId"
@@ -185,26 +157,13 @@ function App() {
path="/recipientSignPdf/:docId"
element={<PdfRequestFiles />}
/>
{isEnableSubscription && (
<>
<Route path="/teams" element={<TeamList />} />
<Route
path="/generatetoken"
element={<LazyPage Page={GenerateToken} />}
/>
<Route
path="/webhook"
element={<LazyPage Page={Webhook} />}
/>
</>
)}
<Route path="/users" element={<UserList />} />
<Route
path="/preferences"
element={<LazyPage Page={Preferences} />}
/>
</Route>
<Route path="/sso" element={<SSOVerify />} />
<Route path="/success" element={<DocSuccessPage />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 805 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

+184 -184
View File
@@ -1,184 +1,184 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="691.365" height="586.346" viewBox="0 0 691.365 586.346">
<defs>
<linearGradient id="linear-gradient" x1="0.5" y1="1" x2="0.5" y2="-0.001" gradientUnits="objectBoundingBox">
<stop offset="0" stop-color="gray" stop-opacity="0.251"/>
<stop offset="0.54" stop-color="gray" stop-opacity="0.122"/>
<stop offset="1" stop-color="gray" stop-opacity="0.102"/>
</linearGradient>
</defs>
<g id="Group_644" data-name="Group 644" transform="translate(-588.635 -80)">
<g id="Group_642" data-name="Group 642" transform="translate(22538.635 6999)">
<path id="Path_472" data-name="Path 472" d="M482.764,637.675c51.612-29.22,67.336-29.693,124.954-17.169,81.646,17.758,88.43-49.93,108.306-186.933,33.946-233.98-108.306-295.737-108.306-295.737s-94.6-8.1-150.536-38.963C377.726,55.027,286.9,49.443,207.046,86.542c-51.069,23.7-93.125,56.714-100.31,102.034-20.09,126.746,12.9,196.58-48.819,237.954S48.944,581.5,96.263,577.564c34.968-2.906,80.842,52.415,130.687,60.111,17.382,2.684,88.7-13.043,99.578,0C341.327,655.4,464.763,647.87,482.764,637.675Z" transform="translate(-21979.895 -6980.982)" fill="#00ddf1" opacity="0.1"/>
<g id="Group_641" data-name="Group 641" transform="translate(-21545.08 -6816.88)">
<rect id="Rectangle_263" data-name="Rectangle 263" width="217.867" height="15.073" transform="translate(0 39.052)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_264" data-name="Rectangle 264" width="217.867" height="15.073" transform="translate(0 104.137)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_265" data-name="Rectangle 265" width="217.867" height="15.073" transform="translate(0 171.964)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_266" data-name="Rectangle 266" width="13.702" height="39.052" transform="translate(19.183)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_267" data-name="Rectangle 267" width="13.702" height="2.74" transform="translate(19.183 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_268" data-name="Rectangle 268" width="13.702" height="2.74" transform="translate(19.183 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_269" data-name="Rectangle 269" width="13.702" height="39.052" transform="translate(39.737)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_270" data-name="Rectangle 270" width="13.702" height="2.74" transform="translate(39.737 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_271" data-name="Rectangle 271" width="13.702" height="2.74" transform="translate(39.737 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_272" data-name="Rectangle 272" width="13.702" height="39.052" transform="translate(60.29)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_273" data-name="Rectangle 273" width="13.702" height="2.74" transform="translate(60.29 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_274" data-name="Rectangle 274" width="13.702" height="2.74" transform="translate(60.29 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_275" data-name="Rectangle 275" width="13.702" height="39.052" transform="translate(80.844)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_276" data-name="Rectangle 276" width="13.702" height="2.74" transform="translate(80.844 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_277" data-name="Rectangle 277" width="13.702" height="2.74" transform="translate(80.844 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_278" data-name="Rectangle 278" width="13.702" height="39.052" transform="translate(101.397)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_279" data-name="Rectangle 279" width="13.702" height="2.74" transform="translate(101.397 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_280" data-name="Rectangle 280" width="13.702" height="2.74" transform="translate(101.397 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_281" data-name="Rectangle 281" width="13.702" height="39.052" transform="translate(121.95)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_282" data-name="Rectangle 282" width="13.702" height="2.74" transform="translate(121.95 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_283" data-name="Rectangle 283" width="13.702" height="2.74" transform="translate(121.95 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_284" data-name="Rectangle 284" width="13.702" height="39.052" transform="translate(142.504)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_285" data-name="Rectangle 285" width="13.702" height="2.74" transform="translate(142.504 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_286" data-name="Rectangle 286" width="13.702" height="2.74" transform="translate(142.504 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_287" data-name="Rectangle 287" width="13.702" height="39.052" transform="matrix(0.894, -0.448, 0.448, 0.894, 135.847, 138.025)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_288" data-name="Rectangle 288" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 138.614, 143.541)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_289" data-name="Rectangle 289" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 148.128, 162.529)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_290" data-name="Rectangle 290" width="13.702" height="39.052" transform="translate(19.183 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_291" data-name="Rectangle 291" width="13.702" height="2.74" transform="translate(19.183 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_292" data-name="Rectangle 292" width="13.702" height="2.74" transform="translate(19.183 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_293" data-name="Rectangle 293" width="13.702" height="39.052" transform="translate(39.737 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_294" data-name="Rectangle 294" width="13.702" height="2.74" transform="translate(39.737 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_295" data-name="Rectangle 295" width="13.702" height="2.74" transform="translate(39.737 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_296" data-name="Rectangle 296" width="13.702" height="39.052" transform="translate(60.29 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_297" data-name="Rectangle 297" width="13.702" height="2.74" transform="translate(60.29 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_298" data-name="Rectangle 298" width="13.702" height="2.74" transform="translate(60.29 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_299" data-name="Rectangle 299" width="13.702" height="39.052" transform="translate(145.93 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_300" data-name="Rectangle 300" width="13.702" height="2.74" transform="translate(145.93 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_301" data-name="Rectangle 301" width="13.702" height="2.74" transform="translate(145.93 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_302" data-name="Rectangle 302" width="13.702" height="39.052" transform="translate(166.483 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_303" data-name="Rectangle 303" width="13.702" height="2.74" transform="translate(166.483 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_304" data-name="Rectangle 304" width="13.702" height="2.74" transform="translate(166.483 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_305" data-name="Rectangle 305" width="13.702" height="39.052" transform="translate(187.036 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_306" data-name="Rectangle 306" width="13.702" height="2.74" transform="translate(187.036 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_307" data-name="Rectangle 307" width="13.702" height="2.74" transform="translate(187.036 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_308" data-name="Rectangle 308" width="13.702" height="39.052" transform="translate(19.183 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_309" data-name="Rectangle 309" width="13.702" height="2.74" transform="translate(19.183 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_310" data-name="Rectangle 310" width="13.702" height="2.74" transform="translate(19.183 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_311" data-name="Rectangle 311" width="13.702" height="39.052" transform="translate(39.737 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_312" data-name="Rectangle 312" width="13.702" height="2.74" transform="translate(39.737 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_313" data-name="Rectangle 313" width="13.702" height="2.74" transform="translate(39.737 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_314" data-name="Rectangle 314" width="13.702" height="39.052" transform="translate(60.29 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_315" data-name="Rectangle 315" width="13.702" height="2.74" transform="translate(60.29 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_316" data-name="Rectangle 316" width="13.702" height="2.74" transform="translate(60.29 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_317" data-name="Rectangle 317" width="13.702" height="39.052" transform="translate(80.844 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_318" data-name="Rectangle 318" width="13.702" height="2.74" transform="translate(80.844 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_319" data-name="Rectangle 319" width="13.702" height="2.74" transform="translate(80.844 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_320" data-name="Rectangle 320" width="13.702" height="39.052" transform="translate(101.397 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_321" data-name="Rectangle 321" width="13.702" height="2.74" transform="translate(101.397 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_322" data-name="Rectangle 322" width="13.702" height="2.74" transform="translate(101.397 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_323" data-name="Rectangle 323" width="13.702" height="39.052" transform="translate(121.95 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_324" data-name="Rectangle 324" width="13.702" height="2.74" transform="translate(121.95 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_325" data-name="Rectangle 325" width="13.702" height="2.74" transform="translate(121.95 160.317)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_473" data-name="Path 473" d="M797.862,159.09v35.571h-13.7V156.48Q791.039,157.693,797.862,159.09Z" transform="translate(-621.103 -155.61)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_326" data-name="Rectangle 326" width="13.702" height="2.74" transform="translate(163.057 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_327" data-name="Rectangle 327" width="13.702" height="2.74" transform="translate(163.057 27.405)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_474" data-name="Path 474" d="M827.862,165.719v30.83h-13.7V162.43c1.781.391,3.556.8,5.33,1.22Q823.7,164.636,827.862,165.719Z" transform="translate(-630.549 -157.483)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_475" data-name="Path 475" d="M827.862,166.285v.685h-13.7v-2.74h5.33Q823.7,165.2,827.862,166.285Z" transform="translate(-630.549 -158.05)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_328" data-name="Rectangle 328" width="13.702" height="2.74" transform="translate(183.611 27.405)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_476" data-name="Path 476" d="M691.358,287.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.179c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-589.693 -196.649)" fill="#00ddf1"/>
<path id="Path_477" data-name="Path 477" d="M686.2,296.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.277,10.277,0,0,1,686.2,296.239Z" transform="translate(-589.706 -196.595)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_478" data-name="Path 478" d="M691.42,290.06s1.542,1.213,5.282,0" transform="translate(-591.9 -197.672)" fill="#00ddf1" opacity="0.2"/>
<path id="Path_479" data-name="Path 479" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1"/>
<path id="Path_480" data-name="Path 480" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1" opacity="0.2"/>
<path id="Path_481" data-name="Path 481" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1"/>
<path id="Path_482" data-name="Path 482" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_483" data-name="Path 483" d="M694.75,283.958s1.089-5.207,8.16-5.878" transform="translate(-592.949 -193.9)" fill="#00ddf1"/>
<path id="Path_484" data-name="Path 484" d="M819.358,386.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.18c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-629.999 -227.823)" fill="#00ddf1"/>
<path id="Path_485" data-name="Path 485" d="M814.2,395.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.276,10.276,0,0,1,814.2,395.239Z" transform="translate(-630.011 -227.769)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_486" data-name="Path 486" d="M819.42,389.06s1.542,1.213,5.282,0" transform="translate(-632.206 -228.846)" fill="#00ddf1" opacity="0.2"/>
<path id="Path_487" data-name="Path 487" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1"/>
<path id="Path_488" data-name="Path 488" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1" opacity="0.2"/>
<path id="Path_489" data-name="Path 489" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1"/>
<path id="Path_490" data-name="Path 490" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_491" data-name="Path 491" d="M822.75,382.958s1.089-5.207,8.16-5.878" transform="translate(-633.254 -225.073)" fill="#00ddf1"/>
</g>
</g>
<g id="Group_643" data-name="Group 643" transform="translate(7 -63)">
<g id="Group_639" data-name="Group 639" transform="translate(21233 8372)">
<ellipse id="Ellipse_73" data-name="Ellipse 73" cx="49.851" cy="8.007" rx="49.851" ry="8.007" transform="translate(-20118.443 -7772.555)" fill="#00abf6" opacity="0.1"/>
<rect id="Rectangle_182" data-name="Rectangle 182" width="3.208" height="72.224" transform="translate(-20070.199 -7917.934)" fill="#535461"/>
<path id="Path_268" data-name="Path 268" d="M1025.906,587.251l-.2,3.254-.287,4.6-.118,1.913-.281,4.6-.123,1.913-.286,4.6-3.233,52.22a8.851,8.851,0,0,1-8.83,8.268H995.731a8.841,8.841,0,0,1-8.82-8.3l-3.244-52.225-.281-4.6-.117-1.913-.292-4.6-.117-1.913-.281-4.6-.2-3.254a4.6,4.6,0,0,1,4.574-4.865h34.391a4.6,4.6,0,0,1,4.569,4.9Z" transform="translate(-21072.734 -8433.181)" fill="#565661"/>
<path id="Path_269" data-name="Path 269" d="M990.949,517.36l-.287,4.589H948.106l-.286-4.589Z" transform="translate(-21037.977 -8360.036)" fill="#9d9cb5"/>
<path id="Path_270" data-name="Path 270" d="M990.93,530.08l-.281,4.594H948.9l-.286-4.594Z" transform="translate(-21038.363 -8366.248)" fill="#9d9cb5"/>
<path id="Path_271" data-name="Path 271" d="M990.912,542.8l-.287,4.589H949.686L949.4,542.8Z" transform="translate(-21038.748 -8372.46)" fill="#9d9cb5"/>
<path id="Path_272" data-name="Path 272" d="M933.875,490.464c18.684,13.517,26.466,34.656,26.466,34.656s-22.511-.773-41.2-14.289-26.46-34.651-26.46-34.651S915.2,476.953,933.875,490.464Z" transform="translate(-21028.934 -8381.328)" fill="#00abf6"/>
<path id="Path_273" data-name="Path 273" d="M892.68,476.18s23.365,12.969,30.865,23.708,36.8,25.233,36.8,25.233" transform="translate(-21028.934 -8381.328)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
<path id="Path_274" data-name="Path 274" d="M975.393,443.457c10.171,7.352,14.407,18.863,14.407,18.863s-12.279-.425-22.424-7.776S953,435.68,953,435.68,965.227,436.1,975.393,443.457Z" transform="translate(-21058.393 -8361.549)" fill="#00abf6"/>
<path id="Path_275" data-name="Path 275" d="M953,435.68s12.719,7.06,16.8,12.9,20.03,13.737,20.03,13.737" transform="translate(-21058.393 -8361.549)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
<path id="Path_276" data-name="Path 276" d="M1040.3,470.483c-11.68,12.836-14.151,29.612-14.151,29.612s16.469-4.042,28.139-16.883,14.151-29.612,14.151-29.612S1051.981,457.647,1040.3,470.483Z" transform="translate(-21094.119 -8370.301)" fill="#00abf6"/>
<path id="Path_277" data-name="Path 277" d="M1068.45,453.61s-15.215,13.159-19.083,22.224S1026.15,500.1,1026.15,500.1" transform="translate(-21094.119 -8370.306)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
</g>
<g id="Group_638" data-name="Group 638" transform="translate(-40 36)">
<rect id="Rectangle_254" data-name="Rectangle 254" width="12.343" height="37.459" transform="translate(741.459 392.675)" fill="#3f3d56"/>
<rect id="Rectangle_255" data-name="Rectangle 255" width="12.343" height="39.629" transform="translate(1073.647 390.726)" fill="#3f3d56"/>
<path id="Path_418" data-name="Path 418" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" fill="#65617d"/>
<path id="Path_419" data-name="Path 419" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" opacity="0.2"/>
<path id="Path_420" data-name="Path 420" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" fill="#3f3d56"/>
<path id="Path_421" data-name="Path 421" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" opacity="0.1"/>
<path id="Path_422" data-name="Path 422" d="M360.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(436.74 -122.381)" fill="#3f3d56"/>
<rect id="Rectangle_256" data-name="Rectangle 256" width="30.644" height="20.464" transform="translate(1046.362 534.189)" fill="#3f3d56"/>
<rect id="Rectangle_257" data-name="Rectangle 257" width="30.644" height="20.464" transform="translate(1046.362 534.189)" opacity="0.1"/>
<rect id="Rectangle_258" data-name="Rectangle 258" width="30.644" height="20.464" transform="translate(1045.062 534.189)" fill="#3f3d56"/>
<path id="Path_423" data-name="Path 423" d="M223.943,265.82v3.723l-12.343-.318v-3.4Z" transform="translate(529.859 126.848)" opacity="0.1"/>
<path id="Path_424" data-name="Path 424" d="M358.741,271.246,343.909,377.789a2.794,2.794,0,0,1-2.839,2.41L222.314,377.12l-2.254-.058a2.794,2.794,0,0,1-2.7-3.164l16.651-111.533a2.794,2.794,0,0,1,2.949-2.358l119.186,8.069a2.787,2.787,0,0,1,2.6,3.17Z" transform="translate(476.307 16.556)" fill="#65617d"/>
<path id="Path_425" data-name="Path 425" d="M169.158,99.16,156.6,197.472l109.142,3.463,13.422-94.85Z" transform="translate(549.128 185.237)" fill="#00ddf1"/>
<rect id="Rectangle_259" data-name="Rectangle 259" width="132.965" height="112.605" rx="5.33" transform="translate(853.849 282.877)" fill="#65617d"/>
<rect id="Rectangle_260" data-name="Rectangle 260" width="120.836" height="93.765" transform="translate(860.125 290.458)" fill="#00ddf1"/>
<path id="Path_426" data-name="Path 426" d="M735.273,262.82v2.462l-12.343.253V262.82Z" transform="translate(350.717 127.899)" opacity="0.1"/>
<path id="Path_427" data-name="Path 427" d="M833.626,371a2,2,0,0,1-1.949,2.131l-121.615,2.514a2,2,0,0,1-2.033-1.813L697.575,265.156a2,2,0,0,1,1.9-2.189l120.693-6,1.3-.065a1.994,1.994,0,0,1,2.092,1.819Z" transform="translate(308.06 17.643)" fill="#65617d"/>
<path id="Path_428" data-name="Path 428" d="M628.93,100.238,739.157,94.82l9.31,98.748-109.356,2.6Z" transform="translate(383.65 186.757)" fill="#00ddf1"/>
<path id="Path_429" data-name="Path 429" d="M899.741,371a2,2,0,0,1-1.949,2.131L886.28,256.963l1.3-.065a2,2,0,0,1,2.092,1.819Z" transform="translate(241.945 17.643)" opacity="0.1"/>
<rect id="Rectangle_261" data-name="Rectangle 261" width="12.343" height="11.096" transform="translate(741.459 419.038)" opacity="0.1"/>
<path id="Path_431" data-name="Path 431" d="M281.574,483.65v3.229l-30.644.819V483.65Z" transform="translate(516.08 50.533)" opacity="0.1"/>
<path id="Path_432" data-name="Path 432" d="M390.8,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h-.033a2.6,2.6,0,0,1-2.566-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H388.23A2.6,2.6,0,0,1,390.8,483.886Z" transform="translate(460.942 -60.983)" fill="#65617d"/>
<path id="Path_434" data-name="Path 434" d="M194.6,319.15H309.914v96.311L194.6,418.06Z" transform="translate(535.815 108.164)" fill="#d4dfec"/>
<rect id="Rectangle_262" data-name="Rectangle 262" width="12.343" height="11.317" transform="translate(1073.647 419.038)" opacity="0.1"/>
<path id="Path_435" data-name="Path 435" d="M709.574,483.65v4.164L678.93,487V483.65Z" transform="translate(366.132 50.533)" opacity="0.1"/>
<path id="Path_436" data-name="Path 436" d="M797.782,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0l-124.532-3.313a2.6,2.6,0,0,1-2.5-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H795.19a2.6,2.6,0,0,1,2.592,2.566Z" transform="translate(318.365 -60.983)" fill="#65617d"/>
<path id="Path_437" data-name="Path 437" d="M866.289,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0V481.32h0a2.6,2.6,0,0,1,2.6,2.566Z" transform="translate(249.859 -60.983)" opacity="0.1"/>
<path id="Path_438" data-name="Path 438" d="M713.414,319.15H598.1v96.311l115.314,2.6Z" transform="translate(394.451 108.164)" fill="#d4dfec"/>
<path id="Path_439" data-name="Path 439" d="M593.3,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h0a2.6,2.6,0,0,1-2.6-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H590.73A2.6,2.6,0,0,1,593.3,483.886Z" transform="translate(389.997 -60.983)" fill="#65617d"/>
<path id="Path_440" data-name="Path 440" d="M397.1,319.15H512.414v96.311L397.1,418.06Z" transform="translate(464.87 108.164)" fill="#d4dfec"/>
<path id="Path_441" data-name="Path 441" d="M597.889,533.65V542.1l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643-114.086,1.059h-.1l-29.007.273-.312.007L106.1,546.643V536.9l131.562-.864h.312l26.629-.182h.123l183.9-1.215h.188l.11-.006h.305l3.112-.019h.948l48.393-.325,2.858-.013.637-.007Z" transform="translate(566.82 33.015)" opacity="0.1"/>
<circle id="Ellipse_99" data-name="Ellipse 99" cx="33.347" cy="33.347" r="33.347" transform="translate(887.196 363.44)" fill="#fbbebe"/>
<path id="Path_442" data-name="Path 442" d="M383.74,592.973a.806.806,0,0,1,.065-.227c.65-2.332,1.806-6.276,2.67-9.427.11-.39.214-.773.312-1.137.955-3.554,2.813-11.876,3.846-17.066.364-1.826.65-3.248.708-3.963.13-1.1,2.085-5.795,4.3-11.194,1.553-3.794,3.248-7.939,4.489-11.434a39.876,39.876,0,0,0,1.9-6.4,3.313,3.313,0,0,0,0-1.384,9.543,9.543,0,0,1-.312-2.274h0c-.149-4.028,1.052-10.394,2.91-13.065,2.274-3.248,3.9-15.371,3.9-15.371s3.365.487,3.9-2.293a4,4,0,0,0,.065-.91,4.815,4.815,0,0,0-.058-.721c-.578-4.054,9.563-8.368,15.267-9.3a12.769,12.769,0,0,1,1.949-.182,17.694,17.694,0,0,0,4.06-.935q.909-.279,1.949-.65c2.391-.806,5.249-1.871,8.283-3.04,11.408-4.424,25.22-10.394,25.22-10.394.1-.461.208-.9.331-1.338a18,18,0,0,1,10.622-12.3h0v-1.3c0-2.241-.1-4.548-.195-6.847,0-.448-.039-.9-.065-1.345v-.734c0-.455-.046-.91-.071-1.358l-.117-.091a10.66,10.66,0,0,1-2.534-2.729,9.916,9.916,0,0,1-.923-1.949c-.812-2.2-1-4.658-1.546-6.984l-.117-.481a54.579,54.579,0,0,0-2.6-6.873c-1.436-3.521-2.54-7.146-3.638-10.817q-1-3.333-2.02-6.678l-1.026-3.4c-.195-.65-.39-1.3-.539-1.949h0a8.627,8.627,0,0,1-.169-.91h0a6.5,6.5,0,0,1,.156-2.995,20.854,20.854,0,0,1,.858-1.949,5.113,5.113,0,0,0,.325-.9h0a1.947,1.947,0,0,0,.052-.208,3.667,3.667,0,0,0,.071-.7,11.993,11.993,0,0,0-.338-2.527.045.045,0,0,1,0-.032,1.661,1.661,0,0,0-.032-.175,16.564,16.564,0,0,1-.234-1.949,18.483,18.483,0,0,1,2.254-10.115l.123-.234a16.383,16.383,0,0,1,1.2-1.806h0a9.122,9.122,0,0,1,.65-.793,2.594,2.594,0,0,1-3.606-.936,2.475,2.475,0,0,1-.149-.292c-.033-.078-.059-.162-.085-.247-.455-1.592.338-3.287,1.377-4.58a12.012,12.012,0,0,1,3.729-3.053h0a2.51,2.51,0,0,1,.26-.136,11.125,11.125,0,0,1,1.715-.689h0a9.781,9.781,0,0,1,1.059-.286l-2.41-.221-.974-.091A9.414,9.414,0,0,1,479,361.527h.7a7.625,7.625,0,0,1,1.02.084A2.885,2.885,0,0,1,479,358.8a9.362,9.362,0,0,0,5.97,1.988h.247a19.494,19.494,0,0,1-.221-2.124h0a21.545,21.545,0,0,1,0-2.9,5.483,5.483,0,0,0,4.4,1.468l-1.351-1.884a6.165,6.165,0,0,0,4.262,1.332l.312-.033a4.547,4.547,0,0,0,.773-.149h.058c-.123-.156-.234-.318-.344-.487l-.039-.078a6.282,6.282,0,0,1-.65-1.358,4.284,4.284,0,0,1-.156-.565h0a5.684,5.684,0,0,1,.292-3.541c2.1,1.754,4.4,4.151,7.146,2.339s2.839-5.139,7.146-4.8a6.626,6.626,0,0,1,5.464,3.976,4.385,4.385,0,0,1,4.983-1.137,4.45,4.45,0,0,1,2.521,4.476c1.786-.448,3.71-.89,5.425-.214s2.82,2.865,1.845,4.327a.931.931,0,0,1-.091.13c-.8,1.033-2.566,1.6-2.378,2.9l.273.195.845.611q4.97,3.586,9.68,7.536c1.611,1.351,3.326,2.975,3.437,5.087a5.617,5.617,0,0,1-.156,1.54h0c-.1.507-.247,1.013-.357,1.52a12.676,12.676,0,0,0-.221,3.93h0c.045.383.1.76.175,1.143.994,5.022,5.009,9.167,5.405,14.292v.364a11.546,11.546,0,0,1-.045,1.644c-.3,3.118-1.741,6.113-2.91,9.1-.072.182-.143.364-.208.546a90.5,90.5,0,0,0-4.548,18.294,65.14,65.14,0,0,1-1.3,6.555,17.365,17.365,0,0,1-3.307,6.776,10.536,10.536,0,0,1-2.183,1.949,12.132,12.132,0,0,1-1.722.916v.039c.071.4.13.793.2,1.189.039.208.071.416.11.65h0a.089.089,0,0,0,0,.039c.546,3.073,1.169,6.081,1.826,8.939l.331,1.423c8.186,2.956,9.154,9.011,9.212,11.33a6.259,6.259,0,0,1,0,.929s12.343.325,17.216,3.9a13.21,13.21,0,0,0,2.93,1.494,53.917,53.917,0,0,0,6.009,1.877c1.046.273,2.137.539,3.248.8,1.949.448,4.015.884,6.009,1.3,5.321,1.072,10.33,1.949,12.87,2.6,6.061,1.533,19.925,14.825,19.925,14.825s1.624,13.409,3.248,15.364,4.548,8.829,4.548,8.829l10.745,19.106,4.548,8.036h2.923a4.97,4.97,0,0,1,2.085,1.364c.773.689,1.631,1.546,2.384,2.326l1.247,1.332.78.864s4.723.117,6,3.852c3.846-.11,8.569.143,11.876,1.624a9.187,9.187,0,0,1,1.3.7,5.639,5.639,0,0,1,2.683,3.677,5.422,5.422,0,0,1,.117.935c0,.37,0,.741,0,1.1-.234,4.918-3.437,9.011-6.38,11.765l.208.37s-.1.234-.286.65c-.052.123-.117.26-.188.416h0c-.383.819-.994,2.046-1.793,3.482a41.136,41.136,0,0,1-6.217,8.556l-.188.195-37.511.344-.11-.13-7.276-8.679-3.619-4.314-.11-.136-.1,1.033-.351,3.443-.845,8.322-.045.416v.11l-.1.961.071-.961h-.188l-69.513.65-114.092,1.26v.546h-.091v-.546l-29.072.312Z" transform="translate(418.007 -14.269)" fill="url(#linear-gradient)"/>
<path id="Path_443" data-name="Path 443" d="M586.185,511.522s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.691-.039-2.891-.143-5.886-.292-8.868-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522c-2.6,8.53-1.748,20.51.227,31.755.578,3.281,1.247,6.5,1.949,9.55a223.892,223.892,0,0,0,6.269,22.036Z" transform="translate(367.711 -46.521)" fill="#fbbebe"/>
<path id="Path_444" data-name="Path 444" d="M586.185,537.354s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.692,9.855-4.21,22.394-1.072,22.394-1.072,13.2-2.631,21.666-2.118,27.078-.273A223.888,223.888,0,0,0,586.185,537.354Z" transform="translate(367.711 -72.353)" opacity="0.1"/>
<path id="Path_445" data-name="Path 445" d="M413.346,651.973v-.546l114.092-1.241,69.513-.65h.195l-.071.961.1-.961h.123v-.11l.89-8.738.338-3.307.1-1.033.091-.864,4.015-13.389,4.015-13.37,3.378-11.258,5.158-17.2s-2.923-6.821-4.548-8.77-3.248-15.267-3.248-15.267-13.864-13.208-19.925-14.728c-2.54-.65-7.555-1.514-12.883-2.6-1.994-.4-4.041-.832-6.009-1.3-1.111-.253-2.2-.52-3.248-.786a57.086,57.086,0,0,1-6.009-1.858,13.448,13.448,0,0,1-2.924-1.488c-4.775-3.5-16.742-3.9-17.2-3.9h0a.269.269,0,0,1,0-.045v-.058a6.66,6.66,0,0,0,0-.819,5.208,5.208,0,0,0-.059-.91.82.82,0,0,0-.045-.325c-.032-.234-.071-.481-.13-.741a3.226,3.226,0,0,0-.078-.344,11.181,11.181,0,0,0-.812-2.267c-.045-.1-.1-.214-.156-.318a11.861,11.861,0,0,0-1.1-1.709c-.084-.11-.175-.227-.273-.338a12.721,12.721,0,0,0-1.767-1.715c-.175-.136-.357-.279-.546-.409a3.026,3.026,0,0,0-.331-.234l-.578-.377-.539-.312c-.474-.26-.974-.513-1.52-.754-.169-.078-.351-.149-.533-.221s-.468-.182-.708-.273h-.039l-.487-.195c-.37-.123-.747-.234-1.143-.344l-.6-.156-.65-.149-.65-.136c-.65-.13-1.345-.247-2.072-.338l-.741-.091-.76-.078-.786-.065-1.117-.065-1.3-.045a81.017,81.017,0,0,0-17.054,1.814,46.946,46.946,0,0,0-11.855-1.093h-.565l-.65.039h-.416l-.838.078a6.224,6.224,0,0,0-.845.1c-.279.039-.572.071-.851.117l-.429.071c-.422.071-.851.149-1.3.24q-.214.039-.429.1c-.565.123-1.13.273-1.689.442-.279.078-.559.169-.838.266-.143.039-.279.091-.416.143l-.416.149-.409.162-.487.195c-.247.1-.487.214-.728.331-.136.058-.273.123-.4.195s-.266.13-.39.2a18.03,18.03,0,0,0-4.034,2.846l-.331.318-.325.331c-.11.11-.214.227-.318.344a15.473,15.473,0,0,0-1.182,1.462c-.1.136-.2.273-.3.416a17.335,17.335,0,0,0-1.117,1.9c-.084.156-.162.318-.24.474a21.243,21.243,0,0,0-1.3,3.411q-.182.65-.331,1.3s-13.753,6.068-25.161,10.46c-3.034,1.169-5.892,2.228-8.283,3.027-.7.234-1.345.448-1.949.65a17.347,17.347,0,0,1-4.06.935,12.778,12.778,0,0,0-1.949.182c-5.243.851-14.227,4.548-15.208,8.257a2.4,2.4,0,0,0-.065.91v.071c.65,4.548-3.9,3.9-3.9,3.9s-1.624,12.019-3.9,15.267c-1.644,2.339-2.768,7.555-2.91,11.492h0a12.741,12.741,0,0,0,.286,3.658v.091c.26.871-.364,3.248-1.423,6.3-1.15,3.346-2.826,7.5-4.437,11.427-2.43,5.925-4.716,11.3-4.859,12.486-.058.526-.227,1.468-.461,2.683-.948,4.95-3.053,14.377-4.093,18.19-.773,2.845-1.9,6.743-2.67,9.42a.91.91,0,0,1-.065.227l29.124-.286v.546h.091Z" transform="translate(417.856 -73.042)" fill="#00ddf1"/>
<path id="Path_446" data-name="Path 446" d="M445.44,607.57s37.03,19.165,50.348,16.891Z" transform="translate(396.391 -105.214)" opacity="0.1"/>
<path id="Path_447" data-name="Path 447" d="M561.44,595.57s36.056,11.694,48.074,13.318S561.44,595.57,561.44,595.57Z" transform="translate(355.751 -101.01)" opacity="0.1"/>
<path id="Path_448" data-name="Path 448" d="M585.443,530.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,512.821,585.339,526.866,585.443,530.686Z" transform="translate(371.74 -73.754)" opacity="0.1"/>
<path id="Path_449" data-name="Path 449" d="M585.443,528.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,510.821,585.339,524.866,585.443,528.686Z" transform="translate(371.74 -73.053)" fill="#00ddf1"/>
<path id="Path_450" data-name="Path 450" d="M776.94,672.008s22.192-3.411,22.628,6.334S788.25,694.9,788.25,694.9Z" transform="translate(280.251 -127.587)" fill="#fbbebe"/>
<path id="Path_451" data-name="Path 451" d="M578.17,479.936a30.558,30.558,0,0,1-2.923,1.13c-7,2.417-14.065,4.853-21.439,5.736s-15.111.1-21.543-3.573a18.685,18.685,0,0,1-1.949-1.267c-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522C575.318,456.711,576.195,468.69,578.17,479.936Z" transform="translate(367.508 -46.521)" opacity="0.1"/>
<path id="Path_452" data-name="Path 452" d="M536.9,360.29a6.153,6.153,0,0,1-5.405-1.137l1.351,1.878a5.5,5.5,0,0,1-4.4-1.462,22.146,22.146,0,0,0,.208,5.035,9.433,9.433,0,0,1-6.211-1.949,2.872,2.872,0,0,0,1.715,2.8,9.316,9.316,0,0,0-10.135,6.958l3.391.312a12,12,0,0,0-6.782,4.138c-1.039,1.3-1.832,2.962-1.377,4.548s2.651,2.6,3.846,1.468a18.151,18.151,0,0,0-3.963,14.806,9.4,9.4,0,0,1,.3,3.417,28.993,28.993,0,0,1-1.228,3.047c-.65,1.9-.032,3.956.546,5.847l3.047,10.018c1.1,3.619,2.2,7.244,3.645,10.745a53.328,53.328,0,0,1,2.6,6.828c.819,3.144.851,6.588,2.6,9.336a12.948,12.948,0,0,0,4.665,4.112c6.425,3.677,14.188,4.457,21.542,3.573s14.409-3.32,21.406-5.736a20.141,20.141,0,0,0,4.444-1.949c4.84-3.17,5.918-9.654,6.821-15.364a87.242,87.242,0,0,1,4.736-18.71c1.416-3.567,3.248-7.185,2.936-11.005-.4-5.074-4.411-9.186-5.405-14.175a12.344,12.344,0,0,1,.045-5.048,11.473,11.473,0,0,0,.513-3.053c-.11-2.092-1.826-3.7-3.437-5.048q-5.243-4.379-10.823-8.277c-.188-1.3,1.579-1.858,2.378-2.884,1.13-1.449-.039-3.748-1.754-4.424s-3.638-.234-5.425.214a4.281,4.281,0,0,0-7.5-3.32,6.627,6.627,0,0,0-5.47-3.95c-4.307-.338-4.392,2.956-7.146,4.768s-5.028-.578-7.146-2.319A6.029,6.029,0,0,0,536.9,360.29Z" transform="translate(374.5 -15.625)" fill="#3f3d56"/>
<path id="Path_453" data-name="Path 453" d="M768.171,669.128s-.578,1.377-1.644,3.372a44.83,44.83,0,0,1-5.847,8.556l-.175.188h-1.052l-37.511.344h-.9l-.11-.13-7.276-8.634-2.741-3.255-.11-.136-.234-.273-.13-.156-.169-.2-5.334-6.328,1.949-8.121,3.32-13.682.084-.331.65-2.6.461-1.949,1.546-6.354,5.3-21.861,9.42,4.872,9.933,17.541,5.334,9.42h2.923c.552,0,1.819,1.065,3.092,2.293.455.435.91.9,1.3,1.3,1.156,1.2,2.072,2.222,2.072,2.222s4.736.1,6.009,3.833a4.537,4.537,0,0,1,.162.552,26.191,26.191,0,0,0,1.644,4.262c.838,1.826,1.891,3.9,2.956,5.97,1.981,3.8,4,7.419,4.736,8.731l.091.162Z" transform="translate(305.476 -105.217)" fill="#00ddf1"/>
<path id="Path_454" data-name="Path 454" d="M615.681,653.093l-.065.929v.065l-.11,1.494-.864,12.025L613.6,682.093l-.3,4.08-.65,8.731v.11l-69.513.65L429.061,696.9v.546h-.091v-.728l.292-9.329.559-18.229L430.3,653.7l.045-1.54a6.841,6.841,0,0,1,7.419-6.607c3.066.26,7.049.578,11.811.916l1.949.143c27.5,1.91,78.322,4.379,129.606,1.3,1.078-.059,2.163-.123,3.248-.2,7.952-.5,15.9-1.143,23.764-1.949a6.841,6.841,0,0,1,7.536,7.3Z" transform="translate(402.161 -118.514)" opacity="0.1"/>
<path id="Path_455" data-name="Path 455" d="M429.08,698.857v-.546l114.092-1.241,69.513-.65h.195v-.11l.65-8.731.279-3.9,1.065-14.689.838-11.629v-.286a6.893,6.893,0,0,0-.071-1.6.2.2,0,0,0,0-.065,6.751,6.751,0,0,0-1.3-3.014,6.847,6.847,0,0,0-6.12-2.6c-7.867.806-15.813,1.449-23.764,1.949-1.085.078-2.17.143-3.248.2-51.284,3.047-102.106.578-129.606-1.3l-1.949-.143c-4.762-.338-8.744-.65-11.811-.916a6.847,6.847,0,0,0-7.3,5.542,7.146,7.146,0,0,0-.117,1.065l-.442,14.4-.6,18.236-.286,9.329v.728Z" transform="translate(402.122 -119.926)" fill="#3f3d56"/>
<path id="Path_456" data-name="Path 456" d="M615.925,661.692l-.8,9.959L613.937,686.5l-.3,3.716-.65,8.322-.039.416v.11h0l-.1.961.071-.961h-.188l-69.513.65L429.11,700.944v-.182l.318-9.329.65-18.229.416-12.116a4.132,4.132,0,0,1,1.988-3.411,4.061,4.061,0,0,1,2.456-.585c1.364.11,3.04.24,5,.383,3.177.234,7.094.507,11.655.793,8.445.533,19.041,1.1,31.183,1.572,33.782,1.3,79.388,1.735,122.707-2.059,1.949-.169,3.963-.357,5.931-.546a4.066,4.066,0,0,1,2.261.429,3.346,3.346,0,0,1,.559.351,3.989,3.989,0,0,1,1.3,1.6,2.315,2.315,0,0,1,.162.4A4.08,4.08,0,0,1,615.925,661.692Z" transform="translate(402.112 -122.559)" opacity="0.1"/>
<path id="Path_457" data-name="Path 457" d="M360.914,677.87v2.072a3.982,3.982,0,0,1-2.274,3.6l-26.051.5a3.976,3.976,0,0,1-2.319-3.619v-1.949Z" transform="translate(436.74 -129.843)" opacity="0.1"/>
<path id="Path_458" data-name="Path 458" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" fill="#3f3d56"/>
<path id="Path_459" data-name="Path 459" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" opacity="0.1"/>
<path id="Path_460" data-name="Path 460" d="M282.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(516.984 42.387)" fill="#3f3d56"/>
<path id="Path_461" data-name="Path 461" d="M552.228,360.039A6.334,6.334,0,0,1,551,357.22a6.165,6.165,0,0,0,.273,2.988A5.847,5.847,0,0,0,552.228,360.039Z" transform="translate(359.434 -17.505)"/>
<path id="Path_462" data-name="Path 462" d="M540.1,366.9a22.83,22.83,0,0,1-.24-2.4c0,.8,0,1.6,0,2.41Z" transform="translate(363.311 -20.056)"/>
<path id="Path_463" data-name="Path 463" d="M621.706,398.627a11.912,11.912,0,0,1,.234-1.722,11.7,11.7,0,0,0,.52-3.047,4.008,4.008,0,0,0-.1-.689c-.11.6-.286,1.2-.416,1.787a12.433,12.433,0,0,0-.234,3.671Z" transform="translate(334.652 -30.1)"/>
<path id="Path_464" data-name="Path 464" d="M600.922,368.894l.708.513a12.8,12.8,0,0,0,1.67-1.442,2.384,2.384,0,0,0,.247-2.365,1.95,1.95,0,0,1-.247.416C602.5,367.042,600.734,367.607,600.922,368.894Z" transform="translate(341.923 -20.441)"/>
<path id="Path_465" data-name="Path 465" d="M518.455,380.29a8,8,0,0,0-.565,1.481,11.8,11.8,0,0,1,3.248-1.228Z" transform="translate(371.008 -25.587)"/>
<path id="Path_466" data-name="Path 466" d="M531.888,368.979a9.637,9.637,0,0,1-1.221-.8,2.758,2.758,0,0,0,.058.767,7.7,7.7,0,0,1,1.163.033Z" transform="translate(366.534 -21.345)"/>
<path id="Path_467" data-name="Path 467" d="M587.579,428.33c-.266,3.151-1.735,6.185-2.923,9.173a87.2,87.2,0,0,0-4.736,18.717c-.9,5.71-1.949,12.194-6.821,15.358a19.488,19.488,0,0,1-4.437,1.949c-7,2.423-14.065,4.853-21.439,5.736s-15.111.11-21.543-3.567a12.993,12.993,0,0,1-4.664-4.112c-1.735-2.748-1.767-6.191-2.6-9.336a53.424,53.424,0,0,0-2.6-6.834c-1.436-3.5-2.54-7.146-3.638-10.739q-1.527-5.009-3.047-10.024a18.716,18.716,0,0,1-.741-3.086,11.644,11.644,0,0,0,.741,5.035q1.52,5.022,3.047,10.024c1.1,3.619,2.2,7.244,3.638,10.739a53.417,53.417,0,0,1,2.6,6.834c.819,3.144.851,6.587,2.6,9.336a12.993,12.993,0,0,0,4.664,4.112c6.432,3.677,14.188,4.45,21.543,3.567s14.409-3.313,21.439-5.736a19.487,19.487,0,0,0,4.437-1.949c4.84-3.164,5.918-9.647,6.821-15.358a87.2,87.2,0,0,1,4.736-18.717c1.416-3.56,3.248-7.179,2.936-11.005C587.592,428.408,587.579,428.369,587.579,428.33Z" transform="translate(374.359 -42.418)"/>
<path id="Path_468" data-name="Path 468" d="M510.318,421.419v-.071a9.1,9.1,0,0,0-.3-3.411,17.272,17.272,0,0,1-.253-2.157,17.769,17.769,0,0,0,.253,4.106C510.123,420.393,510.24,420.906,510.318,421.419Z" transform="translate(373.864 -38.021)"/>
<path id="Path_469" data-name="Path 469" d="M512.452,397.929a18.02,18.02,0,0,1,1.7-2.384c-1.189,1.137-3.385.123-3.846-1.455a3.788,3.788,0,0,0,0,1.949,2.852,2.852,0,0,0,2.15,1.89Z" transform="translate(373.712 -30.422)"/>
<path id="Path_470" data-name="Path 470" d="M762.946,656.57s2.059,7.471,0,13.968S762.946,656.57,762.946,656.57Z" transform="translate(285.475 -122.381)" opacity="0.1"/>
<path id="Path_471" data-name="Path 471" d="M776,668.3s.864,9.485-1.085,11.168S776,668.3,776,668.3Z" transform="translate(281.189 -126.49)" opacity="0.1"/>
<line id="Line_88" data-name="Line 88" y1="0.65" x2="66.011" transform="translate(733.267 442.068)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
<line id="Line_89" data-name="Line 89" y1="0.643" x2="66.011" transform="translate(733.267 450.176)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
<line id="Line_90" data-name="Line 90" y1="0.65" x2="66.011" transform="translate(733.267 458.277)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
</g>
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="691.365" height="586.346" viewBox="0 0 691.365 586.346">
<defs>
<linearGradient id="linear-gradient" x1="0.5" y1="1" x2="0.5" y2="-0.001" gradientUnits="objectBoundingBox">
<stop offset="0" stop-color="gray" stop-opacity="0.251"/>
<stop offset="0.54" stop-color="gray" stop-opacity="0.122"/>
<stop offset="1" stop-color="gray" stop-opacity="0.102"/>
</linearGradient>
</defs>
<g id="Group_644" data-name="Group 644" transform="translate(-588.635 -80)">
<g id="Group_642" data-name="Group 642" transform="translate(22538.635 6999)">
<path id="Path_472" data-name="Path 472" d="M482.764,637.675c51.612-29.22,67.336-29.693,124.954-17.169,81.646,17.758,88.43-49.93,108.306-186.933,33.946-233.98-108.306-295.737-108.306-295.737s-94.6-8.1-150.536-38.963C377.726,55.027,286.9,49.443,207.046,86.542c-51.069,23.7-93.125,56.714-100.31,102.034-20.09,126.746,12.9,196.58-48.819,237.954S48.944,581.5,96.263,577.564c34.968-2.906,80.842,52.415,130.687,60.111,17.382,2.684,88.7-13.043,99.578,0C341.327,655.4,464.763,647.87,482.764,637.675Z" transform="translate(-21979.895 -6980.982)" fill="#00ddf1" opacity="0.1"/>
<g id="Group_641" data-name="Group 641" transform="translate(-21545.08 -6816.88)">
<rect id="Rectangle_263" data-name="Rectangle 263" width="217.867" height="15.073" transform="translate(0 39.052)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_264" data-name="Rectangle 264" width="217.867" height="15.073" transform="translate(0 104.137)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_265" data-name="Rectangle 265" width="217.867" height="15.073" transform="translate(0 171.964)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_266" data-name="Rectangle 266" width="13.702" height="39.052" transform="translate(19.183)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_267" data-name="Rectangle 267" width="13.702" height="2.74" transform="translate(19.183 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_268" data-name="Rectangle 268" width="13.702" height="2.74" transform="translate(19.183 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_269" data-name="Rectangle 269" width="13.702" height="39.052" transform="translate(39.737)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_270" data-name="Rectangle 270" width="13.702" height="2.74" transform="translate(39.737 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_271" data-name="Rectangle 271" width="13.702" height="2.74" transform="translate(39.737 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_272" data-name="Rectangle 272" width="13.702" height="39.052" transform="translate(60.29)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_273" data-name="Rectangle 273" width="13.702" height="2.74" transform="translate(60.29 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_274" data-name="Rectangle 274" width="13.702" height="2.74" transform="translate(60.29 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_275" data-name="Rectangle 275" width="13.702" height="39.052" transform="translate(80.844)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_276" data-name="Rectangle 276" width="13.702" height="2.74" transform="translate(80.844 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_277" data-name="Rectangle 277" width="13.702" height="2.74" transform="translate(80.844 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_278" data-name="Rectangle 278" width="13.702" height="39.052" transform="translate(101.397)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_279" data-name="Rectangle 279" width="13.702" height="2.74" transform="translate(101.397 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_280" data-name="Rectangle 280" width="13.702" height="2.74" transform="translate(101.397 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_281" data-name="Rectangle 281" width="13.702" height="39.052" transform="translate(121.95)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_282" data-name="Rectangle 282" width="13.702" height="2.74" transform="translate(121.95 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_283" data-name="Rectangle 283" width="13.702" height="2.74" transform="translate(121.95 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_284" data-name="Rectangle 284" width="13.702" height="39.052" transform="translate(142.504)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_285" data-name="Rectangle 285" width="13.702" height="2.74" transform="translate(142.504 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_286" data-name="Rectangle 286" width="13.702" height="2.74" transform="translate(142.504 27.405)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_287" data-name="Rectangle 287" width="13.702" height="39.052" transform="matrix(0.894, -0.448, 0.448, 0.894, 135.847, 138.025)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_288" data-name="Rectangle 288" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 138.614, 143.541)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_289" data-name="Rectangle 289" width="13.702" height="2.74" transform="matrix(0.894, -0.448, 0.448, 0.894, 148.128, 162.529)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_290" data-name="Rectangle 290" width="13.702" height="39.052" transform="translate(19.183 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_291" data-name="Rectangle 291" width="13.702" height="2.74" transform="translate(19.183 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_292" data-name="Rectangle 292" width="13.702" height="2.74" transform="translate(19.183 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_293" data-name="Rectangle 293" width="13.702" height="39.052" transform="translate(39.737 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_294" data-name="Rectangle 294" width="13.702" height="2.74" transform="translate(39.737 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_295" data-name="Rectangle 295" width="13.702" height="2.74" transform="translate(39.737 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_296" data-name="Rectangle 296" width="13.702" height="39.052" transform="translate(60.29 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_297" data-name="Rectangle 297" width="13.702" height="2.74" transform="translate(60.29 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_298" data-name="Rectangle 298" width="13.702" height="2.74" transform="translate(60.29 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_299" data-name="Rectangle 299" width="13.702" height="39.052" transform="translate(145.93 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_300" data-name="Rectangle 300" width="13.702" height="2.74" transform="translate(145.93 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_301" data-name="Rectangle 301" width="13.702" height="2.74" transform="translate(145.93 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_302" data-name="Rectangle 302" width="13.702" height="39.052" transform="translate(166.483 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_303" data-name="Rectangle 303" width="13.702" height="2.74" transform="translate(166.483 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_304" data-name="Rectangle 304" width="13.702" height="2.74" transform="translate(166.483 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_305" data-name="Rectangle 305" width="13.702" height="39.052" transform="translate(187.036 65.771)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_306" data-name="Rectangle 306" width="13.702" height="2.74" transform="translate(187.036 71.937)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_307" data-name="Rectangle 307" width="13.702" height="2.74" transform="translate(187.036 93.176)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_308" data-name="Rectangle 308" width="13.702" height="39.052" transform="translate(19.183 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_309" data-name="Rectangle 309" width="13.702" height="2.74" transform="translate(19.183 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_310" data-name="Rectangle 310" width="13.702" height="2.74" transform="translate(19.183 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_311" data-name="Rectangle 311" width="13.702" height="39.052" transform="translate(39.737 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_312" data-name="Rectangle 312" width="13.702" height="2.74" transform="translate(39.737 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_313" data-name="Rectangle 313" width="13.702" height="2.74" transform="translate(39.737 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_314" data-name="Rectangle 314" width="13.702" height="39.052" transform="translate(60.29 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_315" data-name="Rectangle 315" width="13.702" height="2.74" transform="translate(60.29 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_316" data-name="Rectangle 316" width="13.702" height="2.74" transform="translate(60.29 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_317" data-name="Rectangle 317" width="13.702" height="39.052" transform="translate(80.844 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_318" data-name="Rectangle 318" width="13.702" height="2.74" transform="translate(80.844 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_319" data-name="Rectangle 319" width="13.702" height="2.74" transform="translate(80.844 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_320" data-name="Rectangle 320" width="13.702" height="39.052" transform="translate(101.397 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_321" data-name="Rectangle 321" width="13.702" height="2.74" transform="translate(101.397 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_322" data-name="Rectangle 322" width="13.702" height="2.74" transform="translate(101.397 160.317)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_323" data-name="Rectangle 323" width="13.702" height="39.052" transform="translate(121.95 132.912)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_324" data-name="Rectangle 324" width="13.702" height="2.74" transform="translate(121.95 139.078)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_325" data-name="Rectangle 325" width="13.702" height="2.74" transform="translate(121.95 160.317)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_473" data-name="Path 473" d="M797.862,159.09v35.571h-13.7V156.48Q791.039,157.693,797.862,159.09Z" transform="translate(-621.103 -155.61)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_326" data-name="Rectangle 326" width="13.702" height="2.74" transform="translate(163.057 6.166)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_327" data-name="Rectangle 327" width="13.702" height="2.74" transform="translate(163.057 27.405)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_474" data-name="Path 474" d="M827.862,165.719v30.83h-13.7V162.43c1.781.391,3.556.8,5.33,1.22Q823.7,164.636,827.862,165.719Z" transform="translate(-630.549 -157.483)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_475" data-name="Path 475" d="M827.862,166.285v.685h-13.7v-2.74h5.33Q823.7,165.2,827.862,166.285Z" transform="translate(-630.549 -158.05)" fill="#00ddf1" opacity="0.1"/>
<rect id="Rectangle_328" data-name="Rectangle 328" width="13.702" height="2.74" transform="translate(183.611 27.405)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_476" data-name="Path 476" d="M691.358,287.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.179c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-589.693 -196.649)" fill="#00ddf1"/>
<path id="Path_477" data-name="Path 477" d="M686.2,296.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.277,10.277,0,0,1,686.2,296.239Z" transform="translate(-589.706 -196.595)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_478" data-name="Path 478" d="M691.42,290.06s1.542,1.213,5.282,0" transform="translate(-591.9 -197.672)" fill="#00ddf1" opacity="0.2"/>
<path id="Path_479" data-name="Path 479" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1"/>
<path id="Path_480" data-name="Path 480" d="M690.93,277.21s2.638,7.153,2.418,9.023l2.09-.219s-3.521-6.269-2.638-8.8Z" transform="translate(-591.746 -193.626)" fill="#00ddf1" opacity="0.2"/>
<path id="Path_481" data-name="Path 481" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1"/>
<path id="Path_482" data-name="Path 482" d="M702.92,277.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S701.536,284.391,702.92,277.6Z" transform="translate(-592.952 -193.414)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_483" data-name="Path 483" d="M694.75,283.958s1.089-5.207,8.16-5.878" transform="translate(-592.949 -193.9)" fill="#00ddf1"/>
<path id="Path_484" data-name="Path 484" d="M819.358,386.276s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.4,9.4,0,0,0,9.146,5.18c4.35-.233,5.8-3.549,6.166-6.639a6.851,6.851,0,0,0-9.057-7.3Z" transform="translate(-629.999 -227.823)" fill="#00ddf1"/>
<path id="Path_485" data-name="Path 485" d="M814.2,395.239a6.68,6.68,0,0,1-.089-6.426c2-3.659,6.166-2.254,6.166-2.254l.212-.075a6.762,6.762,0,0,1,4.885.11,6.762,6.762,0,0,0-5.762-.548l-.212.075s-4.152-1.411-6.166,2.254a6.68,6.68,0,0,0,.089,6.426,9.982,9.982,0,0,0,4.7,4.446A10.276,10.276,0,0,1,814.2,395.239Z" transform="translate(-630.011 -227.769)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_486" data-name="Path 486" d="M819.42,389.06s1.542,1.213,5.282,0" transform="translate(-632.206 -228.846)" fill="#00ddf1" opacity="0.2"/>
<path id="Path_487" data-name="Path 487" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1"/>
<path id="Path_488" data-name="Path 488" d="M818.93,376.21s2.638,7.153,2.419,9.023l2.09-.219s-3.522-6.269-2.638-8.8Z" transform="translate(-632.051 -224.799)" fill="#00ddf1" opacity="0.2"/>
<path id="Path_489" data-name="Path 489" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1"/>
<path id="Path_490" data-name="Path 490" d="M830.92,376.6s-4.159-2.124-6.015-.37-2.144,6.241-2.144,6.241S829.536,383.391,830.92,376.6Z" transform="translate(-633.257 -224.588)" fill="#00ddf1" opacity="0.1"/>
<path id="Path_491" data-name="Path 491" d="M822.75,382.958s1.089-5.207,8.16-5.878" transform="translate(-633.254 -225.073)" fill="#00ddf1"/>
</g>
</g>
<g id="Group_643" data-name="Group 643" transform="translate(7 -63)">
<g id="Group_639" data-name="Group 639" transform="translate(21233 8372)">
<ellipse id="Ellipse_73" data-name="Ellipse 73" cx="49.851" cy="8.007" rx="49.851" ry="8.007" transform="translate(-20118.443 -7772.555)" fill="#00abf6" opacity="0.1"/>
<rect id="Rectangle_182" data-name="Rectangle 182" width="3.208" height="72.224" transform="translate(-20070.199 -7917.934)" fill="#535461"/>
<path id="Path_268" data-name="Path 268" d="M1025.906,587.251l-.2,3.254-.287,4.6-.118,1.913-.281,4.6-.123,1.913-.286,4.6-3.233,52.22a8.851,8.851,0,0,1-8.83,8.268H995.731a8.841,8.841,0,0,1-8.82-8.3l-3.244-52.225-.281-4.6-.117-1.913-.292-4.6-.117-1.913-.281-4.6-.2-3.254a4.6,4.6,0,0,1,4.574-4.865h34.391a4.6,4.6,0,0,1,4.569,4.9Z" transform="translate(-21072.734 -8433.181)" fill="#565661"/>
<path id="Path_269" data-name="Path 269" d="M990.949,517.36l-.287,4.589H948.106l-.286-4.589Z" transform="translate(-21037.977 -8360.036)" fill="#9d9cb5"/>
<path id="Path_270" data-name="Path 270" d="M990.93,530.08l-.281,4.594H948.9l-.286-4.594Z" transform="translate(-21038.363 -8366.248)" fill="#9d9cb5"/>
<path id="Path_271" data-name="Path 271" d="M990.912,542.8l-.287,4.589H949.686L949.4,542.8Z" transform="translate(-21038.748 -8372.46)" fill="#9d9cb5"/>
<path id="Path_272" data-name="Path 272" d="M933.875,490.464c18.684,13.517,26.466,34.656,26.466,34.656s-22.511-.773-41.2-14.289-26.46-34.651-26.46-34.651S915.2,476.953,933.875,490.464Z" transform="translate(-21028.934 -8381.328)" fill="#00abf6"/>
<path id="Path_273" data-name="Path 273" d="M892.68,476.18s23.365,12.969,30.865,23.708,36.8,25.233,36.8,25.233" transform="translate(-21028.934 -8381.328)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
<path id="Path_274" data-name="Path 274" d="M975.393,443.457c10.171,7.352,14.407,18.863,14.407,18.863s-12.279-.425-22.424-7.776S953,435.68,953,435.68,965.227,436.1,975.393,443.457Z" transform="translate(-21058.393 -8361.549)" fill="#00abf6"/>
<path id="Path_275" data-name="Path 275" d="M953,435.68s12.719,7.06,16.8,12.9,20.03,13.737,20.03,13.737" transform="translate(-21058.393 -8361.549)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
<path id="Path_276" data-name="Path 276" d="M1040.3,470.483c-11.68,12.836-14.151,29.612-14.151,29.612s16.469-4.042,28.139-16.883,14.151-29.612,14.151-29.612S1051.981,457.647,1040.3,470.483Z" transform="translate(-21094.119 -8370.301)" fill="#00abf6"/>
<path id="Path_277" data-name="Path 277" d="M1068.45,453.61s-15.215,13.159-19.083,22.224S1026.15,500.1,1026.15,500.1" transform="translate(-21094.119 -8370.306)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/>
</g>
<g id="Group_638" data-name="Group 638" transform="translate(-40 36)">
<rect id="Rectangle_254" data-name="Rectangle 254" width="12.343" height="37.459" transform="translate(741.459 392.675)" fill="#3f3d56"/>
<rect id="Rectangle_255" data-name="Rectangle 255" width="12.343" height="39.629" transform="translate(1073.647 390.726)" fill="#3f3d56"/>
<path id="Path_418" data-name="Path 418" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" fill="#65617d"/>
<path id="Path_419" data-name="Path 419" d="M597.889,523.665v8.446l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643L264.408,535.2h-.1l-29.007.273-.312.007L106.1,536.658v-9.745l30.306-18.19,60.808-.507,1.3-.007.429-.007,1.3-.019,29.2-.24,1.306-.007.416-.007,1.3-.006,9.362-.078.247-.006,23.1-.195h.188l180.214-1.52,4.411-.032h.442l3.365-.033,24.459-.2,1.3-.02,1.2-.013h.624l7.607-.058,1.507-.02,2.02-.013,6.334-.058,10.05-.084,1.3-.013,2.261-.013h.422l48.536-.416Z" transform="translate(566.82 43)" opacity="0.2"/>
<path id="Path_420" data-name="Path 420" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" fill="#3f3d56"/>
<path id="Path_421" data-name="Path 421" d="M358.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(437.441 -122.381)" opacity="0.1"/>
<path id="Path_422" data-name="Path 422" d="M360.914,656.57v15.91a3.982,3.982,0,0,1-2.274,3.6,3.9,3.9,0,0,1-1.624.39l-22.673.481a3.9,3.9,0,0,1-1.754-.37,3.976,3.976,0,0,1-2.319-3.619V656.57Z" transform="translate(436.74 -122.381)" fill="#3f3d56"/>
<rect id="Rectangle_256" data-name="Rectangle 256" width="30.644" height="20.464" transform="translate(1046.362 534.189)" fill="#3f3d56"/>
<rect id="Rectangle_257" data-name="Rectangle 257" width="30.644" height="20.464" transform="translate(1046.362 534.189)" opacity="0.1"/>
<rect id="Rectangle_258" data-name="Rectangle 258" width="30.644" height="20.464" transform="translate(1045.062 534.189)" fill="#3f3d56"/>
<path id="Path_423" data-name="Path 423" d="M223.943,265.82v3.723l-12.343-.318v-3.4Z" transform="translate(529.859 126.848)" opacity="0.1"/>
<path id="Path_424" data-name="Path 424" d="M358.741,271.246,343.909,377.789a2.794,2.794,0,0,1-2.839,2.41L222.314,377.12l-2.254-.058a2.794,2.794,0,0,1-2.7-3.164l16.651-111.533a2.794,2.794,0,0,1,2.949-2.358l119.186,8.069a2.787,2.787,0,0,1,2.6,3.17Z" transform="translate(476.307 16.556)" fill="#65617d"/>
<path id="Path_425" data-name="Path 425" d="M169.158,99.16,156.6,197.472l109.142,3.463,13.422-94.85Z" transform="translate(549.128 185.237)" fill="#00ddf1"/>
<rect id="Rectangle_259" data-name="Rectangle 259" width="132.965" height="112.605" rx="5.33" transform="translate(853.849 282.877)" fill="#65617d"/>
<rect id="Rectangle_260" data-name="Rectangle 260" width="120.836" height="93.765" transform="translate(860.125 290.458)" fill="#00ddf1"/>
<path id="Path_426" data-name="Path 426" d="M735.273,262.82v2.462l-12.343.253V262.82Z" transform="translate(350.717 127.899)" opacity="0.1"/>
<path id="Path_427" data-name="Path 427" d="M833.626,371a2,2,0,0,1-1.949,2.131l-121.615,2.514a2,2,0,0,1-2.033-1.813L697.575,265.156a2,2,0,0,1,1.9-2.189l120.693-6,1.3-.065a1.994,1.994,0,0,1,2.092,1.819Z" transform="translate(308.06 17.643)" fill="#65617d"/>
<path id="Path_428" data-name="Path 428" d="M628.93,100.238,739.157,94.82l9.31,98.748-109.356,2.6Z" transform="translate(383.65 186.757)" fill="#00ddf1"/>
<path id="Path_429" data-name="Path 429" d="M899.741,371a2,2,0,0,1-1.949,2.131L886.28,256.963l1.3-.065a2,2,0,0,1,2.092,1.819Z" transform="translate(241.945 17.643)" opacity="0.1"/>
<rect id="Rectangle_261" data-name="Rectangle 261" width="12.343" height="11.096" transform="translate(741.459 419.038)" opacity="0.1"/>
<path id="Path_431" data-name="Path 431" d="M281.574,483.65v3.229l-30.644.819V483.65Z" transform="translate(516.08 50.533)" opacity="0.1"/>
<path id="Path_432" data-name="Path 432" d="M390.8,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h-.033a2.6,2.6,0,0,1-2.566-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H388.23A2.6,2.6,0,0,1,390.8,483.886Z" transform="translate(460.942 -60.983)" fill="#65617d"/>
<path id="Path_434" data-name="Path 434" d="M194.6,319.15H309.914v96.311L194.6,418.06Z" transform="translate(535.815 108.164)" fill="#d4dfec"/>
<rect id="Rectangle_262" data-name="Rectangle 262" width="12.343" height="11.317" transform="translate(1073.647 419.038)" opacity="0.1"/>
<path id="Path_435" data-name="Path 435" d="M709.574,483.65v4.164L678.93,487V483.65Z" transform="translate(366.132 50.533)" opacity="0.1"/>
<path id="Path_436" data-name="Path 436" d="M797.782,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0l-124.532-3.313a2.6,2.6,0,0,1-2.5-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H795.19a2.6,2.6,0,0,1,2.592,2.566Z" transform="translate(318.365 -60.983)" fill="#65617d"/>
<path id="Path_437" data-name="Path 437" d="M866.289,483.886V596.471a2.6,2.6,0,0,1-2.6,2.566h0V481.32h0a2.6,2.6,0,0,1,2.6,2.566Z" transform="translate(249.859 -60.983)" opacity="0.1"/>
<path id="Path_438" data-name="Path 438" d="M713.414,319.15H598.1v96.311l115.314,2.6Z" transform="translate(394.451 108.164)" fill="#d4dfec"/>
<path id="Path_439" data-name="Path 439" d="M593.3,483.886V593.158a2.6,2.6,0,0,1-2.5,2.566l-124.506,3.313h0a2.6,2.6,0,0,1-2.6-2.566V483.886a2.6,2.6,0,0,1,2.566-2.566H590.73A2.6,2.6,0,0,1,593.3,483.886Z" transform="translate(389.997 -60.983)" fill="#65617d"/>
<path id="Path_440" data-name="Path 440" d="M397.1,319.15H512.414v96.311L397.1,418.06Z" transform="translate(464.87 108.164)" fill="#d4dfec"/>
<path id="Path_441" data-name="Path 441" d="M597.889,533.65V542.1l-98.7.909-1.052.02-37.511.344-.9.013-11.4.1h-.338l-69.5.643-114.086,1.059h-.1l-29.007.273-.312.007L106.1,546.643V536.9l131.562-.864h.312l26.629-.182h.123l183.9-1.215h.188l.11-.006h.305l3.112-.019h.948l48.393-.325,2.858-.013.637-.007Z" transform="translate(566.82 33.015)" opacity="0.1"/>
<circle id="Ellipse_99" data-name="Ellipse 99" cx="33.347" cy="33.347" r="33.347" transform="translate(887.196 363.44)" fill="#fbbebe"/>
<path id="Path_442" data-name="Path 442" d="M383.74,592.973a.806.806,0,0,1,.065-.227c.65-2.332,1.806-6.276,2.67-9.427.11-.39.214-.773.312-1.137.955-3.554,2.813-11.876,3.846-17.066.364-1.826.65-3.248.708-3.963.13-1.1,2.085-5.795,4.3-11.194,1.553-3.794,3.248-7.939,4.489-11.434a39.876,39.876,0,0,0,1.9-6.4,3.313,3.313,0,0,0,0-1.384,9.543,9.543,0,0,1-.312-2.274h0c-.149-4.028,1.052-10.394,2.91-13.065,2.274-3.248,3.9-15.371,3.9-15.371s3.365.487,3.9-2.293a4,4,0,0,0,.065-.91,4.815,4.815,0,0,0-.058-.721c-.578-4.054,9.563-8.368,15.267-9.3a12.769,12.769,0,0,1,1.949-.182,17.694,17.694,0,0,0,4.06-.935q.909-.279,1.949-.65c2.391-.806,5.249-1.871,8.283-3.04,11.408-4.424,25.22-10.394,25.22-10.394.1-.461.208-.9.331-1.338a18,18,0,0,1,10.622-12.3h0v-1.3c0-2.241-.1-4.548-.195-6.847,0-.448-.039-.9-.065-1.345v-.734c0-.455-.046-.91-.071-1.358l-.117-.091a10.66,10.66,0,0,1-2.534-2.729,9.916,9.916,0,0,1-.923-1.949c-.812-2.2-1-4.658-1.546-6.984l-.117-.481a54.579,54.579,0,0,0-2.6-6.873c-1.436-3.521-2.54-7.146-3.638-10.817q-1-3.333-2.02-6.678l-1.026-3.4c-.195-.65-.39-1.3-.539-1.949h0a8.627,8.627,0,0,1-.169-.91h0a6.5,6.5,0,0,1,.156-2.995,20.854,20.854,0,0,1,.858-1.949,5.113,5.113,0,0,0,.325-.9h0a1.947,1.947,0,0,0,.052-.208,3.667,3.667,0,0,0,.071-.7,11.993,11.993,0,0,0-.338-2.527.045.045,0,0,1,0-.032,1.661,1.661,0,0,0-.032-.175,16.564,16.564,0,0,1-.234-1.949,18.483,18.483,0,0,1,2.254-10.115l.123-.234a16.383,16.383,0,0,1,1.2-1.806h0a9.122,9.122,0,0,1,.65-.793,2.594,2.594,0,0,1-3.606-.936,2.475,2.475,0,0,1-.149-.292c-.033-.078-.059-.162-.085-.247-.455-1.592.338-3.287,1.377-4.58a12.012,12.012,0,0,1,3.729-3.053h0a2.51,2.51,0,0,1,.26-.136,11.125,11.125,0,0,1,1.715-.689h0a9.781,9.781,0,0,1,1.059-.286l-2.41-.221-.974-.091A9.414,9.414,0,0,1,479,361.527h.7a7.625,7.625,0,0,1,1.02.084A2.885,2.885,0,0,1,479,358.8a9.362,9.362,0,0,0,5.97,1.988h.247a19.494,19.494,0,0,1-.221-2.124h0a21.545,21.545,0,0,1,0-2.9,5.483,5.483,0,0,0,4.4,1.468l-1.351-1.884a6.165,6.165,0,0,0,4.262,1.332l.312-.033a4.547,4.547,0,0,0,.773-.149h.058c-.123-.156-.234-.318-.344-.487l-.039-.078a6.282,6.282,0,0,1-.65-1.358,4.284,4.284,0,0,1-.156-.565h0a5.684,5.684,0,0,1,.292-3.541c2.1,1.754,4.4,4.151,7.146,2.339s2.839-5.139,7.146-4.8a6.626,6.626,0,0,1,5.464,3.976,4.385,4.385,0,0,1,4.983-1.137,4.45,4.45,0,0,1,2.521,4.476c1.786-.448,3.71-.89,5.425-.214s2.82,2.865,1.845,4.327a.931.931,0,0,1-.091.13c-.8,1.033-2.566,1.6-2.378,2.9l.273.195.845.611q4.97,3.586,9.68,7.536c1.611,1.351,3.326,2.975,3.437,5.087a5.617,5.617,0,0,1-.156,1.54h0c-.1.507-.247,1.013-.357,1.52a12.676,12.676,0,0,0-.221,3.93h0c.045.383.1.76.175,1.143.994,5.022,5.009,9.167,5.405,14.292v.364a11.546,11.546,0,0,1-.045,1.644c-.3,3.118-1.741,6.113-2.91,9.1-.072.182-.143.364-.208.546a90.5,90.5,0,0,0-4.548,18.294,65.14,65.14,0,0,1-1.3,6.555,17.365,17.365,0,0,1-3.307,6.776,10.536,10.536,0,0,1-2.183,1.949,12.132,12.132,0,0,1-1.722.916v.039c.071.4.13.793.2,1.189.039.208.071.416.11.65h0a.089.089,0,0,0,0,.039c.546,3.073,1.169,6.081,1.826,8.939l.331,1.423c8.186,2.956,9.154,9.011,9.212,11.33a6.259,6.259,0,0,1,0,.929s12.343.325,17.216,3.9a13.21,13.21,0,0,0,2.93,1.494,53.917,53.917,0,0,0,6.009,1.877c1.046.273,2.137.539,3.248.8,1.949.448,4.015.884,6.009,1.3,5.321,1.072,10.33,1.949,12.87,2.6,6.061,1.533,19.925,14.825,19.925,14.825s1.624,13.409,3.248,15.364,4.548,8.829,4.548,8.829l10.745,19.106,4.548,8.036h2.923a4.97,4.97,0,0,1,2.085,1.364c.773.689,1.631,1.546,2.384,2.326l1.247,1.332.78.864s4.723.117,6,3.852c3.846-.11,8.569.143,11.876,1.624a9.187,9.187,0,0,1,1.3.7,5.639,5.639,0,0,1,2.683,3.677,5.422,5.422,0,0,1,.117.935c0,.37,0,.741,0,1.1-.234,4.918-3.437,9.011-6.38,11.765l.208.37s-.1.234-.286.65c-.052.123-.117.26-.188.416h0c-.383.819-.994,2.046-1.793,3.482a41.136,41.136,0,0,1-6.217,8.556l-.188.195-37.511.344-.11-.13-7.276-8.679-3.619-4.314-.11-.136-.1,1.033-.351,3.443-.845,8.322-.045.416v.11l-.1.961.071-.961h-.188l-69.513.65-114.092,1.26v.546h-.091v-.546l-29.072.312Z" transform="translate(418.007 -14.269)" fill="url(#linear-gradient)"/>
<path id="Path_443" data-name="Path 443" d="M586.185,511.522s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.691-.039-2.891-.143-5.886-.292-8.868-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522c-2.6,8.53-1.748,20.51.227,31.755.578,3.281,1.247,6.5,1.949,9.55a223.892,223.892,0,0,0,6.269,22.036Z" transform="translate(367.711 -46.521)" fill="#fbbebe"/>
<path id="Path_444" data-name="Path 444" d="M586.185,537.354s-64.641,7.8-58.469,0c2.235-2.82,2.852-11.174,2.729-20.692,9.855-4.21,22.394-1.072,22.394-1.072,13.2-2.631,21.666-2.118,27.078-.273A223.888,223.888,0,0,0,586.185,537.354Z" transform="translate(367.711 -72.353)" opacity="0.1"/>
<path id="Path_445" data-name="Path 445" d="M413.346,651.973v-.546l114.092-1.241,69.513-.65h.195l-.071.961.1-.961h.123v-.11l.89-8.738.338-3.307.1-1.033.091-.864,4.015-13.389,4.015-13.37,3.378-11.258,5.158-17.2s-2.923-6.821-4.548-8.77-3.248-15.267-3.248-15.267-13.864-13.208-19.925-14.728c-2.54-.65-7.555-1.514-12.883-2.6-1.994-.4-4.041-.832-6.009-1.3-1.111-.253-2.2-.52-3.248-.786a57.086,57.086,0,0,1-6.009-1.858,13.448,13.448,0,0,1-2.924-1.488c-4.775-3.5-16.742-3.9-17.2-3.9h0a.269.269,0,0,1,0-.045v-.058a6.66,6.66,0,0,0,0-.819,5.208,5.208,0,0,0-.059-.91.82.82,0,0,0-.045-.325c-.032-.234-.071-.481-.13-.741a3.226,3.226,0,0,0-.078-.344,11.181,11.181,0,0,0-.812-2.267c-.045-.1-.1-.214-.156-.318a11.861,11.861,0,0,0-1.1-1.709c-.084-.11-.175-.227-.273-.338a12.721,12.721,0,0,0-1.767-1.715c-.175-.136-.357-.279-.546-.409a3.026,3.026,0,0,0-.331-.234l-.578-.377-.539-.312c-.474-.26-.974-.513-1.52-.754-.169-.078-.351-.149-.533-.221s-.468-.182-.708-.273h-.039l-.487-.195c-.37-.123-.747-.234-1.143-.344l-.6-.156-.65-.149-.65-.136c-.65-.13-1.345-.247-2.072-.338l-.741-.091-.76-.078-.786-.065-1.117-.065-1.3-.045a81.017,81.017,0,0,0-17.054,1.814,46.946,46.946,0,0,0-11.855-1.093h-.565l-.65.039h-.416l-.838.078a6.224,6.224,0,0,0-.845.1c-.279.039-.572.071-.851.117l-.429.071c-.422.071-.851.149-1.3.24q-.214.039-.429.1c-.565.123-1.13.273-1.689.442-.279.078-.559.169-.838.266-.143.039-.279.091-.416.143l-.416.149-.409.162-.487.195c-.247.1-.487.214-.728.331-.136.058-.273.123-.4.195s-.266.13-.39.2a18.03,18.03,0,0,0-4.034,2.846l-.331.318-.325.331c-.11.11-.214.227-.318.344a15.473,15.473,0,0,0-1.182,1.462c-.1.136-.2.273-.3.416a17.335,17.335,0,0,0-1.117,1.9c-.084.156-.162.318-.24.474a21.243,21.243,0,0,0-1.3,3.411q-.182.65-.331,1.3s-13.753,6.068-25.161,10.46c-3.034,1.169-5.892,2.228-8.283,3.027-.7.234-1.345.448-1.949.65a17.347,17.347,0,0,1-4.06.935,12.778,12.778,0,0,0-1.949.182c-5.243.851-14.227,4.548-15.208,8.257a2.4,2.4,0,0,0-.065.91v.071c.65,4.548-3.9,3.9-3.9,3.9s-1.624,12.019-3.9,15.267c-1.644,2.339-2.768,7.555-2.91,11.492h0a12.741,12.741,0,0,0,.286,3.658v.091c.26.871-.364,3.248-1.423,6.3-1.15,3.346-2.826,7.5-4.437,11.427-2.43,5.925-4.716,11.3-4.859,12.486-.058.526-.227,1.468-.461,2.683-.948,4.95-3.053,14.377-4.093,18.19-.773,2.845-1.9,6.743-2.67,9.42a.91.91,0,0,1-.065.227l29.124-.286v.546h.091Z" transform="translate(417.856 -73.042)" fill="#00ddf1"/>
<path id="Path_446" data-name="Path 446" d="M445.44,607.57s37.03,19.165,50.348,16.891Z" transform="translate(396.391 -105.214)" opacity="0.1"/>
<path id="Path_447" data-name="Path 447" d="M561.44,595.57s36.056,11.694,48.074,13.318S561.44,595.57,561.44,595.57Z" transform="translate(355.751 -101.01)" opacity="0.1"/>
<path id="Path_448" data-name="Path 448" d="M585.443,530.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,512.821,585.339,526.866,585.443,530.686Z" transform="translate(371.74 -73.754)" opacity="0.1"/>
<path id="Path_449" data-name="Path 449" d="M585.443,528.686c-18.84,3.469-54.311,2.722-69.643,2.2,5.62-20.139,33.022-13.292,33.022-13.292C582.74,510.821,585.339,524.866,585.443,528.686Z" transform="translate(371.74 -73.053)" fill="#00ddf1"/>
<path id="Path_450" data-name="Path 450" d="M776.94,672.008s22.192-3.411,22.628,6.334S788.25,694.9,788.25,694.9Z" transform="translate(280.251 -127.587)" fill="#fbbebe"/>
<path id="Path_451" data-name="Path 451" d="M578.17,479.936a30.558,30.558,0,0,1-2.923,1.13c-7,2.417-14.065,4.853-21.439,5.736s-15.111.1-21.543-3.573a18.685,18.685,0,0,1-1.949-1.267c-.715-14.292-2.436-28.26-2.436-28.26s56.52-26.636,50.023-5.522C575.318,456.711,576.195,468.69,578.17,479.936Z" transform="translate(367.508 -46.521)" opacity="0.1"/>
<path id="Path_452" data-name="Path 452" d="M536.9,360.29a6.153,6.153,0,0,1-5.405-1.137l1.351,1.878a5.5,5.5,0,0,1-4.4-1.462,22.146,22.146,0,0,0,.208,5.035,9.433,9.433,0,0,1-6.211-1.949,2.872,2.872,0,0,0,1.715,2.8,9.316,9.316,0,0,0-10.135,6.958l3.391.312a12,12,0,0,0-6.782,4.138c-1.039,1.3-1.832,2.962-1.377,4.548s2.651,2.6,3.846,1.468a18.151,18.151,0,0,0-3.963,14.806,9.4,9.4,0,0,1,.3,3.417,28.993,28.993,0,0,1-1.228,3.047c-.65,1.9-.032,3.956.546,5.847l3.047,10.018c1.1,3.619,2.2,7.244,3.645,10.745a53.328,53.328,0,0,1,2.6,6.828c.819,3.144.851,6.588,2.6,9.336a12.948,12.948,0,0,0,4.665,4.112c6.425,3.677,14.188,4.457,21.542,3.573s14.409-3.32,21.406-5.736a20.141,20.141,0,0,0,4.444-1.949c4.84-3.17,5.918-9.654,6.821-15.364a87.242,87.242,0,0,1,4.736-18.71c1.416-3.567,3.248-7.185,2.936-11.005-.4-5.074-4.411-9.186-5.405-14.175a12.344,12.344,0,0,1,.045-5.048,11.473,11.473,0,0,0,.513-3.053c-.11-2.092-1.826-3.7-3.437-5.048q-5.243-4.379-10.823-8.277c-.188-1.3,1.579-1.858,2.378-2.884,1.13-1.449-.039-3.748-1.754-4.424s-3.638-.234-5.425.214a4.281,4.281,0,0,0-7.5-3.32,6.627,6.627,0,0,0-5.47-3.95c-4.307-.338-4.392,2.956-7.146,4.768s-5.028-.578-7.146-2.319A6.029,6.029,0,0,0,536.9,360.29Z" transform="translate(374.5 -15.625)" fill="#3f3d56"/>
<path id="Path_453" data-name="Path 453" d="M768.171,669.128s-.578,1.377-1.644,3.372a44.83,44.83,0,0,1-5.847,8.556l-.175.188h-1.052l-37.511.344h-.9l-.11-.13-7.276-8.634-2.741-3.255-.11-.136-.234-.273-.13-.156-.169-.2-5.334-6.328,1.949-8.121,3.32-13.682.084-.331.65-2.6.461-1.949,1.546-6.354,5.3-21.861,9.42,4.872,9.933,17.541,5.334,9.42h2.923c.552,0,1.819,1.065,3.092,2.293.455.435.91.9,1.3,1.3,1.156,1.2,2.072,2.222,2.072,2.222s4.736.1,6.009,3.833a4.537,4.537,0,0,1,.162.552,26.191,26.191,0,0,0,1.644,4.262c.838,1.826,1.891,3.9,2.956,5.97,1.981,3.8,4,7.419,4.736,8.731l.091.162Z" transform="translate(305.476 -105.217)" fill="#00ddf1"/>
<path id="Path_454" data-name="Path 454" d="M615.681,653.093l-.065.929v.065l-.11,1.494-.864,12.025L613.6,682.093l-.3,4.08-.65,8.731v.11l-69.513.65L429.061,696.9v.546h-.091v-.728l.292-9.329.559-18.229L430.3,653.7l.045-1.54a6.841,6.841,0,0,1,7.419-6.607c3.066.26,7.049.578,11.811.916l1.949.143c27.5,1.91,78.322,4.379,129.606,1.3,1.078-.059,2.163-.123,3.248-.2,7.952-.5,15.9-1.143,23.764-1.949a6.841,6.841,0,0,1,7.536,7.3Z" transform="translate(402.161 -118.514)" opacity="0.1"/>
<path id="Path_455" data-name="Path 455" d="M429.08,698.857v-.546l114.092-1.241,69.513-.65h.195v-.11l.65-8.731.279-3.9,1.065-14.689.838-11.629v-.286a6.893,6.893,0,0,0-.071-1.6.2.2,0,0,0,0-.065,6.751,6.751,0,0,0-1.3-3.014,6.847,6.847,0,0,0-6.12-2.6c-7.867.806-15.813,1.449-23.764,1.949-1.085.078-2.17.143-3.248.2-51.284,3.047-102.106.578-129.606-1.3l-1.949-.143c-4.762-.338-8.744-.65-11.811-.916a6.847,6.847,0,0,0-7.3,5.542,7.146,7.146,0,0,0-.117,1.065l-.442,14.4-.6,18.236-.286,9.329v.728Z" transform="translate(402.122 -119.926)" fill="#3f3d56"/>
<path id="Path_456" data-name="Path 456" d="M615.925,661.692l-.8,9.959L613.937,686.5l-.3,3.716-.65,8.322-.039.416v.11h0l-.1.961.071-.961h-.188l-69.513.65L429.11,700.944v-.182l.318-9.329.65-18.229.416-12.116a4.132,4.132,0,0,1,1.988-3.411,4.061,4.061,0,0,1,2.456-.585c1.364.11,3.04.24,5,.383,3.177.234,7.094.507,11.655.793,8.445.533,19.041,1.1,31.183,1.572,33.782,1.3,79.388,1.735,122.707-2.059,1.949-.169,3.963-.357,5.931-.546a4.066,4.066,0,0,1,2.261.429,3.346,3.346,0,0,1,.559.351,3.989,3.989,0,0,1,1.3,1.6,2.315,2.315,0,0,1,.162.4A4.08,4.08,0,0,1,615.925,661.692Z" transform="translate(402.112 -122.559)" opacity="0.1"/>
<path id="Path_457" data-name="Path 457" d="M360.914,677.87v2.072a3.982,3.982,0,0,1-2.274,3.6l-26.051.5a3.976,3.976,0,0,1-2.319-3.619v-1.949Z" transform="translate(436.74 -129.843)" opacity="0.1"/>
<path id="Path_458" data-name="Path 458" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" fill="#3f3d56"/>
<path id="Path_459" data-name="Path 459" d="M280.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(517.685 42.387)" opacity="0.1"/>
<path id="Path_460" data-name="Path 460" d="M282.294,512.532l-33.944.65V507.55l33.944-.65Z" transform="translate(516.984 42.387)" fill="#3f3d56"/>
<path id="Path_461" data-name="Path 461" d="M552.228,360.039A6.334,6.334,0,0,1,551,357.22a6.165,6.165,0,0,0,.273,2.988A5.847,5.847,0,0,0,552.228,360.039Z" transform="translate(359.434 -17.505)"/>
<path id="Path_462" data-name="Path 462" d="M540.1,366.9a22.83,22.83,0,0,1-.24-2.4c0,.8,0,1.6,0,2.41Z" transform="translate(363.311 -20.056)"/>
<path id="Path_463" data-name="Path 463" d="M621.706,398.627a11.912,11.912,0,0,1,.234-1.722,11.7,11.7,0,0,0,.52-3.047,4.008,4.008,0,0,0-.1-.689c-.11.6-.286,1.2-.416,1.787a12.433,12.433,0,0,0-.234,3.671Z" transform="translate(334.652 -30.1)"/>
<path id="Path_464" data-name="Path 464" d="M600.922,368.894l.708.513a12.8,12.8,0,0,0,1.67-1.442,2.384,2.384,0,0,0,.247-2.365,1.95,1.95,0,0,1-.247.416C602.5,367.042,600.734,367.607,600.922,368.894Z" transform="translate(341.923 -20.441)"/>
<path id="Path_465" data-name="Path 465" d="M518.455,380.29a8,8,0,0,0-.565,1.481,11.8,11.8,0,0,1,3.248-1.228Z" transform="translate(371.008 -25.587)"/>
<path id="Path_466" data-name="Path 466" d="M531.888,368.979a9.637,9.637,0,0,1-1.221-.8,2.758,2.758,0,0,0,.058.767,7.7,7.7,0,0,1,1.163.033Z" transform="translate(366.534 -21.345)"/>
<path id="Path_467" data-name="Path 467" d="M587.579,428.33c-.266,3.151-1.735,6.185-2.923,9.173a87.2,87.2,0,0,0-4.736,18.717c-.9,5.71-1.949,12.194-6.821,15.358a19.488,19.488,0,0,1-4.437,1.949c-7,2.423-14.065,4.853-21.439,5.736s-15.111.11-21.543-3.567a12.993,12.993,0,0,1-4.664-4.112c-1.735-2.748-1.767-6.191-2.6-9.336a53.424,53.424,0,0,0-2.6-6.834c-1.436-3.5-2.54-7.146-3.638-10.739q-1.527-5.009-3.047-10.024a18.716,18.716,0,0,1-.741-3.086,11.644,11.644,0,0,0,.741,5.035q1.52,5.022,3.047,10.024c1.1,3.619,2.2,7.244,3.638,10.739a53.417,53.417,0,0,1,2.6,6.834c.819,3.144.851,6.587,2.6,9.336a12.993,12.993,0,0,0,4.664,4.112c6.432,3.677,14.188,4.45,21.543,3.567s14.409-3.313,21.439-5.736a19.487,19.487,0,0,0,4.437-1.949c4.84-3.164,5.918-9.647,6.821-15.358a87.2,87.2,0,0,1,4.736-18.717c1.416-3.56,3.248-7.179,2.936-11.005C587.592,428.408,587.579,428.369,587.579,428.33Z" transform="translate(374.359 -42.418)"/>
<path id="Path_468" data-name="Path 468" d="M510.318,421.419v-.071a9.1,9.1,0,0,0-.3-3.411,17.272,17.272,0,0,1-.253-2.157,17.769,17.769,0,0,0,.253,4.106C510.123,420.393,510.24,420.906,510.318,421.419Z" transform="translate(373.864 -38.021)"/>
<path id="Path_469" data-name="Path 469" d="M512.452,397.929a18.02,18.02,0,0,1,1.7-2.384c-1.189,1.137-3.385.123-3.846-1.455a3.788,3.788,0,0,0,0,1.949,2.852,2.852,0,0,0,2.15,1.89Z" transform="translate(373.712 -30.422)"/>
<path id="Path_470" data-name="Path 470" d="M762.946,656.57s2.059,7.471,0,13.968S762.946,656.57,762.946,656.57Z" transform="translate(285.475 -122.381)" opacity="0.1"/>
<path id="Path_471" data-name="Path 471" d="M776,668.3s.864,9.485-1.085,11.168S776,668.3,776,668.3Z" transform="translate(281.189 -126.49)" opacity="0.1"/>
<line id="Line_88" data-name="Line 88" y1="0.65" x2="66.011" transform="translate(733.267 442.068)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
<line id="Line_89" data-name="Line 89" y1="0.643" x2="66.011" transform="translate(733.267 450.176)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
<line id="Line_90" data-name="Line 90" y1="0.65" x2="66.011" transform="translate(733.267 458.277)" fill="none" stroke="#00ddf1" stroke-miterlimit="10" stroke-width="2"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

+5 -3
View File
@@ -109,7 +109,8 @@ const AddSigner = (props) => {
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey]
label: parseData[props.displayKey],
email: parseData?.Email
});
}
if (props.closePopup) {
@@ -155,7 +156,8 @@ const AddSigner = (props) => {
if (props.details) {
props.details({
value: parseData[props.valueKey],
label: parseData[props.displayKey]
label: parseData[props.displayKey],
email: parseData?.Email
});
}
if (props.closePopup) {
@@ -211,7 +213,7 @@ const AddSigner = (props) => {
</div>
)}
<div className="w-full mx-auto p-2 text-base-content">
<p className="mb-3">{t("add-signer-note")}</p>
<p className="mb-3 text-xs text-gray-500">{t("add-signer-note")}</p>
{isUserExist && (
<div className="mb-3 flex items-center">
<input
-198
View File
@@ -1,198 +0,0 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
const AddTeam = (props) => {
const { t } = useTranslation();
const [formdata, setFormdata] = useState({
name: "",
team: ""
});
const [isLoader, setIsLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
useEffect(() => {
getTeamList();
}, []);
const getTeamList = async () => {
setIsLoader(true);
try {
const teams = await Parse.Cloud.run("getteams", { active: true });
const teamRes = JSON.parse(JSON.stringify(teams));
if (teamRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamRes));
const allUsersteam = _teamRes.find((x) => x.Name === "All Users");
if (allUsersteam) {
setFormdata({ team: allUsersteam.objectId });
}
setTeamList(_teamRes);
}
} catch (err) {
console.log("Err in fetch top level teamList", err);
} finally {
setIsLoader(false);
}
};
const handleDropdown = (e) => {
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
// Define a function to handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
// Extracting values except for the 'name' key
let updatedAncestors = [];
if (formdata.team) {
const Ancestors = teamList.find(
(x) => x.objectId === formdata.team
)?.Ancestors;
if (Ancestors && Ancestors.length > 0) {
updatedAncestors = Ancestors.map((x) => ({
__type: "Pointer",
className: "contracts_Teams",
objectId: x.objectId
}));
} else {
const AllUser = teamList.find((x) => x.objectId === "All Users");
updatedAncestors = [
AllUser,
{
__type: "Pointer",
className: "contracts_Teams",
objectId: formdata.team
}
];
}
}
try {
setIsLoader(true);
let data = { Name: formdata.name };
if (updatedAncestors.length > 0) {
const ParentId = updatedAncestors[updatedAncestors.length - 1];
data["ParentId"] = ParentId?.objectId;
data["Ancestors"] = updatedAncestors;
}
const newTeamRes = await Parse.Cloud.run("addteam", data);
// console.log("teamRes ", newTeamRes);
if (updatedAncestors.length > 0) {
const ParentId = teamList.find((x) => x.objectId === formdata.team);
props.handleTeamInfo({
objectId: newTeamRes.id,
Name: formdata.name,
ParentId: ParentId,
Ancestors: updatedAncestors,
IsActive: true
});
} else {
props.handleTeamInfo({
objectId: newTeamRes.id,
Name: formdata.name,
ParentId: "",
Ancestors: "",
IsActive: true
});
}
if (props.closePopup) {
props.closePopup();
}
setFormdata({ name: "", team: { name: "", objectId: "" } });
props.setIsAlert({ type: "success", msg: "Team created successfully." });
} catch (err) {
console.log("err in save team", err);
if (err.code === 137) {
props.setIsAlert({ type: "danger", msg: "Teams already exists." });
} else if (err.code === 102) {
props.setIsAlert({ type: "warning", msg: "Provide team name." });
} else {
props.setIsAlert({ type: "danger", msg: "Something went wrong." });
}
} finally {
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500);
setIsLoader(false);
}
};
// Define a function to handle the "add yourself" checkbox
const handleReset = () => {
setFormdata({
name: "",
team: { name: "", objectId: "" }
});
};
const handleChange = (e) => {
setFormdata((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title="Add Team" />
{isLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<div className="w-full mx-auto">
<form onSubmit={handleSubmit}>
{/* <h1 className="text-[20px] font-semibold mb-4">Add User</h1> */}
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("report-heading.Parent Team")}
</label>
<select
value={formdata.team}
onChange={(e) => handleDropdown(e)}
name="team"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
>
{teamList.length > 0 &&
teamList.map((x) => (
<option key={x.objectId} value={x.objectId}>
{x.Name}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("reset")}
</div>
</div>
</form>
</div>
</div>
);
};
export default AddTeam;
+118 -298
View File
@@ -2,8 +2,13 @@ import React, { useEffect, useState } from "react";
import Parse from "parse";
import Title from "./Title";
import Loader from "../primitives/Loader";
import { copytoData, fetchSubscriptionInfo } from "../constant/Utils";
import { emailRegex, isEnableSubscription } from "../constant/const";
import {
copytoData,
usertimezone
} from "../constant/Utils";
import {
emailRegex,
} from "../constant/const";
import { useTranslation } from "react-i18next";
function generatePassword(length) {
const characters =
@@ -27,74 +32,23 @@ const AddUser = (props) => {
password: "",
role: ""
});
const [amount, setAmount] = useState({ quantity: 1, price: 0 });
const [planInfo, setPlanInfo] = useState({
priceperUser: 0,
price: 0,
totalAllowedUser: 0,
adminId: ""
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [teamList, setTeamList] = useState([]);
const [allowedUser, setAllowedUser] = useState(0);
const [err, setErr] = useState("");
const role = ["OrgAdmin", "Editor", "User"];
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
useEffect(() => {
getTeamList();
// eslint-disable-next-line
}, []);
const getTeamList = async () => {
if (isEnableSubscription) {
try {
setIsLoader(true);
const resSub = await fetchSubscriptionInfo();
if (!resSub.error) {
setPlanInfo((prev) => ({
...prev,
priceperUser: resSub.price,
totalAllowedUser: resSub.totalAllowedUser,
adminId: resSub.adminId
}));
setAmount((prev) => ({ ...prev, price: resSub.price }));
const res = await Parse.Cloud.run("allowedusers");
if (props.setFormHeader) {
if (res > 0) {
props.setFormHeader(t("add-user"));
} else {
props.setFormHeader(
resSub.adminId !== extUser?.objectId
? "Unauthorized"
: t("Add-seats")
);
}
}
setAllowedUser(res);
} else {
setAllowedUser(0);
}
} catch (err) {
setAllowedUser(0);
console.log("Err in alloweduser", err);
setErr(err.message);
} finally {
setIsLoader(false);
}
}
setFormdata((prev) => ({ ...prev, password: generatePassword(12) }));
const teamRes = await Parse.Cloud.run("getteams", { active: true });
if (teamRes.length > 0) {
const _teamRes = JSON.parse(JSON.stringify(teamRes));
setTeamList(_teamRes);
if (!isEnableSubscription) {
const allUserId =
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
setFormdata((prev) => ({ ...prev, team: allUserId }));
}
}
};
const checkUserExist = async () => {
@@ -161,7 +115,10 @@ const AddUser = (props) => {
objectId: localStorage.getItem("TenantId")
});
}
const timezone = usertimezone;
if (timezone) {
extUser.set("Timezone", timezone);
}
try {
const _users = Parse.Object.extend("User");
const _user = new _users();
@@ -305,37 +262,6 @@ const AddUser = (props) => {
props.setIsAlert({ type: "success", msg: t("copied") });
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
};
const handlePricePerUser = (e) => {
const quantity = e.target.value;
const price = e.target?.value > 0 ? planInfo.priceperUser * quantity : 0;
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleAddOnSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsFormLoader(true);
try {
const resAddon = await Parse.Cloud.run("buyaddonusers", {
users: amount.quantity
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setAllowedUser(amount.quantity);
setPlanInfo((obj) => ({ ...obj, totalAllowedUser: _resAddon.addon }));
}
if (props.handleBuyUsers) {
props.handleBuyUsers(amount.quantity, _resAddon.addon);
}
}
} catch (err) {
console.log("Err in buy addon", err);
props.setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 2000);
setIsFormLoader(false);
}
};
return (
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
<Title title={t("add-user")} />
@@ -344,232 +270,126 @@ const AddUser = (props) => {
<Loader />
</div>
)}
{!isLoader ? (
<>
{err ? (
<div className="flex justify-center items-center h-[70px] text-[18px]">
{err}
</div>
) : (
<div className="w-full mx-auto">
{!isEnableSubscription || allowedUser > 0 ? (
<form onSubmit={handleSubmit}>
{isEnableSubscription && (
<p className="flex justify-center mb-1">
{t("remaing-users")}{" "}
<span
className={`${
allowedUser < 2 ? "op-text-accent" : "op-text-primary"
} font-medium ml-1`}
>
{allowedUser} {t("of")} {planInfo.totalAllowedUser}
</span>
</p>
)}
<div className="mb-3">
<label
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
required
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label className="block text-xs text-gray-700 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]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
{/* added hint for phone number being optional */}
<input
type="text"
name="phone"
placeholder={t("phone-optional")}
value={formdata.phone}
onChange={(e) => handleChange(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
{isEnableSubscription && (
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("team")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
name="team"
value={formdata.team}
onChange={(e) => handleChange(e)}
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{teamList.length > 0 &&
teamList.map((x) => (
<option key={x.objectId} value={x.objectId}>
{x.Name}
</option>
))}
</select>
</div>
)}
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("cancel")}
</div>
</div>
</form>
) : (
<>
{planInfo.adminId !== extUser?.objectId ? (
<div className="mb-3 mt-1 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", {
adminEmail: extUser?.TenantId?.EmailAddress
})}
</div>
) : (
<form onSubmit={handleAddOnSubmit}>
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
{t("additional-users")}
</p>
<div className="mb-3 flex justify-between">
<div className="w-full mx-auto">
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label
htmlFor="quantity"
htmlFor="name"
className="block text-xs text-gray-700 font-semibold"
>
{t("Quantity-of-users")}
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="number"
name="quantity"
value={amount.quantity}
onChange={(e) => handlePricePerUser(e)}
className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
type="text"
name="name"
value={formdata.name}
onChange={(e) => handleChange(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {planInfo.priceperUser})
<div className="mb-3">
<label
htmlFor="email"
className="block text-xs text-gray-700 font-semibold"
>
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
<input
type="email"
name="email"
value={formdata.email}
onChange={(e) => handleChange(e)}
required
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label className="block text-xs text-gray-700 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]">
<div className="break-all">{formdata?.password}</div>
<i
onClick={() => copytoclipboard(formdata?.password)}
className="fa-light fa-copy rounded-full hover:bg-base-300 p-[8px] cursor-pointer "
></i>
</div>
<div className="text-[12px] ml-2 mb-0 text-[red] select-none">
{t("password-generateed")}
</div>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("phone")}
{/* <span className="text-[red] text-[13px]"> *</span> */}
</label>
<input
type="text"
name="phone"
placeholder={t("phone-optional")}
value={formdata.phone}
onChange={(e) => handleChange(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
/>
</div>
<div className="mb-3">
<label
htmlFor="phone"
className="block text-xs text-gray-700 font-semibold"
>
{t("Role")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={formdata.role}
onChange={(e) => handleChange(e)}
name="role"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full text-xs"
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
>
<option defaultValue={""} value={""}>
{t("Select")}
</option>
{role.length > 0 &&
role.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="flex items-center mt-3 gap-2 text-white">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
<div
type="button"
onClick={() => handleReset()}
className="op-btn op-btn-secondary"
>
{t("cancel")}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full">
{t("Proceed")}
</button>
</form>
)}
</>
)}
</div>
)}
</>
) : (
<div className="w-full h-[200px] inset-0 flex justify-center items-center z-50">
<Loader />
</div>
)}
</div>
</div>
);
};
+8 -265
View File
@@ -3,38 +3,18 @@ import axios from "axios";
import SuggestionInput from "./shared/fields/SuggestionInput";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import { emailRegex, isEnableSubscription } from "../constant/const";
import { fetchSubscription } from "../constant/Utils";
import { useNavigate } from "react-router-dom";
import {
emailRegex,
} from "../constant/const";
const BulkSendUi = (props) => {
const { t } = useTranslation();
const navigate = useNavigate();
const [forms, setForms] = useState([]);
const [formId, setFormId] = useState(2);
const formRef = useRef(null);
const [scrollOnNextUpdate, setScrollOnNextUpdate] = useState(false);
const [isSubmit, setIsSubmit] = useState(false);
const [allowedForm, setAllowedForm] = useState(0);
const [isSignatureExist, setIsSignatureExist] = useState();
const [isBulkAvailable, setIsBulkAvailable] = useState(false);
const quantityList = [500, 1000, 5000, 50000];
const [amount, setAmount] = useState({
price: (75.0).toFixed(2),
quantity: 500,
priceperbulksend: 0.15,
totalcredits: 0
});
const [isQuotaReached, setIsQuotaReached] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [isFreePlan, setIsFreePlan] = useState(false);
const [admin, setAdmin] = useState({
objectId: "",
email: "",
isAdmin: true
});
const allowedSigners = 50;
useEffect(() => {
signatureExist();
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -42,75 +22,7 @@ const BulkSendUi = (props) => {
//function to check atleast one signature field exist
const signatureExist = async () => {
if (isEnableSubscription) {
setIsLoader(true);
try {
const subscription = await fetchSubscription();
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (
subscription?.adminId &&
extUser?.objectId === subscription?.adminId
) {
setAdmin((obj) => ({
...obj,
objectId: subscription?.adminId,
isAdmin: true
}));
} else {
setAdmin((obj) => ({
...obj,
isAdmin: false,
email: extUser?.TenantId?.EmailAddress
}));
}
setAdmin((obj) => ({ ...obj, objectId: subscription?.adminId }));
if (subscription?.plan === "freeplan") {
setIsFreePlan(true);
}
const token = props.jwttoken
? { jwttoken: props.jwttoken }
: { "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const axiosres = await axios.post(
`${localStorage.getItem("baseUrl")}functions/allowedcredits`,
{},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
...token
}
}
);
const resCredits = axiosres.data && axiosres.data.result;
if (resCredits) {
const allowedcredits = resCredits?.allowedcredits || 0;
const addoncredits = resCredits?.addoncredits || 0;
const totalcredits = allowedcredits + addoncredits;
if (totalcredits > 0) {
if (subscription?.plan !== "freeplan") {
setIsBulkAvailable(true);
}
}
setAmount((obj) => ({ ...obj, totalcredits: totalcredits }));
}
const getPlaceholder = props?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
holder?.pos?.some((posItem) => posItem?.type === "signature")
)
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
} catch (err) {
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
console.log("Err", err);
}
} else {
setIsBulkAvailable(true);
setAdmin((obj) => ({ ...obj, isAdmin: true }));
const getPlaceholder = props?.Placeholders;
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
placeholderObj?.placeHolder?.some((holder) =>
@@ -119,7 +31,6 @@ const BulkSendUi = (props) => {
);
setIsSignatureExist(checkIsSignatureExistt);
setIsLoader(false);
}
};
useEffect(() => {
if (scrollOnNextUpdate && formRef.current) {
@@ -150,8 +61,6 @@ const BulkSendUi = (props) => {
}
});
setForms((prevForms) => [...prevForms, { Id: 1, fields: users }]);
const totalForms = Math.floor(allowedSigners / users?.length);
setAllowedForm(totalForms);
}
})();
// eslint-disable-next-line
@@ -165,38 +74,6 @@ const BulkSendUi = (props) => {
setForms(newForms);
};
const handleAddForm = (e) => {
e.preventDefault();
// Check if the quick send limit has been reached
if (isEnableSubscription && forms.length >= amount.totalcredits) {
setIsQuotaReached(true);
} else {
if (forms?.length < allowedForm) {
if (props?.Placeholders.length > 0) {
let newForm = [];
props?.Placeholders?.forEach((element) => {
if (!element.signerObjId) {
newForm = [
...newForm,
{
fieldId: element.Id,
email: "",
label: element.Role,
signer: {}
}
];
}
});
setForms([...forms, { Id: formId, fields: newForm }]);
}
setFormId(formId + 1);
setScrollOnNextUpdate(true);
} else {
// If the limit has been reached, throw an error with the appropriate message
alert(t("quick-send-alert-4"));
}
}
};
const handleRemoveForm = (index) => {
const updatedForms = forms.filter((_, i) => i !== index);
@@ -285,16 +162,15 @@ const BulkSendUi = (props) => {
};
const batchQuery = async (Documents) => {
const token = props.jwttoken
? { jwttoken: props.jwttoken }
: { "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const token =
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const functionsUrl = `${localStorage.getItem(
"baseUrl"
)}functions/batchdocuments`;
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
...token
...token,
};
const params = { Documents: JSON.stringify(Documents) };
try {
@@ -310,51 +186,6 @@ const BulkSendUi = (props) => {
setIsSubmit(false);
}
};
const handleAddOnQuickSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsSubmit(true);
try {
const resAddon = await Parse.Cloud.run("buycredits", {
credits: amount.quantity
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setIsBulkAvailable(true);
handleCloseQuotaReached();
setAmount((obj) => ({
...obj,
quantity: 500,
priceperbulksend: 0.15,
price: (75.0).toFixed(2),
totalcredits: obj?.totalcredits + _resAddon.addon
}));
}
}
} catch (err) {
console.log("Err in buy addon", err);
alert(t("something-went-wrong-mssg"));
} finally {
setIsSubmit(false);
}
};
const handlePricePerQuick = async (e) => {
const quantity = e.target?.value;
const price =
quantity > 0
? (Math.round(quantity * amount.priceperbulksend * 100) / 100).toFixed(
2
)
: 500 * amount.priceperbulksend;
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleCloseQuotaReached = () => {
setIsQuotaReached(false);
};
const handleNavigation = () => {
navigate("/subscription");
};
return (
<>
{isLoader ? (
@@ -374,7 +205,7 @@ const BulkSendUi = (props) => {
isSignatureExist ? (
<>
{props.Placeholders?.some((x) => !x.signerObjId) ? (
<div>
<>
<form onSubmit={handleSubmit}>
<div className="min-h-max max-h-[250px] overflow-y-auto">
{forms?.map((form, index) => (
@@ -400,7 +231,6 @@ const BulkSendUi = (props) => {
fieldIndex
)
}
jwttoken={props?.jwttoken}
/>
</div>
))}
@@ -417,15 +247,6 @@ const BulkSendUi = (props) => {
))}
</div>
<div className="flex flex-col mx-4 mb-4 gap-3">
{isEnableSubscription && (
<button
onClick={handleAddForm}
className="op-btn op-btn-primary focus:outline-none"
>
<i className="fa-light fa-plus"></i>{" "}
<span>{t("add-new")}</span>
</button>
)}
<button
type="submit"
className="op-btn op-btn-accent focus:outline-none"
@@ -435,23 +256,7 @@ const BulkSendUi = (props) => {
</button>
</div>
</form>
<ModalUi
isOpen={isQuotaReached}
handleClose={() => handleCloseQuotaReached()}
>
<div className="p-4 flex justify-center items-center flex-col gap-y-3">
<p className="text-center text-base-content">
{t("quota-err-quicksend")}
</p>
<button
onClick={() => setIsBulkAvailable(false)}
className=" op-btn op-btn-primary w-[200px]"
>
{t("buy-credits")}
</button>
</div>
</ModalUi>
</div>
</>
) : (
<div className="text-black p-3 bg-white w-full text-sm md:text-base flex justify-center items-center">
{t("quick-send-alert-1")}
@@ -471,68 +276,6 @@ const BulkSendUi = (props) => {
</>
) : (
<>
{isFreePlan ? (
<div className="w-full h-[130px] flex flex-col justify-center items-center text-center p-4">
<p className="text-base font-medium mb-2.5">
{t("bulk-send-subcription-alert")}
</p>
<button
onClick={() => handleNavigation()}
className="op-btn op-btn-primary"
>
{t("upgrade-now")}
</button>
</div>
) : (
<>
{admin?.isAdmin ? (
<form onSubmit={handleAddOnQuickSubmit} className="p-3">
<p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium">
{t("additional-credits")}
</p>
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("quantity-of-credits")}
<span className="text-[red] text-[13px]">*</span>
</label>
<select
value={amount.quantity}
onChange={(e) => handlePricePerQuick(e)}
name="quantity"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs"
required
>
{quantityList.length > 0 &&
quantityList.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {amount.priceperbulksend})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full mt-2">
{t("Proceed")}
</button>
</form>
) : (
<div className="mx-8 mt-4 mb-8 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", { adminEmail: admin?.email })}
</div>
)}
</>
)}
</>
)}
</>
@@ -0,0 +1,126 @@
import React, { useEffect, useState } from "react";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import Parse from "parse";
const EditContactForm = (props) => {
const { t } = useTranslation();
const [isLoader, setIsLoader] = useState(false);
const [formData, setFormData] = useState({ Name: "", Email: "", Phone: "" });
useEffect(() => {
if (props.contact?.Email) {
setFormData((prev) => ({ ...prev, ...props.contact }));
}
}, [props.contact]);
const handleChange = (e) => {
if (e.target.name === "Email") {
setFormData((prev) => ({
...prev,
[e.target.name]: e.target.value?.toLowerCase()?.replace(/\s/g, "")
}));
} else {
setFormData((prev) => ({ ...prev, [e.target.name]: e.target.value }));
}
};
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
if (props.handleEditContact) {
try {
setIsLoader(true);
const params = {
contactId: props.contact.objectId,
name: formData.Name,
email: formData.Email,
phone: formData.Phone,
tenantId: localStorage.getItem("TenantId")
};
const res = await Parse.Cloud.run("editcontact", params);
const updateContact = {
...res,
Name: formData.Name,
Email: formData.Email,
Phone: formData.Phone
};
props.handleEditContact(updateContact);
} catch (err) {
console.log("err in edit contact ", err);
if (err.code === 137) {
alert(t("contact-already-exists"));
} else {
alert(t("something-went-wrong-mssg"));
}
} finally {
setIsLoader(false);
props.handleClose && props.handleClose();
}
}
};
return (
<div className="h-full p-[20px]">
{isLoader && (
<div className="fixed inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<div className="w-full mx-auto p-2 text-base-content">
<form onSubmit={handleSubmit}>
<div className="mb-3">
<label htmlFor="name" className="block text-xs font-semibold">
{t("name")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="text"
id="name"
name="Name"
value={formData.Name}
onChange={(e) => handleChange(e)}
onInput={(e) => e.target.setCustomValidity("")}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
required
/>
</div>
<div className="mb-3">
<label htmlFor="email" className="block text-xs font-semibold">
{t("email")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<input
type="email"
id="email"
name="Email"
value={formData.Email}
onChange={(e) => handleChange(e)}
onInput={(e) => e.target.setCustomValidity("")}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
required
/>
</div>
<div className="mb-3">
<label htmlFor="phone" className="block text-xs font-semibold">
{t("phone")}
</label>
<input
type="text"
id="phone"
name="Phone"
value={formData.Phone}
onChange={(e) => handleChange(e)}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
placeholder={t("phone-optional")}
/>
</div>
<div className="mt-4 flex gap-x-2 justify-start">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
</button>
</div>
</form>
</div>
</div>
);
};
export default EditContactForm;
+7 -141
View File
@@ -1,44 +1,26 @@
import React, { useState, useEffect } from "react";
import dp from "../assets/images/dp.png";
import FullScreenButton from "./FullScreenButton";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import Parse from "parse";
import { useWindowSize } from "../hook/useWindowSize";
import {
checkIsSubscribed,
formatDate,
getAppLogo,
openInNewTab,
saveLanguageInLocal
} from "../constant/Utils";
import { isEnableSubscription, isStaging } from "../constant/const";
import { validplan } from "../json/plansArr";
import { useTranslation } from "react-i18next";
import ModalUi from "../primitives/ModalUi";
import QuotaCard from "../primitives/QuotaCard";
const Header = ({ showSidebar, setIsMenu }) => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const { width } = useWindowSize();
const current_notification_version = "1"; // Update this with each new feature release
// Get the dismissed version from local storage
const dismissedVersion = localStorage.getItem("notificationDismissedVersion");
const username = localStorage.getItem("username") || "";
const image = localStorage.getItem("profileImg") || dp;
const [isOpen, setIsOpen] = useState(false);
const [isSubscribe, setIsSubscribe] = useState(true);
const [isTeam, setIsTeam] = useState({ plan: "", isValid: false });
const [applogo, setAppLogo] = useState(
localStorage.getItem("appLogo") || " "
);
const [emailUsed, setEmailUsed] = useState(0);
const [isModal, setIsModal] = useState(false);
const [emailResetDate, setEmailResetDate] = useState("");
const [showNotification, setShowNotification] = useState(
(!dismissedVersion || dismissedVersion !== current_notification_version) &&
true
);
const toggleDropdown = () => {
setIsOpen(!isOpen);
if (width <= 768) {
@@ -46,45 +28,16 @@ const Header = ({ showSidebar, setIsMenu }) => {
}
};
useEffect(() => {
checkSubscription();
initializeHead();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function checkSubscription() {
if (isEnableSubscription) {
const subscribe = await checkIsSubscribed();
if (subscribe.isValid) {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
setIsTeam(subscribe);
setIsSubscribe(subscribe.isValid);
try {
const extUser = await Parse.Cloud.run("getUserDetails");
const MonthlyFreeEmails = extUser?.get("MonthlyFreeEmails") || 0;
setEmailUsed(MonthlyFreeEmails);
if (extUser) {
const _extUser = JSON.parse(JSON.stringify(extUser));
if (_extUser?.LastEmailCountReset?.iso) {
const resetDate = formatDate(_extUser?.LastEmailCountReset?.iso);
setEmailResetDate(resetDate);
}
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
}
} catch (err) {
console.log("err in while fetching monthlyfreeEmails", err);
}
} else {
async function initializeHead() {
const applogo = await getAppLogo();
if (applogo?.logo) {
setAppLogo(applogo?.logo);
} else {
setAppLogo(localStorage.getItem("appLogo") || "");
}
}
}
const closeDropdown = async () => {
@@ -128,56 +81,9 @@ const Header = ({ showSidebar, setIsMenu }) => {
document.removeEventListener("click", closeMenuOnOutsideClick);
};
}, [isOpen]);
const handleConsoleRedirect = () => {
if (isStaging) {
window.open(" https://staging-console.opensignlabs.com/");
} else {
window.open("https://console.opensignlabs.com/");
}
};
const handleNavigation = () => {
navigate("/subscription");
};
const handleMailUsed = () => {
setIsModal(!isModal);
};
// Handle dismissing the notification
const dismissNotification = () => {
// Store the current version as dismissed in local storage
localStorage.setItem(
"notificationDismissedVersion",
current_notification_version
);
setShowNotification(false);
};
const handleSetupNowNavigation = () => {
const url = isStaging
? "https://staging-console.opensignlabs.com/fileadapter"
: "https://console.opensignlabs.com/fileadapter";
openInNewTab(url);
};
return (
<div>
{isEnableSubscription && showNotification && (
<div className="flex justify-between items-center shadow py-1 bg-[#CAE4FA] p-1">
<div className="text-center text-[14px] ml-auto">
{t("header-news")}
<span
className="cursor-pointer font-medium underline text-blue-800"
onClick={() => handleSetupNowNavigation()}
>
{" " + t("header-news-btn") + "."}
</span>
</div>
<div
className="ml-auto mr-1 cursor-pointer"
onClick={() => dismissNotification()}
>
<i className="fa-light fa-xmark"></i>
</div>
</div>
)}
<div className="op-navbar bg-base-100 shadow">
<div className="flex-none">
<button
@@ -197,26 +103,6 @@ const Header = ({ showSidebar, setIsMenu }) => {
</div>
</div>
<div id="profile-menu" className="flex-none gap-2">
{!isSubscribe && (
<div>
<button
className="text-xs md:text-sm shadow op-btn op-btn-outline op-btn-sm md:op-btn-md op-btn-accent"
onClick={() => handleNavigation()}
>
{t("upgrade-now")}
</button>
</div>
)}
{isTeam.isValid && !validplan[isTeam.plan] && (
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
{t("pro")}
</div>
)}
{validplan[isTeam.plan] && (
<div className="w-[35px] h-[35px] bg-white rounded-full ring-[1px] text-[13px] ring-offset-2 ring-[#002862] text-[#002862] overflow-hidden font-semibold flex items-center justify-center">
{t("team")}
</div>
)}
<div>
<FullScreenButton />
</div>
@@ -255,7 +141,9 @@ const Header = ({ showSidebar, setIsMenu }) => {
</div>
<ul
tabIndex={0}
className="mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52"
className={`mt-3 z-[1] p-2 shadow op-menu op-menu-sm op-dropdown-content text-base-content bg-base-100 rounded-box w-52 ${
isOpen ? "" : "hidden"
}`}
>
<li onClick={() => openInNewTab("https://docs.opensignlabs.com")}>
<span>
@@ -282,21 +170,6 @@ const Header = ({ showSidebar, setIsMenu }) => {
<i className="fa-light fa-lock"></i> {t("change-password")}
</span>
</li>
{isEnableSubscription && (
<li onClick={() => handleConsoleRedirect()}>
<span>
<i className="fa-light fa-id-card"></i> Console
</span>
</li>
)}
{isEnableSubscription && isTeam?.plan === "freeplan" && (
<li className="cursor-pointer" onClick={handleMailUsed}>
<span>
<i className="fa-light fa-envelope"></i>
{emailUsed}/15 {t("sent-this-month")}
</span>
</li>
)}
<li onClick={closeDropdown}>
<span>
<i className="fa-light fa-arrow-right-from-bracket"></i>{" "}
@@ -307,13 +180,6 @@ const Header = ({ showSidebar, setIsMenu }) => {
</div>
</div>
</div>
<ModalUi isOpen={isModal}>
<QuotaCard
isPaidInfo={true}
handlClose={handleMailUsed}
emailResetDate={emailResetDate}
/>
</ModalUi>
</div>
);
};
@@ -1,220 +0,0 @@
import React, { useState } from "react";
import Parse from "parse";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
// import FacebookLogin from "react-facebook-login";
const LoginFacebook = ({
FBCred,
thirdpartyLoginfn,
thirdpartyLoader,
setThirdpartyLoader
}) => {
const { t } = useTranslation();
const [isModal, setIsModal] = useState(false);
const [userDetails, setUserDetails] = useState({
Phone: "",
Company: "",
Name: "",
Email: ""
});
const [fBDetails, setFBDetails] = useState({
Id: "",
AccessToken: "",
Name: "",
Email: ""
});
const responseFacebook = async (response) => {
// console.log("response ", response);
if (response.userID) {
setThirdpartyLoader(true);
const details = {
Email: response.email,
Name: response.name
};
setUserDetails({ ...userDetails, ...details });
const fDetails = {
Id: response.userID,
AccessToken: response.accessToken,
Name: response.name,
Email: response.email
};
setFBDetails({ ...fBDetails, ...fDetails });
const res = await checkExtUser(fDetails);
}
};
const checkExtUser = async (details) => {
// const extUser = new Parse.Query("contracts_Users");
// extUser.equalTo("Email", details.Email);
// const extRes = await extUser.first();
const params = { email: details.Email };
const extRes = await Parse.Cloud.run("getUserDetails", params);
// console.log("extRes ", extRes);
if (extRes) {
const params = { ...details, Phone: extRes?.get("Phone") || "" };
const payload = await Parse.Cloud.run("facebooksign", params);
// console.log("payload ", payload);
if (payload && payload.sessiontoken) {
const billingDate =
extRes.get("Next_billing_date") && extRes.get("Next_billing_date");
// console.log("billingDate expired", billingDate > new Date());
const LocalUserDetails = {
name: details.Name,
email: details.Email,
phone: extRes?.get("Phone") || "",
company: extRes.get("Company")
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(payload.sessiontoken, billingDate);
}
return { msg: "exist" };
} else {
setIsModal(true);
setThirdpartyLoader(false);
return { msg: "notexist" };
}
};
const handleSubmitbtn = async () => {
if (userDetails.Phone && userDetails.Company) {
setThirdpartyLoader(true);
const params = { ...fBDetails, Phone: userDetails?.Phone || "" };
const payload = await Parse.Cloud.run("facebooksign", params);
// console.log("payload ", payload);
if (payload && payload.sessiontoken) {
const params = {
userDetails: {
name: userDetails.Name,
email: userDetails.Email,
// "passsword":userDetails.Phone,
phone: userDetails?.Phone || "",
role: "contracts_User",
company: userDetails.Company
}
};
const userSignUp = await Parse.Cloud.run("usersignup", params);
// console.log("userSignUp ", userSignUp);
if (userSignUp && userSignUp.sessionToken) {
const LocalUserDetails = {
name: userDetails.Name,
email: userDetails.Email,
phone: userDetails?.Phone || "",
company: userDetails.Company
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(userSignUp.sessionToken);
} else {
alert(userSignUp.message);
}
} else {
alert(t("server-error"));
}
} else {
alert(t("fill-required-details!"));
}
};
return (
<div className="relative">
{thirdpartyLoader && (
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
<Loader />
</div>
)}
{/* <FacebookLogin
appId={FBCred}
fields="name,email,picture"
callback={responseFacebook}
cssClass="btn btn-info btn-md btn-fb"
icon="fa fa-facebook-f"
/> */}
{isModal && (
<div
className="modal fade show block z-[1]"
id="exampleModal"
tabIndex="-1"
role="dialog"
>
<div className="modal-dialog" role="document">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">{t("sign-up-form")}</h5>
<span>
<span></span>
</span>
</div>
<div className="modal-body">
<form>
<div className="form-group">
<label htmlFor="Phone" className="col-form-label">
{t("phone")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<input
type="tel"
className="form-control"
id="Phone"
value={userDetails.Phone}
onChange={(e) =>
setUserDetails({
...userDetails,
Phone: e.target.value
})
}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="form-group">
<label htmlFor="Company" className="col-form-label">
{t("company")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<input
type="text"
className="form-control"
id="Company"
value={userDetails.Company}
onChange={(e) =>
setUserDetails({
...userDetails,
Company: e.target.value
})
}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div>
<button
type="button"
onClick={() => handleSubmitbtn()}
className="btn btn-info mr-[10px]"
>
{t("sign-up")}
</button>
<button
type="button"
className="btn btn-secondary w-[90px]"
onClick={() => setIsModal(false)}
>
{t("cancel")}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
)}
</div>
);
};
export default LoginFacebook;
-280
View File
@@ -1,280 +0,0 @@
import React, { useState, useRef } from "react";
import Parse from "parse";
import { jwtDecode } from "jwt-decode";
import { useScript } from "../hook/useScript";
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import { saveLanguageInLocal } from "../constant/Utils";
/*
* `GoogleSignInBtn`as it's name indicates it render google sign in button
* and in this `useScript` in which we have created for generate google sign button
* and when user click on sign in with google it will be verify on server side
* and then generate token
*/
const GoogleSignInBtn = ({
GoogleCred,
thirdpartyLoginfn,
thirdpartyLoader,
setThirdpartyLoader
}) => {
const { t, i18n } = useTranslation();
const [isModal, setIsModal] = useState(false);
const googleBtn = useRef();
const [userDetails, setUserDetails] = useState({
Phone: "",
Company: "",
Name: "",
Email: "",
Destination: ""
});
const [googleDetails, setGoogleDetails] = useState({
Id: "",
TokenId: "",
Gmail: "",
Name: ""
});
useScript("https://accounts.google.com/gsi/client", () => {
window.google.accounts.id.initialize({
client_id: GoogleCred,
callback: responseGoogle,
auto_select: false
});
window.google.accounts.id.renderButton(googleBtn.current, {
theme: "outline",
size: "large",
width: "187px"
});
});
const clearStorage = async () => {
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let userSettings = localStorage.getItem("userSettings");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", userSettings);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
};
const responseGoogle = async (response) => {
clearStorage();
setThirdpartyLoader(true);
// console.log("response ", response);
if (response.credential) {
const data = jwtDecode(response.credential);
// console.log("data ", data);
if (data.sub && data.email) {
const details = { Email: data.email, Name: data.name };
setUserDetails({ ...userDetails, ...details });
const Gdetails = {
Id: data.sub,
TokenId: response.credential,
Gmail: data.email,
Name: data.name
};
setGoogleDetails({ ...googleDetails, ...Gdetails });
await checkExtUser(Gdetails);
}
}
};
const checkExtUser = async (details) => {
const params = { email: details.Gmail };
const extRes = await Parse.Cloud.run("getUserDetails", params);
if (extRes) {
// const params = { ...details, Phone: extRes?.get("Phone") || "" };
const params = { ...details, extUserId: extRes.objectId };
const payload = await Parse.Cloud.run("googlesign", params);
if (payload && payload.sessiontoken) {
const LocalUserDetails = {
name: details.Name,
email: details.Gmail,
phone: payload?.phone || "",
company: payload.company
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(payload.sessiontoken);
}
return { msg: "exist" };
} else {
setIsModal(true);
setThirdpartyLoader(false);
return { msg: "notexist" };
}
};
const handleSubmitbtn = async () => {
if (userDetails.Phone && userDetails.Company) {
setThirdpartyLoader(true);
// e.preventDefault()
// console.log("handelSubmit", userDetails);
const params = { ...googleDetails, Phone: userDetails?.Phone };
const payload = await Parse.Cloud.run("googlesign", params);
// console.log("payload ", payload);
if (payload && payload.sessiontoken) {
const params = {
userDetails: {
name: userDetails.Name,
email: userDetails.Email,
// "passsword":userDetails.Phone,
phone: userDetails?.Phone || "",
role: "contracts_User",
company: userDetails.Company,
jobTitle: userDetails.Destination
}
};
const userSignUp = await Parse.Cloud.run("usersignup", params);
// console.log("userSignUp ", userSignUp);
if (userSignUp && userSignUp.sessionToken) {
const LocalUserDetails = {
name: userDetails.Name,
email: userDetails.Email,
phone: userDetails?.Phone || "",
company: userDetails.Company
// jobTitle: userDetails.JobTitle
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(userSignUp.sessionToken);
} else {
alert(userSignUp.message);
}
} else if (
payload &&
payload.message.replace(/ /g, "_") === "Internal_server_err"
) {
alert(t("server-error"));
}
} else {
alert(t("fill-required-details!"));
}
};
const handleCloseModal = async () => {
setIsModal(false);
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
};
return (
<div className="relative">
{thirdpartyLoader && (
<div className="fixed flex justify-center items-center inset-0 bg-black bg-opacity-25 z-20 ">
<Loader />
</div>
)}
<div ref={googleBtn} className="text-sm"></div>
<ModalUi showClose={false} isOpen={isModal} title={t("sign-up-form")}>
<form className="px-4 py-3 text-base-content">
<div className="mb-3">
<label htmlFor="Phone" className="block text-xs font-semibold">
{t("phone")} <span className="text-[13px] text-[red]">*</span>
</label>
<input
type="tel"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
id="Phone"
value={userDetails.Phone}
onChange={(e) =>
setUserDetails({
...userDetails,
Phone: e.target.value
})
}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="mb-3">
<label htmlFor="Company" className="block text-xs font-semibold">
{t("company")} <span className="text-[13px] text-[red]">*</span>
</label>
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
id="Company"
value={userDetails.Company}
onChange={(e) =>
setUserDetails({
...userDetails,
Company: e.target.value
})
}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="mb-3">
<label htmlFor="JobTitle" className="block text-xs font-semibold">
{t("job-title")} <span className="text-[13px] text-[red]">*</span>
</label>
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
id="JobTitle"
value={userDetails.Destination}
onChange={(e) =>
setUserDetails({
...userDetails,
Destination: e.target.value
})
}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="flex flex-row gap-2">
<button
type="button"
className="op-btn op-btn-primary"
onClick={() => handleSubmitbtn()}
>
{t("sign-up")}
</button>
<button
type="button"
className="op-btn op-btn-ghost"
onClick={handleCloseModal}
>
{t("cancel")}
</button>
</div>
</form>
</ModalUi>
</div>
);
};
export default GoogleSignInBtn;
+11 -10
View File
@@ -6,22 +6,14 @@ function RotateAlert(props) {
const { t } = useTranslation();
return (
<ModalUi
isOpen={props.isRotate}
isOpen={props.showRotateAlert}
title={t("Rotation-alert")}
handleClose={() => props.setIsRotate({ status: false, degree: 0 })}
handleClose={() => props.setShowRotateAlert({ status: false, degree: 0 })}
>
{" "}
<div className="p-[20px] h-full">
<p>{t("rotate-alert-mssg")}</p>
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
<div className="flex gap-1">
<button
onClick={() => props.setIsRotate({ status: false, degree: 0 })}
type="button"
className="op-btn op-btn-ghost shadow-md"
>
{t("no")}
</button>
<button
onClick={() => props.handleRemoveWidgets()}
type="button"
@@ -29,6 +21,15 @@ function RotateAlert(props) {
>
{t("yes")}
</button>
<button
onClick={() =>
props.setShowRotateAlert({ status: false, degree: 0 })
}
type="button"
className="op-btn op-btn-ghost shadow-md"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
@@ -1,5 +1,5 @@
import React from "react";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { openInNewTab } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
@@ -30,15 +30,27 @@ const DashboardButton = (props) => {
} w-full shadow-md px-3 py-2 op-card bg-base-100`}
>
<div className="flex flex-row items-center">
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
<i
className={`${
props.Icon ? props.Icon : "fa-light fa-info"
} text-[25px] lg:text-[30px]`}
></i>
</span>
<div className=" text-lg ml-3 text-base-content">
<div className="flex flex-row items-center">
<span className="rounded-full bg-base-content bg-opacity-20 w-[60px] h-[60px] self-start flex justify-center items-center">
<i
className={`${
props.Icon ? props.Icon : "fa-light fa-info"
} text-[25px] lg:text-[30px]`}
></i>
</span>
</div>
<div className="text-lg ml-3 text-base-content">
{t(`sidebar.${props.Label}`)}
{props.Label === "Sign yourself" && (
<div className="text-gray-500 text-xs mt-1">
{t("signyour-self-button")}
</div>
)}
{props.Label === "Request signatures" && (
<div className="text-gray-500 text-xs mt-1">
{t("requestsign-button")}
</div>
)}
</div>
</div>
</div>
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
import axios from "axios";
import Parse from "parse";
import getReplacedHashQuery from "../../constant/getReplacedHashQuery";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import Tooltip from "../../primitives/Tooltip";
import { useTranslation } from "react-i18next";
@@ -133,7 +133,7 @@ const DashboardCard = (props) => {
skip: 0,
limit: 200
};
const url = `${parseBaseUrl}/functions/getReport`;
const url = `${parseBaseUrl}functions/getReport`;
await axios
.post(url, params, {
headers: {
@@ -36,12 +36,12 @@ function DashboardReport(props) {
// below useEffect call when isNextRecord state is true and fetch next record
useEffect(() => {
if (isNextRecord) {
getReportData(props.Record.reportId, List.length, 200);
getReportData(props.Record.reportId, List.length, 20);
}
// eslint-disable-next-line
}, [isNextRecord]);
const getReportData = async (id, skipUserRecord = 0, limit = 200) => {
const getReportData = async (id, skipUserRecord = 0, limit = 20) => {
setIsLoader(true);
const json = reportJson(id);
if (json) {
@@ -56,8 +56,10 @@ function DashboardReport(props) {
sessiontoken: localStorage.getItem("accesstoken")
};
try {
const params = { reportId: id, skip: skipUserRecord, limit: limit };
const url = `${localStorage.getItem("baseUrl")}/functions/getReport`;
const skipRecord = id === "5Go51Q7T8r" ? 0 : skipUserRecord;
const limitRecord = id === "5Go51Q7T8r" ? 200 : limit;
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const res = await axios.post(url, params, {
headers: headers,
signal: abortController.signal // is used to cancel fetch query
@@ -94,7 +96,7 @@ function DashboardReport(props) {
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
);
} else {
if (res.data.result.length === docPerPage) {
if (res.data.result.length >= docPerPage) {
setIsMoreDocs(true);
} else {
setIsMoreDocs(false);
@@ -2,13 +2,14 @@ import React, { useState, useEffect, useRef } from "react";
import "../../styles/opensigndrive.css";
import axios from "axios";
import * as ContextMenu from "@radix-ui/react-context-menu";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import Table from "react-bootstrap/Table";
import * as HoverCard from "@radix-ui/react-hover-card";
import ModalUi from "../../primitives/ModalUi";
import FolderModal from "../shared/fields/FolderModal";
import { useTranslation } from "react-i18next";
import { handleDownloadPdf } from "../../constant/Utils";
import { handleDownloadPdf, isMobile } from "../../constant/Utils";
import Parse from "parse";
function DriveBody(props) {
const { t } = useTranslation();
@@ -17,7 +18,7 @@ function DriveBody(props) {
const inputRef = useRef(null);
const [isOpenMoveModal, setIsOpenMoveModal] = useState(false);
const [selectDoc, setSelectDoc] = useState();
const [isDeleteDoc, setIsDeleteDoc] = useState(false);
const [isDeleteDoc, setIsDeleteDoc] = useState({});
const contextMenu = [
{ type: "Download", icon: "fa-light fa-arrow-down" },
{ type: "Rename", icon: "fa-light fa-font" },
@@ -137,7 +138,7 @@ function DriveBody(props) {
}
};
const handleMenuItemClick = async (selectType, data) => {
const handleMenuItemClick = async (selectType, data, deleteType) => {
switch (selectType) {
case "Download": {
await handleDownloadPdf([data]);
@@ -149,7 +150,7 @@ function DriveBody(props) {
break;
}
case "Delete": {
setIsDeleteDoc(true);
setIsDeleteDoc({ status: true, deleteType });
setSelectDoc(data);
break;
}
@@ -163,7 +164,7 @@ function DriveBody(props) {
};
//function for delete document
const handleDeleteDocument = async (docData) => {
setIsDeleteDoc(false);
setIsDeleteDoc({});
const docId = docData.objectId;
const data = {
IsArchive: true
@@ -269,6 +270,50 @@ function DriveBody(props) {
}
};
const checkFolderEmpty = async (docData) => {
let isEmptyFolder = true;
const query = new Parse.Query("contracts_Document");
query.equalTo("Folder", {
__type: "Pointer",
className: "contracts_Document",
objectId: docData.objectId
});
query.notEqualTo("IsArchive", true);
const res = await query.find();
const jsonRes = JSON.parse(JSON.stringify(res));
if (jsonRes && jsonRes.length > 0) {
isEmptyFolder = false;
return isEmptyFolder;
} else {
return isEmptyFolder;
}
};
const handleDeleteFolder = async (docData) => {
setIsDeleteDoc({});
const isEmptyFolder = await checkFolderEmpty(docData);
if (isEmptyFolder) {
const docId = docData?.objectId;
try {
const updateQuery = new Parse.Query("contracts_Document");
const updateObj = await updateQuery.get(docId);
updateObj.set("IsArchive", true);
const res = await updateObj.save();
if (res) {
const updatedData = props.pdfData.filter((x) => x.objectId !== docId);
props.setPdfData(updatedData);
}
} catch (err) {
console.log("Err ", err);
props.setIsAlert({
isShow: true,
alertMessage: t("something-went-wrong-mssg")
});
}
} else {
alert(t("delete-folder-alert-1"));
}
};
//component to handle type of document and render according to type
const handleFolderData = (data, ind, listType) => {
let createddate, status, isDecline, signerExist, isComplete;
@@ -311,6 +356,7 @@ function DriveBody(props) {
</span>
);
};
return listType === "table" ? (
data.Type === "Folder" ? (
<tr onClick={() => handleOnclikFolder(data)}>
@@ -359,7 +405,7 @@ function DriveBody(props) {
) : listType === "list" && data.Type === "Folder" ? (
<div key={ind} className="relative w-[100px] h-[100px] mx-2 my-3">
<ContextMenu.Root>
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
<ContextMenu.Trigger className="flex flex-col justify-center items-center select-none-cls">
{/* folder */}
<div
data-tut={props.dataTutSeventh}
@@ -395,7 +441,7 @@ function DriveBody(props) {
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
/>
) : (
<span className="fileName">{data.Name}</span>
<span className="fileName select-none-cls">{data.Name}</span>
)}
</div>
</ContextMenu.Trigger>
@@ -413,13 +459,20 @@ function DriveBody(props) {
<i className="fa-light fa-font mr-[8px]"></i>
<span>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>
</ContextMenu.Item>
</ContextMenu.Content>
</ContextMenu.Portal>
</ContextMenu.Root>
</div>
) : (
<HoverCard.Root
open={rename ? false : undefined}
open={rename || isMobile ? false : undefined}
openDelay={0}
closeDelay={100}
>
@@ -427,7 +480,10 @@ function DriveBody(props) {
<div>
<ContextMenu.Root>
<div className="relative w-[100px] h-[100px] mx-2 my-3">
<ContextMenu.Trigger className="flex flex-col justify-center items-center">
<ContextMenu.Trigger
asChild
className="flex flex-col justify-center items-center select-none-cls"
>
{/* pdf */}
<div
data-tut={props.dataTutSixth}
@@ -456,14 +512,16 @@ function DriveBody(props) {
}
}}
onBlur={() => handledRenameDoc(data)}
onKeyDown={(e) => handleEnterPress(e, data)}
onKeyDown={(e) => handleEnterPress(e, data, data.Type)}
ref={inputRef}
defaultValue={renameValue}
onChange={(e) => setRenameValue(e.target.value)}
className="op-input op-input-bordered op-input-xs w-[100px] focus:outline-none hover:border-base-content text-[10px]"
/>
) : (
<span className="fileName">{data.Name}</span>
<span className="fileName select-none-cls">
{data.Name}
</span>
)}
</div>
</ContextMenu.Trigger>
@@ -596,22 +654,33 @@ function DriveBody(props) {
/>
)}
<ModalUi
isOpen={isDeleteDoc}
isOpen={isDeleteDoc.status}
title={t("delete-document")}
handleClose={() => setIsDeleteDoc(false)}
handleClose={() => setIsDeleteDoc({})}
>
<div className="h-full p-[20px] text-base-content">
<p>{t("delete-document-alert")}</p>
{isDeleteDoc.deleteType ? (
<p>{t("delete-folder-alert")}</p>
) : (
<p>{t("delete-document-alert")}</p>
)}
<div className="h-[1px] w-full bg-[#9f9f9f] my-[15px]"></div>
<button
onClick={() => handleDeleteDocument(selectDoc)}
onClick={() => {
if (isDeleteDoc.deleteType) {
handleDeleteFolder(selectDoc);
} else {
handleDeleteDocument(selectDoc);
}
}}
type="button"
className="op-btn op-btn-primary mr-2"
>
{t("yes")}
</button>
<button
onClick={() => setIsDeleteDoc(false)}
onClick={() => setIsDeleteDoc({})}
type="button"
className="op-btn op-btn-neutral"
>
@@ -0,0 +1,116 @@
import React from "react";
import { useTranslation } from "react-i18next";
import ModalUi from "../../primitives/ModalUi";
function AgreementContent(props) {
const { t } = useTranslation();
const h2Style = "text-base-content font-medium text-lg";
const ulStyle = "list-disc px-4 py-3";
const handleOnclick = () => {
props.setIsAgreeTour(false);
props.setIsAgree(true);
props.setIsShowAgreeTerms(false);
props.showFirstWidget();
};
return (
<div>
<ModalUi
isOpen={true}
title={t("term-cond-title")}
handleClose={() => props.setIsShowAgreeTerms(false)}
>
<div className="h-[100%] p-[20px]">
<h2 className={h2Style}>{t("term-cond-h")}</h2>
<span className="mt-2">{t("term-cond-p1")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<h2 className={h2Style}>{t("term-cond-h1")}</h2>
<span className="mt-2">{t("term-cond-p2")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p3")}</li>
<li>{t("term-cond-p4")}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<h2 className={h2Style}>{t("term-cond-h2")}</h2>
<span className="mt-2">{t("term-cond-p5")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p6")}</li>
<li>{t("term-cond-p7")}</li>
<li>{t("term-cond-p8")}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<h2 className={h2Style}>{t("term-cond-h3")}</h2>
<span className="mt-2">{t("term-cond-p9")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p10")}</li>
<li>{t("term-cond-p11")}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<h2 className={h2Style}>{t("term-cond-h4")}</h2>
<span className="mt-2">{t("term-cond-p12")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p13")}</li>
<li>{t("term-cond-p14")}</li>
<li>{t("term-cond-p15")}</li>
</ul>
<span>{t("term-cond-p16")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<h2 className={h2Style}>{t("term-cond-h5")}</h2>
<p>{t("term-cond-p17")}</p>
<p className="mt-2">{t("term-cond-p18")}</p>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<h2 className={h2Style}>{t("term-cond-h6")}</h2>
<span className="mt-2">{t("term-cond-p19")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p20")}</li>
<li>{t("term-cond-p21")}</li>
<li>{t("term-cond-p22")}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<h2 className={h2Style}>{t("term-cond-h7")}</h2>
<span className="mt-2">{t("term-cond-p23")}</span>
<ul className={ulStyle}>
<li>{t("term-cond-p24")}</li>
<li>{t("term-cond-p25")}</li>
</ul>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<h2 className={h2Style}>{t("term-cond-h8")}</h2>
<span className="mt-2">{t("term-cond-p26")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<h2 className={h2Style}>{t("term-cond-h9")}</h2>
<span className="mt-2">{t("term-cond-p27")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<span className="mt-2 font-medium">{t("term-cond-p28")}</span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<span className="mt-2">{t("term-cond-p29")}</span>
<a
href="www.opensignlabs.com"
target="_blank"
className="text-blue-700 cursor-pointer"
>
{" "}
www.opensignlabs.com{" "}
</a>
<span>{t("term-cond-p30")}</span>
<span className="font-medium"> support@opensignlabs.com </span>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<div className="mt-6 flex justify-start gap-2">
<button
onClick={() => handleOnclick()}
className="op-btn op-btn-primary"
>
{t("agrre-button")}
</button>
<button
className="op-btn op-btn-ghost"
onClick={() => props.setIsShowAgreeTerms(false)}
>
{t("close")}
</button>
</div>
</div>
</ModalUi>
</div>
);
}
export default AgreementContent;
@@ -0,0 +1,71 @@
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import AgreementContent from "./AgreementContent";
function AgreementSign(props) {
const { t } = useTranslation();
const [isChecked, setIsChecked] = useState(false);
const [isShowAgreeTerms, setIsShowAgreeTerms] = useState(false);
return (
<>
<div className="op-modal op-modal-open absolute z-[448]">
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
<div className="flex flex-row items-center">
<input
data-tut="IsAgree"
className="mr-3 op-checkbox op-checkbox-m"
type="checkbox"
value={isChecked}
onChange={(e) => {
setIsChecked(e.target.checked);
if (e.target.checked) {
props.setIsAgreeTour(false);
}
props.showFirstWidget();
}}
/>
<div className="text-[11px] md:text-base">
<span>{t("agree-p1")}</span>
<span
className="font-bold text-blue-600 cursor-pointer"
onClick={() => {
setIsShowAgreeTerms(true);
props.setIsAgreeTour(false);
}}
>
{t("agree-p2")}
</span>
<span> {t("agree-p3")}</span>
</div>
</div>
<div className="flex ml-[35px] mt-3">
<button
onClick={() => {
if (isChecked) {
props.setIsAgreeTour(false);
props.setIsAgree(true);
} else {
props.setIsAgreeTour(true);
}
}}
className="op-btn op-btn-primary op-btn-sm w-full md:w-auto"
>
{t("agrre-button")}
</button>
</div>
</div>
</div>
{isShowAgreeTerms && (
<AgreementContent
setIsAgreeTour={props.setIsAgreeTour}
setIsAgree={props.setIsAgree}
setIsShowAgreeTerms={setIsShowAgreeTerms}
showFirstWidget={props.showFirstWidget}
/>
)}
</>
);
}
export default AgreementSign;
@@ -1,41 +1,106 @@
import React from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
function DefaultSignature(props) {
const { t } = useTranslation();
const confirmToaddDefaultSign = () => {
if (props?.xyPostion.length > 0) {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("default-sign-alert")
});
const tabName = ["my-signature", "my-initials"];
const [activeTab, setActiveTab] = useState(0);
const confirmToaddDefaultSign = (type) => {
if (!props.isAgree) {
props.setIsAgreeTour(true);
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
});
if (props?.xyPosition.length > 0) {
//check signature or initial widgets exist or not for auto signing
const getCurrentSignerXY = props?.xyPosition.filter(
(data) => data.Id === props.uniqueId
);
const checkIsSignInitialExist = getCurrentSignerXY?.every(
(placeholderObj) =>
placeholderObj?.placeHolder?.some((placeholder) =>
placeholder?.pos?.some((posItem) => posItem?.type === type)
)
);
if (checkIsSignInitialExist) {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("default-sign-alert", { widgetsType: type }),
type: type
});
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("defaultSign-alert", { widgetsType: type })
});
}
} else {
props?.setDefaultSignAlert({
isShow: true,
alertMessage: t("please-select-position!")
});
}
}
};
return (
<div data-tut="reactourThird">
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
{t("signature")}
<p className="text-base-content">{t("signature")}</p>
</div>
<div className="flex justify-center items-center mt-2">
<div role="tablist" className="op-tabs op-tabs-bordered">
{tabName.map((tabData, ind) => (
<div
onClick={() => setActiveTab(ind)}
key={ind}
role="tab"
className={`${
activeTab === ind ? "op-tab-active" : ""
} op-tab flex items-center pb-10 md:pb-0`}
>
<span className="ml-1 text-[7px] font-medium md:font-normal md:text-[12px]">
{t(`${tabData}`)}
</span>
</div>
))}
</div>
</div>
<div className="flex flex-col items-center mt-[10px] font-semibold relative">
<p className="text-base-content">{t("Your-Signature")}</p>
<div className="op-card shadow-md h-[111px] w-[90%] p-2">
<img
alt="signature"
className="w-full h-full object-contain"
src={props?.defaultSignImg}
/>
{activeTab === 0 ? (
<img
alt="signature"
className="w-full h-full object-contain"
src={props?.defaultSignImg}
/>
) : (
activeTab === 1 &&
(props?.myInitial ? (
<img
alt="signature"
className="w-full h-full object-contain"
src={props?.myInitial}
/>
) : (
<div className="flex justify-center items-center h-full">
<span>{t("initial-alert")}</span>
</div>
))
)}
</div>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mt-[10px]"
onClick={() => confirmToaddDefaultSign()}
disabled={!props?.isDefault}
onClick={() =>
confirmToaddDefaultSign(
activeTab === 0 ? "signature" : activeTab === 1 && "initials"
)
}
disabled={
activeTab === 0 && !props?.isDefault
? true
: activeTab === 1 && !props.myInitial
? true
: false
}
>
{t("auto-sign-all")}
</button>
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import LoaderWithMsg from "../../primitives/LoaderWithMsg";
import { contractDocument } from "../../constant/Utils";
import HandleError from "../../primitives/HandleError";
import { useLocation, useNavigate } from "react-router-dom";
import { useLocation, useNavigate } from "react-router";
import { useTranslation } from "react-i18next";
function useQuery() {
return new URLSearchParams(useLocation().search);
@@ -1,8 +1,6 @@
import React, { useEffect, useState } from "react";
import { isEnableSubscription } from "../../constant/const";
import ModalUi from "../../primitives/ModalUi";
import { radioButtonWidget } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade";
import { useTranslation } from "react-i18next";
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
@@ -130,14 +128,6 @@ function DropdownWidgetOption(props) {
setDefaultValue("");
};
const handleSetMinMax = (e) => {
const minValue = e.target.value;
if (minValue > dropdownOptionList.length) {
return "";
} else {
return minValue;
}
};
const handleDefaultCheck = (index) => {
const getDefaultCheck = defaultCheckbox.includes(index);
@@ -181,7 +171,7 @@ function DropdownWidgetOption(props) {
key={index}
className="flex flex-row mb-[5px] items-center"
>
{props.type === "checkbox" && !props.isSignYourself && (
{props.type === "checkbox" && props.isShowAdvanceFeature && (
<input
type="checkbox"
checked={handleDefaultCheck(index)}
@@ -224,65 +214,6 @@ function DropdownWidgetOption(props) {
onClick={handleAddInput}
className="fa-light fa-square-plus text-[25px] ml-[10px] op-text-primary cursor-pointer"
></i>
{isEnableSubscription && (
<div>
{props.type === "checkbox" && !props.isSignYourself && (
<>
<label
className={`${
!props.isSubscribe ? "text-[gray]" : ""
} text-[13px] font-semibold`}
>
{t("minimun-check")}
</label>
{!props.isSubscribe && isEnableSubscription && (
<Upgrade />
)}
<input
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
defaultValue={0}
value={minCount}
disabled={props.isSubscribe ? false : true}
onChange={(e) => {
const count = handleSetMinMax(e);
setMinCount(count);
}}
className={`${
props.isSubscribe ? "" : "pointer-events-none"
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
/>
<label
className={`${
!props.isSubscribe ? "text-[gray]" : ""
} text-[13px] font-semibold`}
>
{t("maximum-check")}
</label>
<input
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
defaultValue={0}
value={maxCount}
disabled={props.isSubscribe ? false : true}
onChange={(e) => {
const count = handleSetMinMax(e);
setMaxCount(count);
}}
className={`${
props.isSubscribe ? "" : "pointer-events-none"
} op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs`}
/>
</>
)}
</div>
)}
</div>
{["dropdown", radioButtonWidget].includes(props.type) && (
<>
@@ -381,9 +312,11 @@ function DropdownWidgetOption(props) {
></span>
</div>
</div>
{["checkbox", radioButtonWidget].includes(props.type) && (
{["checkbox", radioButtonWidget, "dropdown"].includes(
props.type
) && (
<div className="flex flex-row gap-5 my-2 items-center text-center">
{!props.isSignYourself && (
{props.isShowAdvanceFeature && (
<div className="flex items-center">
<input
id="isreadonly"
@@ -397,26 +330,28 @@ function DropdownWidgetOption(props) {
</label>
</div>
)}
<div className="flex items-center">
<input
id="ishidelabel"
type="checkbox"
checked={isHideLabel}
className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsHideLabel(e.target.checked)}
/>
{props.type !== "dropdown" && (
<div className="flex items-center">
<input
id="ishidelabel"
type="checkbox"
checked={isHideLabel}
className="op-checkbox op-checkbox-sm"
onChange={(e) => setIsHideLabel(e.target.checked)}
/>
<label className="ml-1 mb-0" htmlFor="ishidelabel">
{t("hide-labels")}
</label>
</div>
<label className="ml-1 mb-0" htmlFor="ishidelabel">
{t("hide-labels")}
</label>
</div>
)}
</div>
)}
</div>
<div
className={`${
props.type === "checkbox" && !props.isSignYourself
props.type === "checkbox" && props.isShowAdvanceFeature
? "mb-[15px]"
: "my-[15px]"
} w-full h-[1px] bg-[#9f9f9f]`}
+69 -149
View File
@@ -1,11 +1,17 @@
import React, { useState, useEffect } from "react";
import { checkIsSubscribed, getFileName } from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
import React, {
useState,
} from "react";
import {
getFileName
} from "../../constant/Utils";
import { useTranslation } from "react-i18next";
import { Tooltip } from "react-tooltip";
import SignersInput from "../shared/fields/SignersInput";
const EditTemplate = ({ template, onSuccess, jwttoken }) => {
const EditTemplate = ({
template,
onSuccess,
}) => {
const { t } = useTranslation();
const [formData, setFormData] = useState({
Name: template?.Name || "",
@@ -21,19 +27,22 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
NotifyOnSignatures:
template?.NotifyOnSignatures !== undefined
? template?.NotifyOnSignatures
: false
: false,
Bcc: template?.Bcc,
RedirectUrl: template?.RedirectUrl || "",
AllowModifications: template?.AllowModifications || false
});
const [isSubscribe, setIsSubscribe] = useState(false);
useEffect(() => {
fetchSubscription();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const fetchSubscription = async () => {
if (isEnableSubscription) {
const subscribe = await checkIsSubscribed(jwttoken);
setIsSubscribe(subscribe.isValid);
// `isValidURL` is used to check valid webhook url
function isValidURL(value) {
try {
const url = new URL(value);
return url.protocol === "https:" || url.protocol === "http:";
} catch (error) {
return false;
}
};
}
const handleStrInput = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
@@ -42,10 +51,15 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
const handleSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
if (formData.RedirectUrl && !isValidURL(formData?.RedirectUrl)) {
alert(t("invalid-redirect-url"));
return;
}
const isChecked = formData.SendinOrder === "true" ? true : false;
const isTourEnabled = formData?.IsTourEnabled === "false" ? false : true;
const AutoReminder = formData?.AutomaticReminders || false;
const IsEnableOTP = formData.IsEnableOTP === "true" ? true : false;
const allowModify = formData?.AllowModifications || false;
let reminderDate = {};
if (AutoReminder) {
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
@@ -58,21 +72,26 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
SendinOrder: isChecked,
IsEnableOTP: IsEnableOTP,
IsTourEnabled: isTourEnabled,
AllowModifications: allowModify,
...reminderDate
};
onSuccess(data);
};
const handleAutoReminder = () => {
setFormData((prev) => ({
...prev,
AutomaticReminders: !formData.AutomaticReminders
}));
};
// `handleNotifySignChange` is trigger when user change radio of notify on signatures
const handleNotifySignChange = (value) => {
setFormData((obj) => ({ ...obj, NotifyOnSignatures: value }));
};
const handleBcc = (data) => {
if (data && data.length > 0) {
const trimEmail = data.map((item) => ({
objectId: item?.value,
Name: item?.label,
Email: item?.email
}));
setFormData((prev) => ({ ...prev, Bcc: trimEmail }));
}
};
return (
<div className="max-h-[300px] md:max-h-[400px] overflow-y-scroll p-[10px]">
<div className="text-base-content">
@@ -154,119 +173,7 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
</div>
</div>
</div>
{isEnableSubscription && (
<div className="text-xs mt-2">
<span
className={
isSubscribe ? "font-semibold" : "font-semibold text-gray-300"
}
>
{t("auto-reminder")}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<label
className={`${
isSubscribe
? "cursor-pointer"
: "pointer-events-none opacity-50"
} relative block items-center mb-0`}
>
<input
checked={formData.AutomaticReminders}
onChange={handleAutoReminder}
type="checkbox"
className="op-toggle transition-all checked:[--tglbg:#3368ff] checked:bg-white mt-2"
/>
</label>
</div>
)}
{isSubscribe && formData?.AutomaticReminders === true && (
<div className="text-xs mt-2">
<label className="block">
{t("remind-once")}
<span className="text-red-500 text-[13px]">*</span>
</label>
<input
type="number"
value={formData.RemindOnceInEvery}
name="RemindOnceInEvery"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
onChange={handleStrInput}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
)}
{isEnableSubscription && (
<div className="text-xs mt-2">
<label className="block">
<span className={isSubscribe ? "" : " text-gray-300"}>
{t("isenable-otp")}{" "}
<a data-tooltip-id="isenableotp-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>{" "}
{!isSubscribe && isEnableSubscription && <Upgrade />}
</span>
<Tooltip id="isenableotp-tooltip" className="z-50">
<div className="max-w-[200px] md:max-w-[450px]">
<p className="font-bold">{t("isenable-otp")}</p>
<p>{t("isenable-otp-help.p1")}</p>
<p className="p-[5px]">
<ol className="list-disc">
<li>
<span className="font-bold">{t("yes")}: </span>
<span>{t("isenable-otp-help.p2")}</span>
</li>
<li>
<span className="font-bold">{t("no")}: </span>
<span>{t("isenable-otp-help.p3")}</span>
</li>
</ol>
</p>
<p>{t("isenable-otp-help.p4")}</p>
</div>
</Tooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1`}
>
<input
type="radio"
value={"true"}
className="op-radio op-radio-xs"
name="IsEnableOTP"
checked={formData.IsEnableOTP === "true"}
onChange={handleStrInput}
/>
<div className="text-center">{t("yes")}</div>
</div>
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1 `}
>
<input
type="radio"
value={"false"}
name="IsEnableOTP"
className="op-radio op-radio-xs"
checked={formData.IsEnableOTP === "false"}
onChange={handleStrInput}
/>
<div className="text-center">{t("no")}</div>
</div>
</div>
</div>
)}
<div className="text-xs mt-2">
<div className="text-xs mt-3">
<label className="block">
<span>
{t("enable-tour")}
@@ -322,9 +229,6 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
</div>
<div className="text-xs mt-3">
<label
className={`${
isSubscribe || !isEnableSubscription ? "" : "text-gray-300"
} block`}
>
{t("notify-on-signatures")}
<a data-tooltip-id="nos-tooltip" className="ml-1">
@@ -332,7 +236,6 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
{!isSubscribe && isEnableSubscription && <Upgrade />}
<Tooltip id="nos-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<p className="font-bold">{t("notify-on-signatures")}</p>
@@ -343,11 +246,9 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={`${
isSubscribe || !isEnableSubscription
? ""
: "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1`}
className={
"flex items-center gap-2 ml-2 mb-1"
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
@@ -358,11 +259,9 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
<div className="text-center">{t("yes")}</div>
</div>
<div
className={`${
isSubscribe || !isEnableSubscription
? ""
: "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1`}
className={
"flex items-center gap-2 ml-2 mb-1"
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
@@ -374,6 +273,27 @@ const EditTemplate = ({ template, onSuccess, jwttoken }) => {
</div>
</div>
</div>
<div className="text-xs mt-3">
<SignersInput
label={t("Bcc")}
initialData={template?.Bcc}
onChange={handleBcc}
helptextZindex={50}
helpText={t("bcc-help")}
isCaptureAllData
/>
</div>
<div className="text-xs mt-2">
<label className="block">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"
value={formData.RedirectUrl}
onChange={handleStrInput}
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
onInput={(e) => e.target.setCustomValidity("")}
/>
</div>
<div className="mt-[1rem] flex justify-start">
<button type="submit" className="op-btn op-btn-primary">
{t("submit")}
@@ -54,17 +54,20 @@ Quill.register(Font, true);
export const module1 = {
toolbar: {
container: "#toolbar1",
handlers: {
undo: undoChange,
redo: redoChange
}
handlers: { undo: undoChange, redo: redoChange }
},
history: {
delay: 500,
maxStack: 100,
userOnly: true
}
history: { delay: 500, maxStack: 100, userOnly: true }
};
// Modules object for setting up the Quill editor
export const module2 = {
toolbar: {
container: "#toolbar2",
handlers: { undo: undoChange, redo: redoChange }
},
history: { delay: 500, maxStack: 100, userOnly: true }
};
// Formats objects for setting up the Quill editor
export const formats = [
"header",
@@ -15,7 +15,6 @@ function EmailComponent({
sender,
setIsAlert,
extUserId,
activeMailAdapter,
setIsDownloadModal
}) {
const { t } = useTranslation();
@@ -31,14 +30,14 @@ function EmailComponent({
setIsLoading(true);
let sendMail;
const docId = pdfDetails?.[0]?.objectId || "";
const FileAdapterId = pdfDetails?.[0]?.FileAdapterId
? pdfDetails?.[0]?.FileAdapterId
: "";
let presignedUrl = pdfUrl;
try {
const axiosRes = await axios.post(
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
{ url: pdfUrl, docId: docId, fileAdapterId: FileAdapterId },
{
url: pdfUrl,
docId: docId,
},
{
headers: {
"content-type": "Application/json",
@@ -65,13 +64,16 @@ function EmailComponent({
const openSignUrl = "https://www.opensignlabs.com/contact-us";
const themeBGcolor = themeColor;
let params = {
mailProvider: activeMailAdapter,
extUserId: extUserId,
pdfName: pdfName,
url: presignedUrl,
recipient: emailList[i],
subject: `${sender.name} has signed the doc - ${pdfName}`,
from: sender.email,
replyto:
pdfDetails?.[0]?.ExtUserPtr?.Email ||
"",
from:
sender.email,
html:
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body> <div style='background-color:#f5f5f5;padding:20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background-color:white;'> <div><img src=" +
imgPng +
@@ -182,7 +184,7 @@ function EmailComponent({
{!isAndroid && (
<button
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
handleToPrint(e, setIsDownloading, pdfDetails)
}
className="op-btn op-btn-neutral op-btn-sm text-[15px]"
>
+54 -16
View File
@@ -3,9 +3,11 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
import { copytoData } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
function EmbedTab(props) {
const { t } = useTranslation();
const navigate = useNavigate();
const tabName = [
{ title: "React/Next.js", icon: "fa-brands fa-react", color: "#61dafb" },
{ title: "JavaScript", icon: "fa-brands fa-js", color: "#ffd43b" },
@@ -33,7 +35,7 @@ export default function App() {
<Opensign
onLoad={() => console.log("success")}
onLoadError={(error) => console.log(error)}
templateId= "${props.templateId}"
templateId= "${props.templateId ? props.templateId : "#templateId"}"
/>
</div>
);
@@ -60,7 +62,7 @@ import { OpensignComponent } from "@opensign/angular"
selector:'app-root',
standalone: true,
imports: [OpensignComponent],
template:\`<opensign templateId="${props.templateId}"
template:\`<opensign templateId="${props.templateId ? props.templateId : "#templateId"}"
(onLoad)="handleLoad()"
(onLoadError)="handleError($event)"
></opensign>\`,
@@ -79,9 +81,9 @@ export class AppComponent {
];
const jsCodeString = `
<script
src= "https://app.opensignlabs.com/static/js/public-template.bundle.js"
src= "${window.location.origin}/static/js/public-template.bundle.js"
id="opensign-script"
templateId=${props.templateId}
templateId=${props.templateId ? props.templateId : "#templateId"}
></script>
`;
@@ -92,10 +94,12 @@ export class AppComponent {
};
return (
<div className="mt-4 border-t-[1px]">
<h3 className="text-base-content font-bold text-lg pt-[15px] pb-[5px]">
{t("embed-template")}
</h3>
<div className={`${props.templateId && "border-t-[1px] mt-4"}`}>
{props.templateId && (
<h3 className="text-base-content font-bold text-lg pt-[15px] pb-[5px]">
{t("embed-template")}
</h3>
)}
<div className="flex justify-center items-center mt-2">
<div role="tablist" className="op-tabs op-tabs-bordered">
{tabName.map((tabData, ind) => (
@@ -128,7 +132,7 @@ export class AppComponent {
{t(`${data.title}`)}
</p>
{ind === 0 && (
<p className="text-[13px] mt-2">
<p className="text-[15px] mt-2">
{t("public-template-mssg-1")}
</p>
)}
@@ -155,8 +159,19 @@ export class AppComponent {
</div>
);
})}
<p className="font-medium text-[15px]">
{props.isEmbedPage && (
<p className="text-[15px] my-2">
{t("js-snippet-msg-1")}
<span
className="text-blue-600 cursor-pointer px-1"
onClick={() => navigate("/report/6TeaPr321t")}
>
{t("js-snippet-msg-2")}
</span>
<span>{t("js-snippet-msg-3")}</span>
</p>
)}
<p className="font-medium mt-3 text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
@@ -177,8 +192,8 @@ export class AppComponent {
) : activeTab === 1 ? (
<div className="mt-4">
<div>
{/* <p className="font-medium text-[18px]">{t(`${data.title}`)}</p> */}
<p className="font-medium text-[18px]">{t(`Usage`)}</p>
<p className="text-[15px] my-2">{t("js-snippet-msg")}</p>
<div className="relative p-1">
<div
onClick={() => handleCopy(jsCodeString, 0)}
@@ -199,6 +214,18 @@ export class AppComponent {
{jsCodeString}
</SyntaxHighlighter>
</div>
{props.isEmbedPage && (
<p className="text-[15px] my-2">
{t("js-snippet-msg-1")}
<span
className="text-blue-600 cursor-pointer px-1"
onClick={() => navigate("/report/6TeaPr321t")}
>
{t("js-snippet-msg-2")}
</span>
<span>{t("js-snippet-msg-3")}</span>
</p>
)}
</div>
</div>
) : (
@@ -211,7 +238,7 @@ export class AppComponent {
{t(`${data.title}`)}
</p>
{ind === 0 && (
<p className="text-[13px] mt-2">
<p className="text-[15px] mt-2">
{t("angular-npm-mssg-1")}
</p>
)}
@@ -238,8 +265,19 @@ export class AppComponent {
</div>
);
})}
<p className="font-medium text-[15px]">
{props.isEmbedPage && (
<p className="text-[15px] my-2">
{t("js-snippet-msg-1")}
<span
className="text-blue-600 cursor-pointer px-1"
onClick={() => navigate("/report/6TeaPr321t")}
>
{t("js-snippet-msg-2")}
</span>
<span>{t("js-snippet-msg-3")}</span>
</p>
)}
<p className="font-medium mt-3 text-[15px]">
{t("public-template-mssg-3")}
</p>
<p className="my-[6px]">
+263 -142
View File
@@ -1,8 +1,11 @@
import React, { useState } from "react";
import React, { useRef, useState } from "react";
import PrevNext from "./PrevNext";
import {
base64ToArrayBuffer,
deletePdfPage,
handleDownloadCertificate,
handleDownloadPdf,
handleRemoveWidgets,
handleToPrint
} from "../../constant/Utils";
import "../../styles/signature.css";
@@ -10,65 +13,92 @@ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import ModalUi from "../../primitives/ModalUi";
import Loader from "../../primitives/Loader";
import { useTranslation } from "react-i18next";
import { PDFDocument } from "pdf-lib";
function Header({
isPdfRequestFiles,
isPlaceholder,
setIsDecline,
pageNumber,
allPages,
changePage,
pdfUrl,
embedWidgetsData,
pdfDetails,
signerPos,
signersdata,
isMailSend,
alertSendEmail,
isCompleted,
isShowHeader,
decline,
currentSigner,
alreadySign,
isSignYourself,
setIsEmail,
completeBtnTitle,
setIsEditTemplate,
isPublicTemplate,
clickOnZoomIn,
clickOnZoomOut,
handleRotationFun,
isDisableRotate,
templateId,
setIsDownloadModal,
disabledBackBtn
}) {
function Header(props) {
const { t } = useTranslation();
const filterPrefill =
signerPos && signerPos?.filter((data) => data.Role !== "prefill");
props?.signerPos &&
props?.signerPos?.filter((data) => data.Role !== "prefill");
const isMobile = window.innerWidth < 767;
const [isDownloading, setIsDownloading] = useState("");
const isGuestSigner = localStorage.getItem("isGuestSigner");
const enabledBackBtn = disabledBackBtn === true ? false : true;
const [isDeletePage, setIsDeletePage] = useState(false);
const mergePdfInputRef = useRef(null);
const enabledBackBtn = props?.disabledBackBtn === true ? false : true;
//function for show decline alert
const handleDeclinePdfAlert = async () => {
const currentDecline = { currnt: "Sure", isDeclined: true };
setIsDecline(currentDecline);
props?.setIsDecline(currentDecline);
};
const handleDetelePage = async () => {
props?.setIsUploadPdf && props?.setIsUploadPdf(true);
const pdfupdatedData = await deletePdfPage(
props?.pdfArrayBuffer,
props?.pageNumber
);
if (pdfupdatedData?.totalPages === 1) {
alert(t("delete-alert"));
} else {
props?.setPdfBase64Url(pdfupdatedData.base64);
props?.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
setIsDeletePage(false);
handleRemoveWidgets(
props?.setSignerPos,
props?.signerPos,
props?.pageNumber
);
}
};
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) {
alert("Please upload a valid PDF file.");
return;
}
if (!file.type.includes("pdf")) {
alert("Only PDF files are allowed.");
return;
}
try {
const uploadedPdfBytes = await file.arrayBuffer();
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
ignoreEncryption: true
});
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
// Copy pages from the uploaded PDF to the base PDF
const uploadedPdfPages = await basePdfDoc.copyPages(
uploadedPdfDoc,
uploadedPdfDoc.getPageIndices()
);
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
// Save the updated PDF
const pdfBase64 = await basePdfDoc.saveAsBase64({
useObjectStreams: false
});
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
props.setPdfArrayBuffer(pdfBuffer);
props.setPdfBase64Url(pdfBase64);
props.setIsUploadPdf && props.setIsUploadPdf(true);
mergePdfInputRef.current.value = "";
} catch (error) {
mergePdfInputRef.current.value = "";
console.error("Error merging PDF:", error);
}
};
return (
<div className="flex py-[5px]">
{isMobile && isShowHeader ? (
{isMobile && props?.isShowHeader ? (
<div
id="navbar"
className={isGuestSigner ? "stickySignerHead" : "stickyHead"}
className="stickyHead"
style={{
width: isGuestSigner
? window.innerWidth
: window.innerWidth - 30 + "px"
width: window.innerWidth + "px"
}}
>
<div className="flex justify-between items-center py-[5px] px-[10px] ">
<div className="flex justify-between items-center py-[5px] pl-[10px] ">
<div onClick={() => window.history.go(-2)}>
<i
className="fa-light fa-arrow-left text-base-content"
@@ -76,14 +106,14 @@ function Header({
></i>
</div>
<PrevNext
pageNumber={pageNumber}
allPages={allPages}
changePage={changePage}
pageNumber={props?.pageNumber}
allPages={props?.allPages}
changePage={props?.changePage}
/>
{isCompleted || alreadySign ? (
{props?.isCompleted || props?.alreadySign ? (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<div className="op-link op-link-primary no-underline text-[16px] font-semibold pr-[3px] pl-[5px]">
<div className="op-link op-link-primary no-underline text-[16px] font-semibold px-3">
<i
className="fa-light fa-ellipsis-v"
aria-hidden="true"
@@ -98,10 +128,14 @@ function Header({
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => {
if (isCompleted) {
setIsDownloadModal(true);
if (props?.isCompleted) {
props?.setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
);
}
}}
>
@@ -113,12 +147,12 @@ function Header({
{t("download")}
</div>
</DropdownMenu.Item>
{isCompleted && (
{props?.isCompleted && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
handleDownloadCertificate(
pdfDetails,
props?.pdfDetails,
setIsDownloading
)
}
@@ -132,10 +166,10 @@ function Header({
</div>
</DropdownMenu.Item>
)}
{isSignYourself && (
{props?.isSignYourself && (
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => setIsEmail(true)}
onClick={() => props?.setIsEmail(true)}
>
<div className="flex flex-row">
<i
@@ -149,7 +183,7 @@ function Header({
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
handleToPrint(e, setIsDownloading, props?.pdfDetails)
}
>
<div className="flex flex-row">
@@ -168,9 +202,9 @@ function Header({
{/* current signer is checking user send request and check status of pdf sign than if current
user exist than show finish button else no
*/}
{currentSigner && (
{props?.currentSigner && (
<div className="flex items-center" data-tut="reactourFifth">
{decline && (
{props?.decline && (
<div
onClick={() => handleDeclinePdfAlert()}
className="text-[red] border-none font-[650] text-[14px] mr-2"
@@ -178,32 +212,41 @@ function Header({
{t("decline")}
</div>
)}
{isPlaceholder ? (
{props?.isPlaceholder ? (
<div
onClick={() => {
if (!isMailSend) {
alertSendEmail();
if (!props?.isMailSend) {
props?.alertSendEmail();
}
}}
className={`${
isMailSend ? "" : "op-link-primary"
props?.isMailSend ? "" : "op-link-primary"
} op-link no-underline font-[650] text-[14px]`}
data-tut="headerArea"
>
{completeBtnTitle ? completeBtnTitle : t("send")}
{props?.completeBtnTitle
? props?.completeBtnTitle
: t("send")}
</div>
) : (
<div
data-tut="reactourThird"
onClick={() => embedWidgetsData()}
onClick={() => props?.embedWidgetsData()}
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
>
{t("finish")}
</div>
)}
<input
type="file"
className="hidden"
accept="application/pdf"
ref={mergePdfInputRef}
onChange={handleFileUpload}
/>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<div className="font-[650] text-[18px] px-2 ml-[4px] text-base-content no-underline">
<div className="font-[650] text-[18px] px-3 text-base-content no-underline">
<i
className="fa-light fa-ellipsis-v"
aria-hidden="true"
@@ -218,7 +261,11 @@ function Header({
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() =>
handleDownloadPdf(pdfDetails, setIsDownloading)
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
)
}
>
<div className="flex flex-row">
@@ -231,11 +278,34 @@ function Header({
</span>
</div>
</DropdownMenu.Item>
{!isDisableRotate && (
{!props?.isDisablePdfEditTools && (
<>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleRotationFun(90)}
onClick={() => mergePdfInputRef.current.click()}
>
<div className="flex flex-row">
<i className="fa-light fa-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("add-pages")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => setIsDeletePage(true)}
>
<div className="flex flex-row">
<i className="fa-light fa-trash text-gray-500 2xl:text-[30px] mr-[3px]"></i>
<span className="font-[500]">
{t("delete-page")}
</span>
</div>
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => props?.handleRotationFun(90)}
>
<div className="flex flex-row">
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px] mr-[3px]"></i>
@@ -246,7 +316,7 @@ function Header({
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => handleRotationFun(-90)}
onClick={() => props?.handleRotationFun(-90)}
>
<div className="flex flex-row">
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px] mr-[3px]"></i>
@@ -260,7 +330,7 @@ function Header({
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => clickOnZoomIn()}
onClick={() => props?.clickOnZoomIn()}
>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
@@ -269,7 +339,7 @@ function Header({
</DropdownMenu.Item>
<DropdownMenu.Item
className="DropdownMenuItem"
onClick={() => clickOnZoomOut()}
onClick={() => props?.clickOnZoomOut()}
>
<div className="flex flex-row">
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px] mr-[3px]"></i>
@@ -283,11 +353,11 @@ function Header({
</DropdownMenu.Root>
</div>
)}
{isPublicTemplate && (
{props?.isPublicTemplate && (
<div
data-tut="reactourThird"
onClick={() => embedWidgetsData()}
className="border-none font-[650] text-[14px] op-link op-link-primary no-underline"
onClick={() => props?.embedWidgetsData()}
className="border-none font-[650] text-[14px] pr-2 op-link op-link-primary no-underline"
>
{t("sign-now")}
</div>
@@ -299,25 +369,27 @@ function Header({
) : (
<div className="flex flex-wrap justify-between items-center w-full gap-y-1 ml-1">
<PrevNext
pageNumber={pageNumber}
allPages={allPages}
changePage={changePage}
pageNumber={props?.pageNumber}
allPages={props?.allPages}
changePage={props?.changePage}
/>
{isPlaceholder ? (
{props?.isPlaceholder ? (
<>
<div className="flex mx-[100px] lg:mx-0 order-last lg:order-none">
{!isMailSend &&
signersdata.length > 0 &&
signersdata.length !== filterPrefill.length && (
{!props?.isMailSend &&
props?.signersdata.length > 0 &&
props?.signersdata.length !== filterPrefill.length && (
<div>
{filterPrefill.length === 0 ? (
<span className="text-[13px] text-[#f5405e]">
{t("add")} {signersdata.length - filterPrefill.length}{" "}
{t("add")}{" "}
{props?.signersdata.length - filterPrefill.length}{" "}
{t("recipients")} {t("widgets-name.signature")}
</span>
) : (
<span className="text-[13px] text-[#f5405e]">
{t("add")} {signersdata.length - filterPrefill.length}{" "}
{t("add")}{" "}
{props?.signersdata.length - filterPrefill.length}{" "}
{t("more")}
{t("recipients")} {t("widgets-name.signature")}
</span>
@@ -326,9 +398,9 @@ function Header({
)}
</div>
<div className="flex">
{setIsEditTemplate && (
{props?.setIsEditTemplate && (
<button
onClick={() => setIsEditTemplate(true)}
onClick={() => props?.setIsEditTemplate(true)}
className="outline-none border-none text-center mr-[3px]"
>
<i className="fa-light fa-gear fa-lg"></i>
@@ -344,25 +416,25 @@ function Header({
</button>
)}
<button
disabled={isMailSend && true}
disabled={props?.isMailSend && true}
data-tut="headerArea"
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => alertSendEmail()}
onClick={() => props?.alertSendEmail()}
>
{completeBtnTitle
? completeBtnTitle
: isMailSend
{props?.completeBtnTitle
? props?.completeBtnTitle
: props?.isMailSend
? t("sent")
: t("send")}
</button>
</div>
</>
) : isPdfRequestFiles ? (
alreadySign ? (
) : props?.isPdfRequestFiles || props?.isSelfSign ? (
props?.alreadySign || (props?.isSelfSign && props?.isCompleted) ? (
<div className="flex flex-row">
<button
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
handleToPrint(e, setIsDownloading, props?.pdfDetails)
}
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
@@ -373,11 +445,14 @@ function Header({
></i>
<span className="hidden lg:block">{t("print")}</span>
</button>
{isCompleted && (
{props?.isCompleted && (
<button
type="button"
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsDownloading)
handleDownloadCertificate(
props?.pdfDetails,
setIsDownloading
)
}
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
>
@@ -392,10 +467,14 @@ function Header({
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => {
if (isCompleted) {
setIsDownloadModal(true);
if (props?.isCompleted) {
props?.setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading);
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
);
}
}}
>
@@ -408,21 +487,26 @@ function Header({
</div>
) : (
<div className="flex" data-tut="reactourFifth">
{!templateId && (
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
{t("back")}
</button>
)}
{currentSigner && (
{(!props?.templateId && !props?.isSelfSign) ||
(!props.isGuestSignFlow && (
<button
onClick={() => window.history.go(-2)}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
>
{t("back")}
</button>
))}
{props?.currentSigner && (
<>
{templateId && (
{props?.templateId && (
<button
onClick={() =>
handleDownloadPdf(pdfDetails, setIsDownloading)
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
)
}
type="button"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
@@ -430,42 +514,51 @@ function Header({
<span className="hidden lg:block">{t("download")}</span>
</button>
)}
<button
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
onClick={() => handleDeclinePdfAlert()}
>
{t("decline")}
</button>
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => embedWidgetsData()}
>
{t("finish")}
</button>
{!templateId && (
{!props?.isSelfSign && (
<button
className="op-btn op-btn-secondary op-btn-sm mr-[3px] shadow"
onClick={() => handleDeclinePdfAlert()}
>
{t("decline")}
</button>
)}
{!props?.templateId && (
<button
type="button"
className="op-btn op-btn-neutral op-btn-sm mr-[3px] shadow"
className="op-btn op-btn-ghost op-btn-sm mr-[3px]"
onClick={() =>
handleDownloadPdf(pdfDetails, setIsDownloading)
handleDownloadPdf(
props?.pdfDetails,
setIsDownloading,
props.pdfBase64
)
}
>
<i className="fa-light fa-arrow-down font-semibold lg:hidden"></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
)}
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px] shadow"
onClick={() => props?.embedWidgetsData()}
>
{t("finish")}
</button>
</>
)}
</div>
)
) : isCompleted ? (
) : props?.isCompleted ? (
<div className="flex flex-row">
{isCompleted && (
{props?.isCompleted && (
<button
type="button"
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsDownloading)
handleDownloadCertificate(
props?.pdfDetails,
setIsDownloading
)
}
className="op-btn op-btn-secondary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
>
@@ -477,7 +570,7 @@ function Header({
)}
<button
onClick={(e) =>
handleToPrint(e, pdfUrl, setIsDownloading, pdfDetails)
handleToPrint(e, setIsDownloading, props?.pdfDetails)
}
type="button"
className="op-btn op-btn-neutral op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
@@ -488,9 +581,7 @@ function Header({
<button
type="button"
className="op-btn op-btn-primary op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
onClick={() => {
setIsDownloadModal(true);
}}
onClick={() => props?.setIsDownloadModal(true)}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("download")}</span>
@@ -498,18 +589,18 @@ function Header({
<button
type="button"
className="op-btn op-btn-info op-btn-sm gap-0 font-medium text-[12px] mr-[3px] shadow"
onClick={() => setIsEmail(true)}
onClick={() => props?.setIsEmail(true)}
>
<i className="fa-light fa-envelope" aria-hidden="true"></i>
<span className="hidden lg:block ml-1">{t("mail")}</span>
</button>
</div>
) : isPublicTemplate ? (
) : props?.isPublicTemplate ? (
<div className="flex">
<button
type="button"
className="op-btn op-btn-primary op-btn-sm shadow"
onClick={() => embedWidgetsData()}
onClick={() => props?.embedWidgetsData()}
>
{t("sign-now")}
</button>
@@ -526,7 +617,7 @@ function Header({
<button
type="button"
className="op-btn op-btn-primary op-btn-sm mr-[3px]"
onClick={() => embedWidgetsData()}
onClick={() => props?.embedWidgetsData()}
>
{t("finish")}
</button>
@@ -540,17 +631,47 @@ function Header({
</div>
)}
<ModalUi
isOpen={isDownloading === "certificate"}
isOpen={
isDownloading === "certificate" || isDownloading === "certificate_err"
}
title={
isDownloading === "certificate"
isDownloading === "certificate" || isDownloading === "certificate_err"
? t("generating-certificate")
: t("pdf-download")
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
{isDownloading === "certificate"}{" "}
<p>{t("generate-certificate-alert")}</p>
{isDownloading === "certificate" ? (
<p>{t("generate-certificate-alert")}</p>
) : (
<p>{t("generate-certificate-err")}</p>
)}
</div>
</ModalUi>
<ModalUi
isOpen={isDeletePage}
title={t("delete-page")}
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>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<button
onClick={() => handleDetelePage()}
type="button"
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={() => setIsDeletePage(false)}
type="button"
className="op-btn op-btn-ghost"
>
{t("no")}
</button>
</div>
</ModalUi>
</div>
+165 -36
View File
@@ -1,47 +1,176 @@
import React from "react";
import React, { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
base64ToArrayBuffer,
deletePdfPage,
handleRemoveWidgets
} from "../../constant/Utils";
import ModalUi from "../../primitives/ModalUi";
import { PDFDocument } from "pdf-lib";
function PdfZoom(props) {
const { t } = useTranslation();
const mergePdfInputRef = useRef(null);
const [isDeletePage, setIsDeletePage] = useState(false);
const handleDetelePage = async () => {
props.setIsUploadPdf && props.setIsUploadPdf(true);
try {
const pdfupdatedData = await deletePdfPage(
props.pdfArrayBuffer,
props.pageNumber
);
if (pdfupdatedData?.totalPages === 1) {
alert(t("delete-alert"));
} else {
props.setPdfBase64Url(pdfupdatedData.base64);
props.setPdfArrayBuffer(pdfupdatedData.arrayBuffer);
setIsDeletePage(false);
handleRemoveWidgets(
props.setSignerPos,
props.signerPos,
props.pageNumber
);
props.setAllPages(pdfupdatedData.remainingPages || 1);
if (props.allPages === props.pageNumber) {
props.setPageNumber(props.pageNumber - 1);
} else if (props.allPages > 2) {
props.setPageNumber(props.pageNumber);
}
}
} catch (e) {
console.log("error in delete pdf page", e);
}
};
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) {
alert("Please upload a valid PDF file.");
return;
}
if (!file.type.includes("pdf")) {
alert("Only PDF files are allowed.");
return;
}
try {
const uploadedPdfBytes = await file.arrayBuffer();
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
ignoreEncryption: true
});
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
// Copy pages from the uploaded PDF to the base PDF
const uploadedPdfPages = await basePdfDoc.copyPages(
uploadedPdfDoc,
uploadedPdfDoc.getPageIndices()
);
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
// Save the updated PDF
const pdfBase64 = await basePdfDoc.saveAsBase64({
useObjectStreams: false
});
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
props.setPdfArrayBuffer(pdfBuffer);
props.setPdfBase64Url(pdfBase64);
props.setIsUploadPdf && props.setIsUploadPdf(true);
mergePdfInputRef.current.value = "";
} catch (error) {
mergePdfInputRef.current.value = "";
console.error("Error merging PDF:", error);
}
};
return (
<span className="hidden md:flex flex-col gap-1 text-center md:w-[5%] mt-[43px] 2xl:mt-[63px]">
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={() => props.clickOnZoomIn()}
title={t("zoom-in")}
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[30px]"></i>
<>
<span className="hidden 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"
onClick={() => mergePdfInputRef.current.click()}
title={t("add-pages")}
>
<input
type="file"
className="hidden"
accept="application/pdf"
ref={mergePdfInputRef}
onChange={handleFileUpload}
/>
<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)}
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={() => props.clickOnZoomIn()}
title={t("zoom-in")}
>
<i className="fa-light fa-magnifying-glass-plus text-gray-500 2xl:text-[25px]"></i>
</span>
{!props.isDisableEditTools && (
<>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
onClick={() => props.handleRotationFun(90)}
title={t("rotate-right")}
>
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[25px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px] cursor-pointer"
title={t("rotate-left")}
onClick={() => props.handleRotationFun(-90)}
>
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[25px]"></i>
</span>
</>
)}
<span
className="bg-gray-50 px-[4px] 2xl:py-[10px]"
onClick={() => props.clickOnZoomOut()}
style={{
cursor: props.zoomPercent > 0 ? "pointer" : "default"
}}
title={t("zoom-out")}
>
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
</span>
</span>
{!props?.isDisableRotate && (
<>
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
onClick={() => props.handleRotationFun(90)}
title={t("rotate-right")}
>
<i className="fa-light fa-rotate-right text-gray-500 2xl:text-[30px]"></i>
</span>
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px] cursor-pointer"
title={t("rotate-left")}
onClick={() => props.handleRotationFun(-90)}
>
<i className="fa-light fa-rotate-left text-gray-500 2xl:text-[30px]"></i>
</span>
</>
)}
<span
className="bg-gray-50 px-[4px] 2xl:px-[15px] 2xl:py-[10px]"
onClick={() => props.clickOnZoomOut()}
style={{
cursor: props.zoomPercent > 0 ? "pointer" : "default"
}}
title={t("zoom-out")}
<ModalUi
isOpen={isDeletePage}
title={t("delete-page")}
handleClose={() => setIsDeletePage(false)}
>
<i className="fa-light fa-magnifying-glass-minus text-gray-500 2xl:text-[30px]"></i>
</span>
</span>
<div className="h-[100%] p-[20px]">
<p className="font-medium">{t("delete-alert-2")}</p>
<p className="pt-3">{t("delete-note")}</p>
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
<button
onClick={() => handleDetelePage()}
type="button"
className="op-btn op-btn-primary"
>
{t("yes")}
</button>
<button
onClick={() => setIsDeletePage(false)}
type="button"
className="op-btn op-btn-ghost ml-1"
>
{t("no")}
</button>
</div>
</ModalUi>
</>
);
}
+377 -199
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import BorderResize from "./BorderResize";
import PlaceholderBorder from "./PlaceholderBorder";
import { Rnd } from "react-rnd";
@@ -8,7 +8,8 @@ import {
fontsizeArr,
getContainerScale,
handleCopyNextToWidget,
isTabAndMobile,
handleCopySignUrl,
handleHeighlightWidget,
onChangeInput,
radioButtonWidget,
textInputWidget,
@@ -93,11 +94,16 @@ const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
function Placeholder(props) {
//'isTouchDevice' is used to detect whether a device has a touchscreen or is mouse-based
const isTouchDevice = navigator.maxTouchPoints > 0;
const { t } = useTranslation();
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
const [isDraggingEnabled, setDraggingEnabled] = useState(true);
const [isDateModal, setIsDateModal] = useState(false);
const [containerScale, setContainerScale] = useState();
const holdTimeout = useRef(null);
const startTime = useRef(null); // Track when the user starts holdings
const [isDisableDragging, setIsDisableDragging] = useState(true);
const [selectDate, setSelectDate] = useState({
date:
props.pos.type === "date"
@@ -128,7 +134,6 @@ function Placeholder(props) {
width: null,
height: null
});
useEffect(() => {
const getPdfPageWidth = props.pdfOriginalWH.find(
(data) => data.pageNumber === props.pageNumber
@@ -164,17 +169,17 @@ function Placeholder(props) {
}, [props.pos]);
useEffect(() => {
const closeMenuOnOutsideClick = (e) => {
if (!isDraggingEnabled && !e.target.closest("#changeIsDragging")) {
const onOutsideClick = () => {
if (!isDraggingEnabled) {
setDraggingEnabled(true);
}
};
document.addEventListener("click", closeMenuOnOutsideClick);
document.addEventListener("click", onOutsideClick);
return () => {
// Cleanup the event listener when the component unmounts
document.removeEventListener("click", closeMenuOnOutsideClick);
document.removeEventListener("click", onOutsideClick);
};
}, [isDraggingEnabled]);
//function change format array list with selected date and format
@@ -200,7 +205,7 @@ function Placeholder(props) {
};
useEffect(() => {
if (props.isPlaceholder || props.isSignYourself) {
if (props.isPlaceholder || props.isSignYourself || props.isSelfSign) {
selectDate && changeDateFormat();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -226,29 +231,34 @@ function Placeholder(props) {
if (widgetTypeExist) {
setDraggingEnabled(false);
}
if ((props.isNeedSign || props.isSignYourself) && !props.isDragging) {
if (props.isOpenSignPad && !props.isDragging) {
if (props?.ispublicTemplate) {
props.handleUserDetails();
} else {
if (props.pos.type) {
if (
props.pos.type === "signature" ||
props.pos.type === "stamp" ||
props.pos.type === "image"
) {
props.setIsSignPad(true);
props.setSignKey(props.pos.key);
props.setIsStamp(props.pos.isStamp);
} else if (props.pos.type === "initials") {
props.setIsSignPad(true);
props.setSignKey(props.pos.key);
props.setIsStamp(props.pos.isStamp);
props.setIsInitial(true);
}
} else {
if (props?.isNeedSign) {
//funcion is used to height widgets on top if two widgets on overlap
const getCurrentSignerPos = props.xyPosition.find(
(x) => x.Id === props.uniqueId
);
const updateZindex = handleHeighlightWidget(
getCurrentSignerPos,
props.pos.key,
props.pageNumber
);
const updatesignerPos = props.xyPosition.map((x) =>
x.Id === props.uniqueId ? { ...x, placeHolder: updateZindex } : x
);
props.setXyPosition(updatesignerPos);
}
if (
["signature", "stamp", "image", "initials"].includes(props.pos.type)
) {
props.setIsSignPad(true);
props.setSignKey(props.pos.key);
props.setIsStamp(props.pos?.isStamp ? props.pos.isStamp : false);
props.setIsStamp(props.pos.isStamp);
}
if (props.pos.type === "initials") {
props.setIsInitial(true);
}
}
} else if (
@@ -259,7 +269,7 @@ function Placeholder(props) {
if (props.pos.key === props.selectWidgetId) {
props.handleLinkUser(props.data.Id);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
const checkIndex = props.xyPosition.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
@@ -285,20 +295,95 @@ function Placeholder(props) {
}
}
};
const handleOnClickPlaceholder = () => {
const widgetClickHandler = () => {
//The else condition opens the signature pad if it's a request signature flow and the user clicking is identified as a signer.
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
if (!props.isNeedSign) {
props.setWidgetType(props.pos.type);
if (props?.data?.signerObjId === props?.signerObjId && !props.isDragging) {
if (!props.isAgree && !props.isSelfSign) {
props.setIsAgreeTour && props.setIsAgreeTour(true);
} else {
handleWidgetIdandPopup();
}
} //condition for open contact details popup when sign templet from public signing flow
else if (props?.uniqueId && props.data?.Id === props?.uniqueId) {
handleWidgetIdandPopup();
}
if (props.isNeedSign && props.data?.signerObjId === props.signerObjId) {
handleWidgetIdandPopup();
} else if (
props?.isNeedSign &&
props?.uniqueId &&
props.data?.Id === props?.uniqueId
) {
handleWidgetIdandPopup();
} else if (props.isPlaceholder || props.isSignYourself) {
};
const handleOnClickPlaceholder = () => {
//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);
props.setWidgetType(props.pos.type);
handleWidgetIdandPopup();
}
};
@@ -321,7 +406,7 @@ function Placeholder(props) {
"job title",
"email"
].includes(props.pos.type) &&
props.isSignYourself
(props.isSignYourself || props.isSelfSign)
) {
props.handleTextSettingModal(true);
} else {
@@ -331,14 +416,14 @@ function Placeholder(props) {
//condition for only placeholder and template flow
if (props.data && props?.pos?.type !== textWidget) {
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion
const checkIndex = props.xyPosition
.filter((data) => data.Role !== "prefill")
.findIndex((data) => data.Id === props.data.Id);
props.setIsSelectId(checkIndex || 0);
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) {
else if (props.data && props.pos.type === textWidget && !props.isSelfSign) {
props.setTempSignerId(props.uniqueId);
props.setUniqueId(props?.data?.Id);
}
@@ -356,18 +441,22 @@ function Placeholder(props) {
if (props.data && props?.pos?.type !== textWidget) {
props.setUniqueId(props?.data?.Id);
const checkIndex = props.xyPostion
const checkIndex = props.xyPosition
.filter((data) => data.Role !== "prefill")
.findIndex((data) => data.Id === props.data.Id);
props.setIsSelectId(checkIndex || 0);
} else if (props.data && props.pos.type === textWidget) {
props.setIsSelectId && props.setIsSelectId(checkIndex || 0);
} else if (
props.data &&
props.pos.type === textWidget &&
!props.isSelfSign
) {
props.setTempSignerId(props.uniqueId);
props.setUniqueId(props?.data?.Id);
}
//checking widget's type and open widget copy modal for required widgets
if (
["signature", textWidget, "stamp", "initial"].includes(props.pos.type)
["signature", textWidget, "stamp", "initials"].includes(props.pos.type)
) {
props.setIsPageCopy(true);
props.setSignKey(props.pos.key);
@@ -376,14 +465,26 @@ function Placeholder(props) {
handleCopyNextToWidget(
props.pos,
props.pos.type,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data?.Id
);
}
};
//function to save date and format after seleted new date in response field and after finish document it should be emebed new selected date instead of current date
useEffect(() => {
if (props.pos.type === "date") {
const isDateChange = true;
const dateObj = {
date: startDate,
format: selectDate.format
};
handleSaveDate(dateObj, isDateChange); //function to save date and format in local array
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [startDate]);
//function to save date and format on local array onchange date and onclick format
const handleSaveDate = (data, isDateChange) => {
let updateDate = data.date;
@@ -404,9 +505,9 @@ function Placeholder(props) {
onChangeInput(
date,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data.Id,
false,
data?.format,
@@ -418,10 +519,18 @@ function Placeholder(props) {
setSelectDate({ date: date, format: data?.format });
};
const PlaceholderIcon = () => {
// 1- If props.isShowBorder is true, display border's icon for all widgets. OR
// 2- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show border's icon:
// 1- When isAlllowModify is true, show border's icon.
// 2- Do not display border's icon for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
return (
props.isShowBorder && (
(props.isShowBorder ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key))) && (
<>
{(props.isPlaceholder || props.isSignYourself) && (
{(props.isPlaceholder ||
props.isSignYourself ||
props.isSelfSign) && (
<>
{/* condition to add setting icon for signyour-self flow for particular text widgets
and also it have diffrent position
@@ -434,7 +543,8 @@ function Placeholder(props) {
"company",
"job title",
"email"
].includes(props.pos.type) && props.isSignYourself ? (
].includes(props.pos.type) &&
(props.isSignYourself || props.isSelfSign) ? (
<i
onClick={(e) => {
e.stopPropagation();
@@ -452,7 +562,8 @@ function Placeholder(props) {
((!props?.pos?.type && props.pos.isStamp) ||
(props?.pos?.type &&
!["date"].includes(props.pos.type) &&
!props.isSignYourself)) && (
!props.isSignYourself &&
!props.isSelfSign)) && (
<i
onClick={(e) => {
e.stopPropagation();
@@ -472,31 +583,33 @@ function Placeholder(props) {
)
)}
{/* condition for usericon for all widgets except text widgets and signyour-self flow */}
{props.pos.type !== textWidget && !props.isSignYourself && (
<i
data-tut="assignSigner"
className="fa-light fa-user icon"
onClick={(e) => {
e.stopPropagation();
props.handleLinkUser(props.data.Id);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}}
onTouchEnd={(e) => {
e.stopPropagation();
props.handleLinkUser(props.data.Id);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
}}
style={{ color: "#188ae2", right: "32px", top: "-18px" }}
></i>
)}
{props.pos.type !== textWidget &&
!props.isSignYourself &&
!props.isSelfSign && (
<i
data-tut="assignSigner"
className="fa-light fa-user icon"
onClick={(e) => {
e.stopPropagation();
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);
}}
onTouchEnd={(e) => {
e.stopPropagation();
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);
}}
style={{ color: "#188ae2", right: "32px", top: "-18px" }}
></i>
)}
</>
)}
{/* setting icon only for date widgets */}
@@ -510,7 +623,7 @@ function Placeholder(props) {
if (props.data) {
props.setSignKey(props.pos.key);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
const checkIndex = props.xyPosition.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
@@ -523,7 +636,7 @@ function Placeholder(props) {
if (props.data) {
props.setSignKey(props.pos.key);
props.setUniqueId(props.data.Id);
const checkIndex = props.xyPostion.findIndex(
const checkIndex = props.xyPosition.findIndex(
(data) => data.Id === props.data.Id
);
props.setIsSelectId(checkIndex || 0);
@@ -647,7 +760,7 @@ function Placeholder(props) {
const fontSize = (size || 12) * containerScale * props.scale;
//isMinHeight to set text box minimum height
if (isMinHeight) {
return fontSize * 1.2 + "px";
return fontSize * 1.5 + "px";
} else {
return fontSize + "px";
}
@@ -666,7 +779,95 @@ function Placeholder(props) {
return "all-scroll";
}
};
const handleDragging = () => {
//condition for request signing flow
if (props.isNeedSign) {
//enable dragging functionality only if isAlllowModify true on tab that widget and hold 1sec
if (
props.isAlllowModify &&
props?.assignedWidgetId.includes(props.pos.key)
) {
//if 'isTouchDevice' then handle dragging functionality conditionaly
if (isTouchDevice) {
return isDisableDragging;
} else {
//no need to handle dragging functionality it auto enable and working or mouse click devices
return false;
}
} else if (
//condition when 'isAlllowModify' is true and user add new widgets then handle dragging functionality like signyourself flow
props.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key)
) {
return !isDraggingEnabled;
} else {
//if 'isAlllowModify' is false then disbale dragging functionality
return true;
}
} //dragging enable in placeholder,template and not text widget flow
else if (props.isPlaceholder && ![textWidget].includes(props.pos.type)) {
return false;
} //dragging depend on 'isDraggingEnabled' variable in self sign and signyourself flow
else {
return !isDraggingEnabled;
}
};
//function to handle widget background color
const handleBackground = () => {
if (props.data) {
if (props.isNeedSign) {
if (props.data?.Id === props?.uniqueId) {
return props.data?.blockColor + "b0";
} else {
return "#dedddc";
}
} else {
return props.data?.blockColor + "b0";
}
} else {
return "rgba(203, 233, 237, 0.69)";
}
};
const handleTouchEnd = () => {
if (!props.isNeedSign || props.isAlllowModify) {
const holdDuration = Date.now() - startTime.current; // Calculate hold time
clearTimeout(holdTimeout.current); // Cancel timeout if touch ended early
if (holdDuration < 1000) {
try {
navigator.vibrate([]); // Cancel any ongoing vibration
} catch (e) {
console.log("error in navigator.vibrate", e);
}
}
}
if (!props.isNeedSign || props.isAlllowModify) handleOnClickPlaceholder();
};
const handleTouchStart = () => {
clearTimeout(holdTimeout.current); // Ensure no previous timeouts are running
startTime.current = Date.now(); // Store touch start time
holdTimeout.current = setTimeout(() => {
//handlle vibration and tab any widget and hold for 1 sec then show border outside widget and then user can able to drag
if (isDisableDragging) {
if (
props.isNeedSign &&
props.isAlllowModify &&
props?.assignedWidgetId.includes(props.pos.key)
) {
try {
navigator.vibrate(200); // Vibrate for 200ms
} catch (e) {
console.log("error in navigator.vibrate", e);
}
setIsDisableDragging(false);
props.setSelectWidgetId(props.pos.key);
} else if (!props.isNeedSign) {
setIsDisableDragging(false);
}
}
}, 1000); // Hold for 1 second before vibrating
};
return (
<>
<Rnd
@@ -674,6 +875,7 @@ function Placeholder(props) {
data-tut={props.pos.key === props.unSignedWidgetId ? "IsSigned" : ""}
key={props.pos.key}
lockAspectRatio={
!props.isFreeResize &&
![
textWidget,
"email",
@@ -712,11 +914,6 @@ function Placeholder(props) {
style={{
border: "1px solid #007bff",
borderRadius: "2px",
textAlign:
props.pos.type !== "name" &&
props.pos.type !== "company" &&
props.pos.type !== "job title" &&
"center",
cursor: getCursor(),
zIndex:
props.pos.type === "date"
@@ -726,9 +923,9 @@ function Placeholder(props) {
: props?.pos?.zIndex
? props.pos.zIndex
: "5",
background: props.data
? props.data?.blockColor + "b0"
: "rgba(203, 233, 237, 0.69)"
opacity:
props.isNeedSign && props.data?.Id !== props?.uniqueId && "0.4",
background: handleBackground()
}}
onDrag={() => {
setDraggingEnabled(true);
@@ -760,8 +957,8 @@ function Placeholder(props) {
props.handleSignYourselfImageResize(
ref,
props.pos.key,
props.xyPostion,
props.setXyPostion,
props.xyPosition,
props.setXyPosition,
props.index,
containerScale,
props.scale,
@@ -769,17 +966,17 @@ function Placeholder(props) {
props.isResize
);
}}
disableDragging={
props.isNeedSign
? true
: props.isPlaceholder && ![textWidget].includes(props.pos.type)
? false
: !isDraggingEnabled
}
onDragStop={(event, dragElement) =>
disableDragging={handleDragging()}
onDragStop={(event, dragElement) => {
setIsDisableDragging(true);
props.handleStop &&
props.handleStop(event, dragElement, props.data?.Id, props.pos?.key)
}
props.handleStop(
event,
dragElement,
props.data?.Id,
props.pos?.key
);
}}
position={{
x: xPos(props.pos, props.isSignYourself),
y: yPos(props.pos, props.isSignYourself)
@@ -790,125 +987,106 @@ function Placeholder(props) {
h: ref.offsetHeight / (props.scale * containerScale)
});
}}
onClick={() => !props.isResize && handleOnClickPlaceholder()}
// onClick={() =>
// !props.isResize && !isMobile && handleOnClickPlaceholder()
// }
>
{props.isShowBorder &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
props.pos.key === props.selectWidgetId ? (
{props.pos.key === props.selectWidgetId &&
((props.isShowBorder &&
![radioButtonWidget, "checkbox"].includes(props.pos.type)) ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key))) ? (
<BorderResize
right={-12}
top={-11}
pos={props.pos}
posHeight={props.posHeight}
isSignYourself={props.isSignYourself}
isSignYourself={props.isSignYourself || props.isSelfSign}
/>
) : props.data && props.isNeedSign && props.pos.type !== "checkbox" ? (
props.data?.signerObjId === props.signerObjId &&
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" ? (
![radioButtonWidget, "checkbox"].includes(props.pos.type) ? (
<BorderResize
posHeight={props.posHeight}
isSignYourself={props.isSignYourself}
isSignYourself={props.isSignYourself || props.isSelfSign}
pos={props.pos}
/>
) : (
<></>
)
) : (
props.pos.type !== radioButtonWidget &&
props.pos.type !== "checkbox" &&
![radioButtonWidget, "checkbox"].includes(props.pos.type) &&
props.pos.key === props.selectWidgetId && <BorderResize />
)}
{props.isShowBorder && props.pos.key === props.selectWidgetId && (
<PlaceholderBorder
setDraggingEnabled={setDraggingEnabled}
{/* 1- Show a border if props.pos.key === props.selectWidgetId, indicating the current user's selected widget.
2- If props.isShowBorder is true, display borders for all widgets.
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show borders:
1- When isAlllowModify is true, show borders.
2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
*/}
{props.pos.key === props.selectWidgetId &&
(props.isShowBorder ||
!isDisableDragging ||
(props?.isAlllowModify &&
!props?.assignedWidgetId.includes(props.pos.key))) && (
<PlaceholderBorder
setDraggingEnabled={setDraggingEnabled}
pos={props.pos}
isPlaceholder={props.isPlaceholder}
getCheckboxRenderWidth={getCheckboxRenderWidth}
scale={props.scale}
containerScale={containerScale}
placeholderBorder={placeholderBorder}
/>
)}
<div
className="flex items-stretch justify-center"
style={{
left: xPos(props.pos, props.isSignYourself),
top: yPos(props.pos, props.isSignYourself),
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself),
zIndex: "10"
}}
onTouchEnd={() => handleTouchEnd()}
onClick={() => handleOnClickPlaceholder()}
onTouchStart={() => handleTouchStart()}
>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPosition={props.xyPosition}
index={props.index}
setXyPosition={props.setXyPosition}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
getCheckboxRenderWidth={getCheckboxRenderWidth}
scale={props.scale}
containerScale={containerScale}
placeholderBorder={placeholderBorder}
isSignYourself={props.isSignYourself}
isSelfSign={props.isSelfSign}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
calculateFont={calculateFont}
/>
)}
{isTabAndMobile ? (
<div
className="flex items-stretch justify-center"
style={{
left: xPos(props.pos, props.isSignYourself),
top: yPos(props.pos, props.isSignYourself),
width:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posWidth(props.pos, props.isSignYourself),
height:
props.pos.type === radioButtonWidget ||
props.pos.type === "checkbox"
? "auto"
: props.posHeight(props.pos, props.isSignYourself),
zIndex: "10"
}}
onTouchEnd={() => handleOnClickPlaceholder()}
onClick={() => handleOnClickPlaceholder()}
>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPostion={props.xyPostion}
index={props.index}
setXyPostion={props.setXyPostion}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
setDraggingEnabled={setDraggingEnabled}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
calculateFont={calculateFont}
/>
</div>
) : (
<>
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
<PlaceholderType
pos={props.pos}
xyPostion={props.xyPostion}
index={props.index}
setXyPostion={props.setXyPostion}
data={props.data}
setSignKey={props.setSignKey}
isShowDropdown={props?.isShowDropdown}
isPlaceholder={props.isPlaceholder}
isSignYourself={props.isSignYourself}
signerObjId={props.signerObjId}
handleUserName={props.handleUserName}
setDraggingEnabled={setDraggingEnabled}
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
isNeedSign={props.isNeedSign}
setSelectDate={setSelectDate}
selectDate={selectDate}
setValidateAlert={props.setValidateAlert}
setStartDate={setStartDate}
startDate={startDate}
handleSaveDate={handleSaveDate}
xPos={props.xPos}
calculateFont={calculateFont}
/>
</>
)}
</div>
</Rnd>
<ModalUi isOpen={isDateModal} title={t("widget-info")} showClose={false}>
<div className="h-[100%] p-[20px]">
@@ -43,9 +43,9 @@ function PlaceholderBorder(props) {
};
return (
<div
onMouseEnter={() => !isMobile && props?.setDraggingEnabled(true)}
onMouseEnter={() => !isMobile && props?.setDraggingEnabled(false)}
onTouchEnd={() =>
props.pos.type === textWidget && props?.setDraggingEnabled(true)
props.pos.type === textWidget && props?.setDraggingEnabled(false)
}
className="absolute inline-block w-[14px] h-[14px] border-[0.2px] overflow-hidden border-dashed"
style={{
@@ -19,56 +19,89 @@ function PlaceholderCopy(props) {
//function for get copy placeholder position
const getCopyPlaceholderPosition = (
type,
currentPlaceholder,
newPlaceholder,
newPageNumber,
existPlaceholderPosition
) => {
//remove existing page position when used option copy but first or copy but last
const filterPosition =
existPlaceholderPosition &&
existPlaceholderPosition.filter(
(data) => data.xPosition !== currentPlaceholder.xPosition
(data) => data.xPosition !== newPlaceholder.xPosition
);
//first option - copy all page but not first
//copy all page placeholder at requested position except first page
if (newPageNumber === 1 && type === 3) {
if (filterPosition && filterPosition.length > 0) {
//when user choose this option and placed widget on first page then do not remove first page widget
//it should be copy all page included first page on same postion
if (props.pageNumber === 1) {
return {
pageNumber: newPageNumber,
pos: [...existPlaceholderPosition]
};
} //else when user copy widget and choose this option and not at first page then copy all page but not at first
else if (filterPosition && filterPosition.length > 0) {
return {
pageNumber: newPageNumber,
pos: [...filterPosition]
};
}
}
//second option - copy all page but not last
//copy all page placeholder at requested position except last page
else if (newPageNumber === props.allPages && type === 2) {
if (existPlaceholderPosition) {
//when user choose this option and placed widget on last page then do not remove last page widget
//it should be copy all page included last page on same postion
if (props.pageNumber === props.allPages) {
return {
pageNumber: newPageNumber,
pos: [...existPlaceholderPosition]
};
} else {
return {
pageNumber: newPageNumber,
pos: [...filterPosition]
};
}
}
}
//this optin - copy all page with exiting widgets on same page
//copy all page placeholder at requested position with existing placeholder position
else if (existPlaceholderPosition) {
if (newPageNumber === props.pageNumber) {
return {
pageNumber: props.pageNumber,
pos: [...existPlaceholderPosition]
};
} else {
return {
pageNumber: newPageNumber,
pos: [...filterPosition]
pos: [...existPlaceholderPosition, newPlaceholder]
};
}
}
//copy all page placeholder at requested position with existing placeholder position
else if (existPlaceholderPosition) {
return {
pageNumber: newPageNumber,
pos: [...filterPosition, currentPlaceholder]
};
}
//copy all page placeholder at requested position
else {
return {
pageNumber: newPageNumber,
pos: [currentPlaceholder]
};
if (newPageNumber === props.pageNumber) {
return {
pageNumber: props.pageNumber,
pos: [...existPlaceholderPosition]
};
} else {
return {
pageNumber: newPageNumber,
pos: [newPlaceholder]
};
}
}
};
//function for copy placeholder as per select copy type
const copyPlaceholder = (type) => {
let newPlaceholderPosition = [];
let newPageNumber = 1;
const signerPosition = props.xyPostion;
const signerPosition = props.xyPosition;
const signerId = props.signerObjId ? props.signerObjId : props.Id;
//handle placeholder array and copy for multiple signers placeholder at requested location
if (signerId) {
//get current signers data
@@ -90,10 +123,9 @@ function PlaceholderCopy(props) {
const currentPlaceholder = placeholderPosition[0].pos.find(
(position) => position.key === props.signKey
);
for (let i = 0; i < props.allPages; i++) {
const newId = randomId();
currentPlaceholder.key = newId;
const newPlaceholder = { ...currentPlaceholder, key: newId };
//get exist placeholder position for particular page
const existPlaceholder = filterSignerPosition[0].placeHolder.filter(
(data) => data.pageNumber === newPageNumber
@@ -104,11 +136,11 @@ function PlaceholderCopy(props) {
//function for get copy to requested location of placeholder position
const getPlaceholderObj = getCopyPlaceholderPosition(
type,
currentPlaceholder,
newPlaceholder,
newPageNumber,
existPlaceholderPosition
);
if (getPlaceholderObj) {
if (getPlaceholderObj?.pos) {
newPlaceholderPosition.push(getPlaceholderObj);
}
newPageNumber++;
@@ -138,13 +170,13 @@ function PlaceholderCopy(props) {
const signersData = signerPosition;
signersData.splice(0, signerPosition.length, ...updatedSignerPlaceholder);
props.setXyPostion(signersData);
props.setXyPosition(signersData);
}
//handle signyourself array and copy for single signers placeholder at requested location
else {
const xyPostion = props.xyPostion;
const xyPosition = props.xyPosition;
const placeholderPosition = xyPostion.filter(
const placeholderPosition = xyPosition.filter(
(data) => data.pageNumber === props.pageNumber
);
//get current placeholder position data which user want to copy
@@ -154,18 +186,18 @@ function PlaceholderCopy(props) {
for (let i = 0; i < props.allPages; i++) {
//get exist placeholder position for particular page
const existPlaceholder = xyPostion.filter(
const existPlaceholder = xyPosition.filter(
(data) => data.pageNumber === newPageNumber
);
const existPlaceholderPosition =
existPlaceholder[0] && existPlaceholder[0].pos;
const newId = randomId();
currentPlaceholder.key = newId;
const newPlaceholder = { ...currentPlaceholder, key: newId };
//function for get copy to requested location of placeholder position
const getPlaceholderObj = getCopyPlaceholderPosition(
type,
currentPlaceholder,
newPlaceholder,
newPageNumber,
existPlaceholderPosition
);
@@ -175,14 +207,14 @@ function PlaceholderCopy(props) {
newPageNumber++;
}
props.setXyPostion(newPlaceholderPosition);
props.setXyPosition(newPlaceholderPosition);
}
};
//function for getting selected type placeholder copy
const handleApplyCopy = () => {
if (selectCopyType === 4) {
const signerPosition = props.xyPostion;
const signerPosition = props.xyPosition;
let currentXYposition;
const signerId = props.signerObjId ? props.signerObjId : props.Id;
if (signerId) {
@@ -200,16 +232,16 @@ function PlaceholderCopy(props) {
handleCopyNextToWidget(
currentXYposition,
props.widgetType,
props.xyPostion,
props.xyPosition,
props.pageNumber,
props.setXyPostion,
props.setXyPosition,
props?.Id
);
} else {
const getIndex = props?.xyPostion.findIndex(
const getIndex = props?.xyPosition.findIndex(
(data) => data.pageNumber === props.pageNumber
);
const placeholderPosition = props?.xyPostion[getIndex];
const placeholderPosition = props?.xyPosition[getIndex];
//get current placeholder position data which user want to copy
currentXYposition = placeholderPosition.pos.find(
(pos) => pos.key === props.signKey
@@ -218,9 +250,9 @@ function PlaceholderCopy(props) {
handleCopyNextToWidget(
currentXYposition,
props.widgetType,
props.xyPostion,
props.xyPosition,
getIndex,
props.setXyPostion
props.setXyPosition
);
}
} else {
@@ -229,7 +261,7 @@ function PlaceholderCopy(props) {
};
const handleUniqueId = () => {
const signerId = props.signerObjId ? props.signerObjId : props.Id;
if (signerId && props.widgetType === textWidget) {
if (signerId && props.widgetType === textWidget && props.setTempSignerId) {
props.setUniqueId(props?.tempSignerId);
props.setTempSignerId("");
}
@@ -260,7 +292,7 @@ function PlaceholderCopy(props) {
);
})}
<div className="flex bg-[#9f9f9f] w-full my-[15px]"></div>
<div className="flex flex-row bg-[#9f9f9f] w-full my-[15px]"></div>
<button
onClick={() => {
handleApplyCopy();
@@ -274,7 +306,7 @@ function PlaceholderCopy(props) {
</button>
<button
type="button"
className="op-btn op-btn-ghost"
className="op-btn op-btn-ghost ml-2"
onClick={() => handleUniqueId()}
>
{t("cancel")}
@@ -19,6 +19,8 @@ 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 bg-white";
const selectWidgetCls =
"w-full h-full absolute left-0 top-0 border-[1px] border-[#007bff] rounded-[2px] focus:outline-none text-base-content";
const widgetCls =
"select-none-cls overflow-hidden w-full h-full text-black flex flex-col justify-center items-center";
function PlaceholderType(props) {
const { t } = useTranslation();
const type = props?.pos?.type;
@@ -28,7 +30,7 @@ function PlaceholderType(props) {
const inputRef = useRef(null);
const [textValue, setTextValue] = useState();
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
const years = range(1990, getYear(new Date()) + 16, 1);
const years = range(1950, getYear(new Date()) + 16, 1);
const fontSize = props.calculateFont(props.pos.options?.fontSize);
const fontColor = props.pos.options?.fontColor || "black";
const months = [
@@ -45,6 +47,16 @@ function PlaceholderType(props) {
"November",
"December"
];
const textWidgetStyle = {
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif",
overflow: "hidden",
textAlign: "start",
width: "100%",
display: "flex",
height: "100%"
};
const validateExpression = (regexValidation) => {
if (textValue && regexValidation) {
let regexObject = regexValidation;
@@ -61,7 +73,6 @@ function PlaceholderType(props) {
};
const handleInputBlur = () => {
props.setDraggingEnabled(true);
const validateType = props.pos?.options?.validation?.type;
let regexValidation;
if (validateType && validateType !== "text") {
@@ -139,6 +150,11 @@ function PlaceholderType(props) {
if (defaultData) {
setTextValue(defaultData);
}
} else if ([textInputWidget].includes(props.pos?.type)) {
const defaultData = props.pos?.options?.defaultValue;
if (defaultData) {
setTextValue(defaultData);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.pos?.options?.defaultValue]);
@@ -172,9 +188,9 @@ function PlaceholderType(props) {
onChangeInput(
jsonSender && jsonSender[0],
null,
props.xyPostion,
props.xyPosition,
null,
props.setXyPostion,
props.setXyPosition,
props.data.Id,
isDefault
);
@@ -240,9 +256,9 @@ function PlaceholderType(props) {
onChangeInput(
checkedList ? checkedList : updateSelectedCheckbox,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data.Id,
false,
null,
@@ -270,9 +286,9 @@ function PlaceholderType(props) {
onChangeInput(
data,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data.Id,
false,
null,
@@ -283,12 +299,6 @@ function PlaceholderType(props) {
//function to set onchange date
const handleOnDateChange = (date) => {
props.setStartDate(date);
const isDateChange = true;
const dateObj = {
date: date,
format: props.selectDate.format
};
props.handleSaveDate(dateObj, isDateChange);
};
//handle height on enter press in text area
const handleEnterPress = (e) => {
@@ -299,9 +309,9 @@ function PlaceholderType(props) {
height,
props.pos.type,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data?.Id
);
}
@@ -313,15 +323,16 @@ function PlaceholderType(props) {
alt="signature"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full "
className="w-full h-full select-none-cls "
/>
) : (
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation
widgetTypeTraslation,
props.pos
)}
</div>
);
@@ -331,15 +342,16 @@ function PlaceholderType(props) {
alt="stamp"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full"
className="w-full h-full select-none-cls"
/>
) : (
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation
widgetTypeTraslation,
props.pos
)}
</div>
);
@@ -348,12 +360,13 @@ function PlaceholderType(props) {
<div style={{ zIndex: props.isSignYourself && "99" }}>
{props.pos.options?.values?.map((data, ind) => {
return (
<div key={ind} className="flex items-center text-center gap-0.5">
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5"
>
<input
style={{
width: fontSize,
height: fontSize
}}
id={`checkbox-${props.pos.key + ind}`}
style={{ width: fontSize, height: fontSize }}
className={`${
ind === 0 ? "mt-0" : "mt-[5px]"
} flex justify-center op-checkbox rounded-[1px] `}
@@ -391,10 +404,8 @@ function PlaceholderType(props) {
/>
{!props.pos.options?.isHideLabel && (
<label
style={{
fontSize: fontSize,
color: fontColor
}}
htmlFor={`checkbox-${props.pos.key + ind}`}
style={{ fontSize: fontSize, color: fontColor }}
className="text-xs mb-0 text-center"
>
{data}
@@ -407,10 +418,11 @@ function PlaceholderType(props) {
);
case textInputWidget:
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={validatePlaceholder || "please enter value"}
placeholder={validatePlaceholder || t("widgets-name.text")}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -420,39 +432,44 @@ function PlaceholderType(props) {
onChangeInput(
e.target.value,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
className={textWidgetCls}
style={{
fontSize: fontSize,
color: fontColor
}}
className={`${
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
? " bg-black/68 select-none "
: "" + textWidgetCls
}`}
style={{ fontSize: fontSize, color: fontColor }}
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
cols="50"
/>
) : (
<div
style={{
fontSize: fontSize,
color: fontColor,
overflow: "hidden"
}}
>
<span>{widgetTypeTraslation}</span>
<div style={textWidgetStyle} className="select-none-cls">
<span>{textValue || widgetTypeTraslation}</span>
</div>
);
case "dropdown":
return props.data?.signerObjId === props.signerObjId ? (
<select
style={{
fontSize: fontSize,
color: fontColor
}}
className={`${selectWidgetCls} text-[12px] bg-inherit`}
style={{ fontSize: fontSize, color: fontColor }}
className={`${
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
? " disabled:bg-inherit select-none "
: "" + `${selectWidgetCls} text-[12px] bg-inherit`
}`}
id="myDropdown"
value={selectOption}
onChange={(e) => {
@@ -460,20 +477,22 @@ function PlaceholderType(props) {
onChangeInput(
e.target.value,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
disabled={
props.isNeedSign &&
(props.pos.options?.isReadOnly ||
props.data?.signerObjId !== props.signerObjId)
}
>
{/* Default/Title option */}
<option
style={{
fontSize: fontSize,
color: fontColor
}}
style={{ fontSize: fontSize, color: fontColor }}
value=""
disabled
hidden
@@ -484,10 +503,7 @@ function PlaceholderType(props) {
{props.pos?.options?.values?.map((data, ind) => {
return (
<option
style={{
fontSize: fontSize,
color: fontColor
}}
style={{ fontSize: fontSize, color: fontColor }}
key={ind}
value={data}
>
@@ -497,13 +513,7 @@ function PlaceholderType(props) {
})}
</select>
) : (
<div
style={{
fontSize: fontSize,
color: fontColor
}}
className=" overflow-hidden"
>
<div style={textWidgetStyle} className="select-none-cls">
{props.pos?.options?.name
? props.pos.options.name
: widgetTypeTraslation}
@@ -515,24 +525,26 @@ function PlaceholderType(props) {
alt="initials"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full"
className="w-full h-full select-none-cls"
/>
) : (
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation
widgetTypeTraslation,
props.pos
)}
</div>
);
case "name":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={"name"}
placeholder={t("widgets-name.name")}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -542,38 +554,29 @@ function PlaceholderType(props) {
onChangeInput(
e.target.value,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data?.Id,
isDefault
);
}}
className={textWidgetCls}
style={{
fontSize: fontSize,
color: fontColor
}}
style={{ fontSize: fontSize, color: fontColor }}
cols="50"
/>
) : (
<div
style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif",
overflow: "hidden"
}}
>
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
<span>{widgetTypeTraslation}</span>
</div>
);
case "company":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder="Enter text"
placeholder={t("widgets-name.company")}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -582,38 +585,29 @@ function PlaceholderType(props) {
onChangeInput(
e.target.value,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
className={textWidgetCls}
style={{
fontSize: fontSize,
color: fontColor
}}
style={{ fontSize: fontSize, color: fontColor }}
cols="50"
/>
) : (
<div
style={{
fontSize: fontSize,
color: fontColor,
overflow: "hidden",
fontFamily: "Arial, sans-serif"
}}
>
<div style={textWidgetStyle} className="select-none-cls">
<span>{widgetTypeTraslation}</span>
</div>
);
case "job title":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={"job title"}
placeholder={t("widgets-name.job title")}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -622,99 +616,83 @@ function PlaceholderType(props) {
onChangeInput(
e.target.value,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data?.Id,
false
);
}}
className={textWidgetCls}
style={{
fontSize: fontSize,
color: fontColor
}}
style={{ fontSize: fontSize, color: fontColor }}
cols="50"
/>
) : (
<div
style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif",
overflow: "hidden"
}}
>
<div style={textWidgetStyle} className="select-none-cls">
<span>{widgetTypeTraslation}</span>
</div>
);
case "date":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<div>
<DatePicker
renderCustomHeader={({ date, changeYear, changeMonth }) => (
<div className="flex justify-start ml-2 ">
<select
className="bg-transparent outline-none"
value={months[getMonth(date)]}
onChange={({ target: { value } }) =>
changeMonth(months.indexOf(value))
}
>
{months.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<select
className="bg-transparent outline-none"
value={getYear(date)}
onChange={({ target: { value } }) => changeYear(value)}
>
{years.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
)}
disabled={
props.isPlaceholder ||
(props.isNeedSign &&
props.data?.signerObjId !== props.signerObjId)
}
onBlur={handleInputBlur}
closeOnScroll={true}
className={`${selectWidgetCls} outline-[#007bff]`}
selected={
props?.startDate
? props?.startDate
: props.pos.options?.response &&
new Date(props.pos.options.response)
}
onChange={(date) => handleOnDateChange(date)}
popperPlacement="top-end"
customInput={<ExampleCustomInput />}
dateFormat={
props.selectDate
? props.selectDate?.format
: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/yyyy"
}
/>
</div>
<DatePicker
renderCustomHeader={({ date, changeYear, changeMonth }) => (
<div className="flex justify-start ml-2 ">
<select
className="bg-transparent outline-none"
value={months[getMonth(date)]}
onChange={({ target: { value } }) =>
changeMonth(months.indexOf(value))
}
>
{months.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
<select
className="bg-transparent outline-none"
value={getYear(date)}
onChange={({ target: { value } }) => changeYear(value)}
>
{years.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
</div>
)}
disabled={
props.isPlaceholder ||
(props.isNeedSign && props.data?.signerObjId !== props.signerObjId)
}
onBlur={handleInputBlur}
closeOnScroll={true}
className={`${selectWidgetCls} outline-[#007bff]`}
selected={
props?.startDate
? props?.startDate
: props.pos.options?.response &&
new Date(props.pos.options.response)
}
onChange={(date) => handleOnDateChange(date)}
popperPlacement="top-end"
customInput={<ExampleCustomInput />}
dateFormat={
props.selectDate
? props.selectDate?.format
: props.pos?.options?.validation?.format
? props.pos?.options?.validation?.format
: "MM/dd/yyyy"
}
/>
) : (
<div
style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif"
}}
className="items-center overflow-hidden"
style={textWidgetStyle}
className="select-none-cls overflow-hidden"
>
<span>
{props.selectDate
@@ -731,26 +709,33 @@ function PlaceholderType(props) {
alt="image"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full"
className="w-full h-full select-none-cls"
/>
) : (
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
<div className={widgetCls}>
{props?.handleUserName &&
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
widgetTypeTraslation
widgetTypeTraslation,
props.pos
)}
</div>
);
case "email":
return props.isSignYourself ||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
<textarea
ref={inputRef}
placeholder={"enter email"}
placeholder={t("widgets-name.email")}
rows={1}
onKeyDown={handleEnterPress}
onKeyDown={(e) => {
// Prevent new line on Enter key press
if (e.key === "Enter") {
e.preventDefault();
}
}}
value={textValue}
onBlur={handleInputBlur}
onChange={(e) => {
@@ -758,9 +743,9 @@ function PlaceholderType(props) {
onChangeInput(
e.target.value,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data?.Id,
false
);
@@ -771,17 +756,10 @@ function PlaceholderType(props) {
color: fontColor,
fontFamily: "Arial, sans-serif"
}}
cols="50"
cols="1"
/>
) : (
<div
style={{
fontSize: fontSize,
color: fontColor,
fontFamily: "Arial, sans-serif",
overflow: "hidden"
}}
>
<div style={textWidgetStyle} className="select-none-cls">
<span>{widgetTypeTraslation}</span>
</div>
);
@@ -790,8 +768,12 @@ function PlaceholderType(props) {
<div>
{props.pos.options?.values.map((data, ind) => {
return (
<div key={ind} className="flex items-center text-center gap-0.5">
<div
key={ind}
className="select-none-cls flex items-center text-center gap-0.5"
>
<input
id={`radio-${props.pos.key + ind}`}
style={{
width: fontSize,
height: fontSize,
@@ -813,10 +795,8 @@ function PlaceholderType(props) {
/>
{!props.pos.options?.isHideLabel && (
<label
style={{
fontSize: fontSize,
color: fontColor
}}
htmlFor={`radio-${props.pos.key + ind}`}
style={{ fontSize: fontSize, color: fontColor }}
className="text-xs mb-0"
>
{data}
@@ -830,7 +810,7 @@ function PlaceholderType(props) {
case textWidget:
return (
<textarea
placeholder="Enter text"
placeholder={t("widgets-name.text")}
rows={1}
onKeyDown={handleEnterPress}
value={textValue}
@@ -840,9 +820,9 @@ function PlaceholderType(props) {
onChangeInput(
e.target.value,
props.pos.key,
props.xyPostion,
props.xyPosition,
props.index,
props.setXyPostion,
props.setXyPosition,
props.data && props.data?.Id,
false
);
@@ -863,14 +843,19 @@ function PlaceholderType(props) {
alt="image"
draggable="false"
src={props.pos.SignUrl}
className="w-full h-full"
className="w-full h-full "
/>
</div>
) : (
<div className="text-[11px] w-full h-full text-black flex flex-col justify-center items-center">
<div className={widgetCls}>
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
{props?.handleUserName &&
props?.handleUserName(props?.data?.Id, props?.data?.Role)}
props?.handleUserName(
props?.data?.Id,
props?.data?.Role,
null,
props.pos
)}
</div>
);
}
+1 -1
View File
@@ -23,7 +23,7 @@ function PrevNext({ pageNumber, allPages, changePage }) {
<i className="fa-light fa-backward" aria-hidden="true"></i>
</span>
</button>
<span className="text-xs text-base-content font-medium mx-2 2xl:text-[30px]">
<span className="text-xs text-base-content font-medium mx-2 2xl:text-[20px]">
{pageNumber || (allPages ? 1 : "--")} {t("of")} {allPages || "--"}
</span>
<button
@@ -32,7 +32,7 @@ const RecipientList = (props) => {
};
//handle draggable element drop and also used in mobile view on up and down key to chnage sequence of recipient's list
const handleChangeSequence = (e, ind, isUp, isDown) => {
const handleChangeSequence = (e, ind, isUp, isDown, obj) => {
e.preventDefault();
let draggedItemId;
let index = ind;
@@ -66,8 +66,7 @@ const RecipientList = (props) => {
props?.setIsSelectId(index);
props?.setUniqueId(remainingItems[index]?.Id);
props?.setRoleName(remainingItems[index]?.Role);
props?.setBlockColor(remainingItems[index]?.blockColor);
props?.setBlockColor(obj.blockColor);
//change order of placeholder's list using sorting method
//`remainingItems` is correct order of signers after change order
const changeOrderSignerList = props?.signerPos.sort((a, b) => {
@@ -97,7 +96,9 @@ const RecipientList = (props) => {
props.sendInOrder && !isMobile && handleDragOver(e)
}
onDrop={(e) =>
props.sendInOrder && !isMobile && handleChangeSequence(e, ind)
props.sendInOrder &&
!isMobile &&
handleChangeSequence(e, ind, null, null, obj)
}
data-tut="recipientArea"
onMouseEnter={() => setIsHover(ind)}
@@ -196,7 +197,7 @@ const RecipientList = (props) => {
}}
/>
) : (
<span className="text-[13px] 2xl:text-[25px]">
<span className="text-[13px] 2xl:text-[17px]">
{obj.Role}
</span>
)}
@@ -222,7 +223,7 @@ const RecipientList = (props) => {
onClick={(e) => {
if (ind !== 0) {
e.stopPropagation();
handleChangeSequence(e, ind, "up");
handleChangeSequence(e, ind, "up", null, obj);
}
}}
className={ind === 0 ? "text-[gray]" : "text-black"}
@@ -233,7 +234,7 @@ const RecipientList = (props) => {
onClick={(e) => {
if (ind !== props.signersdata.length - 1) {
e.stopPropagation();
handleChangeSequence(e, ind, null, "down");
handleChangeSequence(e, ind, null, "down", obj);
}
}}
className={
@@ -259,7 +260,7 @@ const RecipientList = (props) => {
: "text-base-content"
} cursor-pointer`}
>
<i className="fa-light fa-trash-can 2xl:text-[30px]"></i>
<i className="fa-light fa-trash-can 2xl:text-[22px]"></i>
</div>
)}
<hr />
@@ -2,29 +2,25 @@ import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Document, Page } from "react-pdf";
import { useSelector } from "react-redux";
import { PDFDocument } from "pdf-lib";
import { base64ToArrayBuffer } from "../../constant/Utils";
function RenderAllPdfPage({
signPdfUrl,
allPages,
setAllPages,
setPageNumber,
setSignBtnPosition,
pageNumber,
signerPos,
signerObjectId,
containerWH,
pdfRotateBase64
}) {
function RenderAllPdfPage(props) {
const { t } = useTranslation();
const pageContainer = useRef();
const mergePdfInputRef = useRef(null);
const [signPageNumber, setSignPageNumber] = useState([]);
const [bookmarkColor, setBookmarkColor] = useState("");
const isHeader = useSelector((state) => state.showHeader);
const [pageWidth, setPageWidth] = useState("");
//set all number of pages after load pdf
function onDocumentLoad({ numPages }) {
setAllPages(numPages);
props?.setAllPages(numPages);
//check if signerPos array exist then save page number exist in signerPos array to show bookmark icon
if (signerPos) {
const checkUser = signerPos.filter(
(data) => data.signerObjId === signerObjectId
if (props?.signerPos) {
const checkUser = props?.signerPos.filter(
(data) => data.Id === props?.id
);
setBookmarkColor(checkUser[0]?.blockColor);
let pageNumberArr = [];
@@ -37,9 +33,6 @@ function RenderAllPdfPage({
}
}
}
const pageContainer = useRef();
const isHeader = useSelector((state) => state.showHeader);
const [pageWidth, setPageWidth] = useState("");
useEffect(() => {
const updateSize = () => {
@@ -52,7 +45,7 @@ function RenderAllPdfPage({
const timer = setTimeout(updateSize, 100); // match the transition duration
return () => clearTimeout(timer);
}, [isHeader, pageContainer, containerWH]);
}, [isHeader, 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);
@@ -67,7 +60,46 @@ function RenderAllPdfPage({
)
);
};
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
const pdfDataBase64 = `data:application/pdf;base64,${props?.pdfBase64Url}`;
// `handleFileUpload` is trigger when user click on add pages btn and is used to merge multiple pdf
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) {
alert("Please upload a valid PDF file.");
return;
}
if (!file.type.includes("pdf")) {
alert("Only PDF files are allowed.");
return;
}
try {
const uploadedPdfBytes = await file.arrayBuffer();
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
ignoreEncryption: true
});
const basePdfDoc = await PDFDocument.load(props.pdfArrayBuffer);
// Copy pages from the uploaded PDF to the base PDF
const uploadedPdfPages = await basePdfDoc.copyPages(
uploadedPdfDoc,
uploadedPdfDoc.getPageIndices()
);
uploadedPdfPages.forEach((page) => basePdfDoc.addPage(page));
// Save the updated PDF
const pdfBase64 = await basePdfDoc.saveAsBase64({
useObjectStreams: false
});
const pdfBuffer = base64ToArrayBuffer(pdfBase64);
props.setPdfArrayBuffer(pdfBuffer);
props.setPdfBase64Url(pdfBase64);
props.setIsUploadPdf && props.setIsUploadPdf(true);
mergePdfInputRef.current.value = "";
} catch (error) {
mergePdfInputRef.current.value = "";
console.error("Error merging PDF:", error);
}
};
return (
<div ref={pageContainer} className="hidden w-[20%] bg-base-100 md:block">
<div className="mx-2 pr-2 pt-2 pb-1 text-[15px] text-base-content font-semibold border-b-[1px] border-base-300">
@@ -80,24 +112,24 @@ function RenderAllPdfPage({
<Document
loading={t("loading-doc")}
onLoadSuccess={onDocumentLoad}
file={(pdfRotateBase64 && pdfDataBase64) || signPdfUrl}
file={pdfDataBase64}
>
{Array.from(new Array(allPages), (el, index) => (
{Array.from(new Array(props?.allPages), (el, index) => (
<div
key={index}
className={`${
pageNumber - 1 === index ? "border-[red]" : "border-[#878787]"
} border-2 m-[10px] flex justify-center items-center relative`}
props?.pageNumber - 1 === index
? "border-[red]"
: "border-[#878787]"
} border-2 m-[10px] flex justify-center items-center relative`}
onClick={() => {
setPageNumber(index + 1);
if (setSignBtnPosition) {
setSignBtnPosition([]);
props?.setPageNumber(index + 1);
if (props?.setSignBtnPosition) {
props?.setSignBtnPosition([]);
}
}}
>
{signerPos && addSignatureBookmark(index)}
{/* <div className="relative z-[1] overflow-hidden"> */}
{props?.signerPos && addSignatureBookmark(index)}
<Page
key={`page_${index + 1}`}
pageNumber={index + 1}
@@ -106,10 +138,28 @@ function RenderAllPdfPage({
renderAnnotationLayer={false}
renderTextLayer={false}
/>
{/* </div> */}
</div>
))}
</Document>
{props?.isMergePdfBtn && (
<button
className="mb-2 bg-base-100 px-2 py-2 ring-[0.5px] ring-base-content rounded-box flex gap-1 justify-center items-center"
onClick={() => mergePdfInputRef.current.click()}
title={t("add-pages")}
>
<input
type="file"
className="hidden"
accept="application/pdf"
ref={mergePdfInputRef}
onChange={handleFileUpload}
/>
<i className="fa-light fa-plus text-gray-500"></i>
<span className="text-xs lg:text-sm text-base-content ">
Add pages
</span>
</button>
)}
</div>
</div>
);
+373 -314
View File
@@ -1,137 +1,80 @@
import React from "react";
import React, { useState } from "react";
import RSC from "react-scrollbars-custom";
import { Document, Page } from "react-pdf";
import {
defaultWidthHeight,
getContainerScale,
handleImageResize,
handleSignYourselfImageResize
handleSignYourselfImageResize,
isMobile
} from "../../constant/Utils";
import Placeholder from "./Placeholder";
import Alert from "../../primitives/Alert";
import { useTranslation } from "react-i18next";
function RenderPdf({
pageNumber,
drop,
signerPos,
successEmail,
handleTabDrag,
handleStop,
isDragging,
setIsSignPad,
setIsStamp,
handleDeleteSign,
setSignKey,
pdfDetails,
xyPostion,
pageDetails,
pdfRequest,
signerObjectId,
signedSigners,
pdfLoad,
setPdfLoad,
placeholder,
setSignerPos,
setXyPostion,
index,
containerWH,
setIsResize,
handleLinkUser,
setUniqueId,
signersdata,
setIsPageCopy,
setShowDropdown,
setIsInitial,
setIsValidate,
setWidgetType,
setValidateAlert,
setIsRadio,
setCurrWidgetsDetails,
setSelectWidgetId,
selectWidgetId,
unSignedWidgetId,
setIsCheckbox,
handleNameModal,
handleTextSettingModal,
setTempSignerId,
uniqueId,
pdfOriginalWH,
scale,
setIsSelectId,
ispublicTemplate,
handleUserDetails,
pdfRotateBase64,
fontSize,
setFontSize,
fontColor,
setFontColor,
isResize
}) {
function RenderPdf(props) {
const { t } = useTranslation();
const isMobile = window.innerWidth < 767;
const [scaledHeight, setScaledHeight] = useState();
//check isGuestSigner is present in local if yes than handle login flow header in mobile view
const isGuestSigner = localStorage.getItem("isGuestSigner");
// handle signature block width and height according to screen
const posWidth = (pos, signYourself) => {
const containerScale = getContainerScale(
pdfOriginalWH,
pageNumber,
containerWH
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const defaultWidth = defaultWidthHeight(pos.type).width;
const posWidth = pos.Width ? pos.Width : defaultWidth;
if (signYourself) {
return posWidth * scale * containerScale;
return posWidth * props.scale * containerScale;
} else {
if (pos.isMobile && pos.scale) {
if (pos.IsResize) {
if (scale > 1) {
return posWidth * pos.scale * containerScale * scale;
if (props.scale > 1) {
return posWidth * pos.scale * containerScale * props.scale;
} else {
return posWidth * containerScale;
}
} else {
if (scale > 1) {
return posWidth * pos.scale * containerScale * scale;
if (props.scale > 1) {
return posWidth * pos.scale * containerScale * props.scale;
} else {
return posWidth * pos.scale * containerScale;
}
}
} else {
return posWidth * scale * containerScale;
return posWidth * props.scale * containerScale;
}
}
};
const posHeight = (pos, signYourself) => {
const containerScale = getContainerScale(
pdfOriginalWH,
pageNumber,
containerWH
props.pdfOriginalWH,
props.pageNumber,
props.containerWH
);
const posHeight = pos.Height || defaultWidthHeight(pos.type).height;
if (signYourself) {
return posHeight * scale * containerScale;
return posHeight * props.scale * containerScale;
} else {
if (pos.isMobile && pos.scale) {
if (pos.IsResize) {
if (scale > 1) {
return posHeight * pos.scale * containerScale * scale;
if (props.scale > 1) {
return posHeight * pos.scale * containerScale * props.scale;
} else {
return posHeight * containerScale;
}
} else {
if (scale > 1) {
return posHeight * pos.scale * containerScale * scale;
if (props.scale > 1) {
return posHeight * pos.scale * containerScale * props.scale;
} else {
return posHeight * pos.scale * containerScale;
}
}
} else {
return posHeight * scale * containerScale;
return posHeight * props.scale * containerScale;
}
}
};
@@ -140,25 +83,35 @@ function RenderPdf({
const checkSignedSigners = (data) => {
let checkSign = [];
//condition to handle quick send flow and using normal request sign flow
checkSign = signedSigners
? signedSigners?.filter(
checkSign = props.signedSigners
? props.signedSigners?.filter(
(sign) =>
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
)
: [];
const handleAllUserName = (Id, Role, type) => {
const handleAllUserName = (Id, Role, type, pos) => {
return (
<React.Fragment>
<div className="text-black text-[8px] font-bold">
{
pdfDetails[0].Signers?.find(
props.pdfDetails[0].Signers?.find(
(signer) => signer.objectId === data.signerObjId
)?.Name
}
</div>
{type && <div className="text-[11px] font-bold">{type}</div>}
{type && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
className="font-bold"
>
{type}
</div>
)}
{Role && (
<div className="text-black text-[8px] font-medium">
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
className="text-black font-medium"
>
{`(${Role})`}
</div>
)}
@@ -167,49 +120,71 @@ function RenderPdf({
};
return (
checkSign.length === 0 &&
data.placeHolder.map((placeData, key) => {
data?.placeHolder?.map((placeData, key) => {
return (
<React.Fragment key={key}>
{placeData.pageNumber === pageNumber &&
{placeData.pageNumber === props.pageNumber &&
placeData.pos.map((pos) => {
return (
pos && (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setSignKey={setSignKey}
setIsSignPad={setIsSignPad}
setIsStamp={setIsStamp}
setSignKey={props.setSignKey}
setIsSignPad={props.setIsSignPad}
setIsStamp={props.setIsStamp}
handleSignYourselfImageResize={handleImageResize}
index={pageNumber}
xyPostion={signerPos}
setXyPostion={setSignerPos}
index={props.pageNumber}
xyPosition={props.signerPos}
setXyPosition={props.setSignerPos}
data={data}
setIsResize={setIsResize}
isShowBorder={false}
signerObjId={signerObjectId}
setIsResize={props.setIsResize}
isShowBorder={props.isSelfSign}
isAlllowModify={props.isAlllowModify}
signerObjId={props.signerObjectId}
isShowDropdown={true}
isNeedSign={true}
isNeedSign={props.pdfRequest}
isSelfSign={true}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
handleUserName={handleAllUserName}
isDragging={false}
pdfDetails={pdfDetails}
setIsInitial={setIsInitial}
setValidateAlert={setValidateAlert}
unSignedWidgetId={unSignedWidgetId}
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
setCurrWidgetsDetails={setCurrWidgetsDetails}
uniqueId={uniqueId}
scale={scale}
containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
ispublicTemplate={ispublicTemplate}
handleUserDetails={handleUserDetails}
isResize={isResize}
isDragging={props.isDragging}
pdfDetails={props.pdfDetails}
setIsInitial={props.setIsInitial}
setValidateAlert={props.setValidateAlert}
unSignedWidgetId={props.unSignedWidgetId}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
uniqueId={props.uniqueId}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
ispublicTemplate={props.ispublicTemplate}
handleUserDetails={props.handleUserDetails}
isResize={props.isResize}
setIsAgreeTour={props.setIsAgreeTour}
isAgree={props.isAgree}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
setWidgetType={props.setWidgetType}
setUniqueId={props.setUniqueId}
setIsSelectId={props.setIsSelectId}
handleDeleteSign={props.handleDeleteSign}
setIsPageCopy={props.setIsPageCopy}
handleTextSettingModal={props.handleTextSettingModal}
setIsCheckbox={props.setIsCheckbox}
isFreeResize={false}
isOpenSignPad={true}
assignedWidgetId={props.assignedWidgetId}
isApplyAll={true}
setFontSize={props.setFontSize}
fontSize={props.fontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
setRequestSignTour={props.setRequestSignTour}
/>
</React.Fragment>
)
@@ -220,20 +195,50 @@ function RenderPdf({
})
);
};
const calculateFontsize = (pos) => {
const width = posWidth(pos);
const height = posHeight(pos);
if (height === width || height < width) {
return `${height / 5}px`;
} else if (width < height) {
return `${width / 10}px`;
}
};
//function for render placeholder block over pdf document
const handleUserName = (Id, Role, type) => {
const handleUserName = (Id, Role, type, pos) => {
if (Id) {
const checkSign = signersdata.find((sign) => sign.Id === Id);
const checkSign = props.signersdata.find((sign) => sign.Id === Id);
if (checkSign?.Name) {
return (
<>
<div className="text-black text-[8px] font-medium">
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "8px"
}}
className="text-black font-medium"
>
{checkSign?.Name}
</div>
{type && <div className="text-[11px] font-bold">{type}</div>}
{type && (
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "11px"
}}
className=" font-bold"
>
{type}
</div>
)}
{Role && (
<div className="text-black text-[8px] font-medium">
<div
style={{
fontSize: pos ? calculateFontsize(pos) : "8px"
}}
className="text-black text-[8px] font-medium"
>
{`(${Role})`}
</div>
)}
@@ -242,8 +247,20 @@ function RenderPdf({
} else {
return (
<>
{type && <div className="text-[11px] font-bold">{type}</div>}
<div className="text-black text-[8px] font-medium">{Role}</div>
{type && (
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
className=" font-bold"
>
{type}
</div>
)}
<div
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
className="text-black font-medium"
>
{Role}
</div>
</>
);
}
@@ -256,11 +273,18 @@ function RenderPdf({
);
}
};
const pdfDataBase64 = `data:application/pdf;base64,${pdfRotateBase64}`;
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
//calculate render height of pdf in mobile view
const handlePageLoadSuccess = (page) => {
const containerWidth = props.divRef.current.offsetWidth; // Get container width
const viewport = page.getViewport({ scale: 1 });
const scale = containerWidth / viewport.width; // Scale to fit container width
const scaleHeight = viewport.height * scale;
setScaledHeight(scaleHeight);
};
return (
<>
{successEmail && (
{props.successEmail && (
<Alert type={"success"}>{t("success-email-alert")}</Alert>
)}
{isMobile ? (
@@ -268,12 +292,12 @@ function RenderPdf({
style={{
position: "relative",
boxShadow: "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px",
height: window.innerHeight - 200,
//49 is height of header
height: isGuestSigner ? window.innerHeight - 49 : scaledHeight,
zIndex: 0
}}
className="h-full"
noScrollY={false}
noScrollX={scale === 1 ? true : false}
noScrollY={props.scale === 1 ? true : false}
noScrollX={props.scale === 1 ? true : false}
>
<div
data-tut="reactourForth"
@@ -281,87 +305,104 @@ function RenderPdf({
isGuestSigner ? "30px" : ""
} border-[0.1px] border-[#ebe8e8] overflow-x-auto`}
style={{
width: containerWH?.width && containerWH?.width * scale
width:
props.containerWH?.width &&
props.containerWH?.width * props.scale
}}
ref={drop}
ref={props.drop}
id="container"
>
{containerWH?.width &&
pdfOriginalWH.length > 0 &&
(pdfRequest
? signerPos.map((data, key) => {
{props.containerWH?.width &&
props.pdfOriginalWH.length > 0 &&
(props.pdfRequest || props.isSelfSign
? props.signerPos?.map((data, key) => {
return (
<React.Fragment key={key}>
{checkSignedSigners(data)}
</React.Fragment>
);
})
: placeholder // placeholder mobile
? signerPos?.map((data, ind) => {
: props.placeholder // placeholder mobile
? props.signerPos?.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data?.placeHolder &&
data?.placeHolder.map((placeData, index) => {
return (
<React.Fragment key={index}>
{placeData.pageNumber === pageNumber &&
{placeData.pageNumber === props.pageNumber &&
placeData.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
setIsPageCopy={props.setIsPageCopy}
setSignKey={props.setSignKey}
handleDeleteSign={
props.handleDeleteSign
}
setIsStamp={props.setIsStamp}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={pageNumber}
xyPostion={signerPos}
setXyPostion={setSignerPos}
index={props.pageNumber}
xyPosition={props.signerPos}
setXyPosition={props.setSignerPos}
data={data}
setIsResize={setIsResize}
setShowDropdown={setShowDropdown}
setIsResize={props.setIsResize}
setShowDropdown={
props.setShowDropdown
}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={setUniqueId}
handleLinkUser={handleLinkUser}
setUniqueId={props.setUniqueId}
handleLinkUser={
props.handleLinkUser
}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={isDragging}
setIsValidate={setIsValidate}
setWidgetType={setWidgetType}
setIsRadio={setIsRadio}
setIsCheckbox={setIsCheckbox}
isDragging={props.isDragging}
setIsValidate={props.setIsValidate}
setWidgetType={props.setWidgetType}
setIsRadio={props.setIsRadio}
setIsCheckbox={props.setIsCheckbox}
setCurrWidgetsDetails={
setCurrWidgetsDetails
props.setCurrWidgetsDetails
}
setSelectWidgetId={
setSelectWidgetId
props.setSelectWidgetId
}
selectWidgetId={selectWidgetId}
handleNameModal={handleNameModal}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
selectWidgetId={
props.selectWidgetId
}
handleNameModal={
props.handleNameModal
}
setTempSignerId={
props.setTempSignerId
}
uniqueId={props.uniqueId}
handleTextSettingModal={
handleTextSettingModal
props.handleTextSettingModal
}
scale={scale}
containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
setIsSelectId={setIsSelectId}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
isResize={isResize}
unSignedWidgetId={unSignedWidgetId}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
setIsSelectId={props.setIsSelectId}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
unSignedWidgetId={
props.unSignedWidgetId
}
isFreeResize={true}
/>
</React.Fragment>
);
@@ -372,58 +413,61 @@ function RenderPdf({
</React.Fragment>
);
})
: !pdfDetails?.[0]?.IsCompleted &&
xyPostion.map((data, ind) => {
: !props.pdfDetails?.[0]?.IsCompleted &&
props.xyPosition?.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data.pageNumber === pageNumber &&
data.pos.map((pos) => {
{data.pageNumber === props.pageNumber &&
data.pos.map((pos, id) => {
return (
pos && (
<Placeholder
key={id}
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
setIsPageCopy={props.setIsPageCopy}
setSignKey={props.setSignKey}
handleDeleteSign={props.handleDeleteSign}
setIsStamp={props.setIsStamp}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
handleSignYourselfImageResize
}
index={index}
xyPostion={xyPostion}
setXyPostion={setXyPostion}
containerWH={containerWH}
setIsSignPad={setIsSignPad}
index={props.index}
xyPosition={props.xyPosition}
setXyPosition={props.setXyPosition}
containerWH={props.containerWH}
setIsSignPad={props.setIsSignPad}
isShowBorder={true}
isSignYourself={true}
posWidth={posWidth}
posHeight={posHeight}
pdfDetails={pdfDetails[0]}
isDragging={isDragging}
setIsInitial={setIsInitial}
setWidgetType={setWidgetType}
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
pdfDetails={props.pdfDetails[0]}
isDragging={props.isDragging}
setIsInitial={props.setIsInitial}
setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={setIsCheckbox}
setValidateAlert={setValidateAlert}
setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={
setCurrWidgetsDetails
props.setCurrWidgetsDetails
}
handleTextSettingModal={
handleTextSettingModal
props.handleTextSettingModal
}
scale={scale}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
isResize={isResize}
setIsResize={setIsResize}
scale={props.scale}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
setIsResize={props.setIsResize}
isFreeResize={false}
isOpenSignPad={true}
/>
)
);
@@ -433,26 +477,23 @@ function RenderPdf({
}))}
<Document
onLoadError={() => setPdfLoad(false)}
onLoadError={() => props.setPdfLoad(false)}
loading={t("loading-doc")}
onLoadSuccess={pageDetails}
onLoadSuccess={props.pageDetails}
// ref={pdfRef}'
onClick={() => {
if (setSelectWidgetId) {
setSelectWidgetId("");
if (props.setSelectWidgetId) {
props.setSelectWidgetId("");
}
}}
file={
(pdfRotateBase64 && pdfDataBase64) ||
pdfDetails[0]?.SignedUrl ||
pdfDetails[0].URL
}
file={pdfDataBase64}
>
<Page
scale={scale || 1}
key={index}
pageNumber={pageNumber}
width={containerWH.width}
onLoadSuccess={handlePageLoadSuccess}
scale={props.scale || 1}
key={props.index}
pageNumber={props.pageNumber}
width={props.containerWH.width}
renderAnnotationLayer={false}
renderTextLayer={false}
onGetAnnotationsError={(error) => {
@@ -471,92 +512,109 @@ function RenderPdf({
zIndex: 0
}}
noScrollY={false}
noScrollX={scale === 1 ? true : false}
noScrollX={props.scale === 1 ? true : false}
>
<div
style={{
width: containerWH?.width && containerWH?.width * scale
width:
props.containerWH?.width &&
props.containerWH?.width * props.scale
}}
ref={drop}
ref={props.drop}
id="container"
>
{pdfLoad &&
containerWH?.width &&
pdfOriginalWH.length > 0 &&
(pdfRequest //pdf request sign flow
? signerPos?.map((data, key) => {
{props.pdfLoad &&
props.containerWH?.width &&
props.pdfOriginalWH.length > 0 &&
(props.pdfRequest || props.isSelfSign //pdf request sign flow
? props.signerPos?.map((data, key) => {
return (
<React.Fragment key={key}>
{checkSignedSigners(data)}
</React.Fragment>
);
})
: placeholder //placeholder and template flow
? signerPos.map((data, ind) => {
: props.placeholder //placeholder and template flow
? props.signerPos.map((data, ind) => {
return (
<React.Fragment key={ind}>
{data?.placeHolder &&
data?.placeHolder.map((placeData, index) => {
return (
<React.Fragment key={index}>
{placeData.pageNumber === pageNumber &&
{placeData.pageNumber === props.pageNumber &&
placeData.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
handleStop={handleStop}
setIsPageCopy={props.setIsPageCopy}
setSignKey={props.setSignKey}
handleDeleteSign={
props.handleDeleteSign
}
setIsStamp={props.setIsStamp}
handleTabDrag={props.handleTabDrag}
handleStop={props.handleStop}
handleSignYourselfImageResize={
handleImageResize
}
index={pageNumber}
xyPostion={signerPos}
setXyPostion={setSignerPos}
index={props.pageNumber}
xyPosition={props.signerPos}
setXyPosition={props.setSignerPos}
data={data}
setIsResize={setIsResize}
setShowDropdown={setShowDropdown}
setIsResize={props.setIsResize}
setShowDropdown={
props.setShowDropdown
}
isShowBorder={true}
isPlaceholder={true}
setUniqueId={setUniqueId}
handleLinkUser={handleLinkUser}
setUniqueId={props.setUniqueId}
handleLinkUser={
props.handleLinkUser
}
handleUserName={handleUserName}
isSignYourself={false}
posWidth={posWidth}
posHeight={posHeight}
isDragging={isDragging}
setIsValidate={setIsValidate}
setWidgetType={setWidgetType}
setIsRadio={setIsRadio}
setIsCheckbox={setIsCheckbox}
isDragging={props.isDragging}
setIsValidate={props.setIsValidate}
setWidgetType={props.setWidgetType}
setIsRadio={props.setIsRadio}
setIsCheckbox={props.setIsCheckbox}
setCurrWidgetsDetails={
setCurrWidgetsDetails
props.setCurrWidgetsDetails
}
setSelectWidgetId={
setSelectWidgetId
props.setSelectWidgetId
}
selectWidgetId={selectWidgetId}
handleNameModal={handleNameModal}
setTempSignerId={setTempSignerId}
uniqueId={uniqueId}
selectWidgetId={
props.selectWidgetId
}
handleNameModal={
props.handleNameModal
}
setTempSignerId={
props.setTempSignerId
}
uniqueId={props.uniqueId}
handleTextSettingModal={
handleTextSettingModal
props.handleTextSettingModal
}
scale={scale}
containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
setIsSelectId={setIsSelectId}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
isResize={isResize}
unSignedWidgetId={unSignedWidgetId}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
setIsSelectId={props.setIsSelectId}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
unSignedWidgetId={
props.unSignedWidgetId
}
isFreeResize={true}
/>
</React.Fragment>
);
@@ -567,61 +625,67 @@ function RenderPdf({
</React.Fragment>
);
})
: !pdfDetails?.[0]?.IsCompleted &&
xyPostion?.map((data, ind) => {
: !props.pdfDetails?.[0]?.IsCompleted &&
props.xyPosition?.map((data, ind) => {
// signyourself flow
return (
<React.Fragment key={ind}>
{data.pageNumber === pageNumber &&
{data.pageNumber === props.pageNumber &&
data.pos.map((pos) => {
return (
<React.Fragment key={pos.key}>
<Placeholder
pos={pos}
setIsPageCopy={setIsPageCopy}
setSignKey={setSignKey}
handleDeleteSign={handleDeleteSign}
setIsStamp={setIsStamp}
handleTabDrag={handleTabDrag}
setIsPageCopy={props.setIsPageCopy}
setSignKey={props.setSignKey}
handleDeleteSign={props.handleDeleteSign}
setIsStamp={props.setIsStamp}
handleTabDrag={props.handleTabDrag}
handleStop={(event, dragElement) =>
handleStop(event, dragElement, pos.type)
props.handleStop(
event,
dragElement,
pos.type
)
}
handleSignYourselfImageResize={
handleSignYourselfImageResize
}
index={index}
xyPostion={xyPostion}
setXyPostion={setXyPostion}
setIsSignPad={setIsSignPad}
index={props.index}
xyPosition={props.xyPosition}
setXyPosition={props.setXyPosition}
setIsSignPad={props.setIsSignPad}
isShowBorder={true}
isSignYourself={true}
posWidth={posWidth}
posHeight={posHeight}
pdfDetails={pdfDetails[0]}
isDragging={isDragging}
setIsInitial={setIsInitial}
setWidgetType={setWidgetType}
setSelectWidgetId={setSelectWidgetId}
selectWidgetId={selectWidgetId}
pdfDetails={props.pdfDetails[0]}
isDragging={props.isDragging}
setIsInitial={props.setIsInitial}
setWidgetType={props.setWidgetType}
setSelectWidgetId={props.setSelectWidgetId}
selectWidgetId={props.selectWidgetId}
handleUserName={handleUserName}
setIsCheckbox={setIsCheckbox}
setValidateAlert={setValidateAlert}
setIsCheckbox={props.setIsCheckbox}
setValidateAlert={props.setValidateAlert}
setCurrWidgetsDetails={
setCurrWidgetsDetails
props.setCurrWidgetsDetails
}
handleTextSettingModal={
handleTextSettingModal
props.handleTextSettingModal
}
scale={scale}
containerWH={containerWH}
pdfOriginalWH={pdfOriginalWH}
pageNumber={pageNumber}
fontSize={fontSize}
setFontSize={setFontSize}
fontColor={fontColor}
setFontColor={setFontColor}
isResize={isResize}
setIsResize={setIsResize}
scale={props.scale}
containerWH={props.containerWH}
pdfOriginalWH={props.pdfOriginalWH}
pageNumber={props.pageNumber}
fontSize={props.fontSize}
setFontSize={props.setFontSize}
fontColor={props.fontColor}
setFontColor={props.setFontColor}
isResize={props.isResize}
setIsResize={props.setIsResize}
isFreeResize={false}
isOpenSignPad={true}
/>
</React.Fragment>
);
@@ -632,27 +696,22 @@ function RenderPdf({
{/* this component for render pdf document is in middle of the component */}
<Document
onLoadError={() => setPdfLoad(false)}
onLoadError={() => props.setPdfLoad(false)}
loading={t("loading-doc")}
onLoadSuccess={pageDetails}
onLoadSuccess={props.pageDetails}
onClick={() => {
if (setSelectWidgetId) {
setSelectWidgetId("");
if (props.setSelectWidgetId) {
props.setSelectWidgetId("");
}
}}
// ref={pdfRef}
file={
(pdfRotateBase64 && pdfDataBase64) ||
pdfDetails[0]?.SignedUrl ||
pdfDetails[0].URL
}
file={pdfDataBase64}
>
<Page
key={index}
width={containerWH.width}
scale={scale || 1}
key={props.index}
width={props.containerWH.width}
scale={props.scale || 1}
className={"-z-[1]"} // when user zoom-in in tablet widgets move backward that's why pass -z-[1]
pageNumber={pageNumber}
pageNumber={props.pageNumber}
renderAnnotationLayer={false}
renderTextLayer={false}
onGetAnnotationsError={(error) => {
@@ -5,9 +5,11 @@ import { useTranslation } from "react-i18next";
function SelectLanguage(props) {
const { i18n } = useTranslation();
const languages = [
{ value: "en", text: "English" },
{ value: "es", text: "Española" },
{ value: "fr", text: "Français" }
{ value: "en", text: "English" }, //english
{ value: "es", text: "Española" }, //spanish
{ value: "fr", text: "Français" }, //french
{ value: "it", text: "Italiano" }, //italian
{ value: "de", text: "Deutsch" } //german
];
const defaultLanguage = i18next.language || "en";
const [lang, setLang] = useState(defaultLanguage);
+343 -209
View File
@@ -1,6 +1,15 @@
import React, { useRef, useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
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 Loader from "../../primitives/Loader";
function SignPad(props) {
const { t } = useTranslation();
@@ -10,11 +19,11 @@ function SignPad(props) {
const [isDefaultSign, setIsDefaultSign] = useState(false);
const [isTab, setIsTab] = useState("");
const [isSignImg, setIsSignImg] = useState("");
const [signValue, setSignValue] = useState("");
const [textWidth, setTextWidth] = useState(0);
const [textHeight, setTextHeight] = useState(0);
const [signatureType, setSignatureType] = useState("");
const [isSignTypes, setIsSignTypes] = useState(true);
const [typedSignature, setTypedSignature] = useState("");
const fontOptions = [
{ value: "Fasthand" },
{ value: "Dancing Script" },
@@ -23,28 +32,36 @@ function SignPad(props) {
// Add more font options as needed
];
const [fontSelect, setFontSelect] = useState(fontOptions[0].value);
const senderUser =
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
) &&
localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = JSON.parse(senderUser);
const [isSavedSign, setIsSavedSign] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const accesstoken = localStorage.getItem("accesstoken") || "";
const senderUser = localStorage.getItem(
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
);
const jsonSender = senderUser && JSON.parse(senderUser);
const currentUserName = jsonSender && jsonSender?.name;
useEffect(() => {
handleTab();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.signatureTypes]);
function handleTab() {
const signtypes = props?.signatureTypes || [];
const index = signtypes?.findIndex((x) => x.enabled === true);
if (index !== -1) {
const defaultIndex = signtypes?.findIndex(
(x) =>
x.name === "default" &&
x.enabled === true &&
props.defaultSign &&
(props?.currWidgetsDetails?.type || props.widgetType) !== "image" &&
(props?.currWidgetsDetails?.type || props.widgetType) !== "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 = props?.signatureTypes[index].name;
const tab = props?.signatureTypes[getIndex].name;
if (tab === "draw") {
setIsTab("draw");
setSignatureType("draw");
@@ -54,6 +71,7 @@ function SignPad(props) {
} else if (tab === "typed") {
setIsTab("type");
} else if (tab === "default") {
setIsDefaultSign(true);
setIsTab("mysignature");
} else {
setIsTab(true);
@@ -71,7 +89,6 @@ function SignPad(props) {
//function for clear signature image
const handleClear = () => {
props?.setCurrWidgetsDetails({});
if (isTab === "draw") {
if (canvasRef.current) {
canvasRef.current.clear();
@@ -88,6 +105,176 @@ function SignPad(props) {
props?.setSignature(data);
setIsSignImg(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 signaute, initials, stamp as a default
const handleSaveSign = async () => {
if (isSignImg || props?.image?.src) {
setIsLoader(true);
try {
const User = Parse?.User?.current();
const sanitizename = generateTitleFromFilename(User?.get("name"));
const replaceSpace = sanitizeFileName(sanitizename);
let file;
if (isSignImg) {
file = base64StringtoFile(isSignImg, `${replaceSpace}__sign`);
} else {
file = base64StringtoFile(props?.image?.src, `${replaceSpace}__sign`);
}
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 signaute, initials, stamp
try {
const signCls = new Parse.Object("contracts_Signature");
if (props?.saveSignCheckbox?.signId) {
signCls.id = props.saveSignCheckbox.signId;
}
if (
props.currWidgetsDetails?.type === "initials" ||
props?.widgetType === "initials"
) {
signCls.set("Initials", imageUrl);
} else if (
props.currWidgetsDetails?.type === "signature" ||
props?.widgetType === "signature"
) {
signCls.set("ImageURL", imageUrl);
}
signCls.set("UserId", userId);
const signRes = await signCls.save();
if (signRes) {
props.saveSignCheckbox.signId;
props.setSaveSignCheckbox((prev) => ({
...prev,
signId: signRes?.id
}));
const _signRes = JSON.parse(JSON.stringify(signRes));
if (
props.currWidgetsDetails?.type === "signature" ||
props?.widgetType === "signature"
) {
const defaultSign = await getBase64FromUrl(
_signRes?.ImageURL,
true
);
props.setDefaultSignImg(defaultSign);
} else if (
props.currWidgetsDetails?.type === "initials" ||
props?.widgetType === "initials"
) {
const defaultInitials = await getBase64FromUrl(
_signRes?.Initials,
true
);
props.setMyInitial(defaultInitials);
}
alert(t("saved-successfully"));
}
return signRes;
} catch (err) {
console.log(err);
alert(`${err.message}`);
} finally {
setIsLoader(false);
}
}
} catch (err) {
console.log("Err while saving signature", err);
}
}
};
const handleSaveBtn = async () => {
if (accesstoken && isSavedSign) {
await handleSaveSign();
resetToDefault();
} else {
resetToDefault();
}
};
const resetToDefault = () => {
props?.setCurrWidgetsDetails({});
if (!props?.image) {
if (isTab === "mysignature") {
setIsSignImg("");
if (props?.isInitial) {
props?.onSaveSign(signatureType, "initials");
} else {
props?.onSaveSign(null, "default");
}
} else {
if (isTab === "type") {
setIsSignImg("");
props?.onSaveSign(
null,
false,
!props?.isInitial && textWidth > 150 ? 150 : textWidth,
!props?.isInitial && textHeight > 35 ? 35 : textHeight,
typedSignature
);
} else {
setIsSignImg("");
canvasRef.current.clear();
props?.onSaveSign(signatureType);
}
}
setPenColor("blue");
} else {
setIsSignImg("");
props?.onSaveImage(signatureType);
}
props?.setIsSignPad(false);
props?.setIsInitial && props?.setIsInitial(false);
props?.setIsImageSelect(false);
setIsDefaultSign(false);
props?.setImage();
handleTab();
props?.setIsStamp(false);
};
//save button component
const SaveBtn = () => {
return (
@@ -102,48 +289,14 @@ function SignPad(props) {
</button>
)}
<button
onClick={() => {
props?.setCurrWidgetsDetails({});
if (!props?.image) {
if (isTab === "mysignature") {
setIsSignImg("");
if (props?.isInitial) {
props?.onSaveSign(signatureType, "initials");
} else {
props?.onSaveSign(null, "default");
}
} else {
if (isTab === "type") {
setIsSignImg("");
props?.onSaveSign(
null,
false,
!props?.isInitial && textWidth > 150 ? 150 : textWidth,
!props?.isInitial && textHeight > 35 ? 35 : textHeight
);
} else {
setIsSignImg("");
canvasRef.current.clear();
props?.onSaveSign(signatureType);
}
}
setPenColor("blue");
} else {
setIsSignImg("");
props?.onSaveImage(signatureType);
}
props?.setIsSignPad(false);
props?.setIsInitial(false);
props?.setIsImageSelect(false);
setIsDefaultSign(false);
props?.setImage();
handleTab();
setSignValue("");
props?.setIsStamp(false);
}}
onClick={() => handleSaveBtn()}
type="button"
className={`${
isSignImg || props?.image || isDefaultSign || textWidth
isSignImg ||
props?.image ||
isDefaultSign ||
textWidth ||
props.isAutoSign
? ""
: "pointer-events-none"
} op-btn op-btn-primary shadow-lg`}
@@ -151,7 +304,8 @@ function SignPad(props) {
(isTab === "draw" && isSignImg) ||
(isTab === "image" && props?.image) ||
(isTab === "mysignature" && isDefaultSign) ||
(isTab === "type" && signValue)
(isTab === "type" && typedSignature) ||
props.isAutoSign
? false
: props?.image
? false
@@ -163,9 +317,24 @@ function SignPad(props) {
</div>
);
};
const autoSignAll = () => {
return (
<label className="cursor-pointer flex items-center mb-[6px] text-center text-[11px] md:text-base">
<input
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
type="checkbox"
value={props.isAutoSign}
onChange={(e) => {
props.setIsAutoSign(e.target.checked);
}}
/>
{t("auto-sign-mssg")}
</label>
);
};
//useEffect for set already draw or save signature url/text url of signature text type and draw type for initial type and signature type widgets
useEffect(() => {
if (props?.currWidgetsDetails && canvasRef.current) {
if (props?.currWidgetsDetails && canvasRef.current && props.isSignPad) {
const isWidgetType = props?.currWidgetsDetails?.type;
const signatureType = props?.currWidgetsDetails?.signatureType;
const url = props?.currWidgetsDetails?.SignUrl;
@@ -181,13 +350,14 @@ function SignPad(props) {
) {
canvasRef.current.fromDataURL(url);
}
}
const trimmedName = currentUserName && currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = props?.isInitial ? firstCharacter : currentUserName;
setSignValue(userName || "");
setFontSelect("Fasthand");
const trimmedName = currentUserName && currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = props?.isInitial ? firstCharacter : currentUserName;
const signatureValue = props?.currWidgetsDetails?.typeSignature;
setTypedSignature(signatureValue || userName || "");
setFontSelect("Fasthand");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.isSignPad]);
@@ -207,19 +377,19 @@ function SignPad(props) {
loadFont();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fontSelect]);
useEffect(() => {
// Load the default signature after the component mounts
if (canvasRef.current) {
canvasRef.current.fromDataURL(isSignImg);
}
if (isTab === "type") {
const trimmedName = signValue
? signValue?.trim()
const trimmedName = typedSignature
? typedSignature?.trim()
: currentUserName?.trim();
const firstCharacter = trimmedName?.charAt(0);
const userName = props?.isInitial ? firstCharacter : signValue;
setSignValue(userName);
const userName = props?.isInitial ? firstCharacter : typedSignature;
const signatureValue = props?.currWidgetsDetails?.typeSignature;
setTypedSignature(signatureValue || userName || "");
convertToImg(fontSelect, userName);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -271,40 +441,19 @@ function SignPad(props) {
const dataUrl = canvasElement.toDataURL("image/png");
props?.setSignature(dataUrl);
};
const PenColorComponent = (props) => {
return (
<div className="flex flex-row items-start">
<div className="flex flex-row items-center m-[5px] gap-2">
{allColor.map((data, key) => {
return (
<i
style={{
margin: "5px",
color: data,
borderBottom:
key === 0 && penColor === "blue"
? "2px solid blue"
: key === 1 && penColor === "red"
? "2px solid red"
: key === 2 && penColor === "black"
? "2px solid black"
: "2px solid white"
}}
key={key}
onClick={() => {
props?.convertToImg &&
props?.convertToImg(fontSelect, signValue, data);
if (key === 0) {
setPenColor("blue");
} else if (key === 1) {
setPenColor("red");
} else if (key === 2) {
setPenColor("black");
}
props?.convertToImg(fontSelect, typedSignature, data);
setPenColor(allColor[key]);
}}
key={key}
className="fa-light fa-pen-nib"
width={20}
height={20}
className={`border-b-[2px] ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
></i>
);
})}
@@ -316,19 +465,35 @@ function SignPad(props) {
const handleCancelBtn = () => {
setPenColor("blue");
props?.setIsSignPad(false);
props?.setIsInitial(false);
props?.setIsInitial && props?.setIsInitial(false);
props?.setIsImageSelect(false);
setIsDefaultSign(false);
props?.setImage();
handleTab();
setSignValue("");
props?.setIsStamp(false);
};
const savesigncheckbox = (
<label className="cursor-pointer flex items-center mb-0 text-center text-[11px] md:text-base">
<input
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
type="checkbox"
checked={isSavedSign}
onChange={(e) => setIsSavedSign(e.target.checked)}
/>
Save {props?.currWidgetsDetails?.type || props?.widgetType}
</label>
);
return (
<div>
{props?.isSignPad && (
<div className="op-modal op-modal-open">
<div className="op-modal-box px-[13px] pt-2 pb-0">
{isLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
{isSignTypes ? (
<>
<div className="flex justify-between text-base-content items-center">
@@ -344,6 +509,51 @@ function SignPad(props) {
</span>
) : (
<>
{!props?.isInitial &&
props?.defaultSign &&
isTabEnabled("default") ? (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
props?.setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-signature")}
</span>
</div>
) : (
props?.isInitial &&
props?.myInitial &&
isTabEnabled("default") && (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
props?.setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-initials")}
</span>
</div>
)
)}
{isTabEnabled("draw") && (
<div>
<span
@@ -405,51 +615,6 @@ function SignPad(props) {
</span>
</div>
)}
{!props?.isInitial &&
props?.defaultSign &&
isTabEnabled("default") ? (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
props?.setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-signature")}
</span>
</div>
) : (
props?.isInitial &&
props?.myInitial &&
isTabEnabled("default") && (
<div>
<span
onClick={() => {
setIsDefaultSign(true);
props?.setIsImageSelect(true);
setIsTab("mysignature");
setSignatureType("");
props?.setImage();
}}
className={`${
isTab === "mysignature"
? "op-link-primary"
: "no-underline"
} op-link underline-offset-8 ml-[2px]`}
>
{t("my-initials")}
</span>
</div>
)
)}
</>
)}
</div>
@@ -467,21 +632,7 @@ function SignPad(props) {
<>
<div className="flex justify-center">
<div
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
marginBottom: 6,
cursor: "pointer"
}}
className={
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
}
className={`${props?.isInitial ? "intialSignatureCanvas" : "signatureCanvas"} bg-white border-[1.3px] border-[#007bff] flex flex-col justify-center items-center mb-[6px] cursor-pointer`}
>
<img
alt="stamp img"
@@ -495,6 +646,7 @@ function SignPad(props) {
/>
</div>
</div>
{props.setIsAutoSign && autoSignAll()}
<div className="flex justify-end">
<SaveBtn />
</div>
@@ -503,21 +655,7 @@ function SignPad(props) {
!props?.image ? (
<div className="flex justify-center">
<div
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
marginBottom: 6,
cursor: "pointer"
}}
className={
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
}
className={`${props?.isInitial ? "intialSignatureCanvas" : "signatureCanvas"} bg-white border-[1.3px] border-[#007bff] flex flex-col justify-center items-center mb-[6px] cursor-pointer`}
onClick={() => props?.imageRef.current.click()}
>
<input
@@ -536,27 +674,18 @@ function SignPad(props) {
<>
<div className="flex justify-center">
<div
style={{
border: "1.3px solid #007bff",
borderRadius: "2px",
marginBottom: 6,
overflow: "hidden"
}}
className={
props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas"
}
className={`${props?.isInitial ? "intialSignatureCanvas" : "signatureCanvas"} bg-white border-[1.3px] border-[#007bff] mb-[6px] overflow-hidden`}
>
<img
alt="print img"
ref={props?.imageRef}
src={props?.image.src}
draggable="false"
className=" object-contain h-full w-full"
className="object-contain h-full w-full"
/>
</div>
</div>
{props.setIsAutoSign && autoSignAll()}
<div className="flex justify-end">
<SaveBtn />
</div>
@@ -576,10 +705,14 @@ function SignPad(props) {
style={{ fontFamily: fontSelect, color: penColor }}
type="text"
className="ml-1 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-[20px]"
placeholder="Your signature"
value={signValue}
placeholder={
props?.isInitial
? t("initial-type")
: t("signature-type")
}
value={typedSignature}
onChange={(e) => {
setSignValue(e.target.value);
setTypedSignature(e.target.value);
convertToImg(fontSelect, e.target.value);
}}
/>
@@ -598,26 +731,28 @@ function SignPad(props) {
}}
onClick={() => {
setFontSelect(font.value);
convertToImg(font.value, signValue);
convertToImg(font.value, typedSignature);
}}
>
<div
style={{
padding: "5px 10px 5px 10px",
fontSize: "20px",
color: penColor
}}
className="py-[5px] px-[10px] text-[20px]"
style={{ color: penColor }}
>
{signValue ? signValue : "Your signature"}
{typedSignature
? typedSignature
: "Your signature"}
</div>
</div>
);
})}
</div>
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent convertToImg={convertToImg} />
<SaveBtn />
<div className="flex flex-col justify-between mt-[10px]">
{props.setIsAutoSign && autoSignAll()}
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent convertToImg={convertToImg} />
<SaveBtn />
</div>
</div>
</div>
) : (
@@ -627,15 +762,8 @@ function SignPad(props) {
ref={canvasRef}
penColor={penColor}
canvasProps={{
className: props?.isInitial
? "intialSignatureCanvas"
: "signatureCanvas",
style: {
border: "1.6px solid #007bff",
borderRadius: "2px"
}
className: `${props?.isInitial ? "intialSignatureCanvas" : "signatureCanvas"} border-[1.3px] border-[#007bff]`
}}
// backgroundColor="rgb(255, 255, 255)"
onEnd={() =>
handleSignatureChange(
canvasRef.current?.toDataURL()
@@ -644,16 +772,22 @@ function SignPad(props) {
dotSize={1}
/>
</div>
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent />
<SaveBtn />
<div className="flex flex-col justify-between mt-[10px]">
{props.setIsAutoSign && autoSignAll()}
{accesstoken &&
props?.saveSignCheckbox?.isVisible &&
savesigncheckbox}
<div className="flex flex-row justify-between mt-[10px]">
<PenColorComponent />
<SaveBtn />
</div>
</div>
</>
)}
</div>
</>
) : (
<div className="">
<div>
<div className="relative flex flex-row items-center justify-between">
<div className="text-base-content font-bold text-lg">
Signature
+17 -7
View File
@@ -1,31 +1,41 @@
import React from "react";
import "../../styles/signature.css";
import { useTranslation } from "react-i18next";
function Signedby({ pdfDetails }) {
function Signedby(props) {
const { t } = useTranslation();
const getFirstLetter = (name) => {
const getFirstLetter = (pdfData) => {
const name = props.isSelfSign
? (pdfData?.Signers && pdfData?.Signers[0]?.Name) || "User"
: pdfData.ExtUserPtr?.Name;
const firstLetter = name.charAt(0);
return firstLetter;
};
return (
<div className="hidden md:block w-full 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">
{t("signed-by")}
{props.isSelfSign ? t("user") : t("signed-by")}
</div>
<div className="mt-[2px] bg-base-100">
<div className="bg-[#93a3db] rounded-xl mx-1 flex flex-row items-center py-[10px]">
<div className="bg-[#576081] flex w-[30px] h-[30px] rounded-full justify-center items-center mx-1">
<span className="text-[12px] text-center font-bold text-white uppercase">
{getFirstLetter(pdfDetails.ExtUserPtr.Name)}
{getFirstLetter(props.pdfDetails)}
</span>
</div>
<div className="flex flex-col">
<span className="text-[12px] font-bold text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
{pdfDetails.ExtUserPtr.Name}
{props.isSelfSign
? (props.pdfDetails?.Signers &&
props.pdfDetails?.Signers[0]?.Name) ||
"User"
: props.pdfDetails.ExtUserPtr.Name || "User"}
</span>
<span className="text-[10px] font-medium text-[#424242] w-[100px] whitespace-nowrap overflow-hidden text-ellipsis">
{pdfDetails.ExtUserPtr.Email}
{props.isSelfSign
? (props.pdfDetails?.Signers &&
props.pdfDetails?.Signers[0]?.Email) ||
""
: props.pdfDetails.ExtUserPtr.Email || ""}
</span>
</div>
</div>
@@ -19,7 +19,7 @@ function SignerListPlace(props) {
</sup>
</a>
<Tooltip id="my-tooltip" className="z-[100]">
<div className="max-w-[450px] 2xl:max-w-[500px] 2xl:text-[20px] p-[1px]">
<div className="max-w-[450px] 2xl:max-w-[500px] p-[1px]">
<p className="font-bold pb-[1px]">{t("role-help.p1")}</p>
<p>{t("role-help.p2")} </p>
<p className="font-bold">{t("role-help.p3")}</p>
@@ -5,107 +5,74 @@ import { useDrag } from "react-dnd";
import WidgetList from "./WidgetList";
import {
color,
isMobile,
radioButtonWidget,
textInputWidget,
textWidget,
widgets
} from "../../constant/Utils";
import { useTranslation } from "react-i18next";
function WidgetComponent({
dragSignature,
signRef,
handleDivClick,
handleMouseLeave,
dragStamp,
isSignYourself,
addPositionOfSignature,
signersdata,
isSelectListId,
setIsSelectId,
isSigners,
dataTut,
isMailSend,
handleAddSigner,
setUniqueId,
setRoleName,
handleDeleteUser,
signerPos,
handleRoleChange,
handleOnBlur,
title,
setSignersData,
sendInOrder,
isTemplateFlow,
setBlockColor,
setIsAddSigner,
uniqueId
}) {
function WidgetComponent(props) {
const { t } = useTranslation();
const signRef = useRef(null);
const userInformation = localStorage.getItem("UserInformation");
const [isSignersModal, setIsSignersModal] = useState(false);
const [, signature] = useDrag({
type: "BOX",
item: { type: "BOX", id: 1, text: "signature" }
});
const [, stamp] = useDrag({
type: "BOX",
item: { type: "BOX", id: 2, text: "stamp" }
});
const [, dropdown] = useDrag({
type: "BOX",
item: { type: "BOX", id: 5, text: "dropdown" },
collect: (monitor) => ({
isDragDropdown: !!monitor.isDragging()
})
item: { type: "BOX", id: 5, text: "dropdown" }
});
const [, checkbox] = useDrag({
type: "BOX",
item: { type: "BOX", id: 6, text: "checkbox" },
collect: (monitor) => ({ isDragCheck: !!monitor.isDragging() })
item: { type: "BOX", id: 6, text: "checkbox" }
});
const [, textInput] = useDrag({
type: "BOX",
item: { type: "BOX", id: 7, text: textInputWidget },
collect: (monitor) => ({ isDragTextInput: !!monitor.isDragging() })
item: { type: "BOX", id: 7, text: textInputWidget }
});
const [, initials] = useDrag({
type: "BOX",
item: { type: "BOX", id: 8, text: "initials" },
collect: (monitor) => ({ isDragInitial: !!monitor.isDragging() })
item: { type: "BOX", id: 8, text: "initials" }
});
const [, name] = useDrag({
type: "BOX",
item: { type: "BOX", id: 9, text: "name" },
collect: (monitor) => ({ isDragName: !!monitor.isDragging() })
item: { type: "BOX", id: 9, text: "name" }
});
const [, company] = useDrag({
type: "BOX",
item: { type: "BOX", id: 10, text: "company" },
collect: (monitor) => ({ isDragCompany: !!monitor.isDragging() })
item: { type: "BOX", id: 10, text: "company" }
});
const [, jobTitle] = useDrag({
type: "BOX",
item: { type: "BOX", id: 11, text: "job title" },
collect: (monitor) => ({ isDragJobtitle: !!monitor.isDragging() })
item: { type: "BOX", id: 11, text: "job title" }
});
const [, date] = useDrag({
type: "BOX",
item: { type: "BOX", id: 12, text: "date" },
collect: (monitor) => ({ isDragDate: !!monitor.isDragging() })
item: { type: "BOX", id: 12, text: "date" }
});
const [, image] = useDrag({
type: "BOX",
item: { type: "BOX", id: 13, text: "image" },
collect: (monitor) => ({ isDragImage: !!monitor.isDragging() })
item: { type: "BOX", id: 13, text: "image" }
});
const [, email] = useDrag({
type: "BOX",
item: { type: "BOX", id: 14, text: "email" },
collect: (monitor) => ({ isDragEmail: !!monitor.isDragging() })
item: { type: "BOX", id: 14, text: "email" }
});
const [, radioButton] = useDrag({
type: "BOX",
item: { type: "BOX", id: 15, text: radioButtonWidget },
collect: (monitor) => ({ isDragRadiotton: !!monitor.isDragging() })
item: { type: "BOX", id: 15, text: radioButtonWidget }
});
const [, text] = useDrag({
type: "BOX",
item: { type: "BOX", id: 16, text: textWidget },
collect: (monitor) => ({ isDragText: !!monitor.isDragging() })
item: { type: "BOX", id: 16, text: textWidget }
});
const isMobile = window.innerWidth < 767;
const scrollContainerRef = useRef(null);
const [widget, setWidget] = useState([]);
const handleModal = () => {
setIsSignersModal(!isSignersModal);
@@ -113,8 +80,8 @@ function WidgetComponent({
useEffect(() => {
const widgetRef = [
dragSignature,
dragStamp,
signature,
stamp,
initials,
name,
jobTitle,
@@ -137,37 +104,67 @@ function WidgetComponent({
// eslint-disable-next-line
}, []);
const modifiedWidgets = widget.filter(
(data) =>
![
"dropdown",
radioButtonWidget,
textInputWidget,
"date",
"image",
"checkbox"
].includes(data.type)
);
const unlogedInUserWidgets = widget.filter(
(data) =>
![
"dropdown",
radioButtonWidget,
textInputWidget,
"date",
"image",
"checkbox",
"name",
"email",
"job title",
"company"
].includes(data.type)
);
const filterWidgets = widget.filter(
(data) =>
data.type !== "dropdown" &&
data.type !== radioButtonWidget &&
data.type !== textInputWidget
!["dropdown", radioButtonWidget, textInputWidget].includes(data.type)
);
const textWidgetData = widget.filter((data) => data.type !== textWidget);
const updateWidgets = isSignYourself
const updateWidgets = props.isSignYourself
? filterWidgets
: isTemplateFlow
: props.isTemplateFlow
? textWidgetData
: widget;
: props.isAlllowModify
? userInformation
? modifiedWidgets
: unlogedInUserWidgets
: widget;
const handleSelectRecipient = () => {
if (
signersdata[isSelectListId]?.Email ||
signersdata[isSelectListId]?.Role
props.signersdata[props.isSelectListId]?.Email ||
props.signersdata[props.isSelectListId]?.Role
) {
const userData =
signersdata[isSelectListId]?.Name || signersdata[isSelectListId]?.Role;
props.signersdata[props.isSelectListId]?.Name ||
props.signersdata[props.isSelectListId]?.Role;
const name =
userData?.length > 20 ? `${userData.slice(0, 20)}...` : userData;
return name;
}
};
return (
<>
{isMobile ? (
!isMailSend && (
!props.isMailSend && (
<div id="navbar" className="fixed z-[99] bottom-0 right-0 w-full">
{isSigners && (
{props.isSigners && (
<div className="w-full mb-[5px] flex justify-center items-center gap-1">
<div className="w-full ml-[5px]" onClick={() => handleModal()}>
<select
@@ -175,8 +172,8 @@ function WidgetComponent({
className="w-full op-select op-select-bordered pointer-events-none"
value={handleSelectRecipient()}
style={{
backgroundColor: isSelectListId
? color[isSelectListId % color.length]
backgroundColor: props.blockColor
? props.blockColor
: color[0]
}}
>
@@ -187,19 +184,19 @@ function WidgetComponent({
</div>
<div className="w-[18%]">
{handleAddSigner ? (
{props.handleAddSigner ? (
<button
data-tut="reactourAddbtn"
onClick={() => handleAddSigner()}
onClick={() => props.handleAddSigner()}
className="op-btn op-btn-accent"
>
<i className="fa-light fa-plus "></i>
</button>
) : (
setIsAddSigner && (
props.setIsAddSigner && (
<button
data-tut="addRecipient"
onClick={() => setIsAddSigner(true)}
onClick={() => props.setIsAddSigner(true)}
className="op-btn op-btn-accent"
>
<i className="fa-light fa-plus"></i>
@@ -212,18 +209,16 @@ function WidgetComponent({
<div
data-tut="addWidgets"
ref={scrollContainerRef}
className="bg-base-100 border-[2px] border-t-primary"
>
<div className="flex whitespace-nowrap overflow-x-scroll pt-[10px] pb-[5px] pr-[5px]">
<WidgetList
updateWidgets={updateWidgets}
handleDivClick={handleDivClick}
handleMouseLeave={handleMouseLeave}
handleDivClick={props.handleDivClick}
handleMouseLeave={props.handleMouseLeave}
signRef={signRef}
marginLeft={5}
addPositionOfSignature={addPositionOfSignature}
isMobile={isMobile}
addPositionOfSignature={props.addPositionOfSignature}
/>
</div>
</div>
@@ -231,23 +226,20 @@ function WidgetComponent({
)
) : (
<div
data-tut={dataTut}
data-tut={props.dataTut}
className={`${
isMailSend ? "bg-opacity-50 pointer-events-none" : ""
props.isMailSend ? "bg-opacity-50 pointer-events-none" : ""
} 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>
</div>
<div
className="p-[15px] flex flex-col pt-4 2xl:m-5"
data-tut="addWidgets"
>
<div className="p-[15px] flex flex-col pt-4" data-tut="addWidgets">
<WidgetList
updateWidgets={updateWidgets}
handleDivClick={handleDivClick}
handleMouseLeave={handleMouseLeave}
handleDivClick={props.handleDivClick}
handleMouseLeave={props.handleMouseLeave}
signRef={signRef}
/>
</div>
@@ -255,32 +247,33 @@ function WidgetComponent({
)}
{isSignersModal && (
<ModalUi
title={title ? title : t("recipients")}
title={props.title ? props.title : t("recipients")}
isOpen={isSignersModal}
handleClose={handleModal}
>
{signersdata.length > 0 ? (
{props.signersdata.length > 0 ? (
<div className="max-h-[600px] overflow-auto pb-1">
<RecipientList
signerPos={signerPos}
signersdata={signersdata}
isSelectListId={isSelectListId}
setIsSelectId={setIsSelectId}
setUniqueId={setUniqueId}
setRoleName={setRoleName}
handleDeleteUser={handleDeleteUser}
handleRoleChange={handleRoleChange}
handleOnBlur={handleOnBlur}
signerPos={props.signerPos}
signersdata={props.signersdata}
isSelectListId={props.isSelectListId}
setIsSelectId={props.setIsSelectId}
setUniqueId={props.setUniqueId}
setRoleName={props.setRoleName}
handleDeleteUser={props.handleDeleteUser}
handleRoleChange={props.handleRoleChange}
handleOnBlur={props.handleOnBlur}
handleModal={handleModal}
sendInOrder={sendInOrder}
setSignersData={setSignersData}
setBlockColor={setBlockColor}
uniqueId={uniqueId}
sendInOrder={props.sendInOrder}
setSignersData={props.setSignersData}
setBlockColor={props.setBlockColor}
uniqueId={props.uniqueId}
setSignerPos={props.setSignerPos}
/>
</div>
) : (
<div className=" p-[20px] text-[15px] font-medium text-center">
{t("please-add")} {title ? title : t("recipients")}
{t("please-add")} {props.title ? props.title : t("recipients")}
</div>
)}
</ModalUi>
+7 -16
View File
@@ -1,32 +1,23 @@
import React from "react";
import { getWidgetType } from "../../constant/Utils";
import { getWidgetType, isMobile } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
function WidgetList(props) {
const { t } = useTranslation();
return props.updateWidgets.map((item, ind) => {
return (
<div className="2xl:p-3 mb-[5px]" key={ind}>
<div className="2xl:p-1 mb-[5px]" key={ind}>
<div
data-tut="isSignatureWidget"
className="select-none mx-[2px] md:mx-0 cursor-all-scroll"
onClick={() => {
props.addPositionOfSignature &&
props.addPositionOfSignature("onclick", item);
}}
ref={(element) => {
if (!props.isMobile) {
item.ref(element);
if (element) {
if (props?.signRef) {
props.signRef.current = element;
}
}
}
}}
onMouseMove={props?.handleDivClick}
onMouseDown={() => props?.handleMouseLeave()}
onTouchStart={props?.handleDivClick}
ref={(element) => !isMobile && item.ref(element)}
onMouseMove={(e) => !isMobile && props?.handleDivClick(e)}
onMouseDown={() => !isMobile && props?.handleMouseLeave()}
onTouchStart={(e) => !isMobile && props?.handleDivClick(e)}
>
{item.ref && getWidgetType(item, t(`widgets-name.${item.type}`))}
</div>
@@ -7,9 +7,6 @@ import {
textInputWidget,
textWidget
} from "../../constant/Utils";
import Upgrade from "../../primitives/Upgrade";
import { isEnableSubscription } from "../../constant/const";
import Tooltip from "../../primitives/Tooltip";
import { fontColorArr, fontsizeArr } from "../../constant/Utils";
import { useTranslation } from "react-i18next";
@@ -21,11 +18,11 @@ const WidgetNameModal = (props) => {
defaultValue: "",
status: "required",
hint: "",
textvalidate: ""
textvalidate: "",
isReadOnly: false
});
const [isValid, setIsValid] = useState(true);
const statusArr = ["Required", "Optional"];
const inputOpt = ["text", "email", "number"];
const [signatureType, setSignatureType] = useState([]);
useEffect(() => {
@@ -38,7 +35,8 @@ const WidgetNameModal = (props) => {
textvalidate:
props.defaultdata?.options?.validation?.type === "regex"
? props.defaultdata?.options?.validation?.pattern
: props.defaultdata?.options?.validation?.type || ""
: props.defaultdata?.options?.validation?.type || "",
isReadOnly: props.defaultdata?.options?.isReadOnly || false
});
} else {
setFormdata({
@@ -73,6 +71,7 @@ const WidgetNameModal = (props) => {
props.handleData(formdata);
}
setFormdata({
isReadOnly: false,
name: "",
defaultValue: "",
status: "required",
@@ -114,35 +113,24 @@ const WidgetNameModal = (props) => {
}
}
function handleBlurRegex() {
if (!formdata.textvalidate) {
setFormdata({ ...formdata, textvalidate: "" });
} else {
if (formdata.defaultValue) {
const regexObject = RegexParser(
handleValidation(formdata.textvalidate)
);
const isValidate = regexObject?.test(formdata.defaultValue);
if (isValidate === false) {
setFormdata({ ...formdata, defaultValue: "" });
}
}
}
}
const handleCheckboxChange = (index) => {
// Create a copy of the signatureType array
const updatedSignatureType = [...signatureType];
// Toggle the enabled value for the clicked item
updatedSignatureType[index].enabled = !updatedSignatureType[index].enabled;
// Update the state with the modified array
setSignatureType(updatedSignatureType);
setSignatureType((prev) =>
prev.map((item, i) =>
i === index ? { ...item, enabled: !item.enabled } : item
)
);
};
return (
<ModalUi
isOpen={props.isOpen}
handleClose={props.handleClose && props.handleClose}
title={t("widget-info")}
title={
["signature", "initials"].includes(props.defaultdata?.type)
? t("signature-setting")
: t("widget-info")
}
>
<form
onSubmit={handleSubmit}
@@ -173,69 +161,6 @@ const WidgetNameModal = (props) => {
)}
{props.defaultdata?.type === textInputWidget && (
<>
{isEnableSubscription && (
<div className="mb-[0.75rem]">
<div className="flex items-center gap-1">
<label
htmlFor="textvalidate"
className={`${
!props.isSubscribe && isEnableSubscription
? "bg-opacity-50 pointer-events-none"
: ""
} text-[13px]`}
>
{t("validation")}
</label>
<Tooltip
url={"https://www.w3schools.com/jsref/jsref_obj_regexp.asp"}
/>
{!props.isSubscribe && isEnableSubscription && <Upgrade />}
</div>
<div
className={`${
!props.isSubscribe && isEnableSubscription
? "bg-opacity-50 pointer-events-none"
: ""
} flex flex-row gap-[10px] mb-[0.5rem]`}
>
<div className="w-full relative group">
<input
className="z-20 relative op-input op-input-bordered rounded-r-none op-input-sm focus:outline-none group-hover:border-base-content w-[87%] md:w-[92%] text-xs"
name="textvalidate"
placeholder="Enter custom regular expression"
value={formdata.textvalidate}
onChange={(e) => handleChange(e)}
// onBlur={() => handleBlurRegex()}
/>
<select
className="validationlist op-input op-input-bordered op-input-sm focus:outline-none group-hover:border-base-content w-full text-xs"
name="textvalidate"
value={formdata.textvalidate}
onChange={(e) => handleChange(e)}
onBlur={() => handleBlurRegex()}
>
<option
disabled={formdata?.textvalidate}
className="text-[13px]"
>
Select...
</option>
{inputOpt.map((data, ind) => {
return (
<option
className="text-[13px]"
key={ind}
value={data}
>
{data}
</option>
);
})}
</select>
</div>
</div>
</div>
)}
<div className="mb-[0.75rem]">
<label htmlFor="name" className="text-[13px]">
{t("default-value")}
@@ -268,7 +193,9 @@ const WidgetNameModal = (props) => {
</div>
</>
)}
{!["signature", "initials"].includes(props.defaultdata?.type) && (
{!["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) => {
@@ -295,6 +222,26 @@ const WidgetNameModal = (props) => {
);
})}
</div>
{[textInputWidget].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 mb-0" htmlFor="isreadonly">
{t("read-only")}
</label>
</div>
)}
</div>
)}
{["signature", "initials"].includes(props.defaultdata?.type) && (
@@ -1,60 +0,0 @@
import React, { useCallback, useMemo } from "react";
import { useDropScript } from "../../../hook/useScript";
const DROPBOX_APP_KEY = process.env.REACT_APP_DROPBOX_API_KEY; // App key
const DROPBOX_SDK_URL = "https://www.dropbox.com/static/api/2/dropins.js";
const DROPBOX_SCRIPT_ID = "dropboxjs";
export default function DropboxChooser({ children, onSuccess, onCancel }) {
useDropScript(DROPBOX_SDK_URL, {
attrs: { id: DROPBOX_SCRIPT_ID, "data-app-key": DROPBOX_APP_KEY }
});
const options = useMemo(
() => ({
// Required. Called when a user selects an item in the Chooser.
success: (files) => {
// console.log("success", files);
onSuccess && onSuccess(files);
},
// Optional. Called when the user closes the dialog without selecting a file
// and does not include any parameters.
cancel: () => {
console.log("cancel");
onCancel && onCancel();
},
// Optional. "preview" (default) is a preview link to the document for sharing,
// "direct" is an expiring link to download the contents of the file. For more
linkType: "direct", // or "preview"
multiselect: false, // 是否支持多选
extensions: [".pdf"],
// Optional. A value of false (default) limits selection to files,
// while true allows the user to select both folders and files.
// You cannot specify `linkType: "direct"` when using `folderselect: true`.
folderselect: false // or true
}),
[onSuccess, onCancel]
);
const handleChoose = useCallback(
(e) => {
e.preventDefault();
if (window.Dropbox) {
window.Dropbox.choose(options);
}
},
[options]
);
return (
<div onClick={handleChoose}>
{children || (
<button className="cursor-pointer op-input op-input-bordered focus:outline-none hover:border-base-content max-h-[35px] max-w-[45px] flex justify-center items-center">
<i className="fa-brands fa-dropbox"></i>
</button>
)}
</div>
);
}
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import AsyncSelect from "react-select/async";
import { useTranslation } from "react-i18next";
import axios from "axios";
@@ -9,12 +9,42 @@ const SelectSigners = (props) => {
const [selected, setSelected] = useState();
const [userData, setUserData] = useState({});
const [isError, setIsError] = useState(false);
useEffect(() => {
//condition to check already assign signer exist if yes then show signer's email on dropdown input box
if (userList.length > 0 && props.isExistSigner) {
const alreadyAssign = userList.find(
(item) => item.objectId === props.isExistSigner.signerObjId
);
if (alreadyAssign) {
setSelected({
label: `${alreadyAssign.Name}<${alreadyAssign.Email}>`,
value: alreadyAssign.objectId
});
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userList]);
// `handleOptions` is used to set just save from quick form to selected option in dropdown
const handleOptions = (item) => {
setSelected(item);
const userData = userList.find((x) => x.objectId === item.value);
if (userData) {
setUserData(userData);
//checking if user select no signer option from dropdown
if (item) {
//checking selected signer is already assign to the document or not
const alreadyAssign = props.signersData.some(
(item2) => item2.objectId === item.value
);
if (alreadyAssign) {
alert(t("already-exist-signer"));
setSelected("");
} else {
setSelected(item);
const userData = userList.find((x) => x.objectId === item.value);
if (userData) {
setUserData(userData);
}
}
} else {
setSelected(item);
}
};
const handleAdd = () => {
@@ -28,39 +58,44 @@ const SelectSigners = (props) => {
setTimeout(() => setIsError(false), 1000);
}
};
//function to use remove signer from assigned widgets in create template flow
const handleRemove = () => {
props.handleUnlinkSigner();
if (props.closePopup) {
props.closePopup();
}
};
const loadOptions = async (inputValue) => {
try {
const baseURL = localStorage.getItem("baseUrl");
const url = `${baseURL}functions/getsigners`;
const token = props.jwttoken
? { jwttoken: props.jwttoken }
: { "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const token =
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
...token
};
const searchEmail = inputValue ? inputValue : "";
const axiosRes = await axios.post(url, { searchEmail }, { headers });
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));
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
//and filter signers from total signer's list which already present in document's signers list
const compareArrays = (res, signerObj) => {
return res.filter(
(item1) =>
!signerObj.find((item2) => item2.objectId === item1.objectId)
);
};
// const compareArrays = (res, signerObj) => {
// return res.filter(
// (item1) =>
// !signerObj.find((item2) => item2.objectId === item1.objectId)
// );
// };
//get update signer's List if signersdata is present
const updateSignersList =
props?.signersData && compareArrays(res, props?.signersData);
const result = updateSignersList ? updateSignersList : res;
// const updateSignersList =
// props?.signersData && compareArrays(res, props?.signersData);
const result = res;
setUserList(result);
return await result.map((item) => ({
label: item.Email,
label: `${item.Name}<${item.Email}>`,
value: item.objectId
}));
}
@@ -75,31 +110,45 @@ const SelectSigners = (props) => {
<label className="text-[14px] font-bold">
{t("choose-from-contacts")}
</label>
<AsyncSelect
cacheOptions
defaultOptions
value={selected}
loadingMessage={() => t("loading")}
noOptionsMessage={() => t("contact-not-found")}
loadOptions={loadOptions}
onChange={handleOptions}
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",
menuList: () => "shadow-md rounded-lg overflow-hidden",
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"
}}
/>
<div className="flex gap-2 ">
<div className="flex-1">
<AsyncSelect
cacheOptions
defaultOptions
value={selected}
loadingMessage={() => t("loading")}
noOptionsMessage={() => t("contact-not-found")}
loadOptions={loadOptions}
onChange={handleOptions}
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>
{!props.isContact && (
<button
onClick={() => props.setIsContact(true)}
className="op-btn op-btn-accent op-btn-outline op-btn-sm "
>
<i className="fa-light fa-plus"></i>
</button>
)}
</div>
</div>
<p
className={`${
@@ -108,10 +157,18 @@ const SelectSigners = (props) => {
>
{t("select-signer")}
</p>
<div>
<div className="flex gap-2">
<button className="op-btn op-btn-primary" onClick={() => handleAdd()}>
{t("submit")}
</button>
{props.isExistSigner && props.handleUnlinkSigner && (
<button
className="op-btn op-btn-accent op-btn-outline"
onClick={() => handleRemove()}
>
{t("no-signer")}
</button>
)}
</div>
</div>
</div>
@@ -37,9 +37,32 @@ const SignersInput = (props) => {
const [state, setState] = useState(undefined);
const [selected, setSelected] = useState([]);
const [isModal, setIsModel] = useState(false);
const onChange = (selectedOptions) => setSelected(selectedOptions);
const [modalIsOpen, setModalIsOpen] = useState(false);
useEffect(() => {
// to provide initial data for selected list items in Bcc for edit template
if (props?.initialData && props?.initialData?.length > 0) {
const trimEmail = props?.initialData.map((item) => ({
value: item?.objectId,
label: item?.Name,
email: item?.Email
}));
setSelected(trimEmail);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onChange = (selectedOptions) => {
if (selectedOptions && selectedOptions?.length > 0) {
const trimEmail = selectedOptions.map((item) => ({
...item,
label: item?.label?.split("<")?.shift()
}));
setSelected(trimEmail);
} else {
setSelected(selectedOptions);
}
};
const onSortEnd = ({ oldIndex, newIndex }) => {
const newValue = arrayMove(selected, oldIndex, newIndex);
setSelected(newValue);
@@ -55,7 +78,11 @@ const SignersInput = (props) => {
if (selected && selected.length) {
let newData = [];
selected.forEach((x) => {
newData.push(x.value);
if (props?.isCaptureAllData) {
newData.push(x);
} else {
newData.push(x.value);
}
});
if (props.onChange) {
props.onChange(newData);
@@ -85,17 +112,8 @@ const SignersInput = (props) => {
};
const loadOptions = async (inputValue) => {
try {
const currentUser = Parse.User.current();
const contactbook = new Parse.Query("contracts_Contactbook");
contactbook.equalTo(
"CreatedBy",
Parse.User.createWithoutData(currentUser.id)
);
if (inputValue.length > 1) {
contactbook.matches("Name", new RegExp(inputValue, "i"));
}
contactbook.notEqualTo("IsDeleted", true);
const contactRes = await contactbook.find();
const params = { search: inputValue };
const contactRes = await Parse.Cloud.run("getsigners", params);
if (contactRes) {
const res = JSON.parse(JSON.stringify(contactRes));
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
@@ -114,8 +132,9 @@ const SignersInput = (props) => {
const result = updateSignersList ? updateSignersList : res;
setState(result);
return await result.map((item) => ({
label: item.Name,
label: item.Name + "<" + item.Email + ">",
value: item.objectId,
email: item.Email,
isChecked: true
}));
}
@@ -126,14 +145,19 @@ const SignersInput = (props) => {
return (
<div className="text-xs mt-2 ">
<label className="block relative">
{t("signers")}
{props.label ? props.label : t("signers")}
{props.required && <span className="text-red-500 text-[13px]">*</span>}
<span className="absolute ml-1 text-xs z-30">
<Tooltip id={"signer-tooltip"} message={t("signers-help")} />
<span
className={`z-[${props?.helptextZindex ? props.helptextZindex : 30}] absolute ml-1 text-xs`}
>
<Tooltip
id={`${props.label ? props.label : "signers"}-tooltip`}
message={props.helpText ? props.helpText : t("signers-help")}
/>
</span>
</label>
<div className="flex gap-x-[5px]">
<div className="w-full">
<div className="w-full z-40">
<AsyncSelect
onSortEnd={onSortEnd}
distance={4}
@@ -176,7 +200,7 @@ const SignersInput = (props) => {
</div>
<AddSignerModal isOpen={modalIsOpen}>
<h3 className="text-base-content font-bold text-lg pt-[15px] px-[20px]">
{t("add-signer")}
{t("add-contact")}
</h3>
<button
onClick={handleModalCloseClick}
@@ -25,7 +25,9 @@ const SuggestionInput = (props) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
(async () => {
const res = await findContact(inputValue, props.jwttoken);
const res = await findContact(
inputValue,
);
if (res?.length > 0) {
setSuggestions(res);
setShowSuggestions(true);
@@ -74,7 +76,7 @@ const SuggestionInput = (props) => {
{showSuggestions && (
<ul
ref={ref}
className="absolute z-50 left-0 top-[2.55rem] w-full max-h-[100px] overflow-auto bg-base-200 border border-gray-300 rounded shadow-md"
className="absolute z-50 left-0 top-[2.55rem] w-full max-h-[100px] overflow-y-auto bg-base-200 border border-gray-300 rounded shadow-md"
>
{suggestions.map((suggestion, index) => (
<li
@@ -82,7 +84,7 @@ const SuggestionInput = (props) => {
className="py-2 px-2 w-full text-sm cursor-pointer hover:bg-base-300"
onClick={() => handleSuggestionClick(suggestion)}
>
{suggestion.Email}
{suggestion.Name} {"<" + suggestion.Email + ">"}
</li>
))}
</ul>
@@ -0,0 +1,72 @@
import { t } from "i18next";
import React, { useState } from "react";
import TimezoneSelect from "react-timezone-select";
const TimezoneSelector = (props) => {
const [selectedTimezone, setSelectedTimezone] = useState(props?.timezone);
// Intl.DateTimeFormat().resolvedOptions().timeZone // Default to the user's local timezone
const onChangeTimezone = (timezone) => {
setSelectedTimezone(timezone);
props.setTimezone && props.setTimezone(timezone?.value);
};
// Format date and time for the selected timezone
const formatDate = (date, timezone) => {
return timezone
? new Intl.DateTimeFormat("en-US", {
weekday: "short",
year: "numeric",
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZone: timezone,
hour12: false
}).format(date)
: new Date(date).toUTCString();
};
return (
<>
<div className="max-w-[400px] pr-[20px]">
<h1 className="text-[14px] mb-[0.7rem] font-medium">
{t("select-timezone")}
</h1>
<TimezoneSelect
value={selectedTimezone}
onChange={(timezone) => onChangeTimezone(timezone)}
unstyled
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",
menuList: () => "shadow-md rounded-lg overflow-hidden",
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"
}}
/>
</div>
<div className="mt-[20px] text-[13px]">
<p>
{t("current-time")}:{" "}
<strong>
{formatDate(
new Date(),
selectedTimezone?.value || selectedTimezone
)}
</strong>{" "}
</p>
</div>
</>
);
};
export default TimezoneSelector;
+1 -1
View File
@@ -1,6 +1,6 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { NavLink } from "react-router-dom";
import { NavLink } from "react-router";
const Menu = ({ item, isOpen, closeSidebar }) => {
const { t } = useTranslation();
@@ -3,9 +3,8 @@ import Menu from "./Menu";
import Submenu from "./SubMenu";
import SocialMedia from "./SocialMedia";
import dp from "../../assets/images/dp.png";
import sidebarList from "../../json/menuJson";
import { useNavigate } from "react-router-dom";
import { isEnableSubscription } from "../../constant/const";
import sidebarList, { subSetting } from "../../json/menuJson";
import { useNavigate } from "react-router";
const Sidebar = ({ isOpen, closeSidebar }) => {
const navigate = useNavigate();
@@ -41,47 +40,14 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
if (item.title === "Settings") {
// Make a shallow copy of the item
const newItem = { ...item };
if (isEnableSubscription) {
newItem.children = [
...newItem.children,
{
icon: "fa-light fa-building-memo",
title: "Teams",
target: "_self",
pageType: "",
description: "",
objectId: "teams"
},
{
icon: "fa-light fa-users fa-fw",
title: "Users",
target: "_self",
pageType: "",
description: "",
objectId: "users"
}
];
} else {
const arr = newItem.children.slice(0, 1);
newItem.children = [
...arr,
{
icon: "fa-light fa-users fa-fw",
title: "Users",
target: "_self",
pageType: "",
description: "",
objectId: "users"
}
];
}
newItem.children = [...arr, ...subSetting];
return newItem;
}
return item;
});
setmenuList(newSidebarList);
} else {
if (!isEnableSubscription) {
const newSidebarList = sidebarList.map((item) => {
if (item.title === "Settings") {
// Make a shallow copy of the item
@@ -93,9 +59,6 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
return item;
});
setmenuList(newSidebarList);
} else {
setmenuList(sidebarList);
}
}
}
} catch (e) {
@@ -112,6 +75,7 @@ const Sidebar = ({ isOpen, closeSidebar }) => {
setSubmenuOpen({});
};
const handleProfile = () => {
closeSidebar();
navigate("/profile");
};
return (
@@ -1,6 +1,6 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { NavLink } from "react-router-dom";
import { NavLink } from "react-router";
const SocialMedia = () => {
const { t } = useTranslation();
@@ -1,6 +1,6 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { NavLink } from "react-router-dom";
import { NavLink } from "react-router";
const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
const { t } = useTranslation();
File diff suppressed because it is too large Load Diff
+1 -7
View File
@@ -1,17 +1,11 @@
import logo from "../assets/images/logo.png";
import { isEnableSubscription } from "./const";
export function serverUrl_fn() {
let baseUrl;
if (isEnableSubscription) {
baseUrl = process.env.REACT_APP_SERVERURL
? process.env.REACT_APP_SERVERURL
: window.location.origin + "/api/app";
} else {
baseUrl = process.env.REACT_APP_SERVERURL
? process.env.REACT_APP_SERVERURL
: window.location.origin + "/app";
}
return baseUrl;
}
export const appInfo = {
-9
View File
@@ -4,12 +4,3 @@ export const documentCls = "contracts_Document";
export const themeColor = "#47a3ad";
export const iconColor = "#686968";
export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
export const isEnableSubscription =
process.env.REACT_APP_ENABLE_SUBSCRIPTION &&
process.env.REACT_APP_ENABLE_SUBSCRIPTION?.toLowerCase() === "true"
? true
: false;
export const isStaging =
window.location.origin === "https://staging-app.opensignlabs.com";
export const isPublicStaging =
window.location.origin === "https://staging.opensign.me";
-16
View File
@@ -16,19 +16,3 @@ export const useScript = (url, onload) => {
}, [url, onload]);
};
/**`useScript` hook is generated scripte for google sign in button */
export const useDropScript = (url, onload) => {
useEffect(() => {
const script = document.createElement("script");
//add url parameter to the script src, for load and it will remove after load in return
script.src = url;
script.async = true;
script.defer = true;
script.id = "dropboxjs";
script.setAttribute("data-app-key", "8k0thg9r1t7asqg");
document.head.appendChild(script);
return () => {
document.head.removeChild(script);
};
}, [url, onload]);
};
+1 -1
View File
@@ -25,7 +25,7 @@ i18n
interpolation: {
escapeValue: false // Not needed for react as it escapes by default
},
whitelist: ["en", "es", "fr"] // List of allowed languages
whitelist: ["en", "es", "fr", "it", "de"] // List of allowed languages
});
export default i18n;
+3 -2
View File
@@ -19,8 +19,9 @@ body {
}
@media screen and (max-width: 766px) {
.bBofGr {
width: 14px !important;
.reactour__close {
width: 17px !important;
padding-left: 3px !important;
}
}
+4 -2
View File
@@ -14,13 +14,15 @@ export const formJson = {
msgVar: "Document",
redirectRoute: "placeHolderSign",
Cls: documentCls,
signers: true
signers: true,
bcc: true
},
//json form for template
template: {
title: "New Template",
redirectRoute: "template",
msgVar: "Template",
Cls: templateCls
Cls: templateCls,
bcc: true
}
};
+34 -28
View File
@@ -1,4 +1,3 @@
import { isEnableSubscription } from "../constant/const";
export default function reportJson(id) {
// console.log("json ", json);
@@ -8,31 +7,6 @@ export default function reportJson(id) {
const contactbook = ["Name", "Email", "Phone"];
const dashboardReportHead = ["Title", "File", "Owner", "Signers"];
const templateReport = ["Title", "File", "Owner", "Signers"];
const templateSubAction = isEnableSubscription
? [
{
btnId: "2435",
btnLabel: "Embed",
hoverLabel: "Embed",
btnIcon: "fa-light fa-code",
action: "Embed"
},
{
btnId: "2436",
btnLabel: "Copy TemplateId",
hoverLabel: "Copy TemplateId",
btnIcon: "fa-light fa-copy",
action: "CopyTemplateId"
},
{
btnId: "2437",
btnLabel: "Copy Public URL",
hoverLabel: "Copy Public URL",
btnIcon: "fa-light fa-copy",
action: "CopyPublicURL"
}
]
: [];
switch (id) {
// draft documents report
case "ByHuevtCFY":
@@ -117,6 +91,14 @@ export default function reportJson(id) {
redirectUrl: "",
action: "resend"
},
{
btnId: "1633",
btnLabel: "Rename",
hoverLabel: "Rename",
btnIcon: "fa-light fa-pen-to-square",
redirectUrl: "",
action: "rename"
},
{
btnId: "1688",
btnLabel: "Revoke",
@@ -158,6 +140,7 @@ export default function reportJson(id) {
hoverLabel: "Delete",
btnColor: "op-btn-secondary",
btnIcon: "fa-light fa-trash",
restrictBtn: true,
redirectUrl: "",
action: "delete"
}
@@ -274,6 +257,14 @@ export default function reportJson(id) {
redirectUrl: "",
action: "resend"
},
{
btnId: "1633",
btnLabel: "Rename",
hoverLabel: "Rename",
btnIcon: "fa-light fa-pen-to-square",
redirectUrl: "",
action: "rename"
},
{
btnId: "6788",
btnLabel: "Revoke",
@@ -336,11 +327,18 @@ export default function reportJson(id) {
]
};
// contactbook report
case "5KhaPr482K":
case "contacts":
return {
reportName: "Contactbook",
heading: contactbook,
actions: [
{
btnId: "2674",
hoverLabel: "Edit",
btnColor: "op-btn-primary",
btnIcon: "fa-light fa-pen",
action: "edit"
},
{
btnId: "2204",
hoverLabel: "Delete",
@@ -349,6 +347,7 @@ 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."
@@ -393,7 +392,14 @@ export default function reportJson(id) {
redirectUrl: "template",
action: "redirect"
},
...templateSubAction,
{
btnId: "1633",
btnLabel: "Rename",
hoverLabel: "Rename",
btnIcon: "fa-light fa-pen-to-square",
redirectUrl: "",
action: "rename"
},
{
btnId: "1834",
btnLabel: "Delete",
+29 -18
View File
@@ -1,3 +1,22 @@
export const subSetting = [
{
icon: "fa-light fa-sliders",
title: "Preferences",
target: "_self",
pageType: "",
description: "",
objectId: "preferences"
},
{
icon: "fa-light fa-users fa-fw",
title: "Users",
target: "_self",
pageType: "",
description: "",
objectId: "users"
}
];
const sidebarList = [
{
icon: "fa-light fa-tachometer-alt",
@@ -46,7 +65,7 @@ const sidebarList = [
pageType: "report",
description: "",
objectId: "6TeaPr321t"
}
},
]
},
{
@@ -59,7 +78,7 @@ const sidebarList = [
},
{
icon: "fa-light fa-address-card",
title: "Reports",
title: "Documents",
target: "_self",
pageType: null,
description: "",
@@ -112,17 +131,17 @@ const sidebarList = [
pageType: "report",
description: "",
objectId: "zNqBHXHsYH"
},
{
icon: "fa-light fa-address-book",
title: "Contactbook",
target: "_self",
pageType: "report",
description: "",
objectId: "5KhaPr482K"
}
]
},
{
icon: "fa-light fa-address-book",
title: "Contactbook",
target: "_self",
pageType: "report",
description: "",
objectId: "contacts"
},
{
icon: "fa-light fa-cog",
title: "Settings",
@@ -154,14 +173,6 @@ const sidebarList = [
pageType: "",
description: "",
objectId: "webhook"
},
{
icon: "fa-light fa-sliders",
title: "Preferences",
target: "_self",
pageType: "",
description: "",
objectId: "preferences"
}
]
}
-255
View File
@@ -1,255 +0,0 @@
import { isStaging } from "../constant/const";
const plans = [
{
planName: "OPENSIGN™ FREE",
code: { monthly: "freeplan", yearly: "freeplan" },
img: "free.png",
currency: "",
monthlyPrice: "Free",
yearlyPrice: "Free",
subtitle: "Free Unlimited E-signatures, Forever.",
btn: { text: "Subscribe", color: "op-btn-primary" },
url: "",
target: "_blank",
benefits: [
"Unlimited digital signatures",
"Sign documents yourself",
"Request signatures from others",
"Unlimited templates",
"14 field types",
"Automatic e-signatures",
"Completion certificates",
"Send in order",
"Organize docs in OpenSign™ Drive",
"Document templates",
"Import from Dropbox",
"Contact book",
"Document expiry support",
"Decline document support",
"Email notifications",
"Public profiles",
"And much more"
],
yearlyBenefits: [
"Unlimited digital signatures",
"Sign documents yourself",
"Request signatures from others",
"Unlimited templates",
"14 field types",
"Automatic e-signatures",
"Completion certificates",
"Send in order",
"Organize docs in OpenSign™ Drive",
"Document templates",
"Import from Dropbox",
"Contact book",
"Document expiry support",
"Decline document support",
"Email notifications",
"Public profiles",
"And much more"
]
},
{
planName: "OPENSIGN™ PROFESSIONAL",
code: isStaging
? { monthly: "pro-weekly", yearly: "pro-yearly" }
: { monthly: "professional-monthly", yearly: "professional-yearly" },
img: "professional.png",
currency: "$",
monthlyPrice: "29.99",
yearlyPrice: `9.99<sup style="font-size: 17px;">/month</sup>`,
subtitle: "Exclusive Access to advanced features.",
btn: { text: "Subscribe", color: "op-btn-primary" },
url: isStaging
? "https://billing.zohosecure.in/subscribe/9627f62a09df1c8ce500f2f4dc8328dd84ecda20eeae13878ce581d60240f206/pro-weekly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d1a09dfa8085585cdd4ec4d7f32137f3/professional-monthly",
yearlyUrl: isStaging
? "https://billing.zohosecure.in/subscribe/9627f62a09df1c8ce500f2f4dc8328ddc184411129224f1f29ed84f6cef3b862/pro-yearly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901d8ad1135190dff951330360e47585a71/professional-yearly",
target: "_self",
benefits: [
"Everything in OpenSign™ free",
"Field validations",
"Regular expression validations",
"Organize docs in OpenSign™ Drive",
"Webhooks",
"Zapier integration",
"API Access",
"upto 100 API signatures",
"Custom email templates",
"Connect your own Gmail or SMTP account for sending emails",
"Auto reminders",
"Bulk send (upto 100 docs)",
"Premium Public profile usernames",
"Enforce email-based verification to confirm signer identity",
"Embedded signing"
],
yearlyBenefits: [
"Everything in OpenSign™ free",
"Field validations",
"Regular expression validations",
"Organize docs in OpenSign™ Drive",
"Webhooks",
"Zapier integration",
"API Access",
"upto 240 API signatures",
"Custom email templates",
"Connect your own Gmail or SMTP account for sending emails",
"Auto reminders",
"Bulk send (upto 240 docs)",
"Premium Public profile usernames",
"Enforce email-based verification to confirm signer identity",
"Embedded signing"
]
},
{
planName: "OPENSIGN™ TEAMS",
code: { monthly: "teams-monthly", yearly: "teams-yearly" },
img: "teams.png",
currency: "$",
monthlyPrice: `39.99<sup style="font-size: 17px;">/user</sup>`,
yearlyPrice: `19.99<sup style="font-size: 17px;">/user/month</sup>`,
subtitle: "Exclusive Access to advanced features.",
btn: { text: "Subscribe", color: "op-btn-accent" },
url: isStaging
? "https://billing.zohosecure.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-monthly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901237d0702bfaa959406306635d80f138c/teams-monthly",
yearlyUrl: isStaging
? "https://billing.zohosecure.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-yearly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af9011a864994bbeeec71fcf106188630199d/teams-yearly",
target: "_self",
benefits: [
"Everything in OpenSign™ professional",
"upto 100 API signatures",
"Teams and Organizations",
"Share Templates with teams",
"Share Templates with individuals",
"BYOC - Store your documents in your own cloud storage",
"DocumentId removal from signed docs",
"Bulk send (upto 100 docs)",
"Request Payments (coming soon)",
"Mobile app (coming soon)"
],
yearlyBenefits: [
"Everything in OpenSign™ professional",
"upto 500 API signatures",
"Teams and Organizations",
"Share Templates with teams",
"Share Templates with individuals",
"BYOC - Store your documents in your own cloud storage",
"DocumentId removal from signed docs",
"Bulk send (upto 500 docs)",
"Request Payments (coming soon)",
"Mobile app (coming soon)"
]
},
{
planName: "OPENSIGN™ ENTERPRISE",
img: "enterprise.png",
currency: "",
monthlyPrice: `<p style="font-size: 22px;">Request Price</p>`,
yearlyPrice: `<p style="font-size: 22px;">Request Price</p>`,
subtitle: "Scalable Features with priority support.",
btn: { text: "Contact us", color: "op-btn-primary" },
url: "https://www.opensignlabs.com/contact-us",
target: "_blank",
benefits: [
"All features",
"Custom domain",
"Custom branding",
"Uptime SLA",
"SSO support",
"Priority support"
],
yearlyBenefits: [
"All features",
"Custom domain",
"Custom branding",
"Uptime SLA",
"SSO support",
"Priority support"
]
}
];
export const validplan = {
"team-weekly": true,
"team-yearly": true,
"teams-monthly": true,
"teams-yearly": true,
"enterprise-monthly": true,
"enterprise-yearly": true
};
export const paidUrl = (plan) => {
const teamperiod = {
"team-weekly": "monthly",
"team-yearly": "yearly",
"teams-monthly": "monthly",
"teams-yearly": "yearly"
};
const period = teamperiod[plan] || "";
if (period) {
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const user = {
name: extUser?.Name,
email: extUser?.Email,
company: extUser?.Company,
phone: extUser?.Phone
};
// console.log("userDetails ", userDetails);
const fullname = user && user.name ? user.name.split(" ") : "";
const firstname = fullname?.[0]
? "first_name=" + encodeURIComponent(fullname?.[0])
: "";
const lastname = fullname?.[1]
? "&last_name=" + encodeURIComponent(fullname?.[1])
: "";
const name = firstname ? firstname + lastname : "";
const email =
user && user.email ? "&email=" + encodeURIComponent(user.email) : "";
const company =
user && user.company
? "&company_name=" + encodeURIComponent(user.company)
: "";
const phone =
user && user.phone ? "&mobile=" + encodeURIComponent(user.phone) : "";
const allowedUsers =
localStorage.getItem("allowedUsers") &&
localStorage.getItem("allowedUsers") > 0
? localStorage.getItem("allowedUsers") - 1
: "";
const quantity = allowedUsers
? `addon_code%5B0%5D=extra-teams-users-${period}&addon_quantity%5B0%5D=${allowedUsers}&`
: "";
const details =
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
quantity +
name +
email +
company +
phone;
if (user) {
localStorage.setItem("userDetails", JSON.stringify(user));
}
const url = {
monthly: isStaging
? "https://billing.zohosecure.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-monthly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af901237d0702bfaa959406306635d80f138c/teams-monthly",
yearly: isStaging
? "https://billing.zohosecure.in/subscribe/ed8097273a82b6bf39892c11a3bb3c381eb2705736014cfbdbde1ccf1c7a189d/teams-yearly"
: "https://billing.opensignlabs.com/subscribe/ef798486e6a0a11ea65f2bae8f2af9011a864994bbeeec71fcf106188630199d/teams-yearly"
};
const planurl = url[period] + details;
return planurl;
} else {
return "/subscription";
}
};
export default plans;
+4 -29
View File
@@ -8,10 +8,8 @@ import axios from "axios";
import { useDispatch, useSelector } from "react-redux";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
import { useNavigate, useLocation, Outlet } from "react-router-dom";
import { isEnableSubscription } from "../constant/const";
import { useNavigate, useLocation, Outlet } from "react-router";
import { useCookies } from "react-cookie";
import { fetchSubscription } from "../constant/Utils";
import Loader from "../primitives/Loader";
import { showHeader } from "../redux/reducers/showHeader";
import { useTranslation } from "react-i18next";
@@ -26,7 +24,6 @@ const HomeLayout = () => {
const arr = useSelector((state) => state.TourSteps);
const [isUserValid, setIsUserValid] = useState(true);
const [isLoader, setIsLoader] = useState(true);
// reactour state
const [isCloseBtn, setIsCloseBtn] = useState(true);
const [isTour, setIsTour] = useState(false);
const [tourStatusArr, setTourStatusArr] = useState([]);
@@ -38,6 +35,7 @@ const HomeLayout = () => {
useEffect(() => {
const language = localStorage.getItem("i18nextLng");
i18n.changeLanguage(language);
localStorage.setItem("isGuestSigner", "");
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
@@ -54,7 +52,8 @@ const HomeLayout = () => {
});
if (user) {
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
checkIsSubscribed();
setIsUserValid(true);
setIsLoader(false);
} else {
setIsUserValid(false);
}
@@ -95,30 +94,6 @@ const HomeLayout = () => {
domain: updateDomain
});
};
const handleNavigation = () => {
navigate("/subscription");
};
async function checkIsSubscribed() {
if (isEnableSubscription) {
const res = await fetchSubscription();
if (res.plan === "freeplan") {
setIsUserValid(true);
setIsLoader(false);
} else if (res.billingDate) {
if (new Date(res.billingDate) > new Date()) {
setIsUserValid(true);
setIsLoader(false);
} else {
handleNavigation(res.plan);
}
} else {
handleNavigation(res.plan);
}
} else {
setIsUserValid(true);
setIsLoader(false);
}
}
const showSidebar = () => {
setIsOpen((value) => !value);
dispatch(showHeader(!isOpen));
+5 -3
View File
@@ -1,11 +1,12 @@
import React, { useEffect, useState } from "react";
import Parse from "parse";
import { appInfo } from "../constant/appinfo";
import { NavLink, useNavigate } from "react-router-dom";
import { NavLink, useNavigate } from "react-router";
import {
getAppLogo,
openInNewTab,
saveLanguageInLocal
saveLanguageInLocal,
usertimezone
} from "../constant/Utils";
import { useDispatch } from "react-redux";
import { showTenant } from "../redux/reducers/ShowTenant";
@@ -116,7 +117,8 @@ const AddAdmin = () => {
name: name,
email: email,
phone: phone,
role: "contracts_Admin"
role: "contracts_Admin",
timezone: usertimezone
}
};
try {
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useState } from "react";
import Parse from "parse";
import { Navigate } from "react-router-dom";
import { Navigate } from "react-router";
import Title from "../components/Title";
import { useTranslation } from "react-i18next";
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from "react";
import GetDashboard from "../components/dashboard/GetDashboard";
import { useNavigate, useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router";
import Title from "../components/Title";
import { useDispatch } from "react-redux";
import { saveTourSteps } from "../redux/reducers/TourStepsReducer";
+1 -1
View File
@@ -225,7 +225,7 @@ const DebugPdf = () => {
<div className="flex flex-row justify-between">
{/* this component used to render all pdf pages in left side */}
<RenderAllPdfPage
signPdfUrl={pdf}
pdfBase64Url={pdfDetails.base64}
allPages={allPages}
setAllPages={setAllPages}
setPageNumber={setPageNumber}
+138
View File
@@ -0,0 +1,138 @@
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import {
getBase64FromUrl,
handleDownloadCertificate,
handleDownloadPdf,
handleToPrint
} from "../constant/Utils";
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
import DownloadPdfZip from "../primitives/DownloadPdfZip";
const DocSuccessPage = () => {
const signed = window.location?.search?.includes("docid");
const sent = window.location?.search?.includes("message");
const [isDownloading, setIsDownloading] = useState(false);
const [isDownloadModal, setIsDownloadModal] = useState(false);
const [pdfDetails, setPdfDetails] = useState([]);
const [pdfBase64Url, setPdfBase64Url] = useState("");
const { t } = useTranslation();
useEffect(() => {
initialsetup();
}, []);
const initialsetup = async () => {
const search = window.location.search.split("?")[1];
if (search) {
const urlParams = new URLSearchParams(search);
const docId = urlParams.get("docid");
const docUrl = urlParams.get("docurl");
const certificate = urlParams.get("certificate");
const completed = urlParams?.get("completed") || false;
const details = {
objectId: docId,
SignedUrl: docUrl,
CertificateUrl: certificate,
IsCompleted: completed
};
setPdfDetails([details]);
const base64Pdf = await getBase64FromUrl(docUrl);
if (base64Pdf) {
setPdfBase64Url(base64Pdf);
}
}
};
const handleDownload = () => {
if (pdfDetails?.[0]?.IsCompleted) {
setIsDownloadModal(true);
} else {
handleDownloadPdf(pdfDetails, setIsDownloading, pdfBase64Url);
}
};
return (
<div className="flex h-screen justify-center items-center text-sm md:text-base">
{sent ? (
<div>{t("doc-sent")}</div>
) : signed ? (
<div className="text-center">
<p>
{pdfDetails?.[0]?.IsCompleted
? t("document-signed-alert-4")
: t("document-signed-alert")}
</p>
<div className="m-2">
<button
onClick={(e) => handleToPrint(e, setIsDownloading, pdfDetails)}
type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-neutral"
>
<i className="fa-light fa-print" aria-hidden="true"></i>
<span className="hidden lg:block">{t("print")}</span>
</button>
{pdfDetails?.[0]?.IsCompleted && (
<button
type="button"
onClick={() =>
handleDownloadCertificate(pdfDetails, setIsDownloading)
}
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-secondary"
>
<i
className="fa-light fa-award mx-[3px] lg:mx-0"
aria-hidden="true"
></i>
<span className="hidden lg:block">{t("certificate")}</span>
</button>
)}
<button
type="button"
className="font-[500] text-[13px] mr-[5px] op-btn op-btn-primary"
onClick={() => handleDownload()}
>
<i className="fa-light fa-download" aria-hidden="true"></i>
<span className="hidden lg:block">{t("download")}</span>
</button>
</div>
{isDownloading === "pdf" && (
<div className="fixed z-[1000] inset-0 flex justify-center items-center bg-black bg-opacity-30">
<Loader />
</div>
)}
<ModalUi
isOpen={
isDownloading === "certificate" ||
isDownloading === "certificate_err"
}
title={
isDownloading === "certificate" ||
isDownloading === "certificate_err"
? t("generating-certificate")
: t("pdf-download")
}
handleClose={() => setIsDownloading("")}
>
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
{isDownloading === "certificate" ? (
<p>{t("generate-certificate-alert")}</p>
) : (
<p>{t("generate-certificate-err")}</p>
)}
</div>
</ModalUi>
<DownloadPdfZip
setIsDownloadModal={setIsDownloadModal}
isDownloadModal={isDownloadModal}
pdfDetails={pdfDetails}
isDocId={true}
pdfBase64={pdfBase64Url}
/>
</div>
) : (
<></>
)}
</div>
);
};
export default DocSuccessPage;
+4 -12
View File
@@ -1,14 +1,15 @@
import React, { useEffect, useState } from "react";
import Title from "../components/Title";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import login_img from "../assets/images/login_img.svg";
import Parse from "parse";
import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { emailRegex, isEnableSubscription } from "../constant/const";
import { getAppLogo } from "../constant/Utils";
import {
emailRegex,
} from "../constant/const";
import { useTranslation } from "react-i18next";
function ForgotPassword() {
@@ -70,16 +71,7 @@ function ForgotPassword() {
} catch (err) {
console.log("err while logging out ", err);
}
if (isEnableSubscription) {
const app = await getAppLogo();
if (app?.logo) {
setImage(app?.logo);
} else {
setImage(appInfo?.applogo || undefined);
}
} else {
setImage(appInfo?.applogo || undefined);
}
};
return (
<div>
File diff suppressed because it is too large Load Diff
-473
View File
@@ -1,473 +0,0 @@
import React, { useEffect, useState } from "react";
import Title from "../components/Title";
import axios from "axios";
import Alert from "../primitives/Alert";
import ModalUi from "../primitives/ModalUi";
import { isEnableSubscription } from "../constant/const";
import { checkIsSubscribed, copytoData, openInNewTab } from "../constant/Utils";
import Tooltip from "../primitives/Tooltip";
import Loader from "../primitives/Loader";
import SubscribeCard from "../primitives/SubscribeCard";
import Tour from "reactour";
import { useTranslation } from "react-i18next";
import Parse from "parse";
function GenerateToken() {
const origin = window.location.origin;
const { t } = useTranslation();
const parseBaseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
const [apiToken, SetApiToken] = useState("");
const [isLoader, setIsLoader] = useState(true);
const [isModal, setIsModal] = useState({
generateapi: false,
buyapis: false,
unauthorized: false
});
const [isSubscribe, setIsSubscribe] = useState({
plan: "",
isValid: false,
adminId: "",
adminEmail: ""
});
const [isAlert, setIsAlert] = useState({ type: "success", msg: "" });
const [isTour, setIsTour] = useState(false);
const [amount, setAmount] = useState({
quantity: 500,
priceperapi: 0.15,
allowedcredits: 0,
addoncredits: 0,
totalcredits: 0,
price: (75.0).toFixed(2)
});
const [isFormLoader, setIsFormLoader] = useState(false);
const [apitestToken, setApiTestToken] = useState("");
const quantityList = [500, 1000, 5000, 50000];
useEffect(() => {
fetchToken();
// eslint-disable-next-line
}, []);
const tourSteps = [
{
selector: '[data-tut="apisubscribe"]',
content: t("tour-mssg.generate-token")
}
];
const fetchToken = async () => {
try {
if (isEnableSubscription) {
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe);
}
const url = parseBaseUrl + "functions/getapitoken";
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessiontoken: localStorage.getItem("accesstoken")
};
const res = await axios.post(url, {}, { headers: headers });
if (res) {
const resCredits = await Parse.Cloud.run("allowedcredits");
const allowedcredits = resCredits?.allowedcredits || 0;
const addoncredits = resCredits?.addoncredits || 0;
const totalcredits = allowedcredits + addoncredits;
setAmount((obj) => ({
...obj,
allowedcredits: allowedcredits,
addoncredits: addoncredits,
totalcredits: totalcredits
}));
SetApiToken(res?.data?.result?.result);
}
try {
const body = { email: Parse?.User?.current()?.getEmail() || "" };
const testurl = "https://sandbox.opensignlabs.com/api/gettesttoken";
const testRes = await axios.post(testurl, body);
setApiTestToken(testRes.data?.token);
} catch (err) {
console.log("err in fetch test token", err);
}
} catch (err) {
SetApiToken();
console.log("Err", err);
} finally {
setIsLoader(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
setIsTour(true);
} else {
setIsLoader(true);
setIsModal((obj) => ({ ...obj, generateapi: false }));
try {
const url = parseBaseUrl + "functions/generateapitoken";
const headers = {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessiontoken: localStorage.getItem("accesstoken")
};
const res = await axios.post(url, {}, { headers: headers });
if (res) {
SetApiToken(res.data.result.token);
setIsAlert({ type: "success", msg: t("token-generated") });
} else {
console.error("Error while generating Token");
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
}
} catch (error) {
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
console.log("while generating Token", error);
} finally {
setIsLoader(false);
setTimeout(() => {
setIsAlert({ type: "success", msg: "" });
}, 1500);
}
}
};
const copytoclipboard = (text) => {
copytoData(text);
setIsAlert({ type: "success", msg: t("copied") });
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
};
const handleModal = () => {
if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
setIsTour(true);
} else {
setIsModal((obj) => ({ ...obj, generateapi: !obj.generateapi }));
}
};
const handleBuyAPIsModal = () => {
if (isSubscribe?.plan === "freeplan" && isEnableSubscription) {
setIsTour(true);
} else {
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
if (isSubscribe?.adminId && extUser?.objectId === isSubscribe?.adminId) {
setIsModal((obj) => ({ ...obj, buyapis: !obj.buyapis }));
} else {
setIsSubscribe((obj) => ({
...obj,
adminEmail: extUser?.TenantId?.EmailAddress
}));
setIsModal((obj) => ({ ...obj, unauthorized: !obj.unauthorized }));
}
}
};
const handleCloseModal = () => {
setIsModal({ generateapi: false, buyapis: false, unauthorized: false });
};
const handlePricePerAPIs = (e) => {
const quantity = e.target?.value;
const price =
quantity > 0
? (Math.round(quantity * amount.priceperapi * 100) / 100).toFixed(2)
: (Math.round(500 * amount.priceperapi * 100) / 100).toFixed(2);
setAmount((prev) => ({ ...prev, quantity: quantity, price: price }));
};
const handleAddOnApiSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsFormLoader(true);
try {
const resAddon = await Parse.Cloud.run("buycredits", {
credits: amount.quantity
});
if (resAddon) {
const _resAddon = JSON.parse(JSON.stringify(resAddon));
if (_resAddon.status === "success") {
setAmount((obj) => ({
...obj,
quantity: 500,
priceperapi: 0.15,
price: (75.0).toFixed(2),
totalcredits: obj?.totalcredits + _resAddon.addon,
addoncredits: obj?.addoncredits + _resAddon.addon
}));
}
}
setIsModal((obj) => ({ ...obj, buyapis: false }));
} catch (err) {
console.log("Err in buy addon", err);
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
} finally {
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000);
setIsFormLoader(false);
}
};
const handleGenerateTestToken = async (e) => {
e.preventDefault();
setIsLoader(true);
// setIsModal((obj) => ({ ...obj, generateapi: false }));
try {
const url = "https://sandbox.opensignlabs.com/api/generatetesttoken";
const headers = {
"Content-Type": "application/json",
token: apitestToken || "1"
};
const extUser = JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const body = {
name: extUser?.Name,
email: extUser?.Email,
phone: extUser?.Phone,
company: extUser?.Company,
jobTitle: extUser?.JobTitle
};
const res = await axios.post(url, body, { headers: headers });
if (res) {
setApiTestToken(res.data?.token);
setIsAlert({ type: "success", msg: t("token-generated") });
} else {
console.error("Error while generating Token");
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
}
} catch (error) {
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
console.log("while generating Token", error);
} finally {
setIsLoader(false);
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
}
};
return (
<React.Fragment>
<Title title={"API Token"} />
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
{isLoader ? (
<div className="flex justify-center items-center h-screen">
<Loader />
</div>
) : (
<>
<div className="bg-base-100 text-base-content flex flex-col justify-center shadow-md rounded-box mb-3">
<h1 className={"ml-4 mt-3 mb-2 font-semibold"}>
OpenSign {t("API")}{" "}
<Tooltip
url="https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
isSubscribe={true}
/>
</h1>
<ul className="w-full flex flex-col p-2 text-sm">
<li className="flex flex-col md:flex-row justify-between items-center border-t-[1px] border-gray-300 break-all py-2">
<div className="w-full md:w-[70%] flex-col md:flex-row text-xs md:text-[15px] flex items-center gap-x-5">
<span className="ml-1">
<span className="font-medium">API Test Token : </span>
<span className="text-xs">
<Tooltip message={t("help-test-token")} />
</span>
</span>
<span id="token" className="md:text-end py-2 md:py-0">
<span
className="cursor-pointer"
onClick={() => copytoclipboard(apitestToken)}
>
{apitestToken ? apitestToken : "_____"}
</span>
<button
className="op-btn op-btn-accent op-btn-outline op-btn-sm ml-2 cursor-pointer"
onClick={() => copytoclipboard(apitestToken)}
>
<i className="fa-light fa-copy"></i>
</button>
</span>
</div>
<button
onClick={handleGenerateTestToken}
className="op-btn op-btn-primary w-[210px] md:w-auto"
>
{apitestToken
? t("regenerate-test-token")
: t("generate-test-token")}
</button>
</li>
<li className="flex flex-col md:flex-row justify-between items-center border-y-[1px] border-gray-300 break-all py-2">
<div className="w-full md:w-[70%] flex-col md:flex-row text-xs md:text-[15px] flex items-center gap-x-5">
<span className="ml-1">
<span className="font-medium">{t("api-token")} : </span>
<span className="text-xs">
<Tooltip message={t("help-api-token", { origin })} />
</span>
</span>
<span
id="token"
className={`${
isSubscribe?.plan === "freeplan"
? "bg-white/20 pointer-events-none select-none"
: ""
} md:text-end py-2 md:py-0`}
>
<span
className="cursor-pointer"
onClick={() => copytoclipboard(apiToken)}
>
{isSubscribe?.plan !== "freeplan" && apiToken
? apiToken
: "_____"}
</span>
<button
className="op-btn op-btn-accent op-btn-outline op-btn-sm ml-2 cursor-pointer"
onClick={() => copytoclipboard(apiToken)}
>
<i className="fa-light fa-copy"></i>
</button>
</span>
</div>
<button
onClick={apiToken ? handleModal : handleSubmit}
className="op-btn op-btn-primary w-[210px] md:w-auto"
>
{apiToken ? t("regenerate-token") : t("generate-token")}
</button>
</li>
<div className="text-xs md:text-[15px] my-3 w-full md:w-[70%] flex-col md:flex-row flex items-center gap-x-5">
<span className="font-medium">
{t("remaining-credits")}{" "}
<span className="text-xs">
<Tooltip
message={t("remaining-credits-help", {
allowedcredits: amount?.allowedcredits || 0,
addoncredits: amount?.addoncredits || 0
})}
/>
</span>
</span>
<span className="md:text-end py-2 md:py-0">
{amount.totalcredits}
</span>
</div>
<hr />
</ul>
<div className="flex flex-col md:flex-row items-center justify-center gap-0 md:gap-1">
<button
type="button"
onClick={() =>
openInNewTab(
"https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1"
)
}
className="op-btn op-btn-secondary mt-2 md:mb-3 px-8 w-[210px] md:w-auto"
>
{t("view-docs")}
</button>
<button
type="button"
onClick={() => handleBuyAPIsModal()}
className="op-btn op-btn-accent mt-2 mb-3 px-8 w-[210px] md:w-auto"
>
{t("buy-credits")}
</button>
</div>
<ModalUi
isOpen={isModal.generateapi}
title={apiToken ? t("regenerate-token") : t("generate-token")}
handleClose={handleModal}
>
<div className="m-[20px]">
<div className="text-lg font-normal text-base-content">
{t("generate-token-alert")}
</div>
<hr className="bg-[#ccc] mt-4" />
<div className="flex items-center mt-3 gap-2 text-white">
<button
onClick={handleSubmit}
className="op-btn op-btn-primary ml-[2px]"
>
{t("yes")}
</button>
<button
onClick={handleModal}
className="op-btn op-btn-secondary"
>
{t("no")}
</button>
</div>
</div>
</ModalUi>
{/* buy APIs */}
<ModalUi
isOpen={isModal.buyapis}
title={"Buy Credits"}
handleClose={handleCloseModal}
>
{isFormLoader && (
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
<Loader />
</div>
)}
<form onSubmit={handleAddOnApiSubmit} className="p-3">
<div className="mb-3 flex justify-between">
<label
htmlFor="quantity"
className="block text-xs text-gray-700 font-semibold"
>
{t("quantity-of-credits")}
<span className="text-[red] text-[13px]"> *</span>
</label>
<select
value={amount.quantity}
onChange={(e) => handlePricePerAPIs(e)}
name="quantity"
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-1/4 text-xs"
required
>
{quantityList.length > 0 &&
quantityList.map((x) => (
<option key={x} value={x}>
{x}
</option>
))}
</select>
</div>
<div className="mb-3 flex justify-between">
<label className="block text-xs text-gray-700 font-semibold">
{t("Price")} (1 * {amount.priceperapi})
</label>
<div className="w-1/4 flex justify-center items-center text-sm">
USD {amount.price}
</div>
</div>
<hr className="text-base-content mb-3" />
<button className="op-btn op-btn-primary w-full mt-2">
{t("Proceed")}
</button>
</form>
</ModalUi>
<ModalUi
isOpen={isModal.unauthorized}
title={"Unauthorized"}
handleClose={handleCloseModal}
>
<div className="m-8 flex justify-center text-center items-center font-medium break-all">
{t("unauthorized-modal", {
adminEmail: isSubscribe?.adminEmail
})}
</div>
</ModalUi>
</div>
{isEnableSubscription && isSubscribe?.plan === "freeplan" && (
<div data-tut="apisubscribe">
<SubscribeCard />
</div>
)}
</>
)}
<Tour
onRequestClose={() => setIsTour(false)}
steps={tourSteps}
isOpen={isTour}
closeWithMask={false}
disableKeyboardNavigation={["esc"]}
scrollOffset={-100}
rounded={5}
/>
</React.Fragment>
);
}
export default GenerateToken;
+29 -37
View File
@@ -1,18 +1,19 @@
import React, { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router";
import axios from "axios";
import { emailRegex, isEnableSubscription } from "../constant/const";
import {
emailRegex,
} from "../constant/const";
import {
contractUsers,
getAppLogo,
saveLanguageInLocal
} from "../constant/Utils";
import logo from "../assets/images/logo.png";
import { appInfo } from "../constant/appinfo";
import Parse from "parse";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import SelectLanguage from "../components/pdf/SelectLanguage";
import LoaderWithMsg from "../primitives/LoaderWithMsg";
function GuestLogin() {
const { t, i18n } = useTranslation();
@@ -22,7 +23,10 @@ function GuestLogin() {
const [OTP, setOTP] = useState("");
const [EnterOTP, setEnterOtp] = useState(false);
const [loading, setLoading] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [isLoading, setIsLoading] = useState({
isLoad: true,
message: t("loading-mssg")
});
const [appLogo, setAppLogo] = useState("");
const [documentId, setDocumentId] = useState(id);
const [contactId, setContactId] = useState(contactBookId);
@@ -43,6 +47,7 @@ function GuestLogin() {
}
return true;
} else {
setIsLoading({ isLoad: false });
return false;
}
} catch (err) {
@@ -57,18 +62,11 @@ function GuestLogin() {
//function generate serverUrl and parseAppId from url and save it in local storage
const handleServerUrl = async () => {
if (isEnableSubscription) {
const app = await getAppLogo();
if (app?.logo) {
setAppLogo(app?.logo);
} else {
setAppLogo(logo);
}
} else {
setAppLogo(logo);
}
localStorage.clear();
//save isGuestSigner true in local to handle login flow header in mobile view
localStorage.setItem("isGuestSigner", true);
saveLanguageInLocal(i18n);
const parseId = appInfo.appId;
const newServer = `${appInfo.baseUrl}/`;
@@ -95,6 +93,7 @@ function GuestLogin() {
setContactId(linkContactRes?.contactId);
await navigateToDoc(checkSplit[0], linkContactRes?.contactId);
} catch (err) {
setIsLoading({ isLoad: false });
console.log("Err in link ext contact", err);
}
} else {
@@ -102,8 +101,6 @@ function GuestLogin() {
await navigateToDoc(checkSplit[0], checkSplit[2]);
}
}
setIsLoading(false);
};
//send email OTP function
@@ -154,23 +151,21 @@ function GuestLogin() {
let _user = user.data.result;
await Parse.User.become(_user.sessionToken);
const parseId = localStorage.getItem("parseAppId");
if (_user) {
localStorage.setItem("accesstoken", _user?.sessionToken);
localStorage.setItem("UserInformation", JSON.stringify(_user));
localStorage.setItem(
`Parse/${parseId}/currentUser`,
JSON.stringify(_user)
);
}
const contractUserDetails = await contractUsers();
localStorage.setItem("UserInformation", JSON.stringify(_user));
localStorage.setItem(
`Parse/${parseId}/currentUser`,
JSON.stringify(_user)
);
if (contractUserDetails && contractUserDetails.length > 0) {
localStorage.setItem(
"Extand_Class",
JSON.stringify(contractUserDetails)
);
}
localStorage.setItem("username", _user.name);
localStorage.setItem("accesstoken", _user.sessionToken);
//save isGuestSigner true in local to handle login flow header in mobile view
localStorage.setItem("isGuestSigner", true);
setLoading(false);
if (sendmail === "false") {
navigate(
@@ -219,14 +214,11 @@ function GuestLogin() {
setContact((prev) => ({ ...prev, [e.target.name]: e.target.value }));
};
return (
<div className="p-14">
<div>
{isLoading ? (
<div className="flex flex-col justify-center items-center h-[100vh]">
<Loader />
<span className="text-[13px] text-[gray]">{isLoading.message}</span>
</div>
) : (
<div>
{isLoading.isLoad ? (
<LoaderWithMsg isLoading={isLoading} />
) : (
<div className="p-14 h">
<div className="m-1 md:m-2 p-[30px] text-base-content bg-base-100 op-card shadow-md">
<div className="md:w-[250px] md:h-[66px] inline-block overflow-hidden mt-2 mb-11">
{appLogo && (
@@ -371,9 +363,9 @@ function GuestLogin() {
</div>
)}
</div>
)}
</div>
<SelectLanguage />
<SelectLanguage />
</div>
)}
</div>
);
}
+16 -194
View File
@@ -3,22 +3,21 @@ import Parse from "parse";
import { useDispatch } from "react-redux";
import axios from "axios";
import Title from "../components/Title";
import GoogleSignInBtn from "../components/LoginGoogle";
// import LoginFacebook from "../components/LoginFacebook";
import { NavLink, useNavigate, useLocation } from "react-router-dom";
import { NavLink, useNavigate, useLocation } from "react-router";
import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize";
import ModalUi from "../primitives/ModalUi";
import { emailRegex, isEnableSubscription } from "../constant/const";
import {
emailRegex,
} from "../constant/const";
import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import {
fetchSubscription,
getAppLogo,
openInNewTab,
saveLanguageInLocal
saveLanguageInLocal,
usertimezone
} from "../constant/Utils";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
@@ -42,7 +41,7 @@ function Login() {
baseUrl: localStorage.getItem("baseUrl"),
parseAppId: localStorage.getItem("parseAppId"),
loading: false,
thirdpartyLoader: false
thirdpartyLoader: false,
});
const [userDetails, setUserDetails] = useState({
Company: "",
@@ -50,9 +49,7 @@ function Login() {
});
const [isModal, setIsModal] = useState(false);
const [image, setImage] = useState();
const [isLoginSSO, setIsLoginSSO] = useState(false);
const [errMsg, setErrMsg] = useState();
useEffect(() => {
checkUserExt();
// eslint-disable-next-line
@@ -62,7 +59,9 @@ function Login() {
const app = await getAppLogo();
if (app?.error === "invalid_json") {
setErrMsg(t("server-down"));
} else if (!isEnableSubscription && app?.user === "not_exist") {
} else if (
app?.user === "not_exist"
) {
navigate("/addadmin");
}
if (app?.logo) {
@@ -83,9 +82,7 @@ function Login() {
}
setState({ ...state, [name]: value });
};
const handlePaidRoute = () => {
navigate("/subscription");
};
const handleSubmit = async (event) => {
localStorage.removeItem("accesstoken");
event.preventDefault();
@@ -154,42 +151,9 @@ function Login() {
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
if (isEnableSubscription) {
const LocalUserDetails = {
name: results[0].get("Name"),
email: results[0].get("Email"),
phone: results[0]?.get("Phone") || "",
company: results[0].get("Company")
};
localStorage.setItem(
"userDetails",
JSON.stringify(LocalUserDetails)
);
const res = await fetchSubscription();
const plan = res.plan;
const billingDate = res.billingDate;
if (plan === "freeplan") {
setState({ ...state, loading: false });
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login setState({ ...state, loading: false });
setState({ ...state, loading: false });
navigate(redirectUrl);
} else {
setState({ ...state, loading: false });
handlePaidRoute(plan);
}
} else {
setState({ ...state, loading: false });
handlePaidRoute(plan);
}
} else {
setState({ ...state, loading: false });
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
}
} else {
setState({ ...state, loading: false });
setIsModal(true);
@@ -205,10 +169,6 @@ function Login() {
logOutUser();
}
} else {
if (isEnableSubscription) {
setState({ ...state, loading: false });
setIsModal(true);
} else {
setState({ ...state, loading: false });
setState({
...state,
@@ -217,12 +177,9 @@ function Login() {
alertMsg: "User not found."
});
logOutUser();
}
}
})
.catch((error) => {
// const payload = { sessionToken: user.getSessionToken() };
// handleSubmitbtn(payload);
setState({
...state,
loading: false,
@@ -248,7 +205,7 @@ function Login() {
...state,
loading: false,
alertType: "danger",
alertMsg: "Invalid username or password!"
alertMsg: "Invalid username/password or region"
});
console.error("Error while logging in user", error);
} finally {
@@ -264,18 +221,6 @@ function Login() {
const setThirdpartyLoader = (value) => {
setState({ ...state, thirdpartyLoader: value });
};
const handleFreePlan = async (id) => {
try {
const params = { userId: id };
const res = await Parse.Cloud.run("freesubscription", params);
if (res.status === "error") {
alert(res.result);
}
} catch (err) {
console.log("err in free subscribe", err.message);
alert(t("something-went-wrong-mssg"));
}
};
const thirdpartyLoginfn = async (sessionToken) => {
const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
@@ -285,11 +230,6 @@ function Login() {
"X-Parse-Application-Id": parseAppId
}
});
const param = new URLSearchParams(location.search);
const isFreeplan = param?.get("subscription") === "freeplan";
if (isFreeplan) {
await handleFreePlan(res.data.objectId);
}
await Parse.User.become(sessionToken).then(() => {
window.localStorage.setItem("accesstoken", sessionToken);
});
@@ -340,33 +280,7 @@ function Login() {
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
if (isEnableSubscription) {
const res = await fetchSubscription();
const plan = res.plan;
const billingDate = res.billingDate;
if (plan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
navigate(redirectUrl);
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else {
handlePaidRoute(plan);
}
}
} else {
if (isFreeplan) {
navigate(redirectUrl);
} else {
handlePaidRoute(plan);
}
}
} else {
navigate(redirectUrl);
}
} else {
setState({
...state,
@@ -464,34 +378,8 @@ function Login() {
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
if (isEnableSubscription) {
const userInfo = {
name: results[0].get("Name"),
email: results[0].get("Email"),
phone: results[0]?.get("Phone") || "",
company: results[0]?.get("Company")
};
localStorage.setItem("userDetails", JSON.stringify(userInfo));
const res = await fetchSubscription();
const billingDate = res.billingDate;
const plan = res.plan;
if (plan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
} else {
handlePaidRoute(plan);
}
} else {
handlePaidRoute(plan);
}
} else {
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
}
} else {
setState({ ...state, loading: false });
logOutUser();
@@ -550,7 +438,8 @@ function Login() {
phone: userInformation?.phone || "",
role: "contracts_User",
company: userDetails.Company,
jobTitle: userDetails.Destination
jobTitle: userDetails.Destination,
timezone: usertimezone
}
};
const userSignUp = await Parse.Cloud.run("usersignup", params);
@@ -610,25 +499,6 @@ function Login() {
localStorage.setItem("parseAppId", appid);
};
// `handleSignInWithSSO` is trigger when user click sign in with sso and open sso authorize endpoint
const handleSignInWithSSO = () => {
if (isLoginSSO === false) {
if (state?.email) {
setIsLoginSSO(true);
const encodedEmail = encodeURIComponent(state.email);
const clientUrl = window.location.origin;
const domain = state.email.split("@")?.pop();
const ssoApiUrl =
process.env.SSO_API_URL || "https://sso.opensignlabs.com/api";
openInNewTab(
`${ssoApiUrl}/oauth/authorize?response_type=code&provider=saml&tenant=${domain}&product=OpenSign&redirect_uri=${clientUrl}/sso&state=${encodedEmail}`,
"_self"
);
} else {
alert(t("provide-email"));
}
}
};
return errMsg ? (
<div className="h-screen flex justify-center text-center items-center p-4 text-gray-500 text-base">
@@ -650,7 +520,7 @@ function Login() {
<div
aria-labelledby="loginHeading"
role="region"
className="md:p-10 lg:p-16"
className="pb-1 md:pb-4 pt-10 md:px-10 lg:px-16"
>
<div className="md:p-4 lg:p-10 p-4 bg-base-100 text-base-content op-card">
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
@@ -689,8 +559,6 @@ function Login() {
onInput={(e) => e.target.setCustomValidity("")}
/>
<hr className="my-1 border-none" />
{!isLoginSSO && (
<>
<label className="block text-xs" htmlFor="password">
{t("password")}
</label>
@@ -724,8 +592,7 @@ function Login() {
)}
</span>
</div>
</>
)}
<div className="relative mt-1">
<NavLink
to="/forgetpassword"
@@ -744,53 +611,8 @@ function Login() {
>
{state.loading ? t("loading") : t("login")}
</button>
{isEnableSubscription && (
<button
type="button"
className="op-btn op-btn-accent"
disabled={state.loading}
onClick={() =>
navigate(
location.search
? "/signup" + location.search
: "/signup"
)
}
>
{t("create-account")}
</button>
)}
</div>
</form>
{(appInfo.googleClietId || isEnableSubscription) && (
<div className="op-divider my-4 text-sm">{t("or")}</div>
)}
<div className="flex flex-col justify-center items-center gap-y-3">
{/* {appInfo?.fbAppId && (
<LoginFacebook
FBCred={appInfo.fbAppId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
/>
)} */}
{appInfo?.googleClietId && (
<GoogleSignInBtn
GoogleCred={appInfo.googleClietId}
thirdpartyLoginfn={thirdpartyLoginfn}
thirdpartyLoader={state.thirdpartyLoader}
setThirdpartyLoader={setThirdpartyLoader}
/>
)}
{isEnableSubscription && (
<div
className="cursor-pointer border-[1px] border-gray-300 rounded px-[40px] py-2 font-semibold text-sm hover:border-[#d2e3fc] hover:bg-[#ecf3feb7]"
onClick={() => handleSignInWithSSO()}
>
{t("sign-SSO")}
</div>
)}
</div>
</div>
{width >= 768 && (
<div className="place-self-center">
+27 -55
View File
@@ -2,7 +2,11 @@ import React, { useState, useRef, useEffect } from "react";
import SignatureCanvas from "react-signature-canvas";
import "../styles/managesign.css";
import "../styles/signature.css";
import { generateTitleFromFilename, toDataUrl } from "../constant/Utils";
import {
generateTitleFromFilename,
getSecureUrl,
toDataUrl
} from "../constant/Utils";
import Parse from "parse";
import { SaveFileSize } from "../constant/saveFileSize";
import Alert from "../primitives/Alert";
@@ -198,9 +202,20 @@ const ManageSign = () => {
const parseFile = new Parse.File(file.name, file);
const response = await parseFile.save();
if (response?.url()) {
const tenantId = localStorage.getItem("TenantId");
SaveFileSize(file.size, response.url(), tenantId);
return 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"));
setIsLoader(false);
return false;
}
} else {
alert(t("something-went-wrong-mssg"));
setIsLoader(false);
return false;
}
} catch (err) {
console.log("sign upload err", err);
@@ -323,7 +338,8 @@ const ManageSign = () => {
canvasProps={{
width: "456px",
height: "180px",
className: "signatureCanvas rounded-box"
className:
"signatureCanvas border-[2px] border-[#888] rounded-box"
}}
// backgroundColor="rgb(255, 255, 255)"
onEnd={() =>
@@ -335,35 +351,13 @@ const ManageSign = () => {
<div className="penContainerDefault flex flex-row justify-between">
<div>
{!image && (
<div className="flex flex-row">
<div className="flex flex-row gap-1.5 m-[5px]">
{allColor.map((data, key) => {
return (
<i
style={{
margin: "5px",
color: data,
borderBottom:
key === 0 && penColor === "blue"
? "2px solid blue"
: key === 1 && penColor === "red"
? "2px solid red"
: key === 2 && penColor === "black"
? "2px solid black"
: "2px solid white"
}}
onClick={() => {
if (key === 0) {
setPenColor("blue");
} else if (key === 1) {
setPenColor("red");
} else if (key === 2) {
setPenColor("black");
}
}}
key={key}
className="fa-light fa-pen-nib"
width={20}
height={20}
onClick={() => setPenColor(allColor[key])}
className={`border-b-[2px] ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
></i>
);
})}
@@ -432,35 +426,13 @@ const ManageSign = () => {
<div className="flex flex-row justify-between w-[183px]">
<div>
{!isInitials && (
<div className="flex flex-row">
<div className="flex flex-row gap-1.5 m-[5px]">
{allColor.map((data, key) => {
return (
<i
style={{
margin: "5px",
color: data,
borderBottom:
key === 0 && initialPen === "blue"
? "2px solid blue"
: key === 1 && initialPen === "red"
? "2px solid red"
: key === 2 && initialPen === "black"
? "2px solid black"
: "2px solid white"
}}
className="fa-light fa-pen-nib"
onClick={() => {
if (key === 0) {
setInitialPen("blue");
} else if (key === 1) {
setInitialPen("red");
} else if (key === 2) {
setInitialPen("black");
}
}}
key={key}
width={20}
height={20}
onClick={() => setInitialPen(allColor[key])}
className={`border-b-[2px] ${key === 0 && initialPen === "blue" ? "border-blue-600" : key === 1 && initialPen === "red" ? "border-red-500" : key === 2 && initialPen === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
></i>
);
})}
+77 -41
View File
@@ -1,8 +1,10 @@
import React, { useEffect, useState, useRef } from "react";
import "../styles/opensigndrive.css";
import { iconColor } from "../constant/const";
import {
iconColor,
} from "../constant/const";
import { getDrive } from "../constant/Utils";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import Title from "../components/Title";
import Parse from "parse";
import ModalUi from "../primitives/ModalUi";
@@ -11,10 +13,11 @@ import Tour from "reactour";
import axios from "axios";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
const DriveBody = React.lazy(
() => import("../components/opensigndrive/DriveBody")
);
const dropdowncss =
"absolute right-0 py-2 text-[1rem] text-left bg-white border-[1px] border-gray-300 min-w-1 hover:bg-gray-300 rounded-[0.25rem] z-[800]";
const AppLoader = () => {
return (
<div className="h-[100vh] flex justify-center items-center">
@@ -45,9 +48,9 @@ function Opensigndrive() {
const [handleError, setHandleError] = useState("");
const [folderName, setFolderName] = useState([]);
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
const [isNewFol, setIsNewFol] = useState(false);
const [isOptions, setIsOptions] = useState(false);
const [skip, setSkip] = useState(0);
const limit = 100;
const limit = 50;
const [loading, setLoading] = useState(false);
const sortOrder = ["Ascending", "Descending"];
const sortingValue = ["Name", "Date"];
@@ -223,8 +226,7 @@ function Opensigndrive() {
//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 % 100 === 0) {
if (!loading && pdfData.length % 50 === 0) {
getPdfDocumentList(disableLoading);
}
}
@@ -420,8 +422,8 @@ function Opensigndrive() {
const closeMenuOnOutsideClick = (e) => {
if (isShowSort && !e.target.closest("#menu-container")) {
setIsShowSort(!isShowSort);
} else if (isNewFol && !e.target.closest("#folder-menu")) {
setIsNewFol(!isNewFol);
} else if (isOptions && !e.target.closest("#folder-menu")) {
setIsOptions(!isOptions);
}
};
@@ -432,7 +434,7 @@ function Opensigndrive() {
document.removeEventListener("click", closeMenuOnOutsideClick);
};
// eslint-disable-next-line
}, [isShowSort, isNewFol]);
}, [isShowSort, isOptions]);
const handleFolderTab = (folderData) => {
return folderData.map((data, id) => {
@@ -601,7 +603,7 @@ function Opensigndrive() {
</div>
) : (
<>
<div className="flex flex-row justify-between items-center px-[25px] pt-[20px]">
<div className="flex flex-row justify-between items-center px-[15px] md:px-[25px] pt-[20px]">
{tourData && (
<Tour
onRequestClose={closeTour}
@@ -623,10 +625,8 @@ function Opensigndrive() {
<div className="flex flex-row items-center">
<div
id="folder-menu"
className={
isNewFol ? "dropdown show dropDownStyle" : "dropdown"
}
onClick={() => setIsNewFol(!isNewFol)}
className={`${isOptions ? "dropdown show dropDownStyle" : "dropdown"} hidden md:block`}
onClick={() => setIsOptions(!isOptions)}
>
<div className="sort" data-tut="reactourSecond">
<i
@@ -636,9 +636,10 @@ function Opensigndrive() {
></i>
</div>
<div
className={isNewFol ? "dropdown-menu show" : "dropdown-menu"}
className={`${isOptions ? "block" : "hidden"} ${dropdowncss}`}
// className={isOptions ? "dropdown-menu show" : "dropdown-menu"}
aria-labelledby="dropdownMenuButton"
aria-expanded={isNewFol ? "true" : "false"}
aria-expanded={isOptions ? "true" : "false"}
>
<div className="flex flex-col">
<span
@@ -719,7 +720,6 @@ function Opensigndrive() {
</span>
);
})}
<hr className="hrStyle" />
{sortOrder.map((order, ind) => {
return (
@@ -748,30 +748,66 @@ function Opensigndrive() {
})}
</div>
</div>
<div>
{isList ? (
<div className="sort" onClick={() => setIsList(!isList)}>
<i
onClick={() => setIsList(!isList)}
className="fa-light fa-th-large"
style={{ fontSize: "24px", color: `${iconColor}` }}
aria-hidden="true"
></i>
<div
data-tut="reactourForth"
className="sort"
onClick={() => setIsList(!isList)}
>
<i
className={
isList ? "fa-light fa-th-large" : "fa-light fa-list"
}
style={{ fontSize: "24px", color: `${iconColor}` }}
aria-hidden="true"
></i>
</div>
<div
id="folder-menu"
className={`${isOptions ? "dropdown show dropDownStyle" : "dropdown"} md:hidden`}
onClick={() => setIsOptions(!isOptions)}
>
<div
className="p-[19px] my-2 flex items-center justify-center cursor-pointer hover:bg-[var(--mauve-3)] rounded-[2px] shadow-[0_2px_4px_rgba(168,204,206,0.1)]"
data-tut="reactourSecond"
>
<i
className="fa-light fa-ellipsis-vertical fa-lg"
aria-hidden="true"
style={{ color: `${iconColor}` }}
></i>
</div>
<div
className={`${isOptions ? "block" : "hidden"} ${dropdowncss}`}
aria-labelledby="dropdownMenuButton"
aria-expanded={isOptions ? "true" : "false"}
>
<div className="flex flex-col">
<span
className="dropdown-item text-[10px] md:text-[13px]"
onClick={() => setIsFolder(true)}
>
<i
className="fa-light fa-plus mr-[5px]"
aria-hidden="true"
></i>
{t("create-folder")}
</span>
<span
className="dropdown-item text-[10px] md:text-[13px]"
onClick={() => navigate("/form/sHAnZphf69")}
>
<i className="fa-light fa-pen-nib mr-[5px]"></i>
{t("form-name.Sign Yourself")}
</span>
<span
className="dropdown-item text-[10px] md:text-[13px]"
onClick={() => navigate("/form/8mZzFxbG1z")}
>
<i className="fa-light fa-file-signature mr-[5px]"></i>
{t("form-name.Request Signatures")}
</span>
</div>
) : (
<div
data-tut="reactourForth"
className="sort"
onClick={() => setIsList(!isList)}
>
<i
className="fa-light fa-list"
aria-hidden="true"
style={{ fontSize: "23px", color: `${iconColor}` }}
></i>
</div>
)}
</div>
</div>
</div>
</div>
File diff suppressed because it is too large Load Diff
-384
View File
@@ -1,384 +0,0 @@
import React, { useState, useEffect } from "react";
import Parse from "parse";
import axios from "axios";
import { useDispatch } from "react-redux";
import Title from "../components/Title";
import { useNavigate, useLocation } from "react-router-dom";
import { appInfo } from "../constant/appinfo";
import { showTenant } from "../redux/reducers/ShowTenant";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
const PgSignUp = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
const [parseBaseUrl] = useState(localStorage.getItem("baseUrl"));
const [parseAppId] = useState(localStorage.getItem("parseAppId"));
const [formData, setFormData] = useState({
password: "",
confirmPassword: ""
});
const [validationMessage, setValidationMessage] = useState("");
const [isLoader, setIsLoader] = useState(true);
const [lengthValid, setLengthValid] = useState(false);
const [caseDigitValid, setCaseDigitValid] = useState(false);
const [specialCharValid, setSpecialCharValid] = useState(false);
// below useEffect is used to fetch App data and save to redux state
useEffect(() => {
dispatch(fetchAppInfo());
handleSignedUpUser();
// eslint-disable-next-line
}, []);
//`handleSignedUpUser` is called first time and it only work for user who comes from login with google, facebook or normal sign up flow
const handleSignedUpUser = async () => {
const userDetails = JSON.parse(localStorage.getItem("userDetails"));
if (userDetails && userDetails.email) {
try {
const url = window.location.href;
let paramString = url.split("?")[1];
let queryString = new URLSearchParams(paramString);
let obj = { ...formData };
for (let pair of queryString.entries()) {
obj = { ...obj, [pair[0]]: pair[1] };
}
const zohoRes = await axios.post(
parseBaseUrl + "functions/zohodetails",
{
hostedpagesId: obj.hostedpage_id
},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
}
);
const userSettings = JSON.parse(localStorage.getItem("userSettings"));
const params = { subscription: zohoRes.data.result.subscription };
const baseurl = `${parseBaseUrl}functions/savesubscription`;
try {
const res = await axios.post(baseurl, params, {
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId,
sessiontoken: localStorage.getItem("accesstoken")
}
});
if (res) {
localStorage.removeItem("userDetails");
try {
const extUser = await Parse.Cloud.run("getUserDetails");
const userRole = extUser?.get("UserRole");
const _currentRole = userRole;
const _role = _currentRole.replace("contracts_", "");
localStorage.setItem("_user_role", _role);
const extInfo_stringify = JSON.stringify([extUser]);
localStorage.setItem("Extand_Class", extInfo_stringify);
} catch (err) {
console.log("Err in fetching extuser", err);
}
navigate(
"/" + userSettings[0].pageType + "/" + userSettings[0].pageId
);
setIsLoader(false);
}
} catch (err) {
console.log("err ", err.message);
setIsLoader(false);
}
} catch (err) {
console.log("err ", err);
setIsLoader(false);
}
} else {
setIsLoader(false);
}
};
// "handleSubmit" is used to save user details signup him/her who come from plan-pricing page
const handleSubmit = (e) => {
e.preventDefault();
if (lengthValid && caseDigitValid && specialCharValid) {
setIsLoader(true);
if (formData.password === formData.confirmPassword) {
const url = window.location.href;
let paramString = url.split("?")[1];
let queryString = new URLSearchParams(paramString);
let obj = { ...formData };
for (let pair of queryString.entries()) {
obj = { ...obj, [pair[0]]: pair[1] };
}
saveUser(obj);
// console.log("obj ", obj);}
}
}
};
const saveUser = async (obj) => {
try {
const zohoRes = await axios.post(
parseBaseUrl + "functions/zohodetails",
{
hostedpagesId: obj.hostedpage_id
},
{
headers: {
"Content-Type": "application/json",
"X-Parse-Application-Id": parseAppId
}
}
);
const user = new Parse.User();
user.set("username", zohoRes.data.result.email);
user.set("password", obj.password);
user.set("email", zohoRes.data.result.email);
if (zohoRes?.data?.result?.phone) {
user.set("phone", zohoRes.data.result.phone);
}
user.set("name", zohoRes.data.result.name);
const res = await user.signUp();
// console.log("res ", res);
if (res) {
const params = {
userDetails: {
jobTitle: zohoRes.data.result.jobTitle,
company: zohoRes.data.result.company,
name: zohoRes.data.result.name,
email: zohoRes.data.result.email,
phone: zohoRes?.data?.result?.phone || "",
role: obj.role
},
subscription: zohoRes.data.result.subscription
};
const usersignup = await Parse.Cloud.run("usersignup", params);
// console.log("usersignup ", usersignup);
if (usersignup && usersignup.sessionToken) {
handleNavigation(usersignup.sessionToken);
}
}
// setIsLoader(false);
} catch (error) {
console.log("err ", error);
if (error.message === "Account already exists for this username.") {
alert(t("account-already-exists"));
navigate("/", { replace: true });
} else {
setIsLoader(false);
alert(t("something-went-wrong-mssg"));
}
}
};
const handlePassowordChange = (e) => {
const newPassword = e.target.value;
setFormData({ ...formData, [e.target.name]: e.target.value });
validatePasswords(newPassword, formData.confirmPassword);
// Check conditions separately
setLengthValid(newPassword.length >= 8);
setCaseDigitValid(
/[a-z]/.test(newPassword) &&
/[A-Z]/.test(newPassword) &&
/\d/.test(newPassword)
);
setSpecialCharValid(/[!@#$%^&*()\-_=+{};:,<.>]/.test(newPassword));
};
const handleConFirmPassowordChange = (e) => {
const newConfirmPassword = e.target.value;
setFormData({ ...formData, [e.target.name]: e.target.value });
validatePasswords(formData.password, newConfirmPassword);
};
const validatePasswords = (newPassword, newConfirmPassword) => {
if (newPassword === newConfirmPassword) {
setValidationMessage("Passwords match!");
} else {
setValidationMessage("Passwords not match!");
}
};
const handleNavigation = async (sessionToken) => {
const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
const res = await axios.get(baseUrl + "users/me", {
headers: {
"X-Parse-Session-Token": sessionToken,
"X-Parse-Application-Id": parseAppId
}
});
await Parse.User.become(sessionToken).then(() => {
window.localStorage.setItem("accesstoken", sessionToken);
});
// console.log("me res ", res);
if (res.data) {
let _user = res.data;
localStorage.setItem("UserInformation", JSON.stringify(_user));
localStorage.setItem("accesstoken", _user.sessionToken);
localStorage.setItem("scriptId", true);
if (_user.ProfilePic) {
localStorage.setItem("profileImg", _user.ProfilePic);
} else {
localStorage.setItem("profileImg", "");
}
// Check extended class user role and tenentId
try {
const userSettings = appInfo.settings;
const extUser = await Parse.Cloud.run("getUserDetails");
if (extUser) {
const IsDisabled = extUser?.get("IsDisabled") || false;
if (!IsDisabled) {
const userRole = extUser?.get("UserRole");
const menu =
userRole && userSettings.find((menu) => menu.role === userRole);
if (menu) {
const _currentRole = userRole;
const _role = _currentRole.replace("contracts_", "");
localStorage.setItem("_user_role", _role);
const extInfo_stringify = JSON.stringify([extUser]);
localStorage.setItem("Extand_Class", extInfo_stringify);
const extInfo = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("userEmail", extInfo?.Email);
localStorage.setItem("username", extInfo?.Name);
if (extInfo?.TenantId) {
const tenant = {
Id: extInfo?.TenantId?.objectId || "",
Name: extInfo?.TenantId?.TenantName || ""
};
localStorage.setItem("TenantId", tenant?.Id);
dispatch(showTenant(tenant?.Name));
localStorage.setItem("TenantName", tenant?.Name);
}
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
setIsLoader(false);
alert(t("registered-user-successfully"));
const redirectUrl =
location?.state?.from || `/${menu.pageType}/${menu.pageId}`;
// Redirect to the appropriate URL after successful login
navigate(redirectUrl);
}
} else {
setIsLoader(false);
}
}
} catch (error) {
setIsLoader(false);
console.log("Err in fetch extuser", error);
}
}
};
return (
<div className="w-full h-screen">
<Title title={"Pgsignup page"} />
{isLoader ? (
<div className="flex justify-center items-center h-[100vh]">
<Loader />
</div>
) : (
<div className="py-10 lg:p-16 flex justify-center">
<form
id="signup"
className="w-[90%] md:w-[55%] bg-base-100 text-base-content op-card shadow-md"
onSubmit={handleSubmit}
>
<div className="p-[16px]">
<h1 className="text-2xl md:text-3xl font-bold">
{t("choose-password")}
</h1>
<hr className="border-[1px] border-[#cecece] mb-[5px]" />
<label htmlFor="password">
<b className="text-[13px]">{t("password")}</b>
</label>
<input
type="password"
placeholder={t("enter-password")}
name="password"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={formData.password}
onChange={handlePassowordChange}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<label htmlFor="confirmPassword">
<b className="text-[13px]">{t("confirm-password")}</b>
</label>
<input
type="password"
placeholder={t("confirm-password")}
name="confirmPassword"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
style={{
border: validationMessage
? validationMessage === "Passwords match!"
? "1px solid green"
: "1px solid red"
: ""
}}
value={formData.confirmPassword}
onChange={handleConFirmPassowordChange}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<div
className={`${
validationMessage === "Passwords match!"
? "text-[green]"
: "text-[red]"
} ml-[13px] text-[11px]`}
>
{validationMessage}
</div>
{formData.password.length > 0 && (
<div className="mt-1 text-[11px]">
<p
className={`${
lengthValid ? "text-green-600" : "text-red-600"
}`}
>
{lengthValid ? "✓" : "✗"} {t("password-length")}
</p>
<p
className={`${
caseDigitValid ? "text-green-600" : "text-red-600"
}`}
>
{caseDigitValid ? "✓" : "✗"} {t("password-case")}
</p>
<p
className={`${
specialCharValid ? "text-green-600" : "text-red-600"
}`}
>
{specialCharValid ? "✓" : "✗"} {t("password-special-char")}
</p>
</div>
)}
<button
type="submit"
className="op-btn op-btn-primary w-full mt-[8px]"
>
{t("submit")}
</button>
</div>
</form>
</div>
)}
</div>
);
};
export default PgSignUp;
File diff suppressed because it is too large Load Diff
@@ -1,314 +0,0 @@
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import checkmark from "../assets/images/checkmark.png";
import plansArr from "../json/plansArr";
import Title from "../components/Title";
import Parse from "parse";
import { openInNewTab, saveLanguageInLocal } from "../constant/Utils";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
const listItemStyle = {
paddingLeft: "20px", // Add padding to create space for the image
backgroundImage: `url(${checkmark})`, // Set your image as the list style image
backgroundPosition: "left",
backgroundRepeat: "no-repeat",
backgroundSize: "16px 16px" // Adjust the size of the image
};
const PlanSubscriptions = () => {
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const [yearlyVisible, setYearlyVisible] = useState(true);
const [isLoader, setIsLoader] = useState(true);
const extUser =
localStorage.getItem("Extand_Class") &&
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
const user = {
name: extUser?.Name,
email: extUser?.Email,
company: extUser?.Company,
phone: extUser?.Phone
};
const userDetails = JSON.parse(localStorage.getItem("userDetails")) || user;
// console.log("userDetails ", userDetails);
const fullname =
userDetails && userDetails.name ? userDetails.name.split(" ") : "";
const firstname = fullname?.[0]
? "first_name=" + encodeURIComponent(fullname?.[0])
: "";
const lastname = fullname?.[1]
? "&last_name=" + encodeURIComponent(fullname?.[1])
: "";
const name = firstname ? firstname + lastname : "";
const email =
userDetails && userDetails.email
? "&email=" + encodeURIComponent(userDetails.email)
: "";
const company =
userDetails && userDetails.company
? "&company_name=" + encodeURIComponent(userDetails.company)
: "";
const phone =
userDetails && userDetails.phone
? "&mobile=" + encodeURIComponent(userDetails.phone)
: "";
useEffect(() => {
setIsLoader(false);
detectLanguage();
// eslint-disable-next-line
}, []);
const detectLanguage = () => {
const detectedLanguage = i18n.language || "en";
i18n.changeLanguage(detectedLanguage);
localStorage.setItem("i18nextLng", detectedLanguage);
};
const handleFreePlan = async (item) => {
if (item.url) {
const code = yearlyVisible ? item.code.yearly : item.code.monthly;
const allowedUsers =
localStorage.getItem("allowedUsers") &&
localStorage.getItem("allowedUsers") > 0
? localStorage.getItem("allowedUsers") - 1
: "";
const teamperiod = {
"team-weekly": "monthly",
"team-yearly": "yearly",
"teams-monthly": "monthly",
"teams-yearly": "yearly"
};
const period = teamperiod[code] || "";
const quantity =
allowedUsers && period
? `addon_code%5B0%5D=extra-teams-users-${period}&addon_quantity%5B0%5D=${allowedUsers}&`
: "";
const details =
"?shipping_country_code=US&billing_country_code=US&billing_state_code=CA&" +
quantity +
name +
email +
company +
phone;
const url = yearlyVisible ? item.yearlyUrl + details : item.url + details;
if (user) {
localStorage.setItem("userDetails", JSON.stringify(user));
}
openInNewTab(url, item?.target);
} else {
setIsLoader(true);
try {
const params = { userId: Parse.User.current().id };
const res = await Parse.Cloud.run("freesubscription", params);
if (res.status === "success" && res.result === "already subscribed!") {
setIsLoader(false);
alert(t("subscribed-alert"));
} else if (res.status === "success") {
setIsLoader(false);
navigate("/");
} else if (res.status === "error") {
setIsLoader(false);
alert(res.result);
}
} catch (err) {
setIsLoader(false);
console.log("err in free subscribe", err.message);
alert(t("something-went-wrong-mssg"));
}
}
};
const handleLogout = async () => {
try {
await Parse?.User?.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let appdata = localStorage.getItem("userSettings");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", appdata);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
navigate("/");
};
return (
<>
<Title title={"Subscriptions"} />
{isLoader ? (
<div className="flex items-center justify-center h-screen">
<Loader />
</div>
) : (
<div className="overflow-y-auto max-h-full">
<div className="block my-2">
<div className="flex flex-col justify-center items-center w-full relative">
<button
className="md:hidden op-btn op-btn-primary op-btn-sm h-[40px] w-[250px] shadow-lg"
onClick={() => handleLogout()}
>
Log out
</button>
<div
role="tablist"
className="op-tabs op-tabs-boxed bg-base-100 my-2 shadow-lg transition-all"
>
<a
onClick={() => setYearlyVisible(false)}
role="tab"
className={`${!yearlyVisible ? "op-tab-active" : ""} op-tab`}
>
{t("monthly")}
</a>
<a
onClick={() => setYearlyVisible(true)}
role="tab"
className={`${yearlyVisible ? "op-tab-active" : ""} op-tab`}
>
{t("yearly-upto")}
</a>
</div>
<ul className="op-card flex flex-col md:flex-row h-full bg-base-100 justify-center shadow-lg">
<button
className="hidden md:block -top-12 right-1 absolute w-[150px] h-[40px] op-btn op-btn-primary op-btn-sm shadow-lg"
onClick={() => handleLogout()}
>
{t("log-out")}
</button>
{plansArr.map((item) => (
<li
className="flex flex-col text-center border-collapse border-[1px] border-gray-300 max-w-[250px]"
key={item.planName}
>
<div className="p-2 flex flex-col justify-center items-center max-h-[310px]">
<h3 className="text-[#002862] uppercase">
{item.planName}
</h3>
<div className="w-[120px] h-[120px] overflow-hidden">
<img
className="w-full h-full object-contain"
src={require(`../assets/images/${item?.img}`)}
alt="img"
/>
</div>
<div>
<span className="text-3xl">
{item.currency && <small>{item.currency}</small>}
{yearlyVisible ? (
<>
{item?.yearlyPrice.includes("<") ? (
<div
className="inline-block"
dangerouslySetInnerHTML={{
__html: item?.yearlyPrice
}}
/>
) : (
<span>{item?.yearlyPrice}</span>
)}
</>
) : (
<>
{item?.monthlyPrice.includes("<") ? (
<div
className="inline-block"
dangerouslySetInnerHTML={{
__html: item?.monthlyPrice
}}
/>
) : (
<span>{item?.monthlyPrice}</span>
)}
</>
)}
</span>
<p className="font-semibold pt-2 text-sm">
{yearlyVisible
? t("billed-yearly")
: t("billed-monthly")}
</p>
<div className="max-w-[250px] h-[40px] text-center text-sm my-2">
<div
style={{
textAlign: "center",
backgroundColor: item.subtitlecolor
? item.subtitlecolor
: "white"
}}
>
{item.subtitle.includes("<") ? (
<div
dangerouslySetInnerHTML={{
__html: item.subtitle
}}
/>
) : (
<span>{item.subtitle}</span>
)}
</div>
</div>
</div>
<button
className={`${
item?.btn?.color ? item?.btn?.color : "op-btn-primary"
} w-full text-white py-2 op-btn uppercase hover:text-white cursor-pointer`}
onClick={() => handleFreePlan(item)}
>
{item?.btn?.text}
</button>
</div>
<hr className="w-full bg-gray-300 h-[0.5px]" />
<ul className="mx-1 p-3 text-left break-words text-sm list-none">
{!yearlyVisible &&
item.benefits.map((subitem, index) => (
<li style={listItemStyle} key={index} className="m-1">
<span className="relative">{subitem}</span>
</li>
))}
{yearlyVisible &&
item?.yearlyBenefits?.map((subitem, index) => (
<li style={listItemStyle} key={index} className="m-1">
<span className="relative">{subitem}</span>
</li>
))}
</ul>
</li>
))}
</ul>
</div>
<div className="text-sm flex justify-center items-center mt-3 mb-2">
<hr className="border-[2px] border-gray-350 w-[20%]" />
<span className="px-2 text-base-content cursor-default">or</span>
<hr className="border-[2px] border-gray-350 w-[20%]" />
</div>
<div className="flex flex-col justify-center items-center">
<h3 className="text-[#002862] mt-1 mb-2">
{t("plansubscription-1")}
</h3>
<div
className="op-btn op-btn-primary w-[200px]"
onClick={() =>
openInNewTab("https://github.com/OpenSignLabs/OpenSign")
}
>
{t("visit-github")}
</div>
</div>
</div>
</div>
)}
</>
);
};
export default PlanSubscriptions;
+462 -149
View File
@@ -1,40 +1,63 @@
import React, { useEffect, useState } from "react";
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";
import Tooltip from "../primitives/Tooltip";
import {
checkIsSubscribed,
getTenantDetails,
handleSignatureType,
signatureTypes
signatureTypes,
usertimezone
} from "../constant/Utils";
import Parse from "parse";
import { Tooltip as ReactTooltip } from "react-tooltip";
import { isEnableSubscription } from "../constant/const";
import Upgrade from "../primitives/Upgrade";
import TimezoneSelector from "../components/shared/fields/TimezoneSelector";
import ReactQuill from "react-quill-new";
import "../styles/quill.css";
import EditorToolbar, {
module1,
module2,
formats
} from "../components/pdf/EditorToolbar";
const Preferences = () => {
const { t } = useTranslation();
const editorRef = useRef();
const editorRefCom = useRef();
const Extand_Class = localStorage.getItem("Extand_Class");
const extClass = Extand_Class && JSON.parse(Extand_Class);
const [requestSubject, setRequestSubject] = useState("");
const [completionBody, SetCompletionBody] = useState("");
const [completionsubject, setCompletionSubject] = useState("");
const [requestBody, setRequestBody] = useState("");
const [defaultCompHtml, setDefaultCompHtml] = useState({});
const [tenantId, setTenantId] = useState("");
const [defaultReqHtml, setDefaultReqHtml] = useState({});
const [isalert, setIsAlert] = useState({ type: "success", msg: "" });
const [isTopLoader, setIsTopLoader] = useState(false);
const [isLoader, setIsLoader] = useState(false);
const [signatureType, setSignatureType] = useState([]);
const [errMsg, setErrMsg] = useState("");
const [isNotifyOnSignatures, setIsNotifyOnSignatures] = useState();
const [isSubscribe, setIsSubscribe] = useState(false);
const [timezone, setTimezone] = useState(usertimezone);
const [activeTab, setactiveTab] = useState(0);
const generaltab = { title: t("general"), icon: "fa-light fa-gears" };
const [tab, setTab] = useState([generaltab]);
useEffect(() => {
fetchSignType();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const fetchSignType = async () => {
if (isEnableSubscription) {
const subscribe = await checkIsSubscribed();
setIsSubscribe(subscribe.isValid);
} else {
setIsSubscribe(true);
}
setIsTopLoader(true);
const EmailTab =
extClass?.[0]?.UserRole === "contracts_Admin"
? [{ title: t("email"), icon: "fa-light fa-envelope" }]
: [];
const arr = [generaltab, ...EmailTab];
setTab(arr);
try {
const user = JSON.parse(
localStorage.getItem(
@@ -42,6 +65,7 @@ const Preferences = () => {
)
);
const tenantDetails = await getTenantDetails(user?.objectId);
await tenantEmailTemplate(tenantDetails);
const signatureType = tenantDetails?.SignatureType || [];
const tenantSignTypes = signatureType?.filter((x) => x.enabled === true);
const getUser = await Parse.Cloud.run("getUserDetails");
@@ -50,8 +74,9 @@ const Preferences = () => {
const notifyOnSignatures =
_getUser?.NotifyOnSignatures !== undefined
? _getUser?.NotifyOnSignatures
: "";
: true;
setIsNotifyOnSignatures(notifyOnSignatures);
setTimezone(_getUser?.Timezone || usertimezone);
if (tenantSignTypes?.length > 0) {
const signatureType = _getUser?.SignatureType || signatureTypes;
const updatedSignatureType = await handleSignatureType(
@@ -85,8 +110,13 @@ const Preferences = () => {
// `handleSave` is used save updated value signature type
const handleSave = async () => {
setIsLoader(true);
if (signatureType.length > 0 || isNotifyOnSignatures) {
let params = {};
const Timezone = timezone || usertimezone;
if (
signatureType.length > 0 ||
isNotifyOnSignatures !== undefined ||
Timezone
) {
let params = { Timezone: Timezone };
if (signatureType.length > 0) {
const enabledSignTypes = signatureType?.filter((x) => x.enabled);
const isDefaultSignTypeOnly =
@@ -106,7 +136,7 @@ const Preferences = () => {
params = { ...params, SignatureType: signatureType };
}
}
if (isNotifyOnSignatures) {
if (isNotifyOnSignatures !== undefined) {
params = { ...params, NotifyOnSignatures: isNotifyOnSignatures };
}
try {
@@ -136,6 +166,122 @@ const Preferences = () => {
const handleNotifySignChange = (value) => {
setIsNotifyOnSignatures(value);
};
const tenantEmailTemplate = async (tenantRes) => {
if (tenantRes === "user does not exist!") {
alert("User does not exist");
} else if (tenantRes) {
setIsLoader(true);
const updateRes = tenantRes;
setTenantId(updateRes?.objectId);
const defaultRequestBody = `<p>Hi {{receiver_name}},</p><br><p>We hope this email finds you well. {{sender_name}}&nbsp;has requested you to review and sign&nbsp;{{document_title}}.</p><p>Your signature is crucial to proceed with the next steps as it signifies your agreement and authorization.</p><br><p>{{signing_url}}</p><br><p>If you have any questions or need further clarification regarding the document or the signing process, please contact the sender.</p><br><p>Thanks</p><p> Team OpenSign™</p><br>`;
if (updateRes?.RequestBody) {
setRequestBody(updateRes?.RequestBody);
setRequestSubject(updateRes?.RequestSubject);
} else {
setRequestBody(defaultRequestBody);
setRequestSubject(
`{{sender_name}} has requested you to sign {{document_title}}`
);
}
setDefaultReqHtml({
body: defaultRequestBody,
subject: `{{sender_name}} has requested you to sign {{document_title}}`
});
const defaultCompletionBody = `<p>Hi {{sender_name}},</p><br><p>All parties have successfully signed the document {{document_title}}. Kindly download the document from the attachment.</p><br><p>Thanks</p><p> Team OpenSign™</p><br>`;
if (updateRes?.CompletionBody) {
SetCompletionBody(updateRes?.CompletionBody);
setCompletionSubject(updateRes?.CompletionSubject);
} else {
SetCompletionBody(defaultCompletionBody);
setCompletionSubject(
`Document {{document_title}} has been signed by all parties`
);
}
setDefaultCompHtml({
body: defaultCompletionBody,
subject: `Document {{document_title}} has been signed by all parties`
});
setIsLoader(false);
}
};
//function to save completion email template
const handleSaveCompletionEmail = async (e) => {
e.preventDefault();
try {
setIsLoader(true);
const replacedHtmlBody = completionBody.replace(/"/g, "'");
const htmlBody = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>${replacedHtmlBody}</body></html>`;
const tenantQuery = new Parse.Query("partners_Tenant");
const updateTenantObj = await tenantQuery.get(tenantId);
updateTenantObj.set("CompletionBody", htmlBody);
updateTenantObj.set("CompletionSubject", completionsubject);
const res = await updateTenantObj.save();
if (res) {
const updateRes = JSON.parse(JSON.stringify(res));
SetCompletionBody(updateRes?.CompletionBody);
setCompletionSubject(updateRes?.CompletionSubject);
setIsAlert({ type: "success", msg: "Saved successfully." });
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
}
} catch (err) {
console.log("Err", err);
setIsAlert({ type: "danger", msg: "Something went wrong." });
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
} finally {
setIsLoader(false);
}
};
//function to save request email template
const handleSaveRequestEmail = async (e) => {
e.preventDefault();
try {
setIsLoader(true);
const replacedHtmlBody = requestBody.replace(/"/g, "'");
const htmlBody = `<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>${replacedHtmlBody}</body></html>`;
const tenantQuery = new Parse.Query("partners_Tenant");
const updateTenantObj = await tenantQuery.get(tenantId);
updateTenantObj.set("RequestBody", htmlBody);
updateTenantObj.set("RequestSubject", requestSubject);
const res = await updateTenantObj.save();
if (res) {
const updateRes = JSON.parse(JSON.stringify(res));
setRequestBody(updateRes?.RequestBody);
setRequestSubject(updateRes?.RequestSubject);
setIsAlert({ type: "success", msg: "Saved successfully." });
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
}
} catch (err) {
console.log("Err", err);
setIsAlert({ type: "danger", msg: "Something went wrong." });
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
} finally {
setIsLoader(false);
}
};
//function to use reset form
const handleReset = (request, completion) => {
if (request) {
setRequestBody(defaultReqHtml?.body);
setRequestSubject(defaultReqHtml?.subject);
} else if (completion) {
setCompletionSubject(defaultCompHtml?.subject);
SetCompletionBody(defaultCompHtml?.body);
}
};
//function for handle ontext change and save again text in delta
const handleOnchangeRequest = () => {
if (editorRef.current) {
const html = editorRef.current.editor.root.innerHTML;
setRequestBody(html);
}
};
const handleOnchangeCompletion = () => {
if (editorRefCom.current) {
const html = editorRefCom.current.editor.root.innerHTML;
SetCompletionBody(html);
}
};
return (
<React.Fragment>
@@ -161,148 +307,315 @@ const Preferences = () => {
<h1 className="ml-4 mt-3 mb-2 font-semibold">
OpenSign {t("Preferences")}{" "}
<span>
<Tooltip
message={`OpenSign™ ${t("Preferences")}`}
isSubscribe={true}
/>
<Tooltip message={`OpenSign™ ${t("Preferences")}`} />
</span>
</h1>
<div className="ml-4 mt-1 mb-2 flex flex-col">
<div className="mb-[0.75rem]">
<label
htmlFor="signaturetype"
className="text-[14px] mb-[0.7rem] font-medium"
>
{t("allowed-signature-types")}
<a data-tooltip-id="signtypes-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<ReactTooltip id="signtypes-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<p className="font-bold">
{t("allowed-signature-types")}
</p>
<p>{t("allowed-signature-types-help.p1")}</p>
<p className="p-[5px] ml-2">
<ol className="list-disc">
<li>
<span className="font-bold">Draw: </span>
<span>
{t("allowed-signature-types-help.l1")}
</span>
</li>
<li>
<span className="font-bold">Typed: </span>
<span>
{t("allowed-signature-types-help.l2")}
</span>
</li>
<li>
<span className="font-bold">Upload: </span>
<span>
{t("allowed-signature-types-help.l3")}
</span>
</li>
<li>
<span className="font-bold">Default: </span>
<span>
{t("allowed-signature-types-help.l4")}
</span>
</li>
</ol>
</p>
</div>
</ReactTooltip>
</label>
<div className=" ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
{signatureType.map((type, i) => (
<div
key={i}
className="flex flex-row gap-[5px] items-center"
>
<input
className="mr-[2px] op-checkbox op-checkbox-xs"
type="checkbox"
name="signaturetype"
onChange={() => handleCheckboxChange(i)}
checked={type.enabled}
/>
<div
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default capitalize"
title={`Enabling this allow signers to ${type.name} signature`}
>
{type.name}
</div>
</div>
))}
</div>
</div>
<div className="mb-[0.75rem]">
<label
className={`${
isSubscribe ? "" : "text-gray-300"
} text-[14px] mb-[0.7rem] font-medium`}
>
{t("notify-on-signatures")}
<a data-tooltip-id="nos-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<ReactTooltip id="nos-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<p className="font-bold">{t("notify-on-signatures")}</p>
<p>{t("notify-on-signatures-help.p1")}</p>
<p>{t("notify-on-signatures-help.note")}</p>
</div>
</ReactTooltip>
{!isSubscribe && isEnableSubscription && <Upgrade />}
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div className="flex justify-center items-center mt-2">
<div
role="tablist"
className="op-tabs op-tabs-bordered op-tabs-sm md:op-tabs-md"
>
{tab.map((tabData, ind) => (
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1`}
onClick={() => setactiveTab(ind)}
key={ind}
role="tab"
className={`${activeTab === ind ? "op-tab-active" : ""} op-tab text-xs md:text-base pb-2 md:pb-0 }`}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(true)}
checked={isNotifyOnSignatures === true}
/>
<div className="text-[13px] font-medium cursor-default capitalize">
{t("yes")}
<i className={tabData.icon}></i>
<span className="ml-1 md:ml-2">{tabData.title}</span>
</div>
))}
</div>
</div>
<div className="flex justify-center md:justify-start mt-3 md:mt-4 break-all">
{activeTab === 0 ? (
<>
<div className="ml-4 mt-1 mb-2 flex flex-col">
<div className="mb-[0.75rem]">
<label
htmlFor="signaturetype"
className="text-[14px] mb-[0.7rem] font-medium"
>
{t("allowed-signature-types")}
<a
data-tooltip-id="signtypes-tooltip"
className="ml-1"
>
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<ReactTooltip
id="signtypes-tooltip"
className="z-[999]"
>
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<p className="font-bold">
{t("allowed-signature-types")}
</p>
<p>{t("allowed-signature-types-help.p1")}</p>
<p className="p-[5px] ml-2">
<ol className="list-disc">
<li>
<span className="font-bold">Draw: </span>
<span>
{t("allowed-signature-types-help.l1")}
</span>
</li>
<li>
<span className="font-bold">Type: </span>
<span>
{t("allowed-signature-types-help.l2")}
</span>
</li>
<li>
<span className="font-bold">Upload: </span>
<span>
{t("allowed-signature-types-help.l3")}
</span>
</li>
<li>
<span className="font-bold">Default: </span>
<span>
{t("allowed-signature-types-help.l4")}
</span>
</li>
</ol>
</p>
</div>
</ReactTooltip>
</label>
<div className=" ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
{signatureType.map((type, i) => (
<div
key={i}
className="flex flex-row gap-[5px] items-center"
>
<input
className="mr-[2px] op-checkbox op-checkbox-xs"
type="checkbox"
name="signaturetype"
onChange={() => handleCheckboxChange(i)}
checked={type.enabled}
/>
<div
className="text-[13px] font-medium hover:underline underline-offset-2 cursor-default capitalize"
title={`Enabling this allow signers to ${type.name} signature`}
>
{type?.name === "typed" ? "type" : type?.name}
</div>
</div>
))}
</div>
</div>
<div className="mb-[0.75rem]">
<label
className={
"text-[14px] mb-[0.7rem] font-medium"
}
>
{t("notify-on-signatures")}
<a data-tooltip-id="nos-tooltip" className="ml-1">
<sup>
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
</sup>
</a>
<ReactTooltip id="nos-tooltip" className="z-[999]">
<div className="max-w-[200px] md:max-w-[450px] text-[11px]">
<p className="font-bold">
{t("notify-on-signatures")}
</p>
<p>{t("notify-on-signatures-help.p1")}</p>
<p>{t("notify-on-signatures-help.note")}</p>
</div>
</ReactTooltip>
</label>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={
"flex items-center gap-2 ml-2 mb-1"
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(true)}
checked={isNotifyOnSignatures === true}
/>
<div className="text-[13px] font-medium cursor-default capitalize">
{t("yes")}
</div>
</div>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={
"flex items-center gap-2 ml-2 mb-1"
}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(false)}
checked={isNotifyOnSignatures === false}
/>
<div className="text-[13px] font-medium cursor-default capitalize">
{t("no")}
</div>
</div>
</div>
</div>
</div>
<div className="mb-[0.75rem]">
<TimezoneSelector
timezone={timezone}
setTimezone={setTimezone}
/>
</div>
<div className="mb-[0.75rem]">
<button
className="op-btn op-btn-primary"
onClick={handleSave}
>
{t("save")}
</button>
</div>
</div>
<div className="flex flex-col md:flex-row md:gap-4">
<div
className={`${
isSubscribe ? "" : "pointer-events-none opacity-50"
} flex items-center gap-2 ml-2 mb-1`}
>
<input
className="mr-[2px] op-radio op-radio-xs"
type="radio"
onChange={() => handleNotifySignChange(false)}
checked={isNotifyOnSignatures === false}
/>
<div className="text-[13px] font-medium cursor-default capitalize">
{t("no")}
</div>
</>
) : (
<div className="flex flex-col mx-4 mb-4">
<div className="flex flex-col">
<h1 className="text-[14px] mb-[0.7rem] font-medium">
{t("request-email")}
</h1>
<div className="relative mt-2 mb-4">
<form
onSubmit={handleSaveRequestEmail}
className="p-3 border-[1px] border-base-content rounded-box"
>
<div className="text-lg font-normal">
<label className="text-sm">
{t("subject")}
<Tooltip
id={"request-sub-tooltip"}
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
/>
</label>
<input
required
value={requestSubject}
onChange={(e) =>
setRequestSubject(e.target.value)
}
placeholder={`{{sender_name}} ${t("send-to-sign")} {{document_title}}`}
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
/>
</div>
<div className="text-lg font-normal py-2">
<label className="text-sm mt-3">
{t("body")}
<Tooltip
id={"request-body-tooltip"}
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
/>
</label>
<EditorToolbar containerId="toolbar1" />
<ReactQuill
theme="snow"
value={requestBody}
placeholder="add body of email"
// onChangeSelection={handleTextSelection} // Listen for text selection
ref={editorRef}
modules={module1}
formats={formats}
onChange={handleOnchangeRequest}
/>
</div>
<div className="flex items-center mt-3 gap-2">
<button
disabled={!requestBody || !requestSubject}
className="op-btn op-btn-primary"
type="submit"
>
{t("save")}
</button>
<button
type="button"
className="op-btn op-btn-secondary"
onClick={() => handleReset("request")}
>
{t("reset")}
</button>
</div>
</form>
</div>
<h1 className="text-[14px] mb-[0.7rem] font-medium">
{t("completion-email")}
</h1>
<div className="relative my-2">
<form
onSubmit={handleSaveCompletionEmail}
className="p-3 border-[1px] border-base-content rounded-box"
>
<div className="text-lg font-normal">
<label className="text-sm">
{t("subject")}
<Tooltip
id={"complete-sub-tooltip"}
message={`${t("variables-use")}:{{sender_name}} {{document_title}}`}
/>
</label>
<input
required
value={completionsubject}
onChange={(e) =>
setCompletionSubject(e.target.value)
}
placeholder={`{{sender_name}} ${t("send-to-sign")} {{document_title}}`}
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
/>
</div>
<div className="text-lg font-normal py-2">
<label className="text-sm mt-3">
{t("body")}
<Tooltip
id={"complete-body-tooltip"}
message={`${t("variables-use")}:{{sender_name}} {{document_title}} {{signing_url}}`}
/>
</label>
<EditorToolbar containerId="toolbar2" />
<ReactQuill
theme="snow"
value={completionBody}
placeholder="add body of email "
// onChangeSelection={handleTextSelection} // Listen for text selection
ref={editorRefCom}
modules={module2}
formats={formats}
onChange={handleOnchangeCompletion}
/>
</div>
<div className="flex items-center mt-3 gap-2">
<button
disabled={!completionBody || !completionsubject}
className="op-btn op-btn-primary"
type="submit"
>
{t("save")}
</button>
<button
type="button"
className="op-btn op-btn-secondary"
onClick={() => handleReset(null, "completion")}
>
{t("reset")}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div className="mb-[0.75rem]">
<button
className="op-btn op-btn-primary"
onClick={handleSave}
>
Save
</button>
</div>
)}
</div>
</div>
)}
+11 -16
View File
@@ -3,7 +3,7 @@ import ReportTable from "../primitives/GetReportDisplay";
import Parse from "parse";
import axios from "axios";
import reportJson from "../json/ReportJson";
import { useParams } from "react-router-dom";
import { useParams } from "react-router";
import Title from "../components/Title";
import PageNotFound from "./PageNotFound";
import TourContentWithBtn from "../primitives/TourContentWithBtn";
@@ -24,7 +24,7 @@ const Report = () => {
const [form, setForm] = useState("");
const [tourData, setTourData] = useState([]);
const [isDontShow, setIsDontShow] = useState(false);
const [isPublic, setIsPublic] = useState({});
const [isImport, setIsImport] = useState(false);
const abortController = new AbortController();
const docPerPage = 10;
@@ -48,7 +48,7 @@ const Report = () => {
// below useEffect call when isNextRecord state is true and fetch next record
useEffect(() => {
if (isNextRecord) {
getReportData(List.length, 200);
getReportData(List.length, 20);
}
// eslint-disable-next-line
}, [isNextRecord]);
@@ -56,7 +56,7 @@ const Report = () => {
const handleDontShow = (isChecked) => {
setIsDontShow(isChecked);
};
const getReportData = async (skipUserRecord = 0, limit = 200) => {
const getReportData = async (skipUserRecord = 0, limit = 20) => {
// setIsLoader(true);
const json = reportJson(id);
if (json) {
@@ -65,6 +65,7 @@ const Report = () => {
setReportName(json.reportName);
setForm(json.form);
setReportHelp(json?.helpMsg);
setIsImport(json?.import || false);
const currentUser = Parse.User.current().id;
const headers = {
@@ -73,8 +74,10 @@ const Report = () => {
sessiontoken: localStorage.getItem("accesstoken")
};
try {
const params = { reportId: id, skip: skipUserRecord, limit: limit };
const url = `${localStorage.getItem("baseUrl")}/functions/getReport`;
const skipRecord = id === "4Hhwbp482K" ? 0 : skipUserRecord;
const limitRecord = id === "4Hhwbp482K" ? 200 : limit;
const params = { reportId: id, skip: skipRecord, limit: limitRecord };
const url = `${localStorage.getItem("baseUrl")}functions/getReport`;
const res = await axios.post(url, params, {
headers: headers,
signal: abortController.signal // is used to cancel fetch query
@@ -145,7 +148,7 @@ const Report = () => {
prevRecord.length > 0 ? [...prevRecord, ...arr] : arr
);
} else {
if (res.data.result.length === docPerPage) {
if (res.data.result.length >= docPerPage) {
setIsMoreDocs(true);
} else {
setIsMoreDocs(false);
@@ -157,13 +160,6 @@ const Report = () => {
? [...prevRecord, ...res.data.result]
: res.data.result
);
const listData = res.data.result;
setIsPublic(
listData.reduce((acc, item) => {
acc[item.objectId] = item?.IsPublic || false;
return acc;
}, {})
);
}
}
setIsLoader(false);
@@ -201,8 +197,7 @@ const Report = () => {
report_help={reporthelp}
tourData={tourData}
isDontShow={isDontShow}
setIsPublic={setIsPublic}
isPublic={isPublic}
isImport={isImport}
/>
) : (
<PageNotFound prefix={"Report"} />
-362
View File
@@ -1,362 +0,0 @@
import React, { useEffect, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import Parse from "parse";
import { appInfo } from "../constant/appinfo";
import { emailRegex, isEnableSubscription } from "../constant/const";
import { fetchSubscription } from "../constant/Utils";
import { useDispatch } from "react-redux";
import { showTenant } from "../redux/reducers/ShowTenant";
import ModalUi from "../primitives/ModalUi";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
const SSOVerify = () => {
const { t } = useTranslation();
const location = useLocation();
const navigate = useNavigate();
const dispatch = useDispatch();
const [isModal, setIsModal] = useState(false);
const [message, setMessage] = useState("Verifying SSO...");
const [isLoader, setIsLoader] = useState(false);
const [userDetails, setUserDetails] = useState({
name: "",
email: "",
phone: "",
Destination: "",
Company: ""
});
useEffect(() => {
linkUserWithSSO();
// eslint-disable-next-line
}, []);
// `linkUserWithSSO` is used to sign in or sign up a user using an SSO code and check if the user is present in the extended class
const linkUserWithSSO = async () => {
const param = new URLSearchParams(location.search);
const code = param?.get("code");
const state = param?.get("state");
try {
// The `ssosign` cloud function is used to sign in or sign up a user
const ssosign = await Parse.Cloud.run("ssosign", {
code: code,
email: state
});
localStorage.setItem("accesstoken", ssosign.sessiontoken);
// `checkExtUser` checks if the user is present in the extended class `contracts_Users` and if not, initiates the new user flow
await checkExtUser(ssosign);
} catch (err) {
setMessage("Error: " + err.message);
console.log("err", err.message);
}
};
const handlePaidRoute = () => {
navigate("/subscription");
};
const checkExtUser = async (ssosign) => {
const params = { email: ssosign?.email };
try {
// `isextenduser` checks if the current user is present in the extended class
const extRes = await Parse.Cloud.run("isextenduser", params);
if (extRes?.isUserExist) {
// `getUserDetails` retrieves the current user's details from the extended class
const extRes = await Parse.Cloud.run("getUserDetails", params);
if (ssosign && ssosign.sessiontoken) {
const LocalUserDetails = {
name: extRes.Name,
email: extRes.email,
phone: extRes?.get("Phone") || "",
company: extRes.get("Company")
};
localStorage.setItem("userDetails", JSON.stringify(LocalUserDetails));
thirdpartyLoginfn(ssosign.sessiontoken);
}
} else {
setIsModal(true);
setUserDetails((prev) => ({
...prev,
name: ssosign.name,
email: ssosign.email,
phone: ssosign?.phone || ""
}));
}
} catch (err) {
console.log("Err in isextenduser or getuserdetails cloud function", err);
setMessage("Error: " + err.message);
}
};
// `handleSubmitbtn` is used to create a user in the extended class
const handleSubmitbtn = async (e) => {
e.preventDefault();
if (!emailRegex.test(userDetails.email)) {
alert("Please enter a valid email address.");
} else {
setIsLoader(true);
let phone;
if (userDetails?.phone) {
phone = validateInput(userDetails?.phone);
} else {
phone = true;
}
if (userDetails.Destination && userDetails.Company && phone) {
const payload = { sessionToken: localStorage.getItem("accesstoken") };
if (payload && payload.sessionToken) {
const params = {
userDetails: {
name: userDetails.name,
email: userDetails.email,
phone: userDetails?.phone || "",
role: "contracts_User",
company: userDetails.Company,
jobTitle: userDetails.Destination
}
};
try {
const userSignUp = await Parse.Cloud.run("usersignup", params);
if (userSignUp && userSignUp.sessionToken) {
const LocalUserDetails = params.userDetails;
localStorage.setItem(
"userDetails",
JSON.stringify(LocalUserDetails)
);
await thirdpartyLoginfn(userSignUp.sessionToken);
setIsLoader(false);
} else {
alert(userSignUp.message);
setIsLoader(false);
}
} catch (err) {
console.log("error in usersignup", err);
localStorage.removeItem("accesstoken");
alert(t("something-went-wrong-mssg"));
setIsLoader(false);
}
} else {
localStorage.removeItem("accesstoken");
alert(t("server-error"));
setIsLoader(false);
}
} else {
alert(t("filed-required-correctly"));
setIsLoader(false);
}
}
};
// `thirdpartyLoginfn` is used to save necessary parameters locally for the logged-in user
const thirdpartyLoginfn = async (sessionToken) => {
try {
const validUser = await Parse.User.become(sessionToken);
if (validUser) {
localStorage.setItem("accesstoken", sessionToken);
const _user = JSON.parse(JSON.stringify(validUser));
localStorage.setItem("UserInformation", JSON.stringify(_user));
if (_user.ProfilePic) {
localStorage.setItem("profileImg", _user.ProfilePic);
} else {
localStorage.setItem("profileImg", "");
}
// Check extended class user role and tenentId
try {
const userSettings = appInfo.settings;
await Parse.Cloud.run("getUserDetails")
.then(async (extUser) => {
if (extUser) {
const IsDisabled = extUser?.get("IsDisabled") || false;
if (!IsDisabled) {
const userRole = extUser?.get("UserRole");
const menu =
userRole &&
userSettings.find((menu) => menu.role === userRole);
if (menu) {
const _currentRole = userRole;
const redirectUrl =
location?.state?.from ||
`/${menu.pageType}/${menu.pageId}`;
const _role = _currentRole.replace("contracts_", "");
localStorage.setItem("_user_role", _role);
const extUser_stringify = JSON.stringify([extUser]);
localStorage.setItem("Extand_Class", extUser_stringify);
const _extUser = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("userEmail", _extUser.Email);
localStorage.setItem("username", _extUser.Name);
localStorage.setItem("scriptId", true);
if (_extUser.TenantId) {
const tenant = {
Id: _extUser?.TenantId?.objectId || "",
Name: _extUser?.TenantId?.TenantName || ""
};
localStorage.setItem("TenantId", tenant?.Id);
dispatch(showTenant(tenant?.Name));
localStorage.setItem("TenantName", tenant?.Name);
}
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
if (isEnableSubscription) {
const res = await fetchSubscription();
const plan = res.plan;
const billingDate = res.billingDate;
if (plan === "freeplan") {
navigate(redirectUrl);
} else if (billingDate) {
if (new Date(billingDate) > new Date()) {
localStorage.removeItem("userDetails");
navigate(redirectUrl);
} else {
handlePaidRoute(plan);
}
} else {
handlePaidRoute(plan);
}
} else {
navigate(redirectUrl);
}
}
} else {
alert(t("do-not-access-contact-admin"));
setMessage(t("do-not-access-contact-admin"));
}
}
})
.catch((error) => {
console.error("Err in fetch extuser", error);
alert(t("user-not-exist"));
setMessage(t("user-not-exist"));
});
} catch (err) {
console.log("err in getuserdetails", err);
}
}
} catch (err) {
console.log("Err in become method", err);
}
};
// `handleCloseModal` is triggered when the user wants to close the new user flow modal
const handleCloseModal = async () => {
setIsModal(false);
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
};
// `validateInput` is used to verify the phone pattern
function validateInput(input) {
if (input) {
const pattern = /^(?!.*\+.*\+)[\d+-]*$/;
return pattern.test(input);
} else {
return true;
}
}
return (
<div>
<div className="w-full h-screen flex flex-col justify-center items-center text-sm md:text-xl ">
{message === "Verifying SSO..." && <Loader />}
<div className="text-base-content">{message}</div>
</div>
<ModalUi isOpen={isModal} title="Additional Info" showClose={false}>
<div className="relative">
{isLoader && (
<div className="absolute w-full h-full bg-black bg-opacity-25 flex justify-center items-center">
<Loader />
</div>
)}
<form
className="px-4 py-3 text-base-content"
onSubmit={handleSubmitbtn}
>
<div className="mb-3">
<label htmlFor="Company" className="block text-xs font-semibold">
{t("phone")} <span className="text-[13px] text-[red]">*</span>
</label>
<input
type="tel"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
id="Phone"
value={userDetails.phone}
onChange={(e) =>
setUserDetails({
...userDetails,
phone: e.target.value
})
}
disabled={isLoader}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<p className="text-[10px] text-[red] ml-2">
{!validateInput(userDetails.phone) && "Invalid phone value"}
</p>
</div>
<div className="mb-3">
<label htmlFor="Company" className="block text-xs font-semibold">
{t("company")} <span className="text-[13px] text-[red]">*</span>
</label>
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
id="Company"
value={userDetails.Company}
onChange={(e) =>
setUserDetails({
...userDetails,
Company: e.target.value
})
}
disabled={isLoader}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="mb-3">
<label htmlFor="JobTitle" className="block text-xs font-semibold">
{t("job-title")}
<span className="text-[13px] text-[red]">*</span>
</label>
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
id="JobTitle"
value={userDetails.Destination}
onChange={(e) =>
setUserDetails({
...userDetails,
Destination: e.target.value
})
}
disabled={isLoader}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
</div>
<div className="mt-4">
<button type="submit" className="op-btn op-btn-primary">
{t("login")}
</button>
<button
type="button"
className="op-btn op-btn-ghost ml-2"
onClick={handleCloseModal}
>
{t("cancel")}
</button>
</div>
</form>
</div>
</ModalUi>
</div>
);
};
export default SSOVerify;
-566
View File
@@ -1,566 +0,0 @@
import React, { useState, useEffect } from "react";
import Parse from "parse";
import axios from "axios";
import Title from "../components/Title";
import { useNavigate, useLocation } from "react-router-dom";
import login_img from "../assets/images/login_img.svg";
import { useWindowSize } from "../hook/useWindowSize";
import Alert from "../primitives/Alert";
import { appInfo } from "../constant/appinfo";
import { useDispatch } from "react-redux";
import { fetchAppInfo } from "../redux/reducers/infoReducer";
import { showTenant } from "../redux/reducers/ShowTenant";
import { emailRegex, isEnableSubscription } from "../constant/const";
import {
getAppLogo,
openInNewTab,
saveLanguageInLocal
} from "../constant/Utils";
import Loader from "../primitives/Loader";
import { useTranslation } from "react-i18next";
import SelectLanguage from "../components/pdf/SelectLanguage";
const Signup = () => {
const { width } = useWindowSize();
const { t, i18n } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const dispatch = useDispatch();
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [password, setPassword] = useState("");
const [email, setEmail] = useState("");
const [company, setCompany] = useState("");
const [jobTitle, setJobTitle] = useState("");
const [image, setImage] = useState();
const [state, setState] = useState({
loading: false,
alertType: "success",
alertMsg: ""
});
const [showPassword, setShowPassword] = useState(false);
const [lengthValid, setLengthValid] = useState(false);
const [caseDigitValid, setCaseDigitValid] = useState(false);
const [specialCharValid, setSpecialCharValid] = useState(false);
const [isAuthorize, setIsAuthorize] = useState(false);
const togglePasswordVisibility = () => setShowPassword(!showPassword);
const clearStorage = async () => {
try {
await Parse.User.logOut();
} catch (err) {
console.log("Err while logging out", err);
}
let baseUrl = localStorage.getItem("baseUrl");
let appid = localStorage.getItem("parseAppId");
let applogo = localStorage.getItem("appLogo");
let defaultmenuid = localStorage.getItem("defaultmenuid");
let PageLanding = localStorage.getItem("PageLanding");
let userSettings = localStorage.getItem("userSettings");
localStorage.clear();
saveLanguageInLocal(i18n);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
localStorage.setItem("appLogo", applogo);
localStorage.setItem("defaultmenuid", defaultmenuid);
localStorage.setItem("PageLanding", PageLanding);
localStorage.setItem("userSettings", userSettings);
localStorage.setItem("baseUrl", baseUrl);
localStorage.setItem("parseAppId", appid);
};
const handleFreePlan = async (id) => {
try {
const params = { userId: id };
const res = await Parse.Cloud.run("freesubscription", params);
if (res.status === "error") {
alert(res.result);
}
} catch (err) {
console.log("err in free subscribe", err.message);
alert(t("something-went-wrong-mssg"));
}
};
const handleSubmit = (event) => {
event.preventDefault();
if (!emailRegex.test(email)) {
alert("Please enter a valid email address.");
} else {
if (lengthValid && caseDigitValid && specialCharValid) {
clearStorage();
setState({ loading: true });
const userDetails = {
name: name,
email: email,
phone: phone,
company: company,
jobTitle: jobTitle
};
localStorage.setItem("userDetails", JSON.stringify(userDetails));
try {
event.preventDefault();
var user = new Parse.User();
user.set("name", name);
user.set("email", email);
user.set("password", password);
user.set("phone", phone);
user.set("username", email);
let res = user.save();
res
.then(async (r) => {
if (r) {
let roleData = appInfo.settings;
if (roleData && roleData.length > 0) {
const params = {
userDetails: {
jobTitle: jobTitle,
company: company,
name: name,
email: email,
phone: phone,
role: appInfo.defaultRole
}
};
try {
const usersignup = await Parse.Cloud.run(
"usersignup",
params
);
if (usersignup) {
const param = new URLSearchParams(location.search);
const isFreeplan =
param?.get("subscription") === "freeplan";
if (isFreeplan) {
await handleFreePlan(r.id);
}
handleNavigation(r.getSessionToken(), isFreeplan);
}
} catch (err) {
alert(err.message);
setState({ loading: false });
}
}
}
})
.catch(async (err) => {
if (err.code === 202) {
const params = { email: email };
const res = await Parse.Cloud.run("getUserDetails", params);
// console.log("Res ", res);
if (res) {
alert(t("user-already-exist-name"));
setState({ loading: false });
} else {
// console.log("state.email ", email);
try {
await Parse.User.requestPasswordReset(email).then(
async function (res1) {
if (res1.data === undefined) {
alert(t("verification-code-sent"));
}
}
);
} catch (err) {
console.log(err);
}
setState({ loading: false });
}
} else {
alert(err.message);
setState({ loading: false });
}
});
} catch (error) {
console.log("err ", error);
}
}
}
};
const handleNavigation = async (sessionToken, isFreeplan = false) => {
const baseUrl = localStorage.getItem("baseUrl");
const parseAppId = localStorage.getItem("parseAppId");
const res = await axios.get(baseUrl + "users/me", {
headers: {
"X-Parse-Session-Token": sessionToken,
"X-Parse-Application-Id": parseAppId
}
});
await Parse.User.become(sessionToken).then(() => {
window.localStorage.setItem("accesstoken", sessionToken);
});
// console.log("me res ", res);
if (res.data) {
let _user = res.data;
localStorage.setItem("UserInformation", JSON.stringify(_user));
localStorage.setItem("accesstoken", _user.sessionToken);
localStorage.setItem("scriptId", true);
if (_user.ProfilePic) {
localStorage.setItem("profileImg", _user.ProfilePic);
} else {
localStorage.setItem("profileImg", "");
}
// Check extended class user role and tenentId
try {
const userSettings = appInfo.settings;
await Parse.Cloud.run("getUserDetails")
.then(async (extUser) => {
if (extUser) {
const IsDisabled = extUser?.get("IsDisabled") || false;
if (!IsDisabled) {
const userRole = extUser?.get("UserRole");
const menu =
userRole &&
userSettings.find((menu) => menu.role === userRole);
if (menu) {
const _currentRole = userRole;
const _role = _currentRole.replace("contracts_", "");
localStorage.setItem("_user_role", _role);
const extInfo_stringify = JSON.stringify([extUser]);
localStorage.setItem("Extand_Class", extInfo_stringify);
const extInfo = JSON.parse(JSON.stringify(extUser));
localStorage.setItem("userEmail", extInfo?.Email);
localStorage.setItem("username", extInfo?.Name);
if (extInfo?.TenantId) {
const tenant = {
Id: extInfo?.TenantId?.objectId || "",
Name: extInfo?.TenantId?.TenantName || ""
};
localStorage.setItem("TenantId", tenant?.Id);
dispatch(showTenant(tenant?.Name));
localStorage.setItem("TenantName", tenant?.Name);
}
localStorage.setItem("PageLanding", menu.pageId);
localStorage.setItem("defaultmenuid", menu.menuId);
localStorage.setItem("pageType", menu.pageType);
setState({ loading: false });
if (isEnableSubscription) {
if (isFreeplan) {
navigate(`/${menu.pageType}/${menu.pageId}`);
} else {
navigate(`/subscription`, { replace: true });
}
} else {
alert(t("registered-user-successfully"));
navigate(`/${menu.pageType}/${menu.pageId}`);
}
} else {
setState({
loading: false,
alertType: "danger",
alertMsg: t("role-not-found")
});
setTimeout(() => {
setState({ loading: false, alertMsg: "" });
}, 2000);
}
} else {
setState({
loading: false,
alertType: "danger",
alertMsg: t("do-not-access-contact-admin")
});
setTimeout(() => {
setState({ loading: false, alertMsg: "" });
}, 2000);
}
}
})
.catch((error) => {
console.log("error in fetch extuser", error);
setState({
loading: false,
alertType: "danger",
alertMsg: t("do-not-access-contact-admin")
});
setTimeout(() => {
setState({ loading: false, alertMsg: "" });
}, 2000);
});
} catch (error) {
// alert(`${error.message}`);
setState({
loading: false,
alertType: "danger",
alertMsg: `${error.message}`
});
setTimeout(() => {
setState({ loading: false, alertMsg: "" });
}, 2000);
console.log(error);
}
}
};
useEffect(() => {
dispatch(fetchAppInfo());
saveLogo();
// eslint-disable-next-line
}, []);
const saveLogo = async () => {
if (isEnableSubscription) {
const app = await getAppLogo();
if (app?.logo) {
setImage(app?.logo);
} else {
setImage(appInfo?.applogo || undefined);
}
} else {
setImage(appInfo?.applogo || undefined);
}
};
const handlePasswordChange = (e) => {
const newPassword = e.target.value;
setPassword(newPassword);
// Check conditions separately
setLengthValid(newPassword.length >= 8);
setCaseDigitValid(
/[a-z]/.test(newPassword) &&
/[A-Z]/.test(newPassword) &&
/\d/.test(newPassword)
);
setSpecialCharValid(/[!@#$%^&*()\-_=+{};:,<.>]/.test(newPassword));
};
return (
<div className="">
{state.loading && (
<div className="fixed w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-50">
<Loader />
</div>
)}
<Title title="Signup page" />
{appInfo && appInfo.applogo ? (
<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">
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
{image && (
<img src={image} className="object-contain h-full" alt="logo" />
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
<div>
<form onSubmit={handleSubmit}>
<h2 className="text-[30px] mt-6">{t("create-account")}!</h2>
<div className="w-full my-4 op-card bg-base-100 shadow-md outline outline-1 outline-slate-300/50">
<div className="px-6 py-4 text-xs">
<label className="block ">
{t("name")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={name}
onChange={(e) => setName(e.target.value)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<hr className="my-2 border-none" />
<label>
{t("email")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<input
id="email"
type="email"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={email}
onChange={(e) =>
setEmail(
e.target.value?.toLowerCase()?.replace(/\s/g, "")
)
}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<hr className="my-2 border-none" />
<label>
{t("phone")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<input
type="tel"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={phone}
onChange={(e) => setPhone(e.target.value)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<hr className="my-2 border-none" />
<label>
{t("company")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={company}
onChange={(e) => setCompany(e.target.value)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<hr className="my-2 border-none" />
<label>
{t("job-title")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<input
type="text"
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
value={jobTitle}
onChange={(e) => setJobTitle(e.target.value)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<hr className="my-2 border-none" />
<label>
{t("password")}{" "}
<span className="text-[red] text-[13px]">*</span>
</label>
<div className="relative">
<input
type={showPassword ? "text" : "password"}
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
name="password"
value={password}
onChange={(e) => handlePasswordChange(e)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<span
className={`absolute top-[50%] right-[10px] -translate-y-[50%] cursor-pointer text-base-content`}
onClick={togglePasswordVisibility}
>
{showPassword ? (
<i className="fa-light fa-eye-slash" /> // Close eye icon
) : (
<i className="fa-light fa-eye" /> // Open eye icon
)}
</span>
</div>
{password.length > 0 && (
<div className="mt-1 text-[11px]">
<p
className={`${
lengthValid ? "text-green-600" : "text-red-600"
}`}
>
{lengthValid ? "✓" : "✗"}
{t("password-length")}
</p>
<p
className={`${
caseDigitValid ? "text-green-600" : "text-red-600"
}`}
>
{caseDigitValid ? "✓" : "✗"}
{t("password-case")}
</p>
<p
className={`${
specialCharValid
? "text-green-600"
: "text-red-600"
}`}
>
{specialCharValid ? "✓" : "✗"}{" "}
{t("password-special-char")}
</p>
</div>
)}
<div className="mt-2.5 ml-1 flex flex-row items-center">
<input
type="checkbox"
className="op-checkbox op-checkbox-sm"
id="termsandcondition"
checked={isAuthorize}
onChange={(e) => setIsAuthorize(e.target.checked)}
onInvalid={(e) =>
e.target.setCustomValidity(t("input-required"))
}
onInput={(e) => e.target.setCustomValidity("")}
required
/>
<label
className="text-xs cursor-pointer ml-1 mb-0"
htmlFor="termsandcondition"
>
{t("agreee")}
</label>
<span
className="underline cursor-pointer ml-1"
onClick={() =>
openInNewTab(
"https://www.opensignlabs.com/terms-and-conditions"
)
}
>
{t("term")}
</span>
<span>.</span>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-center text-xs font-bold mt-2">
<button
type="submit"
className="op-btn op-btn-primary"
disabled={state.loading}
>
{state.loading ? t("loading") : t("register")}
</button>
<button
type="button"
className="op-btn op-btn-secondary"
disabled={state.loading}
onClick={() =>
navigate(location.search ? "/" + location.search : "/")
}
>
{t("login")}
</button>
</div>
</form>
</div>
{width >= 768 && (
<div className="self-center">
<div className="mx-auto md:w-[300px] lg:w-[400px] xl:w-[500px]">
<img src={login_img} alt="loader" width="100%" />
</div>
</div>
)}
</div>
</div>
<SelectLanguage />
<Alert type={state.alertType}>{state.alertMsg}</Alert>
</div>
) : (
<div className="fixed w-full h-full flex justify-center items-center z-50">
<Loader />
</div>
)}
</div>
);
};
export default Signup;

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