mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-04 08:48:55 +02:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec68770ff0 | ||
|
|
f5d2027e3a | ||
|
|
8af0010f55 | ||
|
|
d7b7ad6285 | ||
|
|
fca45ced38 | ||
|
|
ac8ab70246 | ||
|
|
8373679f27 | ||
|
|
0a23aabbe4 | ||
|
|
4a85599703 | ||
|
|
3e9b84de72 | ||
|
|
4313d8d0e4 | ||
|
|
3088fdd163 | ||
|
|
8c86fe27ec | ||
|
|
01e09998e5 | ||
|
|
55fdb52bf3 | ||
|
|
08f46a393b | ||
|
|
9345da543a | ||
|
|
208891e10d | ||
|
|
db0fa61b2b | ||
|
|
221f098de6 | ||
|
|
9658761ede | ||
|
|
6aad832e37 | ||
|
|
e4b6be51d9 | ||
|
|
a3b2c797fb | ||
|
|
22d27abb76 | ||
|
|
45e0679ca7 | ||
|
|
2b2aa20deb | ||
|
|
8d6ed3d2b5 | ||
|
|
6064f66cdf | ||
|
|
0ba29eb799 | ||
|
|
d0dd571478 | ||
|
|
ddb63d81a7 | ||
|
|
f78451d3f9 | ||
|
|
28630e2d23 | ||
|
|
c5022eaee6 | ||
|
|
1b7f07a2c4 | ||
|
|
2484c67406 | ||
|
|
abfc3f32f2 | ||
|
|
5c06ff4242 | ||
|
|
1899acfffc | ||
|
|
796ff058c8 | ||
|
|
c88cb5357c | ||
|
|
a6a0325d4b | ||
|
|
b18d17c44d | ||
|
|
a73779e74b | ||
|
|
5b943c1965 | ||
|
|
f73844d730 | ||
|
|
aaadab540a | ||
|
|
83bc466df4 | ||
|
|
d85ed954f1 | ||
|
|
369deb6e26 | ||
|
|
295d1af910 | ||
|
|
080d7c08dc | ||
|
|
c877f9fc5d | ||
|
|
99c7a58d62 | ||
|
|
97642bf857 | ||
|
|
2f67ac7b0a | ||
|
|
23aecb45d9 | ||
|
|
24a95e8852 |
+4
-3
@@ -8,13 +8,11 @@ PUBLIC_URL=https://localhost:3001
|
||||
GENERATE_SOURCEMAP=false
|
||||
# Set it to the URL from where APIs will be accessible, for local development it should be localhost:3000/api/app (use your local port number instead)
|
||||
# REACT_APP_SERVERURL=http://localhost:8080/app
|
||||
# A 12 character long random app identifier. The value of this should be same as APP_ID which is a variable used by backend API.
|
||||
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
|
||||
REACT_APP_APPID=opensign
|
||||
|
||||
|
||||
# Backend ExpressJS config ****************************************************************************************************************************************************************************************
|
||||
# A 12 character long random app identifier. The value of this should be same as REACT_APP_APPID which is a variable used by Frontend React App.
|
||||
APP_ID=opensign
|
||||
# Name of the app. It will be visible in the verification emails sent out.
|
||||
appName=open_sign_server
|
||||
# A 12 character long random secret key that allows access to all the data. It is used in Parse dashboard config to view all the data in the database.
|
||||
@@ -111,3 +109,6 @@ CRUxFgQUDYlgGVxSxuOknhQc256x3++7BDwwMTAhMAkGBSsOAwIaBQAEFFjASdYl
|
||||
|
||||
# Provide Pass pharse of above PFX or p12 document
|
||||
PASS_PHRASE=opensign
|
||||
|
||||
# (DEPRECATED) This should not be changed if provided; it should be 'opensign'.
|
||||
APP_ID=opensign
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{$HOST_URL} {
|
||||
reverse_proxy client:3000
|
||||
handle_path /app/* {
|
||||
handle_path /api/* {
|
||||
reverse_proxy server:8080
|
||||
rewrite * /app{uri}
|
||||
rewrite * {uri}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
|
||||
|
||||
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
|
||||
```
|
||||
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/docker_beta/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
|
||||
export HOST_URL=https://opensign.yourdomain.com && curl --remote-name-all https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && mv .env.local_dev .env.prod && docker compose up --force-recreate
|
||||
```
|
||||
Make sure that you have `Docker` and `git` installed before you run this command -
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:18
|
||||
FROM node:22.14.0
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
@@ -16,8 +16,7 @@ COPY apps/OpenSign/.husky .
|
||||
|
||||
# Define environment variables if needed
|
||||
ENV NODE_ENV=production
|
||||
ENV REACT_APP_DEPLOYMENT=free_selfhost
|
||||
|
||||
ENV GENERATE_SOURCEMAP=false
|
||||
# build
|
||||
RUN npm run build
|
||||
|
||||
|
||||
Generated
+269
-789
File diff suppressed because it is too large
Load Diff
+23
-23
@@ -4,17 +4,17 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@lottiefiles/dotlottie-react": "^0.13.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.13.2",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.1.6",
|
||||
"@react-pdf/renderer": "^4.1.6",
|
||||
"@reduxjs/toolkit": "^2.5.1",
|
||||
"axios": "^1.7.9",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"axios": "^1.8.4",
|
||||
"css-minimizer-webpack-plugin": "^7.0.2",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^23.16.8",
|
||||
"i18next-browser-languagedetector": "^8.0.2",
|
||||
"i18next-http-backend": "^3.0.1",
|
||||
"i18next-browser-languagedetector": "^8.0.4",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
@@ -25,7 +25,7 @@
|
||||
"radix-ui": "^1.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.10.9",
|
||||
"react-confetti": "^6.2.2",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-cookie": "^7.2.2",
|
||||
"react-datepicker": "^7.6.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
@@ -35,16 +35,16 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.4.0",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-quill-new": "^3.3.3",
|
||||
"react-quill-new": "^3.4.6",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-rnd": "^10.4.14",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.1.5",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.10.0",
|
||||
"react-select": "^5.10.1",
|
||||
"react-signature-canvas": "^1.0.7",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-timezone-select": "^3.2.8",
|
||||
@@ -53,11 +53,11 @@
|
||||
"reactour": "^1.19.4",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"regex-parser": "^2.3.0",
|
||||
"regex-parser": "^2.3.1",
|
||||
"serve": "^14.2.4",
|
||||
"styled-components": "^5.3.0",
|
||||
"web-vitals": "^4.2.4",
|
||||
"ws": "^8.18.0",
|
||||
"ws": "^8.18.1",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -96,26 +96,26 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.9",
|
||||
"@babel/core": "^7.26.10",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
"@babel/runtime-corejs2": "^7.26.9",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"babel-loader": "^9.2.1",
|
||||
"@babel/runtime-corejs2": "^7.27.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"babel-loader": "^10.0.0",
|
||||
"commitizen": "^4.3.1",
|
||||
"concurrently": "^9.1.2",
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.23",
|
||||
"dotenv": "^16.4.7",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"eslint": "^9.20.0",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-plugin-prettier": "^5.2.5",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"lint-staged": "^15.4.3",
|
||||
"lint-staged": "^15.5.0",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"postcss": "^8.5.1",
|
||||
"prettier": "^3.5.0",
|
||||
"pretty-quick": "^4.0.0",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-quick": "^4.1.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"terser-webpack-plugin": "^5.3.11",
|
||||
"webpack-cli": "^5.1.4"
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"Storage": "Speicher",
|
||||
"Signing certificate": "Signierzertifikat",
|
||||
"Teams": "Teams",
|
||||
"General": "Allgemein",
|
||||
"Teams-Children": {
|
||||
"Organizations": "Organisationen",
|
||||
"OrgAdmins": "OrgAdmins"
|
||||
@@ -149,7 +150,9 @@
|
||||
"Copy Public URL": "Öffentliche URL kopieren",
|
||||
"extend-expiry-date": "Ablaufdatum verlängern",
|
||||
"Duplicate Template": "Vorlage duplizieren",
|
||||
"Duplicate": "Duplikat"
|
||||
"Duplicate": "Duplikat",
|
||||
"daily-mail-quota": "Tägliches E-Mail-Kontingent",
|
||||
"Save as template": "Als Vorlage speichern"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nr.",
|
||||
@@ -169,7 +172,7 @@
|
||||
"Name": "Name",
|
||||
"Status": "Status",
|
||||
"created-date": "Erstellungsdatum",
|
||||
"Type": "Typ",
|
||||
"Type": "Type",
|
||||
"Logs": "Protokolle",
|
||||
"Expiry-date": "Ablaufdatum"
|
||||
},
|
||||
@@ -245,7 +248,7 @@
|
||||
"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.",
|
||||
"something-went-wrong-mssg": "Etwas ist schiefgelaufen, Das Aktualisieren dieser Seite kann das Problem lösen.",
|
||||
"token-generated": "Token erfolgreich generiert.",
|
||||
"webhook": "Webhook",
|
||||
"update-webhook": "Webhook aktualisieren",
|
||||
@@ -281,6 +284,7 @@
|
||||
"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-non": "Sind Sie sicher, dass Sie diese Vorlage privat 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",
|
||||
@@ -306,7 +310,7 @@
|
||||
"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}}.",
|
||||
"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}}, {{note}}.",
|
||||
"subject": "Betreff",
|
||||
"body": "Inhalt",
|
||||
"add-contact": "Kontakt hinzufügen",
|
||||
@@ -456,6 +460,7 @@
|
||||
"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-mail-alert-you": "Nachfolgende Unterzeichner erhalten E-Mails, sobald Sie das Dokument unterschreiben.",
|
||||
"placeholder-alert-5": "Möchten Sie die Dokumente jetzt unterschreiben?",
|
||||
"placeholder-alert-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!",
|
||||
@@ -519,7 +524,7 @@
|
||||
"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!",
|
||||
"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",
|
||||
@@ -604,6 +609,7 @@
|
||||
"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.",
|
||||
"report-1":"Klicken Sie auf die Schaltfläche „Hinzufügen“, um eine neue Vorlage zu erstellen. Vorlagen sind wiederverwendbare Dokumente, mit denen schnell neue Dokumente mit derselben Struktur und unterschiedlichen Unterzeichnern erstellt werden können. Eine HR-Vorlage für die Einarbeitung könnte beispielsweise vordefinierte Rollen wie „Personalleiter“ und „Neuer Mitarbeiter“ enthalten. Bei jeder Verwendung der Vorlage können Sie die Rolle „Neuer Mitarbeiter“ verschiedenen neuen Mitarbeitern zuweisen, während die Rolle „Personalleiter“ unverändert bleibt. So wird ein nahtloser Einarbeitungsprozess für jeden neuen Mitarbeiter ermöglicht.",
|
||||
"redirect": "Klicken Sie auf die Schaltfläche 'Verwenden', um ein neues Dokument aus einer bestehenden Vorlage zu erstellen.",
|
||||
"bulksend": "Um schnell mehrere Dokumente mithilfe einer vorhandenen Vorlage zu versenden, indem Sie einfach die E-Mail-Adressen der Empfänger erstellen, klicken Sie auf die Schaltfläche ‚Massenversand‘",
|
||||
"option": "Dieses Menü zeigt weitere Optionen wie Bearbeiten und Löschen. Verwenden Sie die Schaltfläche 'Bearbeiten', um Unterzeichnerrollen hinzuzufügen, Felder zu ändern und Ihre Vorlage zu aktualisieren. Änderungen gelten für alle zukünftigen Dokumente, die aus dieser Vorlage erstellt werden, wirken sich jedoch nicht auf vorhandene Dokumente aus. Verwenden Sie die Schaltfläche 'Löschen', um die Vorlage zu entfernen.",
|
||||
@@ -828,7 +834,7 @@
|
||||
"initial-type": "Ihre Initialen",
|
||||
"redirect-url": "Weiterleitungs-URL",
|
||||
"bulk-send": "Massenversand",
|
||||
"select-timezone": "Wählen Sie Ihre Zeitzone",
|
||||
"select-timezone": "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.",
|
||||
@@ -847,5 +853,33 @@
|
||||
"agreement-note": "Hinweis: Durch Ihre Zustimmung unterzeichnen Sie das Dokument nicht sofort. Sie können das Dokument nur elektronisch einsehen. Sie haben die Möglichkeit, es vollständig zu lesen und anschließend zu entscheiden, ob Sie es unterzeichnen möchten.",
|
||||
"draft-template-info-p1": "Um Ihre Vorlage öffentlich zu machen, muss sie entweder eine einzelne Rolle enthalten oder, wenn sie mehrere Rollen umfasst, müssen alle zusätzlichen Rollen bereits den Unterzeichnern zugewiesen sein. Die nicht zugewiesene öffentliche Rolle muss leer bleiben und an erster Stelle stehen.",
|
||||
"visit-below-link": "Besuchen Sie den untenstehenden Link, um mehr zu erfahren -",
|
||||
"storage-help": "Durch die Aktivierung von BYOC können Sie Ihren eigenen S3-Speicher verbinden, sodass Ihre Dateien vollständig unter Ihrer Kontrolle bleiben und keine externen Kopien gespeichert werden. Wenn Ihnen Datenautonomie wichtig ist, erwägen Sie ein Upgrade auf Teams, um diese Funktion freizuschalten."
|
||||
"storage-help": "Durch die Aktivierung von BYOC können Sie Ihren eigenen S3-Speicher verbinden, sodass Ihre Dateien vollständig unter Ihrer Kontrolle bleiben und keine externen Kopien gespeichert werden. Wenn Ihnen Datenautonomie wichtig ist, erwägen Sie ein Upgrade auf Teams, um diese Funktion freizuschalten.",
|
||||
"daily-quota-reached": "Sie haben Ihr tägliches Kontingent erreicht. Für Unterstützung kontaktieren Sie bitte quotas@opensignlabs.com.",
|
||||
"enabled-signature-type": "Aktivierte Signaturtypen",
|
||||
"enabled-signature-type-help": "Die Einstellung 'Aktivierte Signaturtypen' bestimmt, welche Signaturoptionen in Ihrer Organisation verfügbar sind. Wenn Sie beispielsweise die Option 'Zeichnen' deaktivieren, wird sie den Mitgliedern Ihrer Organisation im Signatur-Widget nicht angezeigt, während die anderen drei Optionen weiterhin zugänglich bleiben.",
|
||||
"indexing-public-profile": "Erlaube die Indexierung des öffentlichen Profils durch Suchmaschinen",
|
||||
"user-created-successfully": "Benutzer erfolgreich erstellt.",
|
||||
"only-15-reminder-allowed": "Sie können bis zu 15 automatische Erinnerungen festlegen. Wenn zum Beispiel 'TimeToComplete' auf 15 Tage und 'RemindOnceInEvery' auf 1 Tag eingestellt ist, erreichen Sie das maximale Limit von 15 Erinnerungen. Passen Sie Ihre Einstellungen entsprechend an.",
|
||||
"rate-your-experience": "Wie war Ihre Erfahrung mit {{appName}}?",
|
||||
"thanks-for-feedback": "Danke für Ihr Feedback 🙏",
|
||||
"share-your-feedback": "Teilen Sie Ihr Feedback",
|
||||
"share-your-review": "Teilen Sie Ihre Bewertung",
|
||||
"date-format": "Datumsformat",
|
||||
"document-deleted": "Das Dokument wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
|
||||
"save-as-template-?": "Sind Sie sicher, dass Sie dieses Dokument als Vorlage speichern möchten?",
|
||||
"go-to-manage-templates": "Zu 'Vorlagen verwalten' gehen",
|
||||
"template-created": "Vorlage erstellt",
|
||||
"how-would-you-like-to-proceed?": "Wie möchten Sie fortfahren?",
|
||||
"failed-to-load-refresh-page": "Fehler beim Laden des Dokuments. Bitte versuchen Sie, diese Seite zu aktualisieren.",
|
||||
"document-has-been-signed": "Das Dokument wurde erfolgreich unterschrieben!",
|
||||
"document-has-been-signed-by-you": "Das Dokument wurde erfolgreich von Ihnen unterschrieben!",
|
||||
"participant-completed-signing": "Alle Teilnehmer haben den Signaturprozess abgeschlossen.",
|
||||
"you-will-receive-email-shortly": "✅ Das war's! Sie erhalten in Kürze eine Bestätigungs-E-Mail.",
|
||||
"please-provide-templateid": "Bitte geben Sie templateid an",
|
||||
"this-template-is-not-public": "Dieses template ist nicht öffentlich",
|
||||
"invalid-templateid": "Ungültige templateid",
|
||||
"contact-billing-at-opensign": "Um weitere Plätze hinzuzufügen, kontaktieren Sie bitte OpenSign™ unter <1>billing@opensignlabs.com</1> für Unterstützung.",
|
||||
"title-length-alert": "Der Titel darf höchstens 250 Zeichen lang sein.",
|
||||
"note-length-alert": "Die Notiz darf höchstens 200 Zeichen lang sein.",
|
||||
"description-length-alert": "Die Beschreibung darf höchstens 500 Zeichen lang sein."
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"Storage": "Storage",
|
||||
"Signing certificate": "Signing certificate",
|
||||
"Teams": "Teams",
|
||||
"General": "General",
|
||||
"Teams-Children": {
|
||||
"Organizations": "Organizations",
|
||||
"OrgAdmins": "OrgAdmins"
|
||||
@@ -149,7 +150,9 @@
|
||||
"Copy Public URL": "Copy public URL",
|
||||
"extend-expiry-date": "Extend expiry date",
|
||||
"Duplicate Template": "Duplicate template",
|
||||
"Duplicate": "Duplicate"
|
||||
"Duplicate": "Duplicate",
|
||||
"daily-mail-quota": "Daily Email Quota",
|
||||
"Save as template": "Save as template"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Sr.No",
|
||||
@@ -245,7 +248,7 @@
|
||||
"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.",
|
||||
"something-went-wrong-mssg": "Something went wrong, refreshing this page may solve this issue.",
|
||||
"token-generated": "Token generated successfully.",
|
||||
"webhook": "Webhook",
|
||||
"update-webhook": "Update webhook",
|
||||
@@ -281,11 +284,12 @@
|
||||
"make-template-public": "Make template public",
|
||||
"make-template-private": "Make template private",
|
||||
"make-template-public-alert": "Are you sure you want to make this template public?",
|
||||
"make-template-private-alert-non": "Are you sure you want to make this template private?",
|
||||
"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",
|
||||
"public-url-copy": "Here’s your public URL: ",
|
||||
"public-url-copy": "Here's 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": "You can generate your {{appName}} 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.",
|
||||
@@ -306,12 +310,12 @@
|
||||
"revoke-document": "Revoke document",
|
||||
"revoke-document-alert": "Are you sure you want to revoke this document?",
|
||||
"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}}.",
|
||||
"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}}, {{note}}.",
|
||||
"subject": "Subject",
|
||||
"body": "Body",
|
||||
"add-contact": "Add contact",
|
||||
"edit-contact": "Edit contact",
|
||||
"add-signer-alert": "Contact already exist! Please select it from ‘Signers’ dropdown",
|
||||
"add-signer-alert": "Contact already exist! Please select it from 'Signers' dropdown",
|
||||
"record-delete-alert": "Record deleted successfully!",
|
||||
"record-revoke-alert": "Record revoked successfully!",
|
||||
"mail-sent-alert": "Mail sent successfully.",
|
||||
@@ -456,6 +460,7 @@
|
||||
"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-mail-alert": "You have successfully sent email to {{name}}. Subsequent signers will get email(s) once {{name}} signs the document",
|
||||
"placeholder-mail-alert-you": "Subsequent signers will get email(s) once you 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!",
|
||||
@@ -519,7 +524,7 @@
|
||||
"correct-password": "Please provide correct password",
|
||||
"decrypting-pdf": " Decrypting pdf please wait...",
|
||||
"invalid-otp": "Invalid otp",
|
||||
"user-not-found": "User not found!",
|
||||
"user-not-found": "User not found",
|
||||
"enter-otp-alert": "Please enter OTP!",
|
||||
"get-verification-code": "Get verification code",
|
||||
"get-verification-code-2": "You will get a verification code via Email",
|
||||
@@ -582,7 +587,7 @@
|
||||
},
|
||||
"tour-mssg": {
|
||||
"home-layout-1": "You have logged in successfully! Let's take a look.",
|
||||
"home-layout-2": "To upload documents for self-signing or to request others’ signatures, simply select the respective buttons.",
|
||||
"home-layout-2": "To upload documents for self-signing or to request others' signatures, simply select the respective buttons.",
|
||||
"home-layout-3": "You are ready to start using {{appName}}! If you need support feel free to contact us.",
|
||||
"generate-token": "Upgrade now to generate production API token.",
|
||||
"opensign-drive-1": "Click on the breadcrumb links to easily navigate through the folder hierarchy and view the documents within each folder.",
|
||||
@@ -591,7 +596,7 @@
|
||||
"opensign-drive-4": "Click on this menu to display the documents in list view.",
|
||||
"opensign-drive-5": "The document list is displayed according to the selected sorting option. Icons next to each document indicate its current status.",
|
||||
"opensign-drive-6": "Right-click on a document to see options such as Download, Rename, Move, and Delete. Click on the document to open it.",
|
||||
"opensign-drive-7": "Right-click on any folder to see options. Choose ‘Rename’ to change the folder’s name or click on the folder to navigate through its contents.",
|
||||
"opensign-drive-7": "Right-click on any folder to see options. Choose 'Rename' to change the folder's name or click on the folder to navigate through its contents.",
|
||||
"pdf-request-file-1": "List of signers who still need to sign the document .",
|
||||
"pdf-request-file-2": "Click any of the placeholders appearing on the document to sign. You will then see options to draw your signature, type it, or upload an image .",
|
||||
"pdf-request-file-3": "Click Decline, or Finish buttons to navigate your document. Use the ellipsis menu for additional options, including the Download button .",
|
||||
@@ -604,24 +609,24 @@
|
||||
"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 '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. ",
|
||||
"report-1": "Click the 'Add' button to create a new template. Templates are reusable documents designed to quickly generate new documents with the same structure and varying signers. For example, an HR template for onboarding could have predefined roles like 'HR Manager' and 'New Employee'. Each time you use the template, you can assign the 'New Employee' role to different incoming staff members, while the 'HR Manager' role remains constant, facilitating a seamless onboarding process for each recruit. ",
|
||||
"redirect": "Click the 'Use' button to create a new document from an existing template.",
|
||||
"bulksend": "To quickly send multiple documents using an existing template by just creating the recipient email addresses, click the 'Bulk Send' button.",
|
||||
"option": "This menu reveals more options such as Edit & Delete. Use the 'Edit' button to add signer roles, modify fields, and update your template. Changes will apply to all future documents created from this template but won’t affect existing documents.Use the Delete button you can delete template. ",
|
||||
"option": "This menu reveals more options such as Edit & Delete. Use the 'Edit' button to add signer roles, modify fields, and update your template. Changes will apply to all future documents created from this template but won't affect existing documents.Use the Delete button you can delete template. ",
|
||||
"signyour-self-1": "Select and drag your preferred widgets onto the PDF to customize your document before signing. Choose the perfect spots for each modification to tailor the document to your needs.",
|
||||
"signyour-self-2": "Drag and drop anywhere in this area. You can resize and move it later.",
|
||||
"template-placeholder-1": "Clicking 'Add role' button will allow you to add various signer roles. You can attach users to each role in subsequent steps.",
|
||||
"template-placeholder-2": "Once roles are added, select a role from list to add a place-holder where he is supposed to sign. The placeholder will appear in the same colour as the role name once you drop it on the document.",
|
||||
"template-placeholder-3": "Drag or click on a field to add it to the document.",
|
||||
"template-placeholder-4": "Drag the placeholder for a role anywhere on the document.Remember, it will appear in the same colour as the name of the recipient for easy reference.",
|
||||
"template-placeholder-5": "Clicking 'Next' will store the current template. After saving, you’ll be prompted to create a new document from this template if you wish.",
|
||||
"template-placeholder-5": "Clicking 'Next' will store the current template. After saving, you'll be prompted to create a new document from this template if you wish.",
|
||||
"webhook-1": "Upgrade now to set webhook",
|
||||
"Need your Signature": "Clicking on this card will take you to the list of documents awaiting your review.",
|
||||
"Out for signatures": "Clicking on this card will take you to a list of documents awaiting signature.",
|
||||
"Recent signature requests": "This is a list of documents that are waiting for your signature.",
|
||||
"Recently sent for signatures": "This is a list of documents you've sent to other parties for signature.",
|
||||
"Drafts": "This are documents you have started but have not finalized for sending.",
|
||||
"public-template": "This video demonstrates how to set up your personalized public profile, such as ‘https://opensign.me/your-username’. You’ll also learn how to customize your tagline and make your templates available for public signing.",
|
||||
"public-template": "This video demonstrates how to set up your personalized public profile, such as 'https://opensign.me/your-username'. You'll also learn how to customize your tagline and make your templates available for public signing.",
|
||||
"allowModify-widgets": "You can drag and drop any of these fields onto the document, in addition to the fields already designated for you by the document creator."
|
||||
},
|
||||
"enter-email-plaholder": "Add an email address and hit enter",
|
||||
@@ -655,13 +660,13 @@
|
||||
"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 you’ve completed your testing, you can upgrade to one of our paid plans to generate a production 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 you've 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 you’re using Yarn, you can replace npm install with yarn add @opensign/react.",
|
||||
"public-template-mssg-2": "Ensure you have npm or yarn set up in your project. If you're 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.",
|
||||
@@ -789,7 +794,7 @@
|
||||
"term-cond-p22": "Understand that {{appName}} 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 {{appName}}:",
|
||||
"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 India’s Information Technology Act.",
|
||||
"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 India's 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": "{{appName}} 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.",
|
||||
@@ -829,7 +834,7 @@
|
||||
"initial-type": "Your initials",
|
||||
"redirect-url": "Redirect url",
|
||||
"bulk-send": "Bulk send",
|
||||
"select-timezone": "Select your Timezone",
|
||||
"select-timezone": "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.",
|
||||
@@ -848,5 +853,33 @@
|
||||
"agreement-note": "Note: Agreeing to this does not mean you are signing the document immediately. This only allows you to review the document electronically. You will have the opportunity to read it in full and decide whether to sign it afterward.",
|
||||
"draft-template-info-p1": "To make your template public, it must either contain a single role, or, if it includes multiple roles, all additional roles must already be assigned to signers. The unassigned public role should remain empty and must be placed in the first position.",
|
||||
"visit-below-link": "Visit below link to know more -",
|
||||
"storage-help": "Enabling BYOC lets you connect your own S3 storage so your files remain entirely under your control—no external copies retained. If data autonomy matters to you, consider upgrading to Teams to unlock this feature."
|
||||
"storage-help": "Enabling BYOC lets you connect your own S3 storage so your files remain entirely under your control—no external copies retained. If data autonomy matters to you, consider upgrading to Teams to unlock this feature.",
|
||||
"daily-quota-reached": "You've reached your daily quota. For assistance, please contact quotas@opensignlabs.com.",
|
||||
"enabled-signature-type": "Enabled Signature Types",
|
||||
"enabled-signature-type-help": "The 'Enabled Signature Types' setting determines which signature options are available across your organization. For example, if you disable the 'Draw' option, members of your organization will not see it in the signature widget, while the other three options will remain accessible.",
|
||||
"indexing-public-profile": "Allow indexing of public profile by search engines",
|
||||
"user-created-successfully": "user created successfully.",
|
||||
"only-15-reminder-allowed": "You can set up to 15 automatic reminders. For example, if 'TimeToComplete' is 15 days and 'RemindOnceInEvery' is 1 day, you'll reach the maximum limit of 15 reminders. Adjust your settings accordingly.",
|
||||
"rate-your-experience": "How was your experience with {{appName}}?",
|
||||
"thanks-for-feedback": "Thanks for your feedback 🙏",
|
||||
"share-your-feedback": "Share your feedback",
|
||||
"share-your-review": "Share your review",
|
||||
"date-format": "Date format",
|
||||
"document-deleted": "The document has been deleted or you don't have access. Please contact the sender.",
|
||||
"save-as-template-?": "Are you sure you want to save this document as template?",
|
||||
"go-to-manage-templates": "go to 'Manage templates'",
|
||||
"template-created": "Template Created",
|
||||
"how-would-you-like-to-proceed?": "How would you like to proceed?",
|
||||
"failed-to-load-refresh-page": "Failed to load the document. Please try refreshing this page.",
|
||||
"document-has-been-signed": "The document has been signed successfully!",
|
||||
"document-has-been-signed-by-you": "The document has been successfully signed by you!",
|
||||
"participant-completed-signing": "All participants have completed the signing process.",
|
||||
"you-will-receive-email-shortly": "✅ That's it! You'll receive a confirmation email shortly.",
|
||||
"please-provide-templateid": "Please provide templateid",
|
||||
"this-template-is-not-public": "This template is not public",
|
||||
"invalid-templateid": "Invaldi templateid",
|
||||
"contact-billing-at-opensign": "To add more seats, please contact OpenSign™ at <1>billing@opensignlabs.com</1> for assistance",
|
||||
"title-length-alert": "Title must be at most 250 characters long.",
|
||||
"note-length-alert": "Note must be at most 200 characters long.",
|
||||
"description-length-alert": "Description must be at most 500 characters long."
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"Storage": "Almacenamiento",
|
||||
"Signing certificate": "Certificado de firma",
|
||||
"Teams": "Equipos",
|
||||
"General": "General",
|
||||
"Teams-Children": {
|
||||
"Organizations": "Organizaciones",
|
||||
"OrgAdmins": "OrgAdmins"
|
||||
@@ -149,7 +150,9 @@
|
||||
"Copy Public URL": "Copiar URL pública",
|
||||
"extend-expiry-date": "Date d'expiration",
|
||||
"Duplicate Template": "Plantilla duplicada",
|
||||
"Duplicate": "Duplicada"
|
||||
"Duplicate": "Duplicada",
|
||||
"daily-mail-quota": "Cuota diaria de correos electrónicos",
|
||||
"Save as template": "Guardar como plantilla"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nº",
|
||||
@@ -246,7 +249,7 @@
|
||||
"generate-token-alert": "¿En definitiva quieres regenerar el token? Esto expirará el token antiguo.",
|
||||
"yes": "Sí",
|
||||
"copied": "Copiado",
|
||||
"something-went-wrong-mssg": "Algo salió mal, por favor, intenta de nuevo más tarde.",
|
||||
"something-went-wrong-mssg": "Un problème est survenu, Actualiser cette page peut résoudre le problème.",
|
||||
"token-generated": "Token generado exitosamente.",
|
||||
"webhook": "Webhook",
|
||||
"update-webhook": "Actualizar webhook",
|
||||
@@ -282,6 +285,7 @@
|
||||
"make-template-public": "Convertir la plantilla en pública",
|
||||
"make-template-private": "Convertir la plantilla en privada",
|
||||
"make-template-public-alert": "¿En definitiva quieres convertir esta plantilla en pública?",
|
||||
"make-template-private-alert-non": "¿Está seguro de que desea hacer este plantilla privado?",
|
||||
"make-template-private-alert": "¿En definitiva quieres convertir esta plantilla en privada? Esto lo removerá de tu perfil público.",
|
||||
"public-role": "Rol público",
|
||||
"public-url": "Perfil publico",
|
||||
@@ -307,7 +311,7 @@
|
||||
"revoke-document": "Revocar documento",
|
||||
"revoke-document-alert": "¿En definitiva quieres revocar este documento?",
|
||||
"resend-mail": "Reenviar correo",
|
||||
"resend-mail-help": "Puedes usar las siguientes variables que serán reemplazadas por sus valores reales:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
|
||||
"resend-mail-help": "Puedes usar las siguientes variables que serán reemplazadas por sus valores reales:- {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email}}, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
|
||||
"subject": "Asunto",
|
||||
"body": "Cuerpo",
|
||||
"add-contact": "Agregar contacto",
|
||||
@@ -456,6 +460,7 @@
|
||||
"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-mail-alert-you": "Los firmantes posteriores recibirán correos electrónicos una vez que 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!",
|
||||
@@ -519,7 +524,7 @@
|
||||
"correct-password": "Por favor, proporciona la contraseña correcta",
|
||||
"decrypting-pdf": " Desencriptando PDF, por favor, espera...",
|
||||
"invalid-otp": "OTP inválido",
|
||||
"user-not-found": "¡Usuario no encontrado!",
|
||||
"user-not-found": "Usuario no encontrado",
|
||||
"enter-otp-alert": "¡Por favor, ingresa el OTP!",
|
||||
"get-verification-code": "Obtener código de verificación",
|
||||
"get-verification-code-2": "Obtendrás un código de verificación por correo",
|
||||
@@ -765,8 +770,6 @@
|
||||
"term-cond-p3": "Recibirá y firmará documentos electrónicamente a través de {{appName}}.",
|
||||
"term-cond-p4": "Su firma electrónica es jurídicamente vinculante y equivalente a una firma manuscrita.",
|
||||
"term-cond-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 {{appName}} y comprende que este consentimiento es válido hasta que se retire.",
|
||||
"term-cond-p7": "Usted acepta revisar, firmar y devolver documentos electrónicamente utilizando {{appName}}.",
|
||||
@@ -802,6 +805,8 @@
|
||||
"term-cond-p30": " o correo electrónico",
|
||||
"js-snippet-msg": "Para integrar plantillas {{appName}} en sus sitios web HTML o páginas de destino, puede utilizar el siguiente código:",
|
||||
"js-snippet-msg-1": "Puede obtener el TemplateId en la página Administrar plantillas",
|
||||
"js-snippet-msg-2": "Administrar plantillas",
|
||||
"js-snippet-msg-3": "página.",
|
||||
"agrrement-alert": "Para continuar, debe consentir la divulgación de registros y firmas electrónicas.",
|
||||
"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://",
|
||||
@@ -829,7 +834,7 @@
|
||||
"initial-type": "Tus iniciales",
|
||||
"redirect-url": "URL de redireccionamiento",
|
||||
"bulk-send": "envío masivo",
|
||||
"select-timezone": "Seleccione su zona horaria",
|
||||
"select-timezone": "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.",
|
||||
@@ -848,6 +853,33 @@
|
||||
"agreement-note": "Nota: Aceptar esto no significa que esté firmando el documento de inmediato. Esto solo le permite revisar el documento electrónicamente. Tendrá la oportunidad de leerlo en su totalidad y decidir si desea firmarlo después.",
|
||||
"draft-template-info-p1": "Para hacer que tu plantilla sea pública, debe contener un único rol o, si incluye múltiples roles, todos los roles adicionales deben estar ya asignados a firmantes. El rol público no asignado debe permanecer vacío y debe estar en la primera posición.",
|
||||
"visit-below-link": "Visita el siguiente enlace para saber más -",
|
||||
"upgrade-to-team-plan": "Actualizar a team plan",
|
||||
"storage-help": "Habilitar BYOC te permite conectar tu propio almacenamiento S3 para que tus archivos permanezcan completamente bajo tu control, sin copias externas retenidas. Si la autonomía de los datos es importante para ti, considera actualizar a Teams para desbloquear esta función."
|
||||
"storage-help": "Habilitar BYOC te permite conectar tu propio almacenamiento S3 para que tus archivos permanezcan completamente bajo tu control, sin copias externas retenidas. Si la autonomía de los datos es importante para ti, considera actualizar a Teams para desbloquear esta función.",
|
||||
"daily-quota-reached": "Ha alcanzado su cuota diaria. Para obtener ayuda, comuníquese con quotas@opensignlabs.com.",
|
||||
"enabled-signature-type": "Tipos de firma habilitados",
|
||||
"enabled-signature-type-help": "La configuración de 'Tipos de firma habilitados' determina qué opciones de firma están disponibles en su organización. Por ejemplo, si desactiva la opción 'Dibujar', los miembros de su organización no la verán en el widget de firma, mientras que las otras tres opciones seguirán siendo accesibles.",
|
||||
"indexing-public-profile": "Permitir la indexación del perfil público por los motores de búsqueda",
|
||||
"user-created-successfully": "Usuario creado con éxito.",
|
||||
"only-15-reminder-allowed": "Puede configurar hasta 15 recordatorios automáticos. Por ejemplo, si 'TimeToComplete' es de 15 días y 'RemindOnceInEvery' es de 1 día, alcanzará el límite máximo de 15 recordatorios. Ajuste su configuración en consecuencia.",
|
||||
"rate-your-experience": "¿Cómo fue su experiencia con {{appName}}?",
|
||||
"thanks-for-feedback": "Gracias por su comentario 🙏",
|
||||
"share-your-feedback": "Comparta sus comentarios",
|
||||
"share-your-review": "Comparta su reseña",
|
||||
"date-format": "Formato de fecha",
|
||||
"document-deleted": "El documento ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
|
||||
"save-as-template-?": "¿Está seguro de que desea guardar este documento como plantilla?",
|
||||
"go-to-manage-templates": "Ir a 'Gestionar plantillas'",
|
||||
"template-created": "Plantilla creada",
|
||||
"how-would-you-like-to-proceed?": "¿Cómo le gustaría proceder?",
|
||||
"failed-to-load-refresh-page": "Error al cargar el documento. Intente actualizar esta página.",
|
||||
"document-has-been-signed": "¡El documento ha sido firmado con éxito!",
|
||||
"document-has-been-signed-by-you": "¡El documento ha sido firmado con éxito por usted!",
|
||||
"participant-completed-signing": "Todos los participantes han completado el proceso de firma.",
|
||||
"you-will-receive-email-shortly": "✅ ¡Eso es todo! Recibirá un correo de confirmación en breve.",
|
||||
"please-provide-templateid": "Por favor, proporcione templateid",
|
||||
"this-template-is-not-public": "Esta template no es pública",
|
||||
"invalid-templateid": "templateid no válida",
|
||||
"contact-billing-at-opensign": "Para agregar más asientos, comuníquese con OpenSign™ a <1>billing@opensignlabs.com</1> para obtener ayuda.",
|
||||
"title-length-alert": "El título debe tener como máximo 250 caracteres.",
|
||||
"note-length-alert": "La nota debe tener como máximo 200 caracteres.",
|
||||
"description-length-alert": "La descripción debe tener como máximo 500 caracteres."
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"Storage": "Stockage",
|
||||
"Signing certificate": "Certificat de signature",
|
||||
"Teams": "Équipes",
|
||||
"General": "Général",
|
||||
"Teams-Children": {
|
||||
"Organizations": "Organisations",
|
||||
"OrgAdmins": "OrgAdmins"
|
||||
@@ -170,7 +171,9 @@
|
||||
"Copy Public URL": "Copier l'URL publique",
|
||||
"extend-expiry-date": "Prolonger la date d'expiration",
|
||||
"Duplicate Template": "dupliquer le modèle",
|
||||
"Duplicate": "Double"
|
||||
"Duplicate": "Double",
|
||||
"daily-mail-quota": "Quota d'e-mails quotidien",
|
||||
"Save as template": "Enregistrer comme modèle"
|
||||
},
|
||||
"report-help": {
|
||||
"Draft Documents": "Il s'agit de documents que vous avez commencés mais que vous n'avez pas finalisés pour envoi.",
|
||||
@@ -198,7 +201,7 @@
|
||||
"send-in-order-help": {
|
||||
"p1": "Choisissez la manière dont vous souhaitez que les demandes de signature soient envoyées aux signataires du document :",
|
||||
"p2": "La sélection de cette option enverra initialement la demande de signature au premier signataire. Une fois que le premier signataire a terminé sa partie, le prochain signataire de la séquence recevra la demande. Ce processus se poursuit jusqu'à ce que tous les signataires aient signé le document. Cette méthode garantit que le document est signé dans un ordre spécifique.",
|
||||
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais n’impose aucun ordre de signature entre les participants.",
|
||||
"p3": "La sélection de cette option enverra les liens de signature à tous les signataires simultanément. Chaque signataire peut signer le document à sa convenance, que d'autres signataires aient ou non complété leur signature. Cette méthode est plus rapide mais n'impose aucun ordre de signature entre les participants.",
|
||||
"p4": "Sélectionnez l'option qui correspond le mieux aux besoins de votre traitement de documents."
|
||||
},
|
||||
"no": "Non",
|
||||
@@ -224,7 +227,7 @@
|
||||
"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 d’un 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. L’adresse e-mail de l’utilisateur sélectionné sera ajoutée en Bcc (copie carbone invisible). Chaque utilisateur recevra une notification par e-mail une fois le document terminé.",
|
||||
"bcc-help": "Commencez à taper le nom d'un 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. L'adresse e-mail de l'utilisateur 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",
|
||||
@@ -281,6 +284,7 @@
|
||||
"make-template-public": "Rendre le modèle public",
|
||||
"make-template-private": "Rendre le modèle privé",
|
||||
"make-template-public-alert": "Êtes-vous sûr de vouloir rendre ce modèle public ?",
|
||||
"make-template-private-alert-non": "Êtes-vous sûr de vouloir rendre ce modèle privé ?",
|
||||
"make-template-private-alert": "Êtes-vous sûr de vouloir rendre ce modèle privé ? Cela le supprimera de votre profil public.",
|
||||
"public-role": "Rôle public",
|
||||
"public-url": "Profil public",
|
||||
@@ -306,7 +310,7 @@
|
||||
"revoke-document": "Révoquer le document",
|
||||
"revoke-document-alert": "Êtes-vous sûr de vouloir révoquer ce document ?",
|
||||
"resend-mail": "Renvoyer le courrier",
|
||||
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}.",
|
||||
"resend-mail-help": "Vous pouvez utiliser les variables suivantes qui seront remplacées par leurs valeurs réelles : - {{document_title}}, {{sender_name}}, {{sender_mail}}, {{sender_phone}}, {{receiver_name}}, {{receiver_email} }, {{receiver_phone}}, {{expiry_date}}, {{company_name}}, {{signing_url}}, {{note}}.",
|
||||
"subject": "Sujet",
|
||||
"body": "Corps",
|
||||
"add-contact": "Ajouter le contact",
|
||||
@@ -450,11 +454,13 @@
|
||||
"add-recipients": "Ajouter des destinataires",
|
||||
"loading-mssg": "Cela pourrait prendre du temps",
|
||||
"send-mail": "Envoyer un mail",
|
||||
"signature-field-widget": "Au moins un champ de signature doit être ajouté pour chaque utilisateur. Vous n'avez pas ajouté de champs de signature pour {{signersName}}",
|
||||
"placeholder-alert-1": "Veuillez vous assurer qu'au moins un widget de signature est ajouté pour tous les destinataires.",
|
||||
"placeholder-alert-2": "Veuillez confirmer que vous avez rempli le champ de texte.",
|
||||
"placeholder-alert-3": "Etes-vous sûr de vouloir envoyer ce document pour signature ? ",
|
||||
"placeholder-alert-4": "Vous avez envoyé avec succès des mails à tous les",
|
||||
"placeholder-mail-alert": "Vous avez envoyé un e-mail avec succès à {{name}}. Les signataires suivants recevront un e-mail une fois que {{name}} aura signé le document.",
|
||||
"placeholder-mail-alert-you": "Les signataires suivants recevront un e-mail dès que vous signez 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é !",
|
||||
@@ -518,7 +524,7 @@
|
||||
"correct-password": "Veuillez fournir un mot de passe correct",
|
||||
"decrypting-pdf": "Décryptage du pdf, veuillez patienter...",
|
||||
"invalid-otp": "OTP invalide",
|
||||
"user-not-found": "Utilisateur non trouvé!",
|
||||
"user-not-found": "Utilisateur non trouvé",
|
||||
"enter-otp-alert": "Veuillez saisir OTP !",
|
||||
"get-verification-code": "Obtenir le code de vérification",
|
||||
"get-verification-code-2": "Vous recevrez un code de vérification par e-mail",
|
||||
@@ -557,7 +563,8 @@
|
||||
"do-not-access-contact-admin": "Vous n'y avez pas accès, veuillez contacter l'administrateur.",
|
||||
"filed-required-correctly": "Veuillez remplir correctement les informations requises.",
|
||||
"admin-created": "Administrateur créé",
|
||||
"invalid-masterkey": "Clé principale invalide", "master-key": "La clef maitresse",
|
||||
"invalid-masterkey": "Clé principale invalide",
|
||||
"master-key": "La clef maitresse",
|
||||
"profile-update-alert": "Mise à jour du profil réussie.",
|
||||
"date": "Date",
|
||||
"report-not-found": "Rapport introuvable",
|
||||
@@ -653,7 +660,7 @@
|
||||
"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 à l’un de nos forfaits payants pour générer un jeton de production.",
|
||||
"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 à l'un 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",
|
||||
@@ -827,7 +834,7 @@
|
||||
"initial-type": "Vos initiales",
|
||||
"redirect-url": "URL de redirection",
|
||||
"bulk-send": "Envoi groupé",
|
||||
"select-timezone": "Sélectionnez votre fuseau horaire",
|
||||
"select-timezone": "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.",
|
||||
@@ -846,5 +853,33 @@
|
||||
"agreement-note": "Remarque : Accepter cela ne signifie pas que vous signez immédiatement le document. Cela vous permet uniquement de consulter le document électroniquement. Vous aurez l'opportunité de le lire entièrement et de décider ensuite si vous souhaitez le signer.",
|
||||
"draft-template-info-p1": "Pour rendre votre modèle public, il doit contenir un seul rôle ou, s'il inclut plusieurs rôles, tous les rôles supplémentaires doivent déjà être attribués aux signataires. Le rôle public non attribué doit rester vide et être placé en première position.",
|
||||
"visit-below-link": "Visitez le lien ci-dessous pour en savoir plus -",
|
||||
"storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité."
|
||||
"storage-help": "Activer BYOC vous permet de connecter votre propre stockage S3 afin que vos fichiers restent entièrement sous votre contrôle, sans copie externe conservée. Si l'autonomie des données est importante pour vous, envisagez de passer à l'offre Teams pour débloquer cette fonctionnalité.",
|
||||
"daily-quota-reached": "Vous avez atteint votre quota quotidien. Pour obtenir de l'aide, veuillez contacter quotas@opensignlabs.com.",
|
||||
"enabled-signature-type": "Types de signature activés",
|
||||
"enabled-signature-type-help": "Le paramètre 'Types de signature activés' détermine quelles options de signature sont disponibles dans votre organisation. Par exemple, si vous désactivez l'option 'Dessiner', les membres de votre organisation ne la verront pas dans le widget de signature, tandis que les trois autres options resteront accessibles.",
|
||||
"indexing-public-profile": "Autoriser l'indexation du profil public par les moteurs de recherche",
|
||||
"user-created-successfully": "Utilisateur créé avec succès.",
|
||||
"only-15-reminder-allowed": "Vous pouvez définir jusqu'à 15 rappels automatiques. Par exemple, si 'TimeToComplete' est de 15 jours et 'RemindOnceInEvery' est de 1 jour, vous atteindrez la limite maximale de 15 rappels. Ajustez vos paramètres en conséquence.",
|
||||
"rate-your-experience": "Comment s'est passée votre expérience avec {{appName}} ?",
|
||||
"thanks-for-feedback": "Merci pour votre retour 🙏",
|
||||
"share-your-feedback": "Partagez votre avis",
|
||||
"share-your-review": "Partagez votre avis",
|
||||
"date-format": "Format de date",
|
||||
"document-deleted": "Le document a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
|
||||
"save-as-template-?": "Êtes-vous sûr de vouloir enregistrer ce document comme modèle ?",
|
||||
"go-to-manage-templates": "Aller à 'Gérer les modèles'",
|
||||
"template-created": "Modèle créé",
|
||||
"how-would-you-like-to-proceed?": "Comment souhaitez-vous procéder ?",
|
||||
"failed-to-load-refresh-page": "Échec du chargement du document. Veuillez essayer d'actualiser cette page.",
|
||||
"document-has-been-signed": "Le document a été signé avec succès !",
|
||||
"document-has-been-signed-by-you": "Le document a été signé avec succès par vous !",
|
||||
"participant-completed-signing": "Tous les participants ont terminé le processus de signature.",
|
||||
"you-will-receive-email-shortly": "✅ Voilà, c'est fait ! Vous recevrez un e-mail de confirmation sous peu.",
|
||||
"please-provide-templateid": "Veuillez fournir templateid",
|
||||
"this-template-is-not-public": "Ce template n'est pas public",
|
||||
"invalid-templateid": "templateid invalide",
|
||||
"contact-billing-at-opensign": "Pour ajouter plus de places, veuillez contacter OpenSign™ à l'adresse <1>billing@opensignlabs.com</1> pour obtenir de l'aide.",
|
||||
"title-length-alert": "Le titre doit comporter au maximum 250 caractères.",
|
||||
"note-length-alert": "La note doit comporter au maximum 200 caractères.",
|
||||
"description-length-alert": "La description doit comporter au maximum 500 caractères"
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"Storage": "Archiviazione",
|
||||
"Signing certificate": "Certificato di firma",
|
||||
"Teams": "Squadre",
|
||||
"General": "Generale",
|
||||
"Teams-Children": {
|
||||
"Organizations": "Organizzazioni",
|
||||
"OrgAdmins": "OrgAdmins"
|
||||
@@ -149,7 +150,9 @@
|
||||
"Copy Public URL": "Copia URL pubblico",
|
||||
"extend-expiry-date": "Estendi data di scadenza",
|
||||
"Duplicate Template": "Duplica modello",
|
||||
"Duplicate": "Duplica"
|
||||
"Duplicate": "Duplica",
|
||||
"daily-mail-quota": "Quota e-mail giornaliera",
|
||||
"Save as template": "Salva come modello"
|
||||
},
|
||||
"report-heading": {
|
||||
"Sr.No": "Nr.",
|
||||
@@ -245,7 +248,7 @@
|
||||
"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.",
|
||||
"something-went-wrong-mssg": "Si è verificato un errore, L'aggiornamento della pagina potrebbe risolvere il problema.",
|
||||
"token-generated": "Token generato con successo.",
|
||||
"webhook": "Webhook",
|
||||
"update-webhook": "Aggiorna Webhook",
|
||||
@@ -281,6 +284,7 @@
|
||||
"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-non": "Sei sicuro di voler rendere privato 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",
|
||||
@@ -306,12 +310,12 @@
|
||||
"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}}.",
|
||||
"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}}, {{note}}.",
|
||||
"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’.",
|
||||
"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.",
|
||||
@@ -450,11 +454,13 @@
|
||||
"add-recipients": "Aggiungi destinatari",
|
||||
"loading-mssg": "Questo potrebbe richiedere del tempo",
|
||||
"send-mail": "Invia Mail",
|
||||
"signature-field-widget":"È necessario aggiungere almeno un campo firma per ogni utente. Non hai aggiunto campi firma per {{signersName}}",
|
||||
"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-mail-alert-you": "I firmatari successivi riceveranno un'email non appena firmi 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!",
|
||||
@@ -518,7 +524,7 @@
|
||||
"correct-password": "Fornisci la password corretta",
|
||||
"decrypting-pdf": "Decrittazione PDF, attendi...",
|
||||
"invalid-otp": "OTP non valido",
|
||||
"user-not-found": "Utente non trovato!",
|
||||
"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",
|
||||
@@ -603,7 +609,7 @@
|
||||
"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.",
|
||||
"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.",
|
||||
@@ -620,7 +626,7 @@
|
||||
"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.",
|
||||
"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",
|
||||
@@ -828,7 +834,7 @@
|
||||
"initial-type": "Le tue iniziali",
|
||||
"redirect-url": "URL di reindirizzamento",
|
||||
"bulk-send": "Invio in blocco",
|
||||
"select-timezone": "Seleziona il tuo fuso orario",
|
||||
"select-timezone": "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.",
|
||||
@@ -847,5 +853,33 @@
|
||||
"agreement-note": "Nota: Accettare questo non significa che stai firmando immediatamente il documento. Questo ti consente solo di esaminare il documento elettronicamente. Avrai l'opportunità di leggerlo per intero e decidere successivamente se firmarlo.",
|
||||
"draft-template-info-p1": "Per rendere il tuo modello pubblico, deve contenere un solo ruolo oppure, se include più ruoli, tutti i ruoli aggiuntivi devono essere già assegnati ai firmatari. Il ruolo pubblico non assegnato deve rimanere vuoto e deve essere posizionato per primo.",
|
||||
"visit-below-link": "Visita il link qui sotto per saperne di più -",
|
||||
"storage-help": "Abilitare BYOC ti consente di collegare il tuo archivio S3 in modo che i tuoi file rimangano completamente sotto il tuo controllo, senza copie esterne conservate. Se l'autonomia dei dati è importante per te, considera l'upgrade a Teams per sbloccare questa funzionalità."
|
||||
"storage-help": "Abilitare BYOC ti consente di collegare il tuo archivio S3 in modo che i tuoi file rimangano completamente sotto il tuo controllo, senza copie esterne conservate. Se l'autonomia dei dati è importante per te, considera l'upgrade a Teams per sbloccare questa funzionalità.",
|
||||
"daily-quota-reached": "Hai raggiunto la tua quota giornaliera. Per assistenza, contatta quotas@opensignlabs.com.",
|
||||
"enabled-signature-type": "Tipi di firma abilitati",
|
||||
"enabled-signature-type-help": "L'impostazione 'Tipi di firma abilitati' determina quali opzioni di firma sono disponibili nella tua organizzazione. Ad esempio, se disabiliti l'opzione 'Disegna', i membri della tua organizzazione non la vedranno nel widget della firma, mentre le altre tre opzioni resteranno accessibili.",
|
||||
"indexing-public-profile": "Consenti l'indicizzazione del profilo pubblico dai motori di ricerca",
|
||||
"user-created-successfully": "Utente creato con successo.",
|
||||
"only-15-reminder-allowed": "Puoi impostare fino a 15 promemoria automatici. Ad esempio, se 'TimeToComplete' è di 15 giorni e 'RemindOnceInEvery' è di 1 giorno, raggiungerai il limite massimo di 15 promemoria. Regola le tue impostazioni di conseguenza.",
|
||||
"rate-your-experience": "Com'è stata la sua esperienza con {{appName}}?",
|
||||
"thanks-for-feedback": "Grazie per il tuo feedback 🙏",
|
||||
"share-your-feedback": "Condividi il tuo feedback",
|
||||
"share-your-review": "Condividi la tua recensione",
|
||||
"date-format": "Formato data",
|
||||
"document-deleted": "Il documento è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
|
||||
"save-as-template-?": "Sei sicuro di voler salvare questo documento come modello?",
|
||||
"go-to-manage-templates": "Vai a 'Gestisci modelli'",
|
||||
"template-created": "Modello creato",
|
||||
"how-would-you-like-to-proceed?": "Come desideri procedere?",
|
||||
"failed-to-load-refresh-page": "Impossibile caricare il documento. Prova ad aggiornare questa pagina.",
|
||||
"document-has-been-signed": "Il documento è stato firmato con successo!",
|
||||
"document-has-been-signed-by-you": "Il documento è stato firmato con successo da lei!",
|
||||
"participant-completed-signing": "Tutti i partecipanti hanno completato il processo di firma.",
|
||||
"you-will-receive-email-shortly": "✅ È tutto! Riceverà a breve un'e-mail di conferma.",
|
||||
"please-provide-templateid": "Si prega di fornire templateid",
|
||||
"this-template-is-not-public": "Questo template non è pubblico",
|
||||
"invalid-templateid": "templateid non valido",
|
||||
"contact-billing-at-opensign": " Per aggiungere altri posti, contattare OpenSign™ all'indirizzo <1>billing@opensignlabs.com</1> per assistenza.",
|
||||
"title-length-alert": "Il titolo può contenere al massimo 250 caratteri.",
|
||||
"note-length-alert": "La nota può contenere al massimo 200 caratteri.",
|
||||
"description-length-alert": " La descrizione può contenere al massimo 500 caratteri."
|
||||
}
|
||||
|
||||
@@ -2,13 +2,8 @@ import React, { useEffect, useState } from "react";
|
||||
import Parse from "parse";
|
||||
import Title from "./Title";
|
||||
import Loader from "../primitives/Loader";
|
||||
import {
|
||||
copytoData,
|
||||
usertimezone
|
||||
} from "../constant/Utils";
|
||||
import {
|
||||
emailRegex,
|
||||
} from "../constant/const";
|
||||
import { copytoData, usertimezone } from "../constant/Utils";
|
||||
import { emailRegex } from "../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
function generatePassword(length) {
|
||||
const characters =
|
||||
@@ -46,9 +41,9 @@ const AddUser = (props) => {
|
||||
if (teamRes.length > 0) {
|
||||
const _teamRes = JSON.parse(JSON.stringify(teamRes));
|
||||
setTeamList(_teamRes);
|
||||
const allUserId =
|
||||
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
|
||||
setFormdata((prev) => ({ ...prev, team: allUserId }));
|
||||
const allUserId =
|
||||
_teamRes.find((x) => x.Name === "All Users")?.objectId || "";
|
||||
setFormdata((prev) => ({ ...prev, team: allUserId }));
|
||||
}
|
||||
};
|
||||
const checkUserExist = async () => {
|
||||
@@ -76,167 +71,167 @@ const AddUser = (props) => {
|
||||
setIsFormLoader(true);
|
||||
const res = await checkUserExist();
|
||||
if (res) {
|
||||
props.setIsAlert({ type: "danger", msg: t("user-already-exist") });
|
||||
props.showAlert("danger", t("user-already-exist"));
|
||||
setIsFormLoader(false);
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1000);
|
||||
} else {
|
||||
try {
|
||||
const extUser = new Parse.Object("contracts_Users");
|
||||
extUser.set("Name", formdata.name);
|
||||
if (formdata.phone) {
|
||||
extUser.set("Phone", formdata.phone);
|
||||
}
|
||||
extUser.set("Email", formdata.email);
|
||||
extUser.set("UserRole", `contracts_${formdata.role}`);
|
||||
if (formdata?.team) {
|
||||
extUser.set("TeamIds", [
|
||||
{
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: formdata.team
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (localUser && localUser.OrganizationId) {
|
||||
extUser.set("OrganizationId", {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Organizations",
|
||||
objectId: localUser.OrganizationId.objectId
|
||||
});
|
||||
}
|
||||
if (localUser && localUser.Company) {
|
||||
extUser.set("Company", localUser.Company);
|
||||
}
|
||||
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
extUser.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
}
|
||||
const timezone = usertimezone;
|
||||
if (timezone) {
|
||||
extUser.set("Timezone", timezone);
|
||||
}
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", formdata.name);
|
||||
_user.set("username", formdata.email);
|
||||
_user.set("email", formdata.email);
|
||||
_user.set("password", formdata.password);
|
||||
const extUser = new Parse.Object("contracts_Users");
|
||||
extUser.set("Name", formdata.name);
|
||||
if (formdata.phone) {
|
||||
_user.set("phone", formdata.phone);
|
||||
extUser.set("Phone", formdata.phone);
|
||||
}
|
||||
extUser.set("Email", formdata.email);
|
||||
extUser.set("UserRole", `contracts_${formdata.role}`);
|
||||
if (formdata?.team) {
|
||||
extUser.set("TeamIds", [
|
||||
{
|
||||
__type: "Pointer",
|
||||
className: "contracts_Teams",
|
||||
objectId: formdata.team
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (localUser && localUser.OrganizationId) {
|
||||
extUser.set("OrganizationId", {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Organizations",
|
||||
objectId: localUser.OrganizationId.objectId
|
||||
});
|
||||
}
|
||||
if (localUser && localUser.Company) {
|
||||
extUser.set("Company", localUser.Company);
|
||||
}
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
extUser.setACL(acl);
|
||||
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
if (formdata?.team) {
|
||||
const team = teamList.find(
|
||||
(x) => x.objectId === formdata.team
|
||||
);
|
||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||
y.objectId === team.objectId ? team : y
|
||||
);
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
|
||||
setIsFormLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
if (localStorage.getItem("TenantId")) {
|
||||
extUser.set("TenantId", {
|
||||
__type: "Pointer",
|
||||
className: "partners_Tenant",
|
||||
objectId: localStorage.getItem("TenantId")
|
||||
});
|
||||
}
|
||||
const timezone = usertimezone;
|
||||
if (timezone) {
|
||||
extUser.set("Timezone", timezone);
|
||||
}
|
||||
try {
|
||||
const _users = Parse.Object.extend("User");
|
||||
const _user = new _users();
|
||||
_user.set("name", formdata.name);
|
||||
_user.set("username", formdata.email);
|
||||
_user.set("email", formdata.email);
|
||||
_user.set("password", formdata.password);
|
||||
if (formdata.phone) {
|
||||
_user.set("phone", formdata.phone);
|
||||
}
|
||||
|
||||
const user = await _user.save();
|
||||
if (user) {
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.set("UserId", user);
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
extUser.setACL(acl);
|
||||
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
if (formdata?.team) {
|
||||
const team = teamList.find(
|
||||
(x) => x.objectId === formdata.team
|
||||
);
|
||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||
y.objectId === team.objectId ? team : y
|
||||
);
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
|
||||
setIsFormLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
props.showAlert("success", t("user-created-successfully"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: formdata.email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.set("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: userRes.id
|
||||
});
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
extUser.setACL(acl);
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
if (formdata?.team) {
|
||||
const team = teamList.find(
|
||||
(x) => x.objectId === formdata.team
|
||||
);
|
||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||
y.objectId === team.objectId ? team : y
|
||||
);
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsFormLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
props.showAlert("success", t("user-created-successfully"));
|
||||
} else {
|
||||
setIsFormLoader(false);
|
||||
props.showAlert("danger", t("something-went-wrong-mssg"));
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
if (err.code === 202) {
|
||||
const params = { email: formdata.email };
|
||||
const userRes = await Parse.Cloud.run("getUserId", params);
|
||||
const currentUser = Parse.User.current();
|
||||
extUser.set(
|
||||
"CreatedBy",
|
||||
Parse.User.createWithoutData(currentUser.id)
|
||||
);
|
||||
|
||||
extUser.set("UserId", {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: userRes.id
|
||||
});
|
||||
const acl = new Parse.ACL();
|
||||
acl.setPublicReadAccess(true);
|
||||
acl.setPublicWriteAccess(true);
|
||||
acl.setReadAccess(currentUser.id, true);
|
||||
acl.setWriteAccess(currentUser.id, true);
|
||||
|
||||
extUser.setACL(acl);
|
||||
const res = await extUser.save();
|
||||
|
||||
const parseData = JSON.parse(JSON.stringify(res));
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
if (props.handleUserData) {
|
||||
if (formdata?.team) {
|
||||
const team = teamList.find(
|
||||
(x) => x.objectId === formdata.team
|
||||
);
|
||||
parseData.TeamIds = parseData.TeamIds.map((y) =>
|
||||
y.objectId === team.objectId ? team : y
|
||||
);
|
||||
}
|
||||
props.handleUserData(parseData);
|
||||
}
|
||||
setIsFormLoader(false);
|
||||
setFormdata({
|
||||
name: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
team: "",
|
||||
role: ""
|
||||
});
|
||||
}
|
||||
console.log("err", err);
|
||||
setIsFormLoader(false);
|
||||
props.showAlert("danger", t("something-went-wrong-mssg"));
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
setIsFormLoader(false);
|
||||
props.setIsAlert({
|
||||
type: "danger",
|
||||
msg: t("something-went-wrong-mssg")
|
||||
});
|
||||
} finally {
|
||||
setTimeout(
|
||||
() => props.setIsAlert({ type: "success", msg: "" }),
|
||||
1500
|
||||
);
|
||||
} else {
|
||||
props.showAlert("danger", t("something-went-wrong-mssg"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,8 +254,7 @@ const AddUser = (props) => {
|
||||
|
||||
const copytoclipboard = (text) => {
|
||||
copytoData(text);
|
||||
props.setIsAlert({ type: "success", msg: t("copied") });
|
||||
setTimeout(() => props.setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds
|
||||
props.showAlert("success", t("copied"));
|
||||
};
|
||||
return (
|
||||
<div className="shadow-md rounded-box my-[1px] p-3 bg-base-100 relative">
|
||||
@@ -270,126 +264,120 @@ const AddUser = (props) => {
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<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>
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
<div className="w-full mx-auto">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<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>
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,9 +3,7 @@ import axios from "axios";
|
||||
import SuggestionInput from "./shared/fields/SuggestionInput";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
emailRegex,
|
||||
} from "../constant/const";
|
||||
import { emailRegex } from "../constant/const";
|
||||
const BulkSendUi = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const [forms, setForms] = useState([]);
|
||||
@@ -22,15 +20,15 @@ const BulkSendUi = (props) => {
|
||||
|
||||
//function to check atleast one signature field exist
|
||||
const signatureExist = async () => {
|
||||
setIsDisableBulkSend(false);
|
||||
const getPlaceholder = props?.Placeholders;
|
||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((holder) =>
|
||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||
)
|
||||
);
|
||||
setIsSignatureExist(checkIsSignatureExistt);
|
||||
setIsLoader(false);
|
||||
setIsDisableBulkSend(false);
|
||||
const getPlaceholder = props?.Placeholders;
|
||||
const checkIsSignatureExistt = getPlaceholder?.every((placeholderObj) =>
|
||||
placeholderObj?.placeHolder?.some((holder) =>
|
||||
holder?.pos?.some((posItem) => posItem?.type === "signature")
|
||||
)
|
||||
);
|
||||
setIsSignatureExist(checkIsSignatureExistt);
|
||||
setIsLoader(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (scrollOnNextUpdate && formRef.current) {
|
||||
@@ -74,7 +72,6 @@ const BulkSendUi = (props) => {
|
||||
setForms(newForms);
|
||||
};
|
||||
|
||||
|
||||
const handleRemoveForm = (index) => {
|
||||
const updatedForms = forms.filter((_, i) => i !== index);
|
||||
setForms(updatedForms);
|
||||
@@ -95,82 +92,83 @@ const BulkSendUi = (props) => {
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSubmit(true);
|
||||
if (validateEmails(forms)) {
|
||||
// Create a copy of Placeholders array from props.item
|
||||
let Placeholders = [...props.Placeholders];
|
||||
// Initialize an empty array to store updated documents
|
||||
let Documents = [];
|
||||
// Loop through each form
|
||||
forms.forEach((form) => {
|
||||
//checking if user enter email which already exist as a signer then add user in a signers array
|
||||
let existSigner = [];
|
||||
form.fields.map((data) => {
|
||||
if (data.signer) {
|
||||
existSigner.push(data.signer);
|
||||
}
|
||||
});
|
||||
// Map through the copied Placeholders array to update email values
|
||||
const updatedPlaceholders = Placeholders.map((placeholder) => {
|
||||
// Find the field in the current form that matches the placeholder Id
|
||||
const field = form.fields.find(
|
||||
(element) => parseInt(element.fieldId) === placeholder.Id
|
||||
);
|
||||
// If a matching field is found, update the email value in the placeholder
|
||||
const signer = field?.signer?.objectId ? field.signer : "";
|
||||
if (field) {
|
||||
if (signer) {
|
||||
return {
|
||||
...placeholder,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...placeholder,
|
||||
email: field.email,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
}
|
||||
}
|
||||
// If no matching field is found, keep the placeholder as is
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Push a new document object with updated Placeholders into the Documents array
|
||||
if (existSigner?.length > 0) {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
Signers: props.item.Signers
|
||||
? [...props.item.Signers, ...existSigner]
|
||||
: [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
SignatureType: props.signatureType
|
||||
});
|
||||
setIsSubmit(true);
|
||||
if (validateEmails(forms)) {
|
||||
// Create a copy of Placeholders array from props.item
|
||||
let Placeholders = [...props.Placeholders];
|
||||
// Initialize an empty array to store updated documents
|
||||
let Documents = [];
|
||||
// Loop through each form
|
||||
forms.forEach((form) => {
|
||||
//checking if user enter email which already exist as a signer then add user in a signers array
|
||||
let existSigner = [];
|
||||
form.fields.map((data) => {
|
||||
if (data.signer) {
|
||||
existSigner.push(data.signer);
|
||||
}
|
||||
});
|
||||
await batchQuery(Documents);
|
||||
} else {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
// Map through the copied Placeholders array to update email values
|
||||
const updatedPlaceholders = Placeholders.map((placeholder) => {
|
||||
// Find the field in the current form that matches the placeholder Id
|
||||
const field = form.fields.find(
|
||||
(element) => parseInt(element.fieldId) === placeholder.Id
|
||||
);
|
||||
// If a matching field is found, update the email value in the placeholder
|
||||
const signer = field?.signer?.objectId ? field.signer : "";
|
||||
if (field) {
|
||||
if (signer) {
|
||||
return {
|
||||
...placeholder,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...placeholder,
|
||||
email: field.email,
|
||||
signerObjId: field?.signer?.objectId || "",
|
||||
signerPtr: signer
|
||||
};
|
||||
}
|
||||
}
|
||||
// If no matching field is found, keep the placeholder as is
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Push a new document object with updated Placeholders into the Documents array
|
||||
if (existSigner?.length > 0) {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
Signers: props.item.Signers
|
||||
? [...props.item.Signers, ...existSigner]
|
||||
: [...existSigner]
|
||||
});
|
||||
} else {
|
||||
Documents.push({
|
||||
...props.item,
|
||||
Placeholders: updatedPlaceholders,
|
||||
SignatureType: props.signatureType
|
||||
});
|
||||
}
|
||||
});
|
||||
await batchQuery(Documents);
|
||||
} else {
|
||||
setIsSubmit(false);
|
||||
}
|
||||
};
|
||||
|
||||
const batchQuery = async (Documents) => {
|
||||
const token =
|
||||
{ "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 {
|
||||
@@ -218,7 +216,9 @@ const BulkSendUi = (props) => {
|
||||
className="flex flex-col"
|
||||
key={field.fieldId}
|
||||
>
|
||||
<label>{field.label}</label>
|
||||
<label className="block text-xs font-semibold">
|
||||
{field.label}
|
||||
</label>
|
||||
<SuggestionInput
|
||||
required
|
||||
type="email"
|
||||
@@ -275,8 +275,7 @@ const BulkSendUi = (props) => {
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
</>
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React from "react";
|
||||
import { Document, Page } from "react-pdf";
|
||||
import { Stage, Layer, Rect, Text } from "react-konva";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const RenderDebugPdf = (props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="sticky top-0 p-[10px] z-10 bg-white border-[1px] border-[gray] my-[5px]">
|
||||
@@ -12,10 +14,9 @@ const RenderDebugPdf = (props) => {
|
||||
onMouseMove={props.handleMouseMoveDiv}
|
||||
>
|
||||
<Document
|
||||
onLoadError={() => {
|
||||
props.setPdfLoadFail(false);
|
||||
}}
|
||||
loading={"Loading Document.."}
|
||||
onLoadError={() => props.setPdfLoadFail(false)}
|
||||
loading={t("loading-doc")}
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
ref={props.pdfRef}
|
||||
file={props.pdfUrl}
|
||||
|
||||
@@ -17,8 +17,8 @@ const AddRoleModal = (props) => {
|
||||
onChange={(e) => props.setRoleName(e.target.value)}
|
||||
placeholder={
|
||||
props.signersdata.length > 0
|
||||
? "User " + (props.signersdata.length + 1)
|
||||
: "User 1"
|
||||
? "Role " + (props.signersdata.length + 1)
|
||||
: "Role 1"
|
||||
}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs mt-1"
|
||||
/>
|
||||
|
||||
@@ -1,276 +0,0 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import opensignLogo from "../../assets/images/logo.png";
|
||||
import {
|
||||
Page,
|
||||
Text,
|
||||
View,
|
||||
Document,
|
||||
StyleSheet,
|
||||
Image
|
||||
} from "@react-pdf/renderer";
|
||||
|
||||
function Certificate({ pdfData }) {
|
||||
const [isMultiSigners, setIsMultiSigners] = useState();
|
||||
const [multiSigner, setMultiSigners] = useState([]);
|
||||
const [isLoad, setIsLoad] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
handleSignerData();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const handleSignerData = () => {
|
||||
const checkSigners = pdfData.filter((data) => data.Signers);
|
||||
if (checkSigners && checkSigners.length > 0) {
|
||||
setIsMultiSigners(true);
|
||||
|
||||
const checkSignSigners =
|
||||
pdfData[0].AuditTrail &&
|
||||
pdfData[0].AuditTrail.length > 0 &&
|
||||
pdfData[0].AuditTrail.filter((data) => data.Activity === "Signed");
|
||||
|
||||
setMultiSigners(checkSignSigners);
|
||||
} else {
|
||||
setIsMultiSigners(false);
|
||||
}
|
||||
setIsLoad(true);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
page: {
|
||||
borderRadius: "5px",
|
||||
padding: "10px",
|
||||
backgroundColor: "white"
|
||||
},
|
||||
section1: {
|
||||
border: "1px solid rgb(177, 174, 174)",
|
||||
padding: "20px"
|
||||
},
|
||||
textStyle: {
|
||||
fontWeight: "bold",
|
||||
fontSize: "11px",
|
||||
marginBottom: "10px"
|
||||
},
|
||||
textStyle2: {
|
||||
fontWeight: "600",
|
||||
fontSize: "11px",
|
||||
marginBottom: "10px",
|
||||
color: "gray"
|
||||
},
|
||||
image: {
|
||||
width: "71px",
|
||||
height: "17px"
|
||||
}
|
||||
});
|
||||
|
||||
const generatedDate = () => {
|
||||
const newDate = new Date();
|
||||
const utcTime = newDate.toUTCString();
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={{
|
||||
color: "gray",
|
||||
fontSize: "10px"
|
||||
}}
|
||||
>
|
||||
Generated On {utcTime}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
const changeCompletedDate = () => {
|
||||
const completedOn = pdfData[0].updatedAt;
|
||||
const newDate = new Date(completedOn);
|
||||
const utcTime = newDate.toUTCString();
|
||||
|
||||
return <Text style={styles.textStyle2}>{utcTime}</Text>;
|
||||
};
|
||||
|
||||
const signerName = (data) => {
|
||||
const getSignerName = pdfData[0].Signers.filter(
|
||||
(sign) => sign.objectId === data.UserPtr.objectId
|
||||
);
|
||||
|
||||
return (
|
||||
getSignerName[0] &&
|
||||
getSignerName.length > 0 && (
|
||||
<>
|
||||
<Text style={styles.textStyle}>
|
||||
Name :
|
||||
<Text style={styles.textStyle2}>{getSignerName[0].Name}</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Email :
|
||||
<Text style={styles.textStyle2}>{getSignerName[0].Email}</Text>
|
||||
</Text>
|
||||
</>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
isLoad && (
|
||||
<Document>
|
||||
{/** Page defines a single page of content. */}
|
||||
<Page size="A4" style={styles.page}>
|
||||
<View style={styles.section1}>
|
||||
<View
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: "30px"
|
||||
}}
|
||||
>
|
||||
<Image src={opensignLogo} style={styles.image} />
|
||||
{generatedDate()}
|
||||
</View>
|
||||
|
||||
<View style={{ justifyContent: "center" }}>
|
||||
<Text
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: "20px",
|
||||
fontWeight: "bold",
|
||||
color: "#31bceb",
|
||||
marginBottom: "10px"
|
||||
}}
|
||||
>
|
||||
{" "}
|
||||
Certificate of Completion
|
||||
</Text>
|
||||
<View style={{ border: "1px solid #bdbbbb" }}></View>
|
||||
<View>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "bold",
|
||||
color: "#31bceb",
|
||||
margin: "10px 0px 10px 0px"
|
||||
}}
|
||||
>
|
||||
Summary
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Text style={styles.textStyle}>
|
||||
Document ID :
|
||||
<Text style={styles.textStyle2}>{pdfData[0].objectId}</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Document Name :
|
||||
<Text style={styles.textStyle2}>{pdfData[0].Name}</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Organization :
|
||||
<Text style={styles.textStyle2}>
|
||||
{pdfData[0].ExtUserPtr.Company}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Completed on : {changeCompletedDate()}
|
||||
</Text>
|
||||
{multiSigner && multiSigner.length > 0 && (
|
||||
<Text style={styles.textStyle}>
|
||||
Signers :
|
||||
<Text style={styles.textStyle2}>
|
||||
{multiSigner.length}
|
||||
</Text>
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{isMultiSigners ? (
|
||||
<View style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "bold",
|
||||
color: "#31bceb",
|
||||
margin: "10px 0px 10px 0px"
|
||||
}}
|
||||
>
|
||||
Recipients
|
||||
</Text>
|
||||
|
||||
<View>
|
||||
{multiSigner &&
|
||||
multiSigner.map((data, ind) => {
|
||||
return (
|
||||
<View
|
||||
key={ind}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
border: "0.4px solid #bdbbbb",
|
||||
marginBottom: "10px"
|
||||
}}
|
||||
></View>
|
||||
{signerName(data)}
|
||||
|
||||
<Text style={styles.textStyle}>
|
||||
Accessed from :
|
||||
<Text style={styles.textStyle2}>
|
||||
{data.ipAddress}
|
||||
</Text>
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<View style={{ display: "flex", flexDirection: "column" }}>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: "16px",
|
||||
fontWeight: "bold",
|
||||
color: "#31bceb",
|
||||
margin: "10px 0px 10px 0px"
|
||||
}}
|
||||
>
|
||||
Recipients
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Signers : <Text style={styles.textStyle2}>1</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Name :
|
||||
<Text style={styles.textStyle2}>
|
||||
{pdfData[0].ExtUserPtr.Name}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Email :
|
||||
<Text style={styles.textStyle2}>
|
||||
{pdfData[0].ExtUserPtr.Email}
|
||||
</Text>
|
||||
</Text>
|
||||
<Text style={styles.textStyle}>
|
||||
Accessed from :
|
||||
<Text style={styles.textStyle2}>
|
||||
{pdfData[0].AuditTrail &&
|
||||
pdfData[0].AuditTrail[0].ipAddress}
|
||||
</Text>
|
||||
</Text>
|
||||
|
||||
<Text style={styles.textStyle}>
|
||||
Signed on : {changeCompletedDate()}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Page>
|
||||
</Document>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default Certificate;
|
||||
@@ -30,15 +30,19 @@ function DraftDocument() {
|
||||
documentData === "Error: Something went wrong!" ||
|
||||
(documentData.result && documentData.result.error)
|
||||
) {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: "Error: Something went wrong!"
|
||||
});
|
||||
if (documentData?.result?.error?.includes("deleted")) {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: t("document-deleted")
|
||||
});
|
||||
} else {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setIsLoading({
|
||||
isLoader: false,
|
||||
message: "No data found!"
|
||||
});
|
||||
setIsLoading({ isLoader: false, message: t("no-data") });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
import React, { useState } from "react";
|
||||
import { getFileName } from "../../constant/Utils";
|
||||
import {
|
||||
getFileName
|
||||
} from "../../constant/Utils";
|
||||
maxDescriptionLength,
|
||||
maxNoteLength,
|
||||
maxTitleLength
|
||||
} from "../../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import SignersInput from "../shared/fields/SignersInput";
|
||||
|
||||
const EditTemplate = ({
|
||||
template,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const EditTemplate = ({ template, onSuccess }) => {
|
||||
const appName = "OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const [formData, setFormData] = useState({
|
||||
Name: template?.Name || "",
|
||||
@@ -58,12 +55,31 @@ const EditTemplate = ({
|
||||
alert(t("invalid-redirect-url"));
|
||||
return;
|
||||
}
|
||||
if (formData?.Name?.length > maxTitleLength) {
|
||||
alert(t("title-length-alert"));
|
||||
return;
|
||||
}
|
||||
if (formData?.Note?.length > maxNoteLength) {
|
||||
alert(t("note-length-alert"));
|
||||
return;
|
||||
}
|
||||
if (formData?.Description?.length > maxDescriptionLength) {
|
||||
alert(t("description-length-alert"));
|
||||
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 = {};
|
||||
const remindOnceInEvery = formData?.RemindOnceInEvery;
|
||||
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
|
||||
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
|
||||
if (AutoReminder && reminderCount > 15) {
|
||||
alert(t("only-15-reminder-allowed"));
|
||||
return;
|
||||
}
|
||||
if (AutoReminder) {
|
||||
const RemindOnceInEvery = parseInt(formData?.RemindOnceInEvery);
|
||||
const ReminderDate = new Date(template?.createdAt);
|
||||
@@ -189,7 +205,7 @@ const EditTemplate = ({
|
||||
<Tooltip id="istourenabled-tooltip" className="z-50">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("enable-tour")}</p>
|
||||
<p className="p-[5px]">
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">{t("yes")}: </span>
|
||||
@@ -200,7 +216,7 @@ const EditTemplate = ({
|
||||
<span>{t("istourenabled-help.p2")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
<p>{t("istourenabled-help.p3", { appName: appName })}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -247,11 +263,7 @@ const EditTemplate = ({
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={
|
||||
`flex items-center gap-2 ml-2 mb-1`
|
||||
}
|
||||
>
|
||||
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
@@ -260,11 +272,7 @@ const EditTemplate = ({
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
`flex items-center gap-2 ml-2 mb-1`
|
||||
}
|
||||
>
|
||||
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
|
||||
@@ -1,100 +1,34 @@
|
||||
import React, { useState } from "react";
|
||||
import axios from "axios";
|
||||
import { handleToPrint } from "../../constant/Utils";
|
||||
import { themeColor, emailRegex } from "../../constant/const";
|
||||
import { emailRegex } from "../../constant/const";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Parse from "parse";
|
||||
|
||||
function EmailComponent({
|
||||
isEmail,
|
||||
pdfUrl,
|
||||
setIsEmail,
|
||||
setSuccessEmail,
|
||||
pdfDetails,
|
||||
sender,
|
||||
setIsAlert,
|
||||
extUserId,
|
||||
setIsDownloadModal
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const [emailList, setEmailList] = useState([]);
|
||||
const [emailValue, setEmailValue] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [emailErr, setEmailErr] = useState(false);
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
const isAndroid = /Android/i.test(navigator.userAgent);
|
||||
|
||||
//function for send email
|
||||
const sendEmail = async () => {
|
||||
const pdfName = pdfDetails[0]?.Name;
|
||||
setIsLoading(true);
|
||||
let sendMail;
|
||||
const docId = pdfDetails?.[0]?.objectId || "";
|
||||
let presignedUrl = pdfUrl;
|
||||
try {
|
||||
const axiosRes = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
|
||||
{
|
||||
url: pdfUrl,
|
||||
docId: docId,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"content-type": "Application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
);
|
||||
presignedUrl = axiosRes.data.result;
|
||||
} catch (err) {
|
||||
console.log("err in getsignedurl", err);
|
||||
}
|
||||
for (let i = 0; i < emailList.length; i++) {
|
||||
try {
|
||||
let url = `${localStorage.getItem("baseUrl")}functions/sendmailv3`;
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
};
|
||||
const logo =
|
||||
`<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>`;
|
||||
const opurl =
|
||||
` <a href='www.opensignlabs.com' target=_blank>here</a>`;
|
||||
|
||||
let params = {
|
||||
extUserId: extUserId,
|
||||
pdfName: pdfName,
|
||||
url: presignedUrl,
|
||||
recipient: emailList[i],
|
||||
subject: `${sender.name} has signed the doc - ${pdfName}`,
|
||||
replyto:
|
||||
pdfDetails?.[0]?.ExtUserPtr?.Email ||
|
||||
"",
|
||||
from:
|
||||
sender.email,
|
||||
html:
|
||||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'><div>` +
|
||||
`${logo}</div><div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document Copy</p></div><div>` +
|
||||
`<p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>${pdfName}</strong> is attached to this email. Kindly download the document from the attachment.</p>` +
|
||||
`</div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, please contact the sender ${sender.email} directly. ` +
|
||||
`If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`
|
||||
};
|
||||
sendMail = await axios.post(url, params, { headers: headers });
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
setIsLoading(false);
|
||||
setIsEmail(false);
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
alertMessage: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
}
|
||||
if (sendMail?.data?.result?.status === "success") {
|
||||
const params = { docId: pdfDetails?.[0]?.objectId, recipients: emailList };
|
||||
const sendmail = await Parse.Cloud.run("forwarddoc", params);
|
||||
console.log("sendmail ", sendmail);
|
||||
if (sendmail?.status === "success") {
|
||||
setSuccessEmail(true);
|
||||
setIsEmail(false);
|
||||
setTimeout(() => {
|
||||
@@ -173,7 +107,7 @@ function EmailComponent({
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between items-center py-[10px] px-[20px] border-b-[1px] border-base-content">
|
||||
<span className="text-base-content font-semibold">
|
||||
<span className="text-base-content font-bold text-sm md:text-lg">
|
||||
{t("successfully-signed")}
|
||||
</span>
|
||||
<div className="flex flex-row">
|
||||
@@ -182,14 +116,14 @@ function EmailComponent({
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, setIsDownloading, pdfDetails)
|
||||
}
|
||||
className="op-btn op-btn-neutral op-btn-sm text-[15px]"
|
||||
className="op-btn op-btn-neutral op-btn-sm text-xs md:text-[15px]"
|
||||
>
|
||||
<i className="fa-light fa-print" aria-hidden="true"></i>
|
||||
{t("print")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="op-btn op-btn-primary op-btn-sm text-[15px] ml-2"
|
||||
className="op-btn op-btn-primary op-btn-sm text-xs md:text-[15px] ml-2"
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
setIsDownloadModal(true);
|
||||
@@ -205,12 +139,12 @@ function EmailComponent({
|
||||
{t("email-mssg")}
|
||||
</p>
|
||||
{emailList.length > 0 ? (
|
||||
<div className="p-0 border-[1.5px] op-border-primary rounded w-full text-[15px]">
|
||||
<div className="p-0 border-[1px] op-border-primary w-full rounded-md text-[15px] overflow-hidden">
|
||||
<div className="flex flex-row flex-wrap">
|
||||
{emailList.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-row items-center op-bg-primary m-[4px] rounded-md py-[5px] px-[10px]"
|
||||
className="flex flex-row items-center op-bg-primary mx-[2px] mt-[2px] rounded-md py-[5px] px-[10px]"
|
||||
key={ind}
|
||||
>
|
||||
<span className="text-base-100 text-[13px]">
|
||||
@@ -230,7 +164,7 @@ function EmailComponent({
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded w-full text-[15px] bg-transparent outline-none"
|
||||
className="p-[10px] rounded-md w-full text-[15px] bg-transparent outline-none"
|
||||
onChange={handleEmailValue}
|
||||
onKeyDown={handleEnterPress}
|
||||
onBlur={() => emailValue && handleEnterPress("add")}
|
||||
@@ -247,7 +181,7 @@ function EmailComponent({
|
||||
<input
|
||||
type="email"
|
||||
value={emailValue}
|
||||
className="p-[10px] pb-[20px] rounded w-full text-[15px] outline-none bg-transparent border-[1.5px] op-border-primary"
|
||||
className="p-[10px] pb-[20px] rounded-md w-full text-[15px] outline-none bg-transparent border-[1px] op-border-primary"
|
||||
onChange={handleEmailValue}
|
||||
onKeyDown={handleEnterPress}
|
||||
placeholder={t("enter-email-plaholder")}
|
||||
@@ -265,7 +199,7 @@ function EmailComponent({
|
||||
{t("email-error-1")}
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
{/* <button
|
||||
className={`${
|
||||
emailValue ? "cursor-pointer" : "cursor-default"
|
||||
} op-btn op-btn-primary op-btn-sm m-2 shadow-md`}
|
||||
@@ -273,26 +207,27 @@ function EmailComponent({
|
||||
>
|
||||
<i className="fa-light fa-plus" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<div className="bg-[#e3e2e1] mt-[10px] p-[5px] rounded">
|
||||
<span className="font-bold">{t("report-heading.Note")}: </span>
|
||||
<span className="text-[15px]">{t("email-error-2")}</span>
|
||||
</div>
|
||||
<hr className="w-full my-[15px] bg-base-content" />
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => emailList.length > 0 && sendEmail()}
|
||||
>
|
||||
{t("send")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
<hr className="w-full my-[15px] bg-base-content" /> */}
|
||||
<div className="mt-2">
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-secondary"
|
||||
onClick={() => emailList.length > 0 && sendEmail()}
|
||||
>
|
||||
{t("send")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost ml-2"
|
||||
onClick={() => handleClose()}
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
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" },
|
||||
{ title: "Angular", icon: "fa-brands fa-angular", color: "#ff5733" }
|
||||
];
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
// State to track if the code has been copied
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const reactCode = [
|
||||
{
|
||||
id: 0,
|
||||
title: "Installation",
|
||||
codeString: `npm install @opensign/react`
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "Usage",
|
||||
codeString: `
|
||||
import React from "react";
|
||||
import Opensign from "@opensign/react";
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="app">
|
||||
<Opensign
|
||||
onLoad={() => console.log("success")}
|
||||
onLoadError={(error) => console.log(error)}
|
||||
templateId= "${props.templateId ? props.templateId : "#templateId"}"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
];
|
||||
|
||||
const angularCode = [
|
||||
{
|
||||
id: 0,
|
||||
title: "Installation",
|
||||
codeString: `npm install @opensign/angular`
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "Usage",
|
||||
codeString: `
|
||||
import { Component } from '@angular/core';
|
||||
import { OpensignComponent } from "@opensign/angular"
|
||||
|
||||
@Component({
|
||||
selector:'app-root',
|
||||
standalone: true,
|
||||
imports: [OpensignComponent],
|
||||
template:\`<opensign templateId="${props.templateId ? props.templateId : "#templateId"}"
|
||||
(onLoad)="handleLoad()"
|
||||
(onLoadError)="handleError($event)"
|
||||
></opensign>\`,
|
||||
})
|
||||
export class AppComponent {
|
||||
handleLoad() {
|
||||
console.log("success");
|
||||
}
|
||||
handleError(error: string) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
];
|
||||
const jsCodeString = `
|
||||
<script
|
||||
src= "${window.location.origin}/static/js/public-template.bundle.js"
|
||||
id="opensign-script"
|
||||
templateId=${props.templateId ? props.templateId : "#templateId"}
|
||||
></script>
|
||||
`;
|
||||
|
||||
const handleCopy = (code, ind) => {
|
||||
copytoData(code);
|
||||
setIsCopied({ ...isCopied, [ind]: true });
|
||||
setTimeout(() => setIsCopied(false), 3000);
|
||||
};
|
||||
|
||||
return (
|
||||
<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) => (
|
||||
<div
|
||||
onClick={() => setActiveTab(ind)}
|
||||
key={ind}
|
||||
role="tab"
|
||||
className={`${
|
||||
activeTab === ind ? "op-tab-active" : ""
|
||||
} op-tab flex items-center pb-10 md:pb-0`}
|
||||
>
|
||||
<i
|
||||
className={`${tabData.icon}`}
|
||||
style={{ color: tabData.color }}
|
||||
></i>
|
||||
<span className="ml-1 text-[10px] font-medium md:font-normal md:text-[15px]">
|
||||
{tabData.title}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{activeTab === 0 ? (
|
||||
<div className="mt-4">
|
||||
{reactCode.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind}>
|
||||
<p className="font-medium text-[18px]">
|
||||
{t(`${data.title}`)}
|
||||
</p>
|
||||
{ind === 0 && (
|
||||
<p className="text-[15px] mt-2">
|
||||
{t("public-template-mssg-1")}
|
||||
</p>
|
||||
)}
|
||||
<div className="relative p-1">
|
||||
<div
|
||||
onClick={() => handleCopy(data.codeString, ind)}
|
||||
className="absolute top-[20px] right-[20px] cursor-pointer"
|
||||
>
|
||||
<i className="fa-light fa-copy text-white mr-[2px]" />
|
||||
<span className=" text-white">
|
||||
{isCopied[ind] ? t("copied-code") : t("copy-code")}
|
||||
</span>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
customStyle={{
|
||||
borderRadius: "15px"
|
||||
}}
|
||||
language="javascript"
|
||||
style={tomorrow}
|
||||
>
|
||||
{data.codeString}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</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>
|
||||
)}
|
||||
<p className="font-medium mt-3 text-[15px]">
|
||||
{t("public-template-mssg-3")}
|
||||
</p>
|
||||
<p className="my-[6px]">
|
||||
{" "}
|
||||
{t("public-template-mssg-4")}
|
||||
<a
|
||||
href="https://www.npmjs.com/package/@opensign/react"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="cursor-pointer text-blue-700 "
|
||||
>
|
||||
{" "}
|
||||
OpenSign React package{" "}
|
||||
</a>
|
||||
{t("public-template-mssg-5")}
|
||||
</p>
|
||||
</div>
|
||||
) : activeTab === 1 ? (
|
||||
<div className="mt-4">
|
||||
<div>
|
||||
<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)}
|
||||
className="absolute top-[20px] right-[20px] cursor-pointer"
|
||||
>
|
||||
<i className="fa-light fa-copy text-white mr-[2px]" />
|
||||
<span className=" text-white">
|
||||
{isCopied[0] ? t("copied-code") : t("copy-code")}
|
||||
</span>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
customStyle={{
|
||||
borderRadius: "15px"
|
||||
}}
|
||||
language="javascript"
|
||||
style={tomorrow}
|
||||
>
|
||||
{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>
|
||||
) : (
|
||||
activeTab === 2 && (
|
||||
<div className="mt-4">
|
||||
{angularCode.map((data, ind) => {
|
||||
return (
|
||||
<div key={ind}>
|
||||
<p className="font-medium text-[18px]">
|
||||
{t(`${data.title}`)}
|
||||
</p>
|
||||
{ind === 0 && (
|
||||
<p className="text-[15px] mt-2">
|
||||
{t("angular-npm-mssg-1")}
|
||||
</p>
|
||||
)}
|
||||
<div className="relative p-1">
|
||||
<div
|
||||
onClick={() => handleCopy(data.codeString, ind)}
|
||||
className="absolute top-[20px] right-[20px] cursor-pointer"
|
||||
>
|
||||
<i className="fa-light fa-copy text-white mr-[2px]" />
|
||||
<span className=" text-white">
|
||||
{isCopied[ind] ? t("copied-code") : t("copy-code")}
|
||||
</span>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
customStyle={{
|
||||
borderRadius: "15px"
|
||||
}}
|
||||
language="javascript"
|
||||
style={tomorrow}
|
||||
>
|
||||
{data.codeString}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</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>
|
||||
)}
|
||||
<p className="font-medium mt-3 text-[15px]">
|
||||
{t("public-template-mssg-3")}
|
||||
</p>
|
||||
<p className="my-[6px]">
|
||||
{" "}
|
||||
{t("public-template-mssg-4")}
|
||||
<a
|
||||
href="https://www.npmjs.com/package/@opensign/angular"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="cursor-pointer text-blue-700 "
|
||||
>
|
||||
{" "}
|
||||
OpenSign Angular package{" "}
|
||||
</a>
|
||||
{t("public-template-mssg-5")}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmbedTab;
|
||||
@@ -14,6 +14,7 @@ import ModalUi from "../../primitives/ModalUi";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
|
||||
function Header(props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -50,6 +51,13 @@ function Header(props) {
|
||||
}
|
||||
};
|
||||
|
||||
// `removeFile` is used to remove file if exists
|
||||
const removeFile = (e) => {
|
||||
if (e) {
|
||||
e.target.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) {
|
||||
@@ -60,6 +68,13 @@ function Header(props) {
|
||||
alert("Only PDF files are allowed.");
|
||||
return;
|
||||
}
|
||||
|
||||
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uploadedPdfBytes = await file.arrayBuffer();
|
||||
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "../../constant/Utils";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
|
||||
function PdfZoom(props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -41,6 +42,14 @@ function PdfZoom(props) {
|
||||
console.log("error in delete pdf page", e);
|
||||
}
|
||||
};
|
||||
|
||||
// `removeFile` is used to remove file if exists
|
||||
const removeFile = (e) => {
|
||||
if (e) {
|
||||
e.target.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) {
|
||||
@@ -51,6 +60,12 @@ function PdfZoom(props) {
|
||||
alert("Only PDF files are allowed.");
|
||||
return;
|
||||
}
|
||||
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uploadedPdfBytes = await file.arrayBuffer();
|
||||
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||
|
||||
@@ -25,6 +25,8 @@ const selectFormat = (data) => {
|
||||
switch (data) {
|
||||
case "L":
|
||||
return "MM/dd/yyyy";
|
||||
case "MM/DD/YYYY":
|
||||
return "MM/dd/yyyy";
|
||||
case "DD-MM-YYYY":
|
||||
return "dd-MM-yyyy";
|
||||
case "DD/MM/YYYY":
|
||||
@@ -41,6 +43,8 @@ const selectFormat = (data) => {
|
||||
return "MM.dd.yyyy";
|
||||
case "MMM DD, YYYY":
|
||||
return "MMM dd, yyyy";
|
||||
case "MMMM DD, YYYY":
|
||||
return "MMMM dd, yyyy";
|
||||
case "DD MMMM, YYYY":
|
||||
return "dd MMMM, yyyy";
|
||||
default:
|
||||
@@ -74,25 +78,24 @@ const changeDateToMomentFormat = (format) => {
|
||||
return "L";
|
||||
}
|
||||
};
|
||||
|
||||
//function to get default date
|
||||
const getDefaultdate = (selectedDate, format = "dd-MM-yyyy") => {
|
||||
let date;
|
||||
if (format && format === "dd-MM-yyyy") {
|
||||
const newdate = selectedDate
|
||||
? selectedDate
|
||||
: moment(new Date()).format(changeDateToMomentFormat(format));
|
||||
const [day, month, year] = newdate.split("-");
|
||||
date = new Date(`${year}-${month}-${day}`);
|
||||
} else {
|
||||
date = new Date(selectedDate);
|
||||
}
|
||||
const value = date;
|
||||
return value;
|
||||
};
|
||||
//function to get default format
|
||||
const getDefaultFormat = (dateFormat) => dateFormat || "MM/dd/yyyy";
|
||||
|
||||
//function to convert formated date to new Date() format
|
||||
const getDefaultDate = (dateStr, format) => {
|
||||
//get valid date format for moment to convert formated date to new Date() format
|
||||
const formats = changeDateToMomentFormat(format);
|
||||
const parsedDate = moment(dateStr, formats);
|
||||
let date;
|
||||
if (parsedDate.isValid()) {
|
||||
date = new Date(parsedDate.toISOString());
|
||||
return date;
|
||||
} else {
|
||||
date = new Date();
|
||||
return date;
|
||||
}
|
||||
};
|
||||
|
||||
function Placeholder(props) {
|
||||
//'isTouchDevice' is used to detect whether a device has a touchscreen or is mouse-based
|
||||
const isTouchDevice = navigator.maxTouchPoints > 0;
|
||||
@@ -104,28 +107,16 @@ function Placeholder(props) {
|
||||
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"
|
||||
? moment(
|
||||
getDefaultdate(
|
||||
props?.pos?.options?.response,
|
||||
props.pos?.options?.validation?.format
|
||||
).getTime()
|
||||
).format(
|
||||
changeDateToMomentFormat(props.pos?.options?.validation?.format)
|
||||
)
|
||||
: "",
|
||||
format:
|
||||
props.pos.type === "date"
|
||||
? getDefaultFormat(props.pos?.options?.validation?.format)
|
||||
: ""
|
||||
});
|
||||
const [selectDate, setSelectDate] = useState({});
|
||||
const [dateFormat, setDateFormat] = useState([]);
|
||||
const [clickonWidget, setClickonWidget] = useState({});
|
||||
const [startDate, setStartDate] = useState(
|
||||
props.pos.type === "date" &&
|
||||
getDefaultdate(new Date(), props.pos?.options?.validation?.format)
|
||||
props?.pos?.options?.response
|
||||
? getDefaultDate(
|
||||
props?.pos?.options?.response,
|
||||
props.pos?.options?.validation?.format
|
||||
)
|
||||
: new Date()
|
||||
);
|
||||
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
|
||||
width: null,
|
||||
@@ -164,7 +155,6 @@ function Placeholder(props) {
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [props.pos]);
|
||||
|
||||
useEffect(() => {
|
||||
const onOutsideClick = () => {
|
||||
if (!isDraggingEnabled) {
|
||||
@@ -453,7 +443,9 @@ function Placeholder(props) {
|
||||
|
||||
//checking widget's type and open widget copy modal for required widgets
|
||||
if (
|
||||
["signature", textWidget, "stamp", "initials"].includes(props.pos.type)
|
||||
["signature", textInputWidget, textWidget, "stamp", "initials"].includes(
|
||||
props.pos.type
|
||||
)
|
||||
) {
|
||||
props.setIsPageCopy(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
@@ -476,7 +468,7 @@ function Placeholder(props) {
|
||||
const isDateChange = true;
|
||||
const dateObj = {
|
||||
date: startDate,
|
||||
format: selectDate.format
|
||||
format: getDefaultFormat(props.pos?.options?.validation?.format)
|
||||
};
|
||||
handleSaveDate(dateObj, isDateChange); //function to save date and format in local array
|
||||
}
|
||||
@@ -485,19 +477,18 @@ function Placeholder(props) {
|
||||
//function to save date and format on local array onchange date and onclick format
|
||||
const handleSaveDate = (data, isDateChange) => {
|
||||
let updateDate = data.date;
|
||||
//check if date change by user
|
||||
if (isDateChange) {
|
||||
//`changeDateToMomentFormat` is used to convert date as per required to moment package
|
||||
updateDate = moment(data.date).format(
|
||||
changeDateToMomentFormat(data.format)
|
||||
let date;
|
||||
if (data?.format === "dd-MM-yyyy") {
|
||||
date = isDateChange
|
||||
? moment(updateDate).format(changeDateToMomentFormat(data.format))
|
||||
: updateDate;
|
||||
} else {
|
||||
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
|
||||
const newDate = new Date(updateDate);
|
||||
date = moment(newDate.getTime()).format(
|
||||
changeDateToMomentFormat(data?.format)
|
||||
);
|
||||
}
|
||||
//using moment package is used to change date as per the format provided in selectDate obj e.g. - MM/dd/yyyy -> 03/12/2024
|
||||
//`getDefaultdate` is used to convert update date in new Date() format
|
||||
const date = moment(
|
||||
getDefaultdate(updateDate, data?.format).getTime()
|
||||
).format(changeDateToMomentFormat(data?.format));
|
||||
|
||||
//`onChangeInput` is used to save data related to date in a placeholder field
|
||||
onChangeInput(
|
||||
date,
|
||||
@@ -1100,7 +1091,7 @@ function Placeholder(props) {
|
||||
isSignYourself={props.isSignYourself}
|
||||
isSelfSign={props.isSelfSign}
|
||||
signerObjId={props.signerObjId}
|
||||
handleUserName={props.handleUserName}
|
||||
calculateFontsize={props.calculateFontsize}
|
||||
pdfDetails={props?.pdfDetails && props?.pdfDetails[0]}
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
|
||||
@@ -24,12 +24,13 @@ const widgetCls =
|
||||
function PlaceholderType(props) {
|
||||
const { t } = useTranslation();
|
||||
const type = props?.pos?.type;
|
||||
const widgetTypeTraslation = t(`widgets-name.${props?.pos?.type}`);
|
||||
const widgetTypeTranslation = t(`widgets-name.${props?.pos?.type}`);
|
||||
const [selectOption, setSelectOption] = useState("");
|
||||
const [validatePlaceholder, setValidatePlaceholder] = useState("");
|
||||
const inputRef = useRef(null);
|
||||
const [textValue, setTextValue] = useState();
|
||||
const [selectedCheckbox, setSelectedCheckbox] = useState([]);
|
||||
const [hint, setHint] = useState("");
|
||||
const years = range(1950, getYear(new Date()) + 16, 1);
|
||||
const fontSize = props.calculateFont(props.pos.options?.fontSize);
|
||||
const fontColor = props.pos.options?.fontColor || "black";
|
||||
@@ -150,6 +151,11 @@ function PlaceholderType(props) {
|
||||
if (defaultData) {
|
||||
setTextValue(defaultData);
|
||||
}
|
||||
if (props.pos?.options?.hint) {
|
||||
setHint(props.pos?.options.hint);
|
||||
} else {
|
||||
setHint(props.pos?.type);
|
||||
}
|
||||
} else if ([textInputWidget].includes(props.pos?.type)) {
|
||||
const defaultData = props.pos?.options?.defaultValue;
|
||||
if (defaultData) {
|
||||
@@ -158,7 +164,6 @@ function PlaceholderType(props) {
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.pos?.options?.defaultValue]);
|
||||
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
style={{
|
||||
@@ -327,13 +332,20 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation,
|
||||
props.pos
|
||||
)}
|
||||
{props.pos.type && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos
|
||||
? props.calculateFontsize(props.pos)
|
||||
: "11px"
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "stamp":
|
||||
@@ -346,13 +358,20 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation,
|
||||
props.pos
|
||||
)}
|
||||
{props.pos.type && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos
|
||||
? props.calculateFontsize(props.pos)
|
||||
: "11px"
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "checkbox":
|
||||
@@ -456,7 +475,7 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{textValue || widgetTypeTraslation}</span>
|
||||
<span>{textValue || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "dropdown":
|
||||
@@ -519,7 +538,7 @@ function PlaceholderType(props) {
|
||||
>
|
||||
{props.pos?.options?.name
|
||||
? props.pos.options.name
|
||||
: widgetTypeTraslation}
|
||||
: widgetTypeTranslation}
|
||||
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
|
||||
</div>
|
||||
);
|
||||
@@ -533,13 +552,20 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation,
|
||||
props.pos
|
||||
)}
|
||||
{props.pos.type && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos
|
||||
? props.calculateFontsize(props.pos)
|
||||
: "11px"
|
||||
}}
|
||||
className="font-medium text-center"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "name":
|
||||
@@ -548,7 +574,7 @@ function PlaceholderType(props) {
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={t("widgets-name.name")}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
@@ -571,7 +597,7 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
<span>{widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "company":
|
||||
@@ -580,7 +606,7 @@ function PlaceholderType(props) {
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={t("widgets-name.company")}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
@@ -602,7 +628,7 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
<span>{widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "job title":
|
||||
@@ -611,7 +637,7 @@ function PlaceholderType(props) {
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={t("widgets-name.job title")}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
@@ -633,7 +659,7 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
<span>{widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "date":
|
||||
@@ -712,13 +738,20 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
widgetTypeTraslation,
|
||||
props.pos
|
||||
)}
|
||||
{props.pos.type && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos
|
||||
? props.calculateFontsize(props.pos)
|
||||
: "11px"
|
||||
}}
|
||||
className="font-medium text-center"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "email":
|
||||
@@ -727,7 +760,7 @@ function PlaceholderType(props) {
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={t("widgets-name.email")}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
onKeyDown={(e) => {
|
||||
// Prevent new line on Enter key press
|
||||
@@ -759,7 +792,7 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{widgetTypeTraslation}</span>
|
||||
<span>{widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
@@ -848,13 +881,20 @@ function PlaceholderType(props) {
|
||||
) : (
|
||||
<div className={widgetCls}>
|
||||
{props.pos.isStamp ? <div>stamp</div> : <div>signature</div>}
|
||||
{props?.handleUserName &&
|
||||
props?.handleUserName(
|
||||
props?.data?.Id,
|
||||
props?.data?.Role,
|
||||
null,
|
||||
props.pos
|
||||
)}
|
||||
{props.pos.type && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: props.pos
|
||||
? props.calculateFontsize(props.pos)
|
||||
: "11px"
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Document, Page } from "react-pdf";
|
||||
import { useSelector } from "react-redux";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { base64ToArrayBuffer } from "../../constant/Utils";
|
||||
import { maxFileSize } from "../../constant/const";
|
||||
|
||||
function RenderAllPdfPage(props) {
|
||||
const { t } = useTranslation();
|
||||
@@ -62,6 +63,12 @@ function RenderAllPdfPage(props) {
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${props?.pdfBase64Url}`;
|
||||
|
||||
// `removeFile` is used to remove file if exists
|
||||
const removeFile = (e) => {
|
||||
if (e) {
|
||||
e.target.value = "";
|
||||
}
|
||||
};
|
||||
// `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];
|
||||
@@ -73,6 +80,12 @@ function RenderAllPdfPage(props) {
|
||||
alert("Only PDF files are allowed.");
|
||||
return;
|
||||
}
|
||||
const mb = Math.round(file?.size / Math.pow(1024, 2));
|
||||
if (mb > maxFileSize) {
|
||||
alert(`${t("file-alert-1")} ${maxFileSize} MB`);
|
||||
removeFile(e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uploadedPdfBytes = await file.arrayBuffer();
|
||||
const uploadedPdfDoc = await PDFDocument.load(uploadedPdfBytes, {
|
||||
@@ -110,6 +123,7 @@ function RenderAllPdfPage(props) {
|
||||
autoSignScroll hide-scrollbar max-h-[100vh] `}
|
||||
>
|
||||
<Document
|
||||
error=""
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={onDocumentLoad}
|
||||
file={pdfDataBase64}
|
||||
|
||||
@@ -89,35 +89,6 @@ function RenderPdf(props) {
|
||||
sign?.Id === data?.Id || sign?.objectId === data?.signerObjId
|
||||
)
|
||||
: [];
|
||||
const handleAllUserName = (Id, Role, type, pos) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="text-black text-[8px] font-bold">
|
||||
{
|
||||
props.pdfDetails[0].Signers?.find(
|
||||
(signer) => signer.objectId === data.signerObjId
|
||||
)?.Name
|
||||
}
|
||||
</div>
|
||||
{type && (
|
||||
<div
|
||||
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
|
||||
className="font-bold"
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
)}
|
||||
{Role && (
|
||||
<div
|
||||
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
|
||||
className="text-black font-medium"
|
||||
>
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
return (
|
||||
checkSign.length === 0 &&
|
||||
data?.placeHolder?.map((placeData, key) => {
|
||||
@@ -148,7 +119,6 @@ function RenderPdf(props) {
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
handleUserName={handleAllUserName}
|
||||
isDragging={props.isDragging}
|
||||
pdfDetails={props.pdfDetails}
|
||||
setIsInitial={props.setIsInitial}
|
||||
@@ -185,6 +155,7 @@ function RenderPdf(props) {
|
||||
fontColor={props.fontColor}
|
||||
setFontColor={props.setFontColor}
|
||||
setRequestSignTour={props.setRequestSignTour}
|
||||
calculateFontsize={calculateFontsize}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
@@ -206,73 +177,6 @@ function RenderPdf(props) {
|
||||
return `${width / 10}px`;
|
||||
}
|
||||
};
|
||||
//function for render placeholder block over pdf document
|
||||
|
||||
const handleUserName = (Id, Role, type, pos) => {
|
||||
if (Id) {
|
||||
const checkSign = props.signersdata.find((sign) => sign.Id === Id);
|
||||
if (checkSign?.Name) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
fontSize: pos ? calculateFontsize(pos) : "8px"
|
||||
}}
|
||||
className="text-black font-medium"
|
||||
>
|
||||
{checkSign?.Name}
|
||||
</div>
|
||||
{type && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: pos ? calculateFontsize(pos) : "11px"
|
||||
}}
|
||||
className=" font-bold"
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
)}
|
||||
{Role && (
|
||||
<div
|
||||
style={{
|
||||
fontSize: pos ? calculateFontsize(pos) : "8px"
|
||||
}}
|
||||
className="text-black text-[8px] font-medium"
|
||||
>
|
||||
{`(${Role})`}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{type && (
|
||||
<div
|
||||
style={{ fontSize: pos ? calculateFontsize(pos) : "11px" }}
|
||||
className=" font-bold"
|
||||
>
|
||||
{type}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
style={{ fontSize: pos ? calculateFontsize(pos) : "8px" }}
|
||||
className="text-black font-medium"
|
||||
>
|
||||
{Role}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{type && <div className="text-[11px] font-bold">{type}</div>}
|
||||
<div className="text-black text-[8px] font-medium">{Role}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
const pdfDataBase64 = `data:application/pdf;base64,${props.pdfBase64Url}`;
|
||||
//calculate render height of pdf in mobile view
|
||||
const handlePageLoadSuccess = (page) => {
|
||||
@@ -361,7 +265,6 @@ function RenderPdf(props) {
|
||||
handleLinkUser={
|
||||
props.handleLinkUser
|
||||
}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
@@ -403,6 +306,9 @@ function RenderPdf(props) {
|
||||
props.unSignedWidgetId
|
||||
}
|
||||
isFreeResize={true}
|
||||
calculateFontsize={
|
||||
calculateFontsize
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -448,7 +354,6 @@ function RenderPdf(props) {
|
||||
setWidgetType={props.setWidgetType}
|
||||
setSelectWidgetId={props.setSelectWidgetId}
|
||||
selectWidgetId={props.selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setCurrWidgetsDetails={
|
||||
@@ -468,6 +373,7 @@ function RenderPdf(props) {
|
||||
setIsResize={props.setIsResize}
|
||||
isFreeResize={false}
|
||||
isOpenSignPad={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@@ -475,17 +381,15 @@ function RenderPdf(props) {
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
|
||||
{/* Mobile */}
|
||||
<Document
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadError={() => props.setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
// ref={pdfRef}'
|
||||
onClick={() => {
|
||||
if (props.setSelectWidgetId) {
|
||||
props.setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
onClick={() =>
|
||||
props.setSelectWidgetId && props.setSelectWidgetId("")
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
<Page
|
||||
@@ -573,7 +477,6 @@ function RenderPdf(props) {
|
||||
handleLinkUser={
|
||||
props.handleLinkUser
|
||||
}
|
||||
handleUserName={handleUserName}
|
||||
isSignYourself={false}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
@@ -615,6 +518,9 @@ function RenderPdf(props) {
|
||||
props.unSignedWidgetId
|
||||
}
|
||||
isFreeResize={true}
|
||||
calculateFontsize={
|
||||
calculateFontsize
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -665,7 +571,6 @@ function RenderPdf(props) {
|
||||
setWidgetType={props.setWidgetType}
|
||||
setSelectWidgetId={props.setSelectWidgetId}
|
||||
selectWidgetId={props.selectWidgetId}
|
||||
handleUserName={handleUserName}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setCurrWidgetsDetails={
|
||||
@@ -686,6 +591,7 @@ function RenderPdf(props) {
|
||||
setIsResize={props.setIsResize}
|
||||
isFreeResize={false}
|
||||
isOpenSignPad={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -693,17 +599,16 @@ function RenderPdf(props) {
|
||||
</React.Fragment>
|
||||
);
|
||||
}))}
|
||||
|
||||
{/* large device */}
|
||||
{/* this component for render pdf document is in middle of the component */}
|
||||
<Document
|
||||
error={<p className="mx-2">{t("failed-to-load-refresh-page")}</p>}
|
||||
onLoadError={() => props.setPdfLoad(false)}
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
onClick={() => {
|
||||
if (props.setSelectWidgetId) {
|
||||
props.setSelectWidgetId("");
|
||||
}
|
||||
}}
|
||||
onClick={() =>
|
||||
props.setSelectWidgetId && props.setSelectWidgetId("")
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
<Page
|
||||
|
||||
@@ -31,7 +31,11 @@ const WidgetNameModal = (props) => {
|
||||
name: props.defaultdata?.options?.name || "",
|
||||
defaultValue: props.defaultdata?.options?.defaultValue || "",
|
||||
status: props.defaultdata?.options?.status || "required",
|
||||
hint: props.defaultdata?.options?.hint || "",
|
||||
hint:
|
||||
props.defaultdata?.options?.hint ||
|
||||
(props.defaultdata?.type === textInputWidget
|
||||
? "Enter text"
|
||||
: `Enter ${props.defaultdata?.options?.name}`),
|
||||
textvalidate:
|
||||
props.defaultdata?.options?.validation?.type === "regex"
|
||||
? props.defaultdata?.options?.validation?.pattern
|
||||
@@ -113,7 +117,6 @@ const WidgetNameModal = (props) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleCheckboxChange = (index) => {
|
||||
// Update the state with the modified array
|
||||
setSignatureType((prev) =>
|
||||
@@ -272,12 +275,13 @@ const WidgetNameModal = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{props.defaultdata?.type === textInputWidget && (
|
||||
{props.defaultdata?.type !== textWidget && (
|
||||
<div className="mb-[0.75rem]">
|
||||
<label htmlFor="hint" className="text-[13px]">
|
||||
{t("hint")}
|
||||
</label>
|
||||
<input
|
||||
maxLength={40}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
name="hint"
|
||||
value={formdata.hint}
|
||||
@@ -295,7 +299,7 @@ const WidgetNameModal = (props) => {
|
||||
].includes(props.defaultdata?.type) && (
|
||||
<div className="flex flex-col md:flex-row md:items-center gap-3 mb-3">
|
||||
<div className="flex items-center gap-2 ">
|
||||
<span>{t("font-size")}:</span>
|
||||
<span className="whitespace-nowrap">{t("font-size")}: </span>
|
||||
<select
|
||||
className="ml-[7px] w-[60%] op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content text-xs"
|
||||
value={
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import React, { useState } from "react";
|
||||
import { formatDateTime } from "../../../constant/Utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const DateFormatSelector = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const date = new Date();
|
||||
const [selectedFormat, setSelectedFormat] = useState(props.dateFormat);
|
||||
const [is12Hour, setIs12Hour] = useState(props?.is12HourTime);
|
||||
|
||||
const dateFormats = [
|
||||
"MM/DD/YYYY",
|
||||
"MMMM DD, YYYY",
|
||||
"DD MMMM, YYYY",
|
||||
"DD-MM-YYYY",
|
||||
"DD MMM, YYYY",
|
||||
"YYYY-MM-DD",
|
||||
"MM-DD-YYYY",
|
||||
"MM.DD.YYYY",
|
||||
"MMM DD, YYYY"
|
||||
];
|
||||
|
||||
// Handle format change
|
||||
const handleFormatChange = (event) => {
|
||||
setSelectedFormat(event.target.value);
|
||||
props.setDateFormat && props.setDateFormat(event.target.value);
|
||||
};
|
||||
const handleHrInput = () => {
|
||||
setIs12Hour(!is12Hour);
|
||||
props.setIs12HourTime && props.setIs12HourTime(!is12Hour);
|
||||
};
|
||||
return (
|
||||
<div className="max-w-[400px] pr-[20px]">
|
||||
<label className="text-[14px] mb-[0.7rem] font-medium">
|
||||
{t("date-format")}
|
||||
</label>
|
||||
<select
|
||||
className="op-select op-select-bordered op-select-sm focus:outline-none hover:border-base-content w-full h-full text-[11px]"
|
||||
value={selectedFormat}
|
||||
onChange={handleFormatChange}
|
||||
>
|
||||
{dateFormats.map((format) => (
|
||||
<option key={format} value={format}>
|
||||
{format}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="flex flex-col md:flex-row md:gap-4 mt-[0.75rem] text-[12px]">
|
||||
<div className="flex items-center gap-2 ml-2">
|
||||
<input
|
||||
type="radio"
|
||||
value={true}
|
||||
className="op-radio op-radio-xs"
|
||||
checked={is12Hour}
|
||||
onChange={handleHrInput}
|
||||
/>
|
||||
<div className="text-center">12 hr</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-2">
|
||||
<input
|
||||
type="radio"
|
||||
value={false}
|
||||
className="op-radio op-radio-xs"
|
||||
checked={!is12Hour}
|
||||
onChange={handleHrInput}
|
||||
/>
|
||||
<div className="text-center">24 hr</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-[12px] ml-[10px] text-[13px] font-medium">
|
||||
<strong>
|
||||
{formatDateTime(date, selectedFormat, props?.timezone, is12Hour)}
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DateFormatSelector;
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import AsyncSelect from "react-select/async";
|
||||
import AddSigner from "../../AddSigner";
|
||||
import Parse from "parse";
|
||||
import AddContact from "../../../primitives/AddContact";
|
||||
import Tooltip from "../../../primitives/Tooltip";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { findContact } from "../../../constant/Utils";
|
||||
function arrayMove(array, from, to) {
|
||||
array = array.slice();
|
||||
array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
|
||||
@@ -103,17 +103,21 @@ const SignersInput = (props) => {
|
||||
|
||||
// `handleNewDetails` is used to set just save from quick form to selected option in dropdown
|
||||
const handleNewDetails = (data) => {
|
||||
setState([...state, data]);
|
||||
const user = {
|
||||
value: data["objectId"],
|
||||
label: data["Name"],
|
||||
email: data?.Email
|
||||
};
|
||||
setState([...state, user]);
|
||||
if (selected.length > 0) {
|
||||
setSelected([...selected, data]);
|
||||
setSelected([...selected, user]);
|
||||
} else {
|
||||
setSelected([data]);
|
||||
setSelected([user]);
|
||||
}
|
||||
};
|
||||
const loadOptions = async (inputValue) => {
|
||||
try {
|
||||
const params = { search: inputValue };
|
||||
const contactRes = await Parse.Cloud.run("getsigners", params);
|
||||
const contactRes = await findContact(inputValue);
|
||||
if (contactRes) {
|
||||
const res = JSON.parse(JSON.stringify(contactRes));
|
||||
//compareArrays is a function where compare between two array (total signersList and dcument signers list)
|
||||
@@ -148,7 +152,9 @@ const SignersInput = (props) => {
|
||||
{props.label ? props.label : t("signers")}
|
||||
{props.required && <span className="text-red-500 text-[13px]">*</span>}
|
||||
<span
|
||||
className={`z-[${props?.helptextZindex ? props.helptextZindex : 30}] absolute ml-1 text-xs`}
|
||||
className={`z-[${
|
||||
props?.helptextZindex ? props.helptextZindex : 30
|
||||
}] absolute ml-1 text-xs`}
|
||||
>
|
||||
<Tooltip
|
||||
id={`${props.label ? props.label : "signers"}-tooltip`}
|
||||
@@ -209,9 +215,8 @@ const SignersInput = (props) => {
|
||||
✕
|
||||
</button>
|
||||
{isModal && (
|
||||
<AddSigner
|
||||
valueKey={"objectId"}
|
||||
displayKey={"Name"}
|
||||
<AddContact
|
||||
isDisableTitle
|
||||
details={handleNewDetails}
|
||||
closePopup={handleModalCloseClick}
|
||||
/>
|
||||
|
||||
@@ -7,27 +7,10 @@ const TimezoneSelector = (props) => {
|
||||
// Intl.DateTimeFormat().resolvedOptions().timeZone // Default to the user's local timezone
|
||||
|
||||
const onChangeTimezone = (timezone) => {
|
||||
setSelectedTimezone(timezone);
|
||||
setSelectedTimezone(timezone?.value);
|
||||
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]">
|
||||
@@ -54,11 +37,6 @@ const TimezoneSelector = (props) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-[12px] ml-[10px] text-[13px]">
|
||||
<strong>
|
||||
{formatDate(new Date(), selectedTimezone?.value || selectedTimezone)}
|
||||
</strong>{" "}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,8 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { NavLink } from "react-router";
|
||||
|
||||
const Menu = ({ item, isOpen, closeSidebar }) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
@@ -18,13 +17,15 @@ const Menu = ({ item, isOpen, closeSidebar }) => {
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? " bg-base-300 text-base-content" : ""
|
||||
} flex items-center text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
} flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
tabIndex={isOpen ? 0 : -1}
|
||||
role="menuitem"
|
||||
>
|
||||
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
|
||||
<span className="w-[20px] h-[20px] flex justify-center">
|
||||
<i className={`${item.icon} text-[18px]`} aria-hidden="true"></i>
|
||||
</span>
|
||||
<span className="ml-3 lg:ml-4">
|
||||
{t(`sidebar.${item.title}`, { appName: drivename })}
|
||||
</span>
|
||||
|
||||
@@ -3,8 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import { NavLink } from "react-router";
|
||||
|
||||
const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
|
||||
const { t } = useTranslation();
|
||||
const { title, icon, children } = item;
|
||||
@@ -12,14 +11,16 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
<li role="none" className="my-0.5">
|
||||
<button
|
||||
onClick={() => toggleSubmenu(item.title)}
|
||||
className="flex items-center text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none "
|
||||
className="flex items-center justify-start text-left p-3 lg:p-4 text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none "
|
||||
aria-expanded={submenuOpen}
|
||||
aria-haspopup="true"
|
||||
aria-controls={`submenu-${title}`}
|
||||
>
|
||||
<i className={`${icon} text-[18px]`}></i>
|
||||
<span className="w-[20px] h-[20px] flex justify-center">
|
||||
<i className={`${icon} text-[18px]`}></i>
|
||||
</span>
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<span className="ml-3 lg:ml-4">
|
||||
<span className="ml-3 lg:ml-4 text-start">
|
||||
{t(`sidebar.${item.title}`, { appName })}
|
||||
</span>
|
||||
<i
|
||||
@@ -45,16 +46,18 @@ const Submenu = ({ item, closeSidebar, toggleSubmenu, submenuOpen }) => {
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? "bg-base-300 text-base-content" : ""
|
||||
} flex items-center text-left pl-6 md:pl-8 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
} flex items-center justify-start text-left pl-6 md:pl-8 py-2 text-sm cursor-pointer text-base-content hover:text-base-content focus:bg-base-300 hover:bg-base-300 hover:no-underline focus:outline-none`
|
||||
}
|
||||
onClick={closeSidebar}
|
||||
role="menuitem"
|
||||
tabIndex={submenuOpen ? 0 : -1}
|
||||
>
|
||||
<i
|
||||
className={`${childItem.icon} text-[18px]`}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="w-[15px] h-[15px] flex justify-center">
|
||||
<i
|
||||
className={`${childItem.icon} text-[18px]`}
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</span>
|
||||
<span className="ml-3 lg:ml-4">
|
||||
{t(`sidebar.${item.title}-Children.${childItem.title}`, {
|
||||
appName: drivename
|
||||
|
||||
+154
-137
@@ -7,9 +7,8 @@ import { appInfo } from "./appinfo";
|
||||
import { saveAs } from "file-saver";
|
||||
import printModule from "print-js";
|
||||
import fontkit from "@pdf-lib/fontkit";
|
||||
import {
|
||||
themeColor
|
||||
} from "./const";
|
||||
import { themeColor } from "./const";
|
||||
import { format, toZonedTime } from "date-fns-tz";
|
||||
|
||||
export const fontsizeArr = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28];
|
||||
export const fontColorArr = ["red", "black", "blue", "yellow"];
|
||||
@@ -136,14 +135,12 @@ export const pdfNewWidthFun = (divRef) => {
|
||||
};
|
||||
|
||||
//`contractUsers` function is used to get contract_User details
|
||||
export const contractUsers = async (
|
||||
) => {
|
||||
export const contractUsers = async () => {
|
||||
try {
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/getUserDetails`;
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
const accesstoken = localStorage.getItem("accesstoken");
|
||||
const token =
|
||||
{ "X-Parse-Session-Token": accesstoken };
|
||||
const token = { "X-Parse-Session-Token": accesstoken };
|
||||
const headers = {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -236,17 +233,47 @@ export const widgets = [
|
||||
{ type: "email", icon: "fa-light fa-envelope", iconSize: "20px" }
|
||||
];
|
||||
|
||||
export const getDate = () => {
|
||||
export const getDate = (dateformat) => {
|
||||
const format = dateformat || "MM/DD/YYYY";
|
||||
const date = new Date();
|
||||
const milliseconds = date.getTime();
|
||||
const newDate = moment(milliseconds).format("MM/DD/YYYY");
|
||||
const newDate = moment(milliseconds).format(format);
|
||||
return newDate;
|
||||
};
|
||||
export const addWidgetOptions = (type) => {
|
||||
const defaultOpt = {
|
||||
name: type,
|
||||
status: "required"
|
||||
};
|
||||
|
||||
export const selectFormat = (data) => {
|
||||
switch (data) {
|
||||
case "L":
|
||||
return "MM/dd/yyyy";
|
||||
case "MM/DD/YYYY":
|
||||
return "MM/dd/yyyy";
|
||||
case "DD-MM-YYYY":
|
||||
return "dd-MM-yyyy";
|
||||
case "DD/MM/YYYY":
|
||||
return "dd/MM/yyyy";
|
||||
case "LL":
|
||||
return "MMMM dd, yyyy";
|
||||
case "DD MMM, YYYY":
|
||||
return "dd MMM, yyyy";
|
||||
case "YYYY-MM-DD":
|
||||
return "yyyy-MM-dd";
|
||||
case "MM-DD-YYYY":
|
||||
return "MM-dd-yyyy";
|
||||
case "MM.DD.YYYY":
|
||||
return "MM.dd.yyyy";
|
||||
case "MMM DD, YYYY":
|
||||
return "MMM dd, yyyy";
|
||||
case "MMMM DD, YYYY":
|
||||
return "MMMM dd, yyyy";
|
||||
case "DD MMMM, YYYY":
|
||||
return "dd MMMM, yyyy";
|
||||
default:
|
||||
return "MM/dd/yyyy";
|
||||
}
|
||||
};
|
||||
|
||||
export const addWidgetOptions = (type, signer) => {
|
||||
const defaultOpt = { name: type, status: "required" };
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return defaultOpt;
|
||||
@@ -267,12 +294,16 @@ export const addWidgetOptions = (type) => {
|
||||
return { ...defaultOpt };
|
||||
case "job title":
|
||||
return { ...defaultOpt };
|
||||
case "date":
|
||||
case "date": {
|
||||
const dateFormat = signer?.DateFormat
|
||||
? selectFormat(signer?.DateFormat)
|
||||
: "MM/dd/yyyy";
|
||||
return {
|
||||
...defaultOpt,
|
||||
response: getDate(),
|
||||
validation: { format: "MM/dd/yyyy", type: "date-format" }
|
||||
response: getDate(signer?.DateFormat),
|
||||
validation: { format: dateFormat, type: "date-format" }
|
||||
};
|
||||
}
|
||||
case "image":
|
||||
return defaultOpt;
|
||||
case "email":
|
||||
@@ -293,7 +324,7 @@ export const addWidgetOptions = (type) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const addWidgetSelfsignOptions = (type, getWidgetValue) => {
|
||||
export const addWidgetSelfsignOptions = (type, getWidgetValue, owner) => {
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return { name: "signature" };
|
||||
@@ -323,12 +354,16 @@ export const addWidgetSelfsignOptions = (type, getWidgetValue) => {
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
case "date":
|
||||
case "date": {
|
||||
const dateFormat = owner?.DateFormat
|
||||
? selectFormat(owner?.DateFormat)
|
||||
: "MM/dd/yyyy";
|
||||
return {
|
||||
name: "date",
|
||||
response: getDate(),
|
||||
validation: { format: "MM/dd/yyyy", type: "date-format" }
|
||||
response: getDate(owner?.DateFormat),
|
||||
validation: { format: dateFormat, type: "date-format" }
|
||||
};
|
||||
}
|
||||
case "image":
|
||||
return { name: "image" };
|
||||
case "email":
|
||||
@@ -495,7 +530,7 @@ export const signPdfFun = async (
|
||||
documentId,
|
||||
signerObjectId,
|
||||
objectId,
|
||||
widgets,
|
||||
widgets
|
||||
) => {
|
||||
let isCustomCompletionMail = false;
|
||||
try {
|
||||
@@ -504,10 +539,7 @@ export const signPdfFun = async (
|
||||
if (tenantDetails && tenantDetails === "user does not exist!") {
|
||||
return { status: "error", message: "User does not exist." };
|
||||
} else {
|
||||
if (
|
||||
tenantDetails?.CompletionBody &&
|
||||
tenantDetails?.CompletionSubject
|
||||
) {
|
||||
if (tenantDetails?.CompletionBody && tenantDetails?.CompletionSubject) {
|
||||
isCustomCompletionMail = true;
|
||||
}
|
||||
}
|
||||
@@ -611,6 +643,15 @@ export const createDocument = async (
|
||||
const RedirectUrl = Doc?.RedirectUrl
|
||||
? { RedirectUrl: Doc?.RedirectUrl }
|
||||
: {};
|
||||
const TemplateId = Doc?.objectId
|
||||
? {
|
||||
TemplateId: {
|
||||
__type: "Pointer",
|
||||
className: "contracts_Template",
|
||||
objectId: Doc?.objectId
|
||||
}
|
||||
}
|
||||
: {};
|
||||
const data = {
|
||||
Name: Doc.Name,
|
||||
URL: pdfUrl,
|
||||
@@ -640,9 +681,16 @@ export const createDocument = async (
|
||||
...SignatureType,
|
||||
...NotifyOnSignatures,
|
||||
...Bcc,
|
||||
...RedirectUrl
|
||||
...RedirectUrl,
|
||||
...TemplateId
|
||||
};
|
||||
|
||||
const remindOnceInEvery = Doc?.RemindOnceInEvery;
|
||||
const TimeToCompleteDays = parseInt(Doc?.TimeToCompleteDays);
|
||||
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
|
||||
const AutomaticReminders = Doc.autoreminder;
|
||||
if (AutomaticReminders && reminderCount > 15) {
|
||||
return { status: "error", id: "only-15-reminder-allowed" };
|
||||
}
|
||||
try {
|
||||
const res = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}classes/contracts_Document`,
|
||||
@@ -660,7 +708,7 @@ export const createDocument = async (
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("axois err ", err);
|
||||
return { status: "error", id: "Something Went Wrong!" };
|
||||
return { status: "error", id: "something-went-wrong-mssg" };
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -933,10 +981,7 @@ export const calculateInitialWidthHeight = (widgetData) => {
|
||||
const height = span.offsetHeight;
|
||||
|
||||
document.body.removeChild(span);
|
||||
return {
|
||||
getWidth: width,
|
||||
getHeight: height
|
||||
};
|
||||
return { getWidth: width, getHeight: height };
|
||||
};
|
||||
export const addInitialData = (signerPos, setXyPosition, value, userId) => {
|
||||
function widgetDataValue(type) {
|
||||
@@ -967,9 +1012,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
|
||||
)
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...item
|
||||
};
|
||||
return item;
|
||||
}
|
||||
} else if (item.pos && item.pos.length > 0) {
|
||||
// If there is no nested array, add the new field
|
||||
@@ -985,19 +1028,11 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
|
||||
...item,
|
||||
options: {
|
||||
...item.options,
|
||||
defaultValue: widgetData
|
||||
defaultValue: item?.options?.defaultValue || widgetData
|
||||
}
|
||||
// Width:
|
||||
// calculateInitialWidthHeight(item.type, widgetData).getWidth ||
|
||||
// item?.Width,
|
||||
// Height:
|
||||
// calculateInitialWidthHeight(item.type, widgetData).getHeight ||
|
||||
// item?.Height
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...item
|
||||
};
|
||||
return item;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1005,8 +1040,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
|
||||
|
||||
//function for embed document id
|
||||
export const embedDocId = async (pdfDoc, documentId, allPages) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
// `fontBytes` is used to embed custom font in pdf
|
||||
const fontBytes = await fileasbytes(
|
||||
"https://cdn.opensignlabs.com/webfonts/times.ttf"
|
||||
@@ -1868,12 +1902,9 @@ export const contactBook = async (objectId) => {
|
||||
};
|
||||
|
||||
//function for getting document details from contract_Documents class
|
||||
export const contractDocument = async (
|
||||
documentId,
|
||||
) => {
|
||||
export const contractDocument = async (documentId) => {
|
||||
const data = { docId: documentId };
|
||||
const token =
|
||||
{ sessionToken: localStorage.getItem("accesstoken") };
|
||||
const token = { sessionToken: localStorage.getItem("accesstoken") };
|
||||
const documentDeatils = await axios
|
||||
.post(`${localStorage.getItem("baseUrl")}functions/getDocument`, data, {
|
||||
headers: {
|
||||
@@ -2057,36 +2088,31 @@ export const getFileName = (fileUrl) => {
|
||||
|
||||
//fetch tenant app logo from `partners_Tenant` class by domain name
|
||||
export const getAppLogo = async () => {
|
||||
const domain = window.location.host;
|
||||
try {
|
||||
const tenant = await Parse.Cloud.run("getlogobydomain", {
|
||||
domain: domain
|
||||
});
|
||||
if (tenant) {
|
||||
localStorage.setItem("appname", "OpenSign™");
|
||||
return { logo: tenant?.logo, user: tenant?.user };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in getlogo ", err);
|
||||
if (err?.message?.includes("valid JSON")) {
|
||||
return { logo: appInfo.applogo, user: "exist", error: "invalid_json" };
|
||||
} else {
|
||||
return { logo: appInfo.applogo, user: "exist" };
|
||||
}
|
||||
const domain = window.location.host;
|
||||
try {
|
||||
const tenant = await Parse.Cloud.run("getlogobydomain", {
|
||||
domain: domain
|
||||
});
|
||||
if (tenant) {
|
||||
localStorage.setItem("appname", "OpenSign™");
|
||||
return { logo: tenant?.logo, user: tenant?.user };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err in getlogo ", err);
|
||||
if (err?.message?.includes("valid JSON")) {
|
||||
return { logo: appInfo.applogo, user: "exist", error: "invalid_json" };
|
||||
} else {
|
||||
return { logo: appInfo.applogo, user: "exist" };
|
||||
}
|
||||
}
|
||||
};
|
||||
export const getTenantDetails = async (
|
||||
objectId,
|
||||
contactId
|
||||
) => {
|
||||
export const getTenantDetails = async (objectId, contactId) => {
|
||||
try {
|
||||
const url = `${localStorage.getItem("baseUrl")}functions/gettenant`;
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
const accesstoken = localStorage.getItem("accesstoken");
|
||||
const token =
|
||||
{ "X-Parse-Session-Token": accesstoken };
|
||||
const data =
|
||||
{ userId: objectId, contactId: contactId };
|
||||
const token = { "X-Parse-Session-Token": accesstoken };
|
||||
const data = { userId: objectId, contactId: contactId };
|
||||
const res = await axios.post(url, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -2166,33 +2192,30 @@ export const handleSendOTP = async (email) => {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
|
||||
};
|
||||
const body = { email: email };
|
||||
const body = {
|
||||
email: email
|
||||
};
|
||||
await axios.post(url, body, { headers: headers });
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
}
|
||||
};
|
||||
export const fetchUrl = async (url, pdfName) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
alert("something went wrong, please try again later.");
|
||||
alert("something went wrong, refreshing this page may solve this issue.");
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
const blob = await response.blob();
|
||||
saveAs(blob, `${sanitizeFileName(pdfName)}_signed_by_${appName}.pdf`);
|
||||
} catch (error) {
|
||||
alert("something went wrong, please try again later.");
|
||||
alert("something went wrong, refreshing this page may solve this issue.");
|
||||
console.error("Error downloading the file:", error);
|
||||
}
|
||||
};
|
||||
export const getSignedUrl = async (
|
||||
pdfUrl,
|
||||
docId,
|
||||
templateId
|
||||
) => {
|
||||
export const getSignedUrl = async (pdfUrl, docId, templateId) => {
|
||||
//use only axios here due to public template sign
|
||||
const axiosRes = await axios.post(
|
||||
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
|
||||
@@ -2253,16 +2276,13 @@ export const handleDownloadPdf = async (
|
||||
setIsDownloading && setIsDownloading("pdf");
|
||||
const docId = pdfDetails?.[0]?.objectId || "";
|
||||
try {
|
||||
const url = await getSignedUrl(
|
||||
pdfUrl,
|
||||
docId,
|
||||
);
|
||||
const url = await getSignedUrl(pdfUrl, docId);
|
||||
await fetchUrl(url, pdfName);
|
||||
setIsDownloading && setIsDownloading("");
|
||||
} catch (err) {
|
||||
console.log("err in getsignedurl", err);
|
||||
setIsDownloading("");
|
||||
alert("something went wrong, please try again later.");
|
||||
alert("something went wrong, refreshing this page may solve this issue.");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2286,7 +2306,7 @@ export const handleToPrint = async (event, setIsDownloading, pdfDetails) => {
|
||||
`${localStorage.getItem("baseUrl")}/functions/getsignedurl`,
|
||||
{
|
||||
url: pdfUrl,
|
||||
docId: docId,
|
||||
docId: docId
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
@@ -2320,7 +2340,7 @@ export const handleToPrint = async (event, setIsDownloading, pdfDetails) => {
|
||||
} catch (err) {
|
||||
setIsDownloading("");
|
||||
console.log("err in getsignedurl", err);
|
||||
alert("something went wrong, please try again later.");
|
||||
alert("something went wrong, refreshing this page may solve this issue.");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2330,8 +2350,7 @@ export const handleDownloadCertificate = async (
|
||||
setIsDownloading,
|
||||
isZip
|
||||
) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
if (pdfDetails?.length > 0 && pdfDetails[0]?.CertificateUrl) {
|
||||
try {
|
||||
await fetch(pdfDetails[0] && pdfDetails[0]?.CertificateUrl);
|
||||
@@ -2407,7 +2426,7 @@ export const handleDownloadCertificate = async (
|
||||
} catch (err) {
|
||||
setIsDownloading("certificate_err");
|
||||
console.log("err in download in certificate", err);
|
||||
alert("something went wrong, please try again later.");
|
||||
alert("something went wrong, refreshing this page may solve this issue.");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -2415,14 +2434,13 @@ export const handleDownloadCertificate = async (
|
||||
export function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // Escape special characters
|
||||
}
|
||||
export async function findContact(
|
||||
value,
|
||||
) {
|
||||
export async function findContact(value) {
|
||||
try {
|
||||
const baseURL = localStorage.getItem("baseUrl");
|
||||
const url = `${baseURL}functions/getsigners`;
|
||||
const token =
|
||||
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
|
||||
const token = {
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
@@ -2634,23 +2652,20 @@ export function base64ToArrayBuffer(base64) {
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
export const convertBase64ToFile = async (
|
||||
pdfName,
|
||||
pdfBase64,
|
||||
) => {
|
||||
export const convertBase64ToFile = async (pdfName, pdfBase64) => {
|
||||
const fileName = sanitizeFileName(pdfName) + ".pdf";
|
||||
try {
|
||||
const pdfFile = new Parse.File(fileName, { base64: pdfBase64 });
|
||||
// Save the Parse File if needed
|
||||
const pdfData = await pdfFile.save();
|
||||
const pdfUrl = pdfData.url();
|
||||
const fileRes = await getSecureUrl(pdfUrl);
|
||||
if (fileRes?.url) {
|
||||
return fileRes.url;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in convertbase64tofile", e);
|
||||
try {
|
||||
const pdfFile = new Parse.File(fileName, { base64: pdfBase64 });
|
||||
// Save the Parse File if needed
|
||||
const pdfData = await pdfFile.save();
|
||||
const pdfUrl = pdfData.url();
|
||||
const fileRes = await getSecureUrl(pdfUrl);
|
||||
if (fileRes?.url) {
|
||||
return fileRes.url;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error in convertbase64tofile", e);
|
||||
}
|
||||
};
|
||||
export const onClickZoomIn = (scale, zoomPercent, setScale, setZoomPercent) => {
|
||||
setScale(scale + 0.1 * scale);
|
||||
@@ -2868,20 +2883,11 @@ export function generatePdfName(length) {
|
||||
|
||||
// Format date and time for the selected timezone
|
||||
export const formatTimeInTimezone = (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,
|
||||
timeZoneName: "short",
|
||||
hour12: false
|
||||
}).format(date)
|
||||
const nyDate = timezone && toZonedTime(date, timezone);
|
||||
const generatedDate = timezone
|
||||
? format(nyDate, "EEE, dd MMM yyyy HH:mm:ss zzz", { timeZone: timezone })
|
||||
: new Date(date).toUTCString();
|
||||
return generatedDate;
|
||||
};
|
||||
|
||||
// `usertimezone` is used to get timezone of current user
|
||||
@@ -2949,13 +2955,10 @@ export const flattenPdf = async (pdfFile) => {
|
||||
};
|
||||
|
||||
export const mailTemplate = (param) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const logo =
|
||||
`<div style='padding:10px'><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' /></div>`;
|
||||
const appName = "OpenSign™";
|
||||
const logo = `<div style='padding:10px'><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' /></div>`;
|
||||
|
||||
const opurl =
|
||||
` <a href='https://www.opensignlabs.com' target=_blank>here</a>.</p></div></div></body></html>`;
|
||||
const opurl = ` <a href='https://www.opensignlabs.com' target=_blank>here</a>.</p></div></div></body></html>`;
|
||||
|
||||
const subject = `${param.senderName} has requested you to sign "${param.title}"`;
|
||||
const body =
|
||||
@@ -2969,8 +2972,10 @@ export const mailTemplate = (param) => {
|
||||
param.senderMail +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td><td></td><td style='color:#626363;font-weight:bold'> " +
|
||||
param.organization +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expire on</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||
param.localExpireDate +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||
param.note +
|
||||
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
|
||||
param.sigingUrl +
|
||||
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
|
||||
@@ -2983,3 +2988,15 @@ export const mailTemplate = (param) => {
|
||||
|
||||
return { subject, body };
|
||||
};
|
||||
|
||||
export function formatDateTime(date, dateFormat, timeZone, is12Hour) {
|
||||
const zonedDate = toZonedTime(date, timeZone); // Convert date to the given timezone
|
||||
const timeFormat = is12Hour ? "hh:mm:ss a" : "HH:mm:ss";
|
||||
return dateFormat
|
||||
? format(
|
||||
zonedDate,
|
||||
`${selectFormat(dateFormat)}, ${timeFormat} 'GMT' XXX`,
|
||||
{ timeZone }
|
||||
)
|
||||
: formatTimeInTimezone(date, timeZone);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import logo from "../assets/images/logo.png";
|
||||
|
||||
export function serverUrl_fn() {
|
||||
let baseUrl;
|
||||
baseUrl = process.env.REACT_APP_SERVERURL
|
||||
? process.env.REACT_APP_SERVERURL
|
||||
: window.location.origin + "/app";
|
||||
|
||||
let baseUrl = process.env.REACT_APP_SERVERURL
|
||||
? process.env.REACT_APP_SERVERURL
|
||||
: window.location.origin + "/api/app";
|
||||
return baseUrl;
|
||||
}
|
||||
export const appInfo = {
|
||||
|
||||
@@ -4,3 +4,7 @@ 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 maxFileSize = 10; // 10MB
|
||||
export const maxTitleLength = 250; // 250 characters
|
||||
export const maxNoteLength = 200; // 200 characters
|
||||
export const maxDescriptionLength = 500; // 500 characters
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
Preview
|
||||
} from "react-dnd-multi-backend";
|
||||
import DragElement from "./components/pdf/DragElement";
|
||||
import TagManager from "react-gtm-module";
|
||||
import Parse from "parse";
|
||||
import "./polyfills";
|
||||
import { serverUrl_fn } from "./constant/appinfo";
|
||||
@@ -56,13 +55,6 @@ const generatePreview = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
if (process.env.REACT_APP_GTM) {
|
||||
const tagManagerArgs = {
|
||||
gtmId: process.env.REACT_APP_GTM
|
||||
};
|
||||
TagManager.initialize(tagManagerArgs);
|
||||
}
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<CookiesProvider defaultSetOptions={{ path: "/" }}>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
export default function reportJson(id) {
|
||||
// console.log("json ", json);
|
||||
const head = ["Title", "Note", "Folder", "File", "Owner", "Signers"];
|
||||
@@ -29,6 +28,25 @@ export default function reportJson(id) {
|
||||
btnIcon: "fa-light fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
},
|
||||
{
|
||||
btnId: "22534",
|
||||
hoverLabel: "option",
|
||||
btnColor: "",
|
||||
restrictBtn: true,
|
||||
textColor: "black",
|
||||
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
|
||||
action: "option",
|
||||
subaction: [
|
||||
{
|
||||
btnId: "1630",
|
||||
btnLabel: "Save as template",
|
||||
hoverLabel: "Save as template",
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
helpMsg:
|
||||
@@ -107,6 +125,14 @@ export default function reportJson(id) {
|
||||
redirectUrl: "",
|
||||
action: "revoke"
|
||||
},
|
||||
{
|
||||
btnId: "0630",
|
||||
btnLabel: "Save as template",
|
||||
hoverLabel: "Save as template",
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
},
|
||||
{
|
||||
btnId: "1488",
|
||||
btnLabel: "Delete",
|
||||
@@ -143,6 +169,25 @@ export default function reportJson(id) {
|
||||
restrictBtn: true,
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
},
|
||||
{
|
||||
btnId: "33534",
|
||||
hoverLabel: "option",
|
||||
btnColor: "",
|
||||
restrictBtn: true,
|
||||
textColor: "black",
|
||||
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
|
||||
action: "option",
|
||||
subaction: [
|
||||
{
|
||||
btnId: "0930",
|
||||
btnLabel: "Save as template",
|
||||
hoverLabel: "Save as template",
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
helpMsg:
|
||||
@@ -169,6 +214,25 @@ export default function reportJson(id) {
|
||||
btnIcon: "fa-light fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
},
|
||||
{
|
||||
btnId: "44534",
|
||||
hoverLabel: "option",
|
||||
btnColor: "",
|
||||
restrictBtn: true,
|
||||
textColor: "black",
|
||||
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
|
||||
action: "option",
|
||||
subaction: [
|
||||
{
|
||||
btnId: "0940",
|
||||
btnLabel: "Save as template",
|
||||
hoverLabel: "Save as template",
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
helpMsg:
|
||||
@@ -212,6 +276,14 @@ export default function reportJson(id) {
|
||||
btnIcon: "fa-light fa-hourglass-end",
|
||||
redirectUrl: "",
|
||||
action: "extendexpiry"
|
||||
},
|
||||
{
|
||||
btnId: "5530",
|
||||
btnLabel: "Save as template",
|
||||
hoverLabel: "Save as template",
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -273,6 +345,14 @@ export default function reportJson(id) {
|
||||
redirectUrl: "",
|
||||
action: "revoke"
|
||||
},
|
||||
{
|
||||
btnId: "7730",
|
||||
btnLabel: "Save as template",
|
||||
hoverLabel: "Save as template",
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
},
|
||||
{
|
||||
btnId: "2000",
|
||||
btnLabel: "Delete",
|
||||
@@ -323,6 +403,25 @@ export default function reportJson(id) {
|
||||
btnIcon: "fa-light fa-trash",
|
||||
redirectUrl: "",
|
||||
action: "delete"
|
||||
},
|
||||
{
|
||||
btnId: "55534",
|
||||
hoverLabel: "option",
|
||||
btnColor: "",
|
||||
restrictBtn: true,
|
||||
textColor: "black",
|
||||
btnIcon: "fa-light fa-ellipsis-vertical fa-lg",
|
||||
action: "option",
|
||||
subaction: [
|
||||
{
|
||||
btnId: "6630",
|
||||
btnLabel: "Save as template",
|
||||
hoverLabel: "Save as template",
|
||||
btnIcon: "fa-light fa-envelope",
|
||||
redirectUrl: "",
|
||||
action: "saveastemplate"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -15,8 +15,7 @@ import { showHeader } from "../redux/reducers/showHeader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const HomeLayout = () => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -54,8 +53,8 @@ const HomeLayout = () => {
|
||||
});
|
||||
if (user) {
|
||||
localStorage.setItem("profileImg", user.get("ProfilePic") || "");
|
||||
setIsUserValid(true);
|
||||
setIsLoader(false);
|
||||
setIsUserValid(true);
|
||||
setIsLoader(false);
|
||||
} else {
|
||||
setIsUserValid(false);
|
||||
}
|
||||
@@ -72,7 +71,7 @@ const HomeLayout = () => {
|
||||
//function to use save data in cookies storage
|
||||
const saveCookies = () => {
|
||||
const main_Domain = window.location.origin;
|
||||
const domainName = window.location.hostname; //app.opensignlabs.com
|
||||
const domainName = window.location.hostname;
|
||||
// Find the index of the first dot in the string
|
||||
const indexOfFirstDot = domainName.indexOf(".");
|
||||
// Remove the first dot and get the substring starting from the next character
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Confetti from "react-confetti"; // Import the confetti library
|
||||
import {
|
||||
getBase64FromUrl,
|
||||
handleDownloadCertificate,
|
||||
@@ -10,19 +11,25 @@ import ModalUi from "../primitives/ModalUi";
|
||||
import Loader from "../primitives/Loader";
|
||||
import DownloadPdfZip from "../primitives/DownloadPdfZip";
|
||||
import Title from "../components/Title";
|
||||
import CheckCircle from "../primitives/CheckCircle";
|
||||
|
||||
const DocSuccessPage = () => {
|
||||
const { t } = useTranslation();
|
||||
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();
|
||||
const [showConfetti, setShowConfetti] = useState(true); // State to control confetti
|
||||
|
||||
useEffect(() => {
|
||||
initialsetup();
|
||||
// Stop confetti after 5 seconds
|
||||
const timer = setTimeout(() => setShowConfetti(false), 5000);
|
||||
return () => clearTimeout(timer);
|
||||
}, []);
|
||||
|
||||
const initialsetup = async () => {
|
||||
const search = window.location.search.split("?")[1];
|
||||
if (search) {
|
||||
@@ -44,6 +51,7 @@ const DocSuccessPage = () => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownload = () => {
|
||||
if (pdfDetails?.[0]?.IsCompleted) {
|
||||
setIsDownloadModal(true);
|
||||
@@ -51,50 +59,79 @@ const DocSuccessPage = () => {
|
||||
handleDownloadPdf(pdfDetails, setIsDownloading, pdfBase64Url);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-screen justify-center items-center text-sm md:text-base">
|
||||
<>
|
||||
<Title title="Success" />
|
||||
{/* Confetti Effect */}
|
||||
{showConfetti && (
|
||||
<Confetti width={window.innerWidth} height={window.innerHeight} />
|
||||
)}
|
||||
{sent ? (
|
||||
<div>{t("doc-sent")}</div>
|
||||
<div className="min-h-screen flex flex-col items-center justify-center p-3 md:p-8 text-center">
|
||||
<div className="max-w-lg md:max-w-2xl bg-white rounded-lg shadow-lg p-3 md:p-10">
|
||||
{t("doc-sent")}
|
||||
</div>
|
||||
</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 className="min-h-screen flex flex-col items-center justify-center p-3 md:p-8 text-center">
|
||||
<div className="max-w-lg md:max-w-2xl bg-white rounded-lg shadow-lg p-3 md:p-10">
|
||||
<div className="flex flex-col items-center space-y-4 ">
|
||||
<CheckCircle className="text-green-500 w-12 h-12 md:w-14 md:h-14" />
|
||||
<h1 className="text-xl md:text-2xl font-semibold text-gray-800">
|
||||
{pdfDetails?.[0]?.IsCompleted
|
||||
? t("document-has-been-signed")
|
||||
: t("document-has-been-signed-by-you")}
|
||||
</h1>
|
||||
{pdfDetails?.[0]?.IsCompleted && (
|
||||
<p className="text-sm md:text-base text-gray-600">
|
||||
{t("participant-completed-signing")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{/* Action Buttons */}
|
||||
<div className="mt-6 flex flex-wrap justify-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="font-medium text-sm md:text-[13px] md:px-4 py-2 op-btn op-btn-primary"
|
||||
onClick={() => handleDownload()}
|
||||
>
|
||||
<i className="fa-light fa-download" aria-hidden="true"></i>
|
||||
<span>{t("download")}</span>
|
||||
</button>
|
||||
|
||||
{pdfDetails?.[0]?.IsCompleted && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
handleDownloadCertificate(pdfDetails, setIsDownloading)
|
||||
}
|
||||
className="font-medium text-sm md:text-[13px] md:px-4 py-2 op-btn op-btn-secondary"
|
||||
>
|
||||
<i
|
||||
className="fa-light fa-award mx-[3px] md:mx-0"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span>{t("certificate")}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(e) =>
|
||||
handleToPrint(e, setIsDownloading, pdfDetails)
|
||||
}
|
||||
type="button"
|
||||
className="font-medium text-sm md:text-[13px] px-4 py-2 op-btn op-btn-neutral"
|
||||
>
|
||||
<i className="fa-light fa-print" aria-hidden="true"></i>
|
||||
<span>{t("print")}</span>
|
||||
</button>
|
||||
</div>
|
||||
{/* Footer Message */}
|
||||
<p className="mt-4 md:mt-6 text-xs md:text-sm text-gray-500">
|
||||
{t("you-will-receive-email-shortly")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{isDownloading === "pdf" && (
|
||||
<div className="fixed z-[1000] inset-0 flex justify-center items-center bg-black bg-opacity-30">
|
||||
@@ -114,7 +151,7 @@ const DocSuccessPage = () => {
|
||||
}
|
||||
handleClose={() => setIsDownloading("")}
|
||||
>
|
||||
<div className="p-3 md:p-5 text-[13px] md:text-base text-center text-base-content">
|
||||
<div className="p-3 md:p-5 text-sm md:text-base text-center text-base-content">
|
||||
{isDownloading === "certificate" ? (
|
||||
<p>{t("generate-certificate-alert")}</p>
|
||||
) : (
|
||||
@@ -129,11 +166,11 @@ const DocSuccessPage = () => {
|
||||
isDocId={true}
|
||||
pdfBase64={pdfBase64Url}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,17 +7,17 @@ import Alert from "../primitives/Alert";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { fetchAppInfo } from "../redux/reducers/infoReducer";
|
||||
import {
|
||||
emailRegex,
|
||||
} from "../constant/const";
|
||||
import { emailRegex } from "../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Loader from "../primitives/Loader";
|
||||
|
||||
function ForgotPassword() {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [state, setState] = useState({ email: "", password: "", hideNav: "" });
|
||||
const [sentStatus, setSentStatus] = useState("");
|
||||
const [toast, setToast] = useState({ type: "", message: "" });
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [image, setImage] = useState();
|
||||
|
||||
const handleChange = (event) => {
|
||||
@@ -40,18 +40,23 @@ function ForgotPassword() {
|
||||
if (!emailRegex.test(state.email)) {
|
||||
alert("Please enter a valid email address.");
|
||||
} else {
|
||||
setIsLoading(true);
|
||||
localStorage.setItem("appLogo", appInfo.applogo);
|
||||
localStorage.setItem("userSettings", JSON.stringify(appInfo.settings));
|
||||
if (state.email) {
|
||||
const username = state.email;
|
||||
try {
|
||||
await Parse.User.requestPasswordReset(username);
|
||||
setSentStatus("success");
|
||||
setToast({ type: "success", message: t("reset-password-alert-1") });
|
||||
} catch (err) {
|
||||
console.log("err ", err.code);
|
||||
setSentStatus("failed");
|
||||
setToast({
|
||||
type: "danger",
|
||||
message: err.message || t("reset-password-alert-2")
|
||||
});
|
||||
} finally {
|
||||
setTimeout(() => setSentStatus(""), 1000);
|
||||
setIsLoading(false);
|
||||
setTimeout(() => setToast({ type: "", message: "" }), 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,17 +76,17 @@ function ForgotPassword() {
|
||||
} catch (err) {
|
||||
console.log("err while logging out ", err);
|
||||
}
|
||||
setImage(appInfo?.applogo || undefined);
|
||||
setImage(appInfo?.applogo || undefined);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
{isLoading && (
|
||||
<div className="fixed w-full h-full flex justify-center items-center bg-black bg-opacity-30 z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
<Title title="Forgot password" />
|
||||
{sentStatus === "success" && (
|
||||
<Alert type="success">{t("reset-password-alert-1")}</Alert>
|
||||
)}
|
||||
{sentStatus === "failed" && (
|
||||
<Alert type={"danger"}>{t("reset-password-alert-2")}</Alert>
|
||||
)}
|
||||
{toast?.message && <Alert type={toast.type}>{toast.message}</Alert>}
|
||||
<div className="md:p-10 lg:p-16">
|
||||
<div className="md:p-4 lg:p-10 p-4 bg-base-100 text-base-content op-card">
|
||||
<div className="w-[250px] h-[66px] inline-block overflow-hidden">
|
||||
|
||||
+239
-220
@@ -18,6 +18,12 @@ import {
|
||||
} from "../constant/Utils";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import axios from "axios";
|
||||
import {
|
||||
maxFileSize,
|
||||
maxDescriptionLength,
|
||||
maxNoteLength,
|
||||
maxTitleLength
|
||||
} from "../constant/const";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import Loader from "../primitives/Loader";
|
||||
@@ -36,10 +42,8 @@ function Form() {
|
||||
}
|
||||
|
||||
const Forms = (props) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const maxFileSize = 20;
|
||||
const abortController = new AbortController();
|
||||
const inputFileRef = useRef(null);
|
||||
const navigate = useNavigate();
|
||||
@@ -81,6 +85,15 @@ const Forms = (props) => {
|
||||
const extUserData =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
const sendinorder =
|
||||
extUserData?.SendinOrder !== undefined && extUserData?.SendinOrder === false
|
||||
? "false"
|
||||
: "true";
|
||||
const istourenabled =
|
||||
extUserData?.IsTourEnabled !== undefined &&
|
||||
extUserData?.IsTourEnabled === false
|
||||
? "false"
|
||||
: "true";
|
||||
useEffect(() => {
|
||||
handleReset();
|
||||
return () => abortController.abort();
|
||||
@@ -92,7 +105,12 @@ const Forms = (props) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const initializeValues = async () => {
|
||||
setFormData((obj) => ({ ...obj, NotifyOnSignatures: true }));
|
||||
setFormData((obj) => ({
|
||||
...obj,
|
||||
NotifyOnSignatures: true,
|
||||
SendinOrder: sendinorder,
|
||||
IsTourEnabled: istourenabled
|
||||
}));
|
||||
};
|
||||
|
||||
function getFileAsArrayBuffer(file) {
|
||||
@@ -130,17 +148,81 @@ const Forms = (props) => {
|
||||
const name = generatePdfName(16);
|
||||
const pdfName = `${name?.split(".")[0]}.pdf`;
|
||||
setfileload(true);
|
||||
try {
|
||||
const res = await getFileAsArrayBuffer(files[0]);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...flatPdf],
|
||||
"application/pdf"
|
||||
);
|
||||
try {
|
||||
const res = await getFileAsArrayBuffer(files[0]);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...flatPdf],
|
||||
"application/pdf"
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The response object will contain information about the uploaded file
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (error) {
|
||||
removeFile(e);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
setIsDecrypting(true);
|
||||
const size = files?.[0].size;
|
||||
const name = generatePdfName(16);
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
||||
let formData = new FormData();
|
||||
formData.append("file", files[0]);
|
||||
formData.append("password", "");
|
||||
const config = {
|
||||
headers: { "content-type": "multipart/form-data" },
|
||||
responseType: "blob"
|
||||
};
|
||||
const response = await axios.post(url, formData, config);
|
||||
const pdfBlob = new Blob([response.data], {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = new File([pdfBlob], name, {
|
||||
type: "application/pdf"
|
||||
});
|
||||
setIsDecrypting(false);
|
||||
setfileload(true);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
// Upload the file to Parse Server
|
||||
const parseFile = new Parse.File(
|
||||
name,
|
||||
[...flatPdf],
|
||||
"application/pdf"
|
||||
);
|
||||
|
||||
await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
@@ -150,16 +232,16 @@ const Forms = (props) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
// The response object will contain information about the uploaded file
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
|
||||
// Retrieve the URL of the uploaded file
|
||||
if (parseFile.url()) {
|
||||
const fileRes = await getSecureUrl(parseFile.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
removeFile();
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
@@ -168,95 +250,22 @@ const Forms = (props) => {
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (error) {
|
||||
removeFile(e);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err?.message?.includes("is encrypted")) {
|
||||
try {
|
||||
await Parse.Cloud.run("encryptedpdf", {
|
||||
email: Parse.User.current().getEmail()
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("err in sending posthog encryptedpdf", err);
|
||||
}
|
||||
try {
|
||||
setIsDecrypting(true);
|
||||
const size = files?.[0].size;
|
||||
const name = generatePdfName(16);
|
||||
const url = "https://ai.nxglabs.in/decryptpdf"; //
|
||||
let formData = new FormData();
|
||||
formData.append("file", files[0]);
|
||||
formData.append("password", "");
|
||||
const config = {
|
||||
headers: { "content-type": "multipart/form-data" },
|
||||
responseType: "blob"
|
||||
};
|
||||
const response = await axios.post(url, formData, config);
|
||||
const pdfBlob = new Blob([response.data], {
|
||||
type: "application/pdf"
|
||||
});
|
||||
const pdfFile = new File([pdfBlob], name, {
|
||||
type: "application/pdf"
|
||||
});
|
||||
} catch (err) {
|
||||
removeFile();
|
||||
if (err?.response?.status === 401) {
|
||||
setIsPassword(true);
|
||||
} else {
|
||||
console.log("Error uploading file: ", err?.response);
|
||||
setIsDecrypting(false);
|
||||
setfileload(true);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
// Upload the file to Parse Server
|
||||
const parseFile = new Parse.File(
|
||||
name,
|
||||
[...flatPdf],
|
||||
"application/pdf"
|
||||
);
|
||||
|
||||
await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Retrieve the URL of the uploaded file
|
||||
if (parseFile.url()) {
|
||||
const fileRes = await getSecureUrl(parseFile.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
removeFile();
|
||||
const title = generateTitleFromFilename(
|
||||
files?.[0]?.name
|
||||
);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (err) {
|
||||
removeFile();
|
||||
if (err?.response?.status === 401) {
|
||||
setIsPassword(true);
|
||||
} else {
|
||||
console.log("Error uploading file: ", err?.response);
|
||||
setIsDecrypting(false);
|
||||
e.target.value = "";
|
||||
}
|
||||
e.target.value = "";
|
||||
}
|
||||
} else {
|
||||
console.log("err ", err);
|
||||
setFileUpload("");
|
||||
removeFile(e);
|
||||
}
|
||||
} else {
|
||||
console.log("err ", err);
|
||||
setFileUpload("");
|
||||
removeFile(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const isImage = files?.[0]?.type.includes("image/");
|
||||
if (isImage) {
|
||||
@@ -281,50 +290,50 @@ const Forms = (props) => {
|
||||
});
|
||||
const size = files?.[0]?.size;
|
||||
const name = generatePdfName(16);
|
||||
const getFile = await pdfDoc.save({
|
||||
useObjectStreams: false
|
||||
});
|
||||
setfileload(true);
|
||||
const pdfName = `${name?.split(".")[0]}.pdf`;
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...getFile],
|
||||
"application/pdf"
|
||||
);
|
||||
const getFile = await pdfDoc.save({
|
||||
useObjectStreams: false
|
||||
});
|
||||
setfileload(true);
|
||||
const pdfName = `${name?.split(".")[0]}.pdf`;
|
||||
const parseFile = new Parse.File(
|
||||
pdfName,
|
||||
[...getFile],
|
||||
"application/pdf"
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The response object will contain information about the uploaded file
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
try {
|
||||
const response = await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round(
|
||||
(loaded * 100) / total
|
||||
);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The response object will contain information about the uploaded file
|
||||
// You can access the URL of the uploaded file using response.url()
|
||||
if (response.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
setfileload(false);
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const title = generateTitleFromFilename(files?.[0]?.name);
|
||||
setFormData((obj) => ({ ...obj, Name: title }));
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile(e);
|
||||
}
|
||||
} catch (error) {
|
||||
} else {
|
||||
removeFile(e);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
removeFile(e);
|
||||
console.error("Error uploading file:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -350,6 +359,18 @@ const Forms = (props) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (fileupload) {
|
||||
if (formData?.Name?.length > maxTitleLength) {
|
||||
alert(t("title-length-alert"));
|
||||
return;
|
||||
}
|
||||
if (formData?.Note?.length > maxNoteLength) {
|
||||
alert(t("note-length-alert"));
|
||||
return;
|
||||
}
|
||||
if (formData?.Description?.length > maxDescriptionLength) {
|
||||
alert(t("description-length-alert"));
|
||||
return;
|
||||
}
|
||||
if (formData.RedirectUrl && !isValidURL(formData?.RedirectUrl)) {
|
||||
alert(t("invalid-redirect-url"));
|
||||
return;
|
||||
@@ -374,19 +395,24 @@ const Forms = (props) => {
|
||||
const isChecked = formData.SendinOrder === "false" ? false : true;
|
||||
const isTourEnabled =
|
||||
formData?.IsTourEnabled === "false" ? false : true;
|
||||
const remindOnceInEvery = parseInt(formData.remindOnceInEvery);
|
||||
const TimeToCompleteDays = parseInt(formData?.TimeToCompleteDays);
|
||||
const AutomaticReminders = formData.autoreminder;
|
||||
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
|
||||
if (AutomaticReminders && reminderCount > 15) {
|
||||
alert(t("only-15-reminder-allowed"));
|
||||
return;
|
||||
}
|
||||
object.set("SendinOrder", isChecked);
|
||||
object.set("AutomaticReminders", formData.autoreminder);
|
||||
object.set("RemindOnceInEvery", parseInt(formData.remindOnceInEvery));
|
||||
object.set("AutomaticReminders", AutomaticReminders);
|
||||
object.set("RemindOnceInEvery", remindOnceInEvery);
|
||||
object.set("IsTourEnabled", isTourEnabled);
|
||||
object.set(
|
||||
"TimeToCompleteDays",
|
||||
parseInt(formData?.TimeToCompleteDays)
|
||||
);
|
||||
object.set("AllowModifications", false);
|
||||
object.set("IsEnableOTP", false);
|
||||
if (formData.NotifyOnSignatures !== undefined) {
|
||||
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
|
||||
}
|
||||
object.set("TimeToCompleteDays", TimeToCompleteDays);
|
||||
object.set("AllowModifications", false);
|
||||
object.set("IsEnableOTP", false);
|
||||
if (formData.NotifyOnSignatures !== undefined) {
|
||||
object.set("NotifyOnSignatures", formData.NotifyOnSignatures);
|
||||
}
|
||||
if (formData?.RedirectUrl) {
|
||||
object.set("RedirectUrl", formData.RedirectUrl);
|
||||
}
|
||||
@@ -425,10 +451,9 @@ const Forms = (props) => {
|
||||
setSigners([]);
|
||||
setBcc([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
const notifySign = extUserData?.NotifyOnSignatures
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
setFormData({
|
||||
Name: "",
|
||||
Description: "",
|
||||
@@ -437,14 +462,14 @@ const Forms = (props) => {
|
||||
? "Note to myself"
|
||||
: "Please review and sign this document",
|
||||
TimeToCompleteDays: 15,
|
||||
SendinOrder: "true",
|
||||
SendinOrder: sendinorder,
|
||||
password: "",
|
||||
file: "",
|
||||
NotifyOnSignatures: notifySign,
|
||||
remindOnceInEvery: 5,
|
||||
autoreminder: false,
|
||||
IsEnableOTP: "false",
|
||||
IsTourEnabled: "true",
|
||||
IsTourEnabled: istourenabled,
|
||||
RedirectUrl: "",
|
||||
AllowModifications: false
|
||||
});
|
||||
@@ -454,7 +479,17 @@ const Forms = (props) => {
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err ", err);
|
||||
setIsAlert({ type: "danger", message: t("something-went-wrong-mssg") });
|
||||
if (err.message === "only 15 reminder allowed") {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
message: t("only-15-reminder-allowed")
|
||||
});
|
||||
} else {
|
||||
setIsAlert({
|
||||
type: "danger",
|
||||
message: t("something-went-wrong-mssg")
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setTimeout(() => setIsAlert({ type: "success", message: "" }), 1000);
|
||||
setIsSubmit(false);
|
||||
@@ -494,10 +529,9 @@ const Forms = (props) => {
|
||||
setSigners([]);
|
||||
setBcc([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
const notifySign = extUserData?.NotifyOnSignatures
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
let obj = {
|
||||
Name: "",
|
||||
Description: "",
|
||||
@@ -506,13 +540,13 @@ const Forms = (props) => {
|
||||
? "Note to myself"
|
||||
: "Please review and sign this document",
|
||||
TimeToCompleteDays: 15,
|
||||
SendinOrder: "true",
|
||||
SendinOrder: sendinorder,
|
||||
password: "",
|
||||
file: "",
|
||||
remindOnceInEvery: 5,
|
||||
autoreminder: false,
|
||||
IsEnableOTP: "false",
|
||||
IsTourEnabled: "true",
|
||||
IsTourEnabled: istourenabled,
|
||||
NotifyOnSignatures: notifySign,
|
||||
RedirectUrl: "",
|
||||
AllowModifications: false
|
||||
@@ -551,36 +585,28 @@ const Forms = (props) => {
|
||||
type: "application/pdf"
|
||||
});
|
||||
setIsDecrypting(false);
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
const parseFile = new Parse.File(name, [...flatPdf], "application/pdf");
|
||||
await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Retrieve the URL of the uploaded file
|
||||
if (parseFile.url()) {
|
||||
const fileRes = await getSecureUrl(parseFile.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
removeFile();
|
||||
const title = generateTitleFromFilename(formData?.file?.name);
|
||||
setFormData((obj) => ({ ...obj, password: "", Name: title }));
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile();
|
||||
setFormData((prev) => ({ ...prev, password: "" }));
|
||||
setIsDecrypting(false);
|
||||
if (inputFileRef.current) {
|
||||
inputFileRef.current.value = ""; // Set file input value to empty string
|
||||
}
|
||||
const res = await getFileAsArrayBuffer(pdfFile);
|
||||
const flatPdf = await flattenPdf(res);
|
||||
const parseFile = new Parse.File(name, [...flatPdf], "application/pdf");
|
||||
await parseFile.save({
|
||||
progress: (progressValue, loaded, total, { type }) => {
|
||||
if (type === "upload" && progressValue !== null) {
|
||||
const percentCompleted = Math.round((loaded * 100) / total);
|
||||
setpercentage(percentCompleted);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Retrieve the URL of the uploaded file
|
||||
if (parseFile.url()) {
|
||||
const fileRes = await getSecureUrl(parseFile.url());
|
||||
if (fileRes.url) {
|
||||
setFileUpload(fileRes.url);
|
||||
removeFile();
|
||||
const title = generateTitleFromFilename(formData?.file?.name);
|
||||
setFormData((obj) => ({ ...obj, password: "", Name: title }));
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(size, fileRes.url, tenantId);
|
||||
return fileRes.url;
|
||||
} else {
|
||||
removeFile();
|
||||
setFormData((prev) => ({ ...prev, password: "" }));
|
||||
@@ -589,6 +615,14 @@ const Forms = (props) => {
|
||||
inputFileRef.current.value = ""; // Set file input value to empty string
|
||||
}
|
||||
}
|
||||
} else {
|
||||
removeFile();
|
||||
setFormData((prev) => ({ ...prev, password: "" }));
|
||||
setIsDecrypting(false);
|
||||
if (inputFileRef.current) {
|
||||
inputFileRef.current.value = ""; // Set file input value to empty string
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
removeFile();
|
||||
if (err?.response?.status === 401) {
|
||||
@@ -710,9 +744,7 @@ const Forms = (props) => {
|
||||
)}
|
||||
<div className="text-xs">
|
||||
<label className="block">
|
||||
{`${`${t("report-heading.File")} (${t("file-type")}`}${
|
||||
")"
|
||||
}`}
|
||||
{`${`${t("report-heading.File")} (${t("file-type")}`}${")"}`}
|
||||
<span className="text-red-500 text-[13px]">*</span>
|
||||
</label>
|
||||
{fileupload.length > 0 ? (
|
||||
@@ -736,9 +768,7 @@ const Forms = (props) => {
|
||||
className="op-file-input op-file-input-bordered op-file-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
onChange={(e) => handleFileInput(e)}
|
||||
ref={inputFileRef}
|
||||
accept={
|
||||
"application/pdf,image/png,image/jpeg"
|
||||
}
|
||||
accept={"application/pdf,image/png,image/jpeg"}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
@@ -839,7 +869,7 @@ const Forms = (props) => {
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("send-in-order")}</p>
|
||||
<p>{t("send-in-order-help.p1")}</p>
|
||||
<p className="p-[5px]">
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
@@ -854,7 +884,7 @@ const Forms = (props) => {
|
||||
<span>{t("send-in-order-help.p3")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
<p>{t("send-in-order-help.p4")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -936,7 +966,7 @@ const Forms = (props) => {
|
||||
{t("send-in-order")}
|
||||
</p>
|
||||
<p>{t("send-in-order-help.p1")}</p>
|
||||
<p className="p-[5px]">
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
@@ -955,7 +985,7 @@ const Forms = (props) => {
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
<p>{t("send-in-order-help.p4")}</p>
|
||||
</div>
|
||||
</Tooltip>
|
||||
@@ -1028,10 +1058,7 @@ const Forms = (props) => {
|
||||
{isAdvanceOpt && (
|
||||
<div
|
||||
style={{
|
||||
height:
|
||||
props.title === "New Template"
|
||||
? "100px"
|
||||
: "280px"
|
||||
height: props.title === "New Template" ? "100px" : "280px"
|
||||
}}
|
||||
className="w-[1px] bg-gray-300 m-auto hidden md:inline-block"
|
||||
></div>
|
||||
@@ -1076,7 +1103,7 @@ const Forms = (props) => {
|
||||
<Tooltip id="istourenabled-tooltip" className="z-[999]">
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("enable-tour")}</p>
|
||||
<p className="p-[5px]">
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
@@ -1089,7 +1116,7 @@ const Forms = (props) => {
|
||||
<span>{t("istourenabled-help.p2")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
{t("istourenabled-help.p3", { appName: appName })}
|
||||
</p>
|
||||
@@ -1140,11 +1167,7 @@ const Forms = (props) => {
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div
|
||||
className={
|
||||
`flex items-center gap-2 ml-2 mb-1`
|
||||
}
|
||||
>
|
||||
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
@@ -1153,11 +1176,7 @@ const Forms = (props) => {
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
`flex items-center gap-2 ml-2 mb-1`
|
||||
}
|
||||
>
|
||||
<div className={`flex items-center gap-2 ml-2 mb-1`}>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router";
|
||||
import axios from "axios";
|
||||
import {
|
||||
emailRegex,
|
||||
} from "../constant/const";
|
||||
import {
|
||||
contractUsers,
|
||||
saveLanguageInLocal
|
||||
} from "../constant/Utils";
|
||||
import { emailRegex } from "../constant/const";
|
||||
import { contractUsers, saveLanguageInLocal } from "../constant/Utils";
|
||||
import logo from "../assets/images/logo.png";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import Parse from "parse";
|
||||
@@ -20,7 +15,9 @@ function GuestLogin() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const { id, userMail, contactBookId, base64url } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [email, setEmail] = useState(userMail);
|
||||
const [email, setEmail] = useState(
|
||||
userMail?.toLowerCase()?.replace(/\s/g, "")
|
||||
);
|
||||
const [OTP, setOTP] = useState("");
|
||||
const [EnterOTP, setEnterOtp] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -63,9 +60,10 @@ function GuestLogin() {
|
||||
|
||||
//function generate serverUrl and parseAppId from url and save it in local storage
|
||||
const handleServerUrl = async () => {
|
||||
setAppLogo(logo);
|
||||
setAppLogo(logo);
|
||||
|
||||
localStorage.clear();
|
||||
localStorage.clear(); // Clears everything
|
||||
localStorage.setItem("appname", "OpenSign™");
|
||||
//save isGuestSigner true in local to handle login flow header in mobile view
|
||||
localStorage.setItem("isGuestSigner", true);
|
||||
saveLanguageInLocal(i18n);
|
||||
@@ -80,12 +78,18 @@ function GuestLogin() {
|
||||
//split url in array from '/'
|
||||
const checkSplit = decodebase64.split("/");
|
||||
setDocumentId(checkSplit[0]);
|
||||
setContact((prev) => ({ ...prev, email: checkSplit[1] }));
|
||||
setEmail(checkSplit[1]);
|
||||
setContact((prev) => ({
|
||||
...prev,
|
||||
email: checkSplit[1]?.toLowerCase()?.replace(/\s/g, "")
|
||||
}));
|
||||
setEmail(checkSplit[1]?.toLowerCase()?.replace(/\s/g, ""));
|
||||
const contactId = checkSplit?.[2];
|
||||
setSendmail(checkSplit[3]);
|
||||
if (!contactId) {
|
||||
const params = { email: checkSplit[1], docId: checkSplit[0] };
|
||||
const params = {
|
||||
email: checkSplit[1]?.toLowerCase()?.replace(/\s/g, ""),
|
||||
docId: checkSplit[0]
|
||||
};
|
||||
try {
|
||||
const linkContactRes = await Parse.Cloud.run(
|
||||
"linkcontacttodoc",
|
||||
@@ -107,9 +111,12 @@ function GuestLogin() {
|
||||
//send email OTP function
|
||||
const SendOtp = async () => {
|
||||
setLoading(true);
|
||||
setEmail(email);
|
||||
setEmail(email?.toLowerCase()?.replace(/\s/g, ""));
|
||||
try {
|
||||
const params = { email: email.toString(), docId: documentId };
|
||||
const params = {
|
||||
email: email?.toLowerCase()?.replace(/\s/g, "")?.toString(),
|
||||
docId: documentId
|
||||
};
|
||||
const Otp = await Parse.Cloud.run("SendOTPMailV1", params);
|
||||
if (Otp) {
|
||||
setLoading(false);
|
||||
@@ -140,7 +147,10 @@ function GuestLogin() {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": parseId
|
||||
};
|
||||
let body = { email: email, otp: OTP };
|
||||
let body = {
|
||||
email: email?.toLowerCase()?.replace(/\s/g, ""),
|
||||
otp: OTP
|
||||
};
|
||||
let user = await axios.post(url, body, { headers: headers });
|
||||
if (user.data.result === "Invalid Otp") {
|
||||
alert(t("invalid-otp"));
|
||||
@@ -185,7 +195,7 @@ function GuestLogin() {
|
||||
};
|
||||
const handleUserData = async (e) => {
|
||||
e.preventDefault();
|
||||
if (!emailRegex.test(contact.email)) {
|
||||
if (!emailRegex.test(contact.email?.toLowerCase()?.replace(/\s/g, ""))) {
|
||||
alert("Please enter a valid email address.");
|
||||
} else {
|
||||
const params = { ...contact, docId: documentId };
|
||||
@@ -212,7 +222,14 @@ function GuestLogin() {
|
||||
}
|
||||
};
|
||||
const handleInputChange = (e) => {
|
||||
setContact((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
if (e.target.name === "email") {
|
||||
setContact((prev) => ({
|
||||
...prev,
|
||||
[e.target.name]: e.target.value?.toLowerCase()?.replace(/\s/g, "")
|
||||
}));
|
||||
} else {
|
||||
setContact((prev) => ({ ...prev, [e.target.name]: e.target.value }));
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
@@ -220,11 +237,15 @@ function GuestLogin() {
|
||||
{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">
|
||||
<div className="pb-1 md:pb-4 pt-10 md:px-10 lg:px-16">
|
||||
<div className="md:p-4 lg:p-10 p-4 text-base-content bg-base-100 op-card shadow-md">
|
||||
<div className="w-[250px] h-[66px] inline-block overflow-hidden mb-6">
|
||||
{appLogo && (
|
||||
<img src={appLogo} className="object-contain" alt="logo" />
|
||||
<img
|
||||
src={appLogo}
|
||||
className="object-contain h-full"
|
||||
alt="logo"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{contactId ? (
|
||||
|
||||
@@ -7,9 +7,7 @@ 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,
|
||||
} from "../constant/const";
|
||||
import { emailRegex } from "../constant/const";
|
||||
import Alert from "../primitives/Alert";
|
||||
import { appInfo } from "../constant/appinfo";
|
||||
import { fetchAppInfo } from "../redux/reducers/infoReducer";
|
||||
@@ -24,8 +22,7 @@ import { useTranslation } from "react-i18next";
|
||||
import SelectLanguage from "../components/pdf/SelectLanguage";
|
||||
|
||||
function Login() {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
const { t, i18n } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -43,7 +40,7 @@ function Login() {
|
||||
baseUrl: localStorage.getItem("baseUrl"),
|
||||
parseAppId: localStorage.getItem("parseAppId"),
|
||||
loading: false,
|
||||
thirdpartyLoader: false,
|
||||
thirdpartyLoader: false
|
||||
});
|
||||
const [userDetails, setUserDetails] = useState({
|
||||
Company: "",
|
||||
@@ -57,13 +54,15 @@ function Login() {
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const showToast = (type, msg) => {
|
||||
setState({ ...state, loading: false, alertType: type, alertMsg: msg });
|
||||
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
|
||||
};
|
||||
const checkUserExt = async () => {
|
||||
const app = await getAppLogo();
|
||||
if (app?.error === "invalid_json") {
|
||||
setErrMsg(t("server-down", { appName: appName }));
|
||||
} else if (
|
||||
app?.user === "not_exist"
|
||||
) {
|
||||
} else if (app?.user === "not_exist") {
|
||||
navigate("/addadmin");
|
||||
}
|
||||
if (app?.logo) {
|
||||
@@ -153,68 +152,33 @@ function Login() {
|
||||
localStorage.setItem("PageLanding", menu.pageId);
|
||||
localStorage.setItem("defaultmenuid", menu.menuId);
|
||||
localStorage.setItem("pageType", menu.pageType);
|
||||
setState({ ...state, loading: false });
|
||||
// Redirect to the appropriate URL after successful login
|
||||
navigate(redirectUrl);
|
||||
setState({ ...state, loading: false });
|
||||
// Redirect to the appropriate URL after successful login
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
setState({ ...state, loading: false });
|
||||
setIsModal(true);
|
||||
}
|
||||
} else {
|
||||
setState({
|
||||
...state,
|
||||
loading: false,
|
||||
alertType: "danger",
|
||||
alertMsg:
|
||||
"You don't have access, please contact the admin."
|
||||
});
|
||||
showToast("danger", t("do-not-access-contact-admin"));
|
||||
logOutUser();
|
||||
}
|
||||
} else {
|
||||
setState({ ...state, loading: false });
|
||||
setState({
|
||||
...state,
|
||||
loading: false,
|
||||
alertType: "danger",
|
||||
alertMsg: "User not found."
|
||||
});
|
||||
logOutUser();
|
||||
showToast("danger", t("user-not-found"));
|
||||
logOutUser();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setState({
|
||||
...state,
|
||||
loading: false,
|
||||
alertType: "danger",
|
||||
alertMsg: `Something went wrong.`
|
||||
});
|
||||
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
|
||||
showToast("danger", t("something-went-wrong-mssg"));
|
||||
console.error("Error while fetching Follow", error);
|
||||
});
|
||||
} catch (error) {
|
||||
setState({
|
||||
...state,
|
||||
loading: false,
|
||||
alertType: "danger",
|
||||
alertMsg: `${error.message}`
|
||||
});
|
||||
console.log(error);
|
||||
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
|
||||
showToast("danger", `${error.message}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setState({
|
||||
...state,
|
||||
loading: false,
|
||||
alertType: "danger",
|
||||
alertMsg: "Invalid username/password or region"
|
||||
});
|
||||
console.error("Error while logging in user", error);
|
||||
} finally {
|
||||
setTimeout(
|
||||
() => setState((prev) => ({ ...prev, alertMsg: "" })),
|
||||
2000
|
||||
);
|
||||
showToast("danger", "Invalid username/password or region");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,55 +246,31 @@ function Login() {
|
||||
localStorage.setItem("PageLanding", menu.pageId);
|
||||
localStorage.setItem("defaultmenuid", menu.menuId);
|
||||
localStorage.setItem("pageType", menu.pageType);
|
||||
navigate(redirectUrl);
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
setState({
|
||||
...state,
|
||||
loading: false,
|
||||
alertType: "danger",
|
||||
alertMsg: "Role not found."
|
||||
});
|
||||
showToast("danger", t("role-not-found"));
|
||||
logOutUser();
|
||||
}
|
||||
} else {
|
||||
setState({
|
||||
...state,
|
||||
loading: false,
|
||||
alertType: "danger",
|
||||
alertMsg: "You don't have access, please contact the admin."
|
||||
});
|
||||
showToast("danger", t("do-not-access-contact-admin"));
|
||||
logOutUser();
|
||||
}
|
||||
} else {
|
||||
setState({
|
||||
...state,
|
||||
alertType: "danger",
|
||||
alertMsg: "User not found."
|
||||
});
|
||||
showToast("danger", t("user-not-found"));
|
||||
logOutUser();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("err in fetching extUser", err);
|
||||
setState({
|
||||
...state,
|
||||
alertType: "danger",
|
||||
alertMsg: `${err.message}`
|
||||
});
|
||||
showToast("danger", `${err.message}`);
|
||||
const payload = { sessionToken: sessionToken };
|
||||
handleSubmitbtn(payload);
|
||||
});
|
||||
} catch (error) {
|
||||
setState({
|
||||
...state,
|
||||
alertType: "danger",
|
||||
alertMsg: `${error.message}`
|
||||
});
|
||||
showToast("danger", `${error.message}`);
|
||||
console.log(error);
|
||||
} finally {
|
||||
setThirdpartyLoader(false);
|
||||
setState({ ...state, loading: false });
|
||||
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -380,40 +320,24 @@ function Login() {
|
||||
localStorage.setItem("PageLanding", menu.pageId);
|
||||
localStorage.setItem("defaultmenuid", menu.menuId);
|
||||
localStorage.setItem("pageType", menu.pageType);
|
||||
// Redirect to the appropriate URL after successful login
|
||||
navigate(redirectUrl);
|
||||
// Redirect to the appropriate URL after successful login
|
||||
navigate(redirectUrl);
|
||||
} else {
|
||||
setState({ ...state, loading: false });
|
||||
logOutUser();
|
||||
}
|
||||
} else {
|
||||
setState({
|
||||
...state,
|
||||
loading: false,
|
||||
alertType: "danger",
|
||||
alertMsg: "You don't have access, please contact the admin."
|
||||
});
|
||||
showToast("danger", t("do-not-access-contact-admin"));
|
||||
logOutUser();
|
||||
}
|
||||
} else {
|
||||
setState({
|
||||
...state,
|
||||
alertType: "danger",
|
||||
alertMsg: "User not found."
|
||||
});
|
||||
showToast("danger", t("user-not-found"));
|
||||
logOutUser();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
setState({
|
||||
...state,
|
||||
alertType: "danger",
|
||||
alertMsg: "Something went wrong, please try again later."
|
||||
});
|
||||
showToast("danger", t("something-went-wrong-mssg"));
|
||||
console.log("err", error);
|
||||
} finally {
|
||||
setState({ ...state, loading: false });
|
||||
setTimeout(() => setState({ ...state, alertMsg: "" }), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -425,8 +349,6 @@ function Login() {
|
||||
e.preventDefault();
|
||||
if (userDetails.Destination && userDetails.Company) {
|
||||
setThirdpartyLoader(true);
|
||||
// console.log("handelSubmit", userDetails);
|
||||
// const payload = await Parse.User.logIn(state.email, state.password);
|
||||
const payload = { sessionToken: localStorage.getItem("accesstoken") };
|
||||
const userInformation = JSON.parse(
|
||||
localStorage.getItem("UserInformation")
|
||||
@@ -466,13 +388,7 @@ function Login() {
|
||||
alert(t("server-error"));
|
||||
}
|
||||
} else {
|
||||
setState({
|
||||
...state,
|
||||
loading: false,
|
||||
alertType: "warning",
|
||||
alertMsg: "Please fill required details."
|
||||
});
|
||||
setTimeout(() => setState((prev) => ({ ...prev, alertMsg: "" })), 2000);
|
||||
showToast("warning", t("fill-required-details!"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -501,7 +417,6 @@ function Login() {
|
||||
localStorage.setItem("parseAppId", appid);
|
||||
};
|
||||
|
||||
|
||||
return errMsg ? (
|
||||
<div className="h-screen flex justify-center text-center items-center p-4 text-gray-500 text-base">
|
||||
{errMsg}
|
||||
@@ -522,7 +437,7 @@ function Login() {
|
||||
<div
|
||||
aria-labelledby="loginHeading"
|
||||
role="region"
|
||||
className="pb-1 md:pb-4 pt-10 md:px-10 lg:px-16"
|
||||
className="pb-1 md:pb-4 pt-10 md:px-10 lg:px-16 h-screen"
|
||||
>
|
||||
<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">
|
||||
@@ -561,39 +476,35 @@ function Login() {
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
/>
|
||||
<hr className="my-1 border-none" />
|
||||
<label className="block text-xs" htmlFor="password">
|
||||
{t("password")}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="password"
|
||||
type={
|
||||
state.passwordVisible ? "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={state.password}
|
||||
autoComplete="current-password"
|
||||
onChange={handleChange}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(
|
||||
t("input-required")
|
||||
)
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
<span
|
||||
className="absolute cursor-pointer top-[50%] right-[10px] -translate-y-[50%] text-base-content"
|
||||
onClick={togglePasswordVisibility}
|
||||
>
|
||||
{state.passwordVisible ? (
|
||||
<i className="fa-light fa-eye-slash text-xs pb-1" /> // Close eye icon
|
||||
) : (
|
||||
<i className="fa-light fa-eye text-xs pb-1 " /> // Open eye icon
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<label className="block text-xs" htmlFor="password">
|
||||
{t("password")}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
id="password"
|
||||
type={state.passwordVisible ? "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={state.password}
|
||||
autoComplete="current-password"
|
||||
onChange={handleChange}
|
||||
onInvalid={(e) =>
|
||||
e.target.setCustomValidity(t("input-required"))
|
||||
}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
/>
|
||||
<span
|
||||
className="absolute cursor-pointer top-[50%] right-[10px] -translate-y-[50%] text-base-content"
|
||||
onClick={togglePasswordVisibility}
|
||||
>
|
||||
{state.passwordVisible ? (
|
||||
<i className="fa-light fa-eye-slash text-xs pb-1" /> // Close eye icon
|
||||
) : (
|
||||
<i className="fa-light fa-eye text-xs pb-1 " /> // Open eye icon
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-1">
|
||||
<NavLink
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import React, { useEffect, useState, useRef } from "react";
|
||||
import "../styles/opensigndrive.css";
|
||||
import {
|
||||
iconColor,
|
||||
} from "../constant/const";
|
||||
import {
|
||||
getDrive
|
||||
} from "../constant/Utils";
|
||||
import { iconColor } from "../constant/const";
|
||||
import { getDrive } from "../constant/Utils";
|
||||
import { useNavigate } from "react-router";
|
||||
import Title from "../components/Title";
|
||||
import Parse from "parse";
|
||||
@@ -29,8 +25,7 @@ const AppLoader = () => {
|
||||
);
|
||||
};
|
||||
function Opensigndrive() {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
const drivename = appName === "OpenSign™" ? "OpenSign™" : "";
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
@@ -130,7 +125,7 @@ function Opensigndrive() {
|
||||
}
|
||||
];
|
||||
const getDetails = async () => {
|
||||
getPdfDocumentList();
|
||||
getPdfDocumentList();
|
||||
};
|
||||
//function for get all pdf document list
|
||||
const getPdfDocumentList = async (disbaleLoading) => {
|
||||
@@ -144,7 +139,7 @@ function Opensigndrive() {
|
||||
try {
|
||||
const driveDetails = await getDrive(docId, skip, limit);
|
||||
if (driveDetails && driveDetails === "Error: Something went wrong!") {
|
||||
setHandleError("Error: Something went wrong!");
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
} else if (driveDetails && driveDetails.length > 0) {
|
||||
const addMoreTour = [
|
||||
{
|
||||
@@ -635,7 +630,9 @@ function Opensigndrive() {
|
||||
<div className="flex flex-row items-center">
|
||||
<div
|
||||
id="folder-menu"
|
||||
className={`${isOptions ? "dropdown show dropDownStyle" : "dropdown"} hidden md:block`}
|
||||
className={`${
|
||||
isOptions ? "dropdown show dropDownStyle" : "dropdown"
|
||||
} hidden md:block`}
|
||||
onClick={() => setIsOptions(!isOptions)}
|
||||
>
|
||||
<div className="sort" data-tut="reactourSecond">
|
||||
@@ -773,7 +770,9 @@ function Opensigndrive() {
|
||||
</div>
|
||||
<div
|
||||
id="folder-menu"
|
||||
className={`${isOptions ? "dropdown show dropDownStyle" : "dropdown"} md:hidden`}
|
||||
className={`${
|
||||
isOptions ? "dropdown show dropDownStyle" : "dropdown"
|
||||
} md:hidden`}
|
||||
onClick={() => setIsOptions(!isOptions)}
|
||||
>
|
||||
<div
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ import axios from "axios";
|
||||
import Parse from "parse";
|
||||
import "../styles/signature.css";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import { maxTitleLength } from "../constant/const";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { useDrop } from "react-dnd";
|
||||
@@ -68,8 +69,8 @@ import AddContact from "../primitives/AddContact";
|
||||
|
||||
function PlaceHolderSign() {
|
||||
const { t } = useTranslation();
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const copyUrlRef = useRef(null);
|
||||
const appName = "OpenSign™";
|
||||
const editorRef = useRef();
|
||||
const { state } = useLocation();
|
||||
const navigate = useNavigate();
|
||||
@@ -150,7 +151,6 @@ function PlaceHolderSign() {
|
||||
status: false,
|
||||
message: ""
|
||||
});
|
||||
const [extUserId, setExtUserId] = useState("");
|
||||
const [isCustomize, setIsCustomize] = useState(false);
|
||||
const [zoomPercent, setZoomPercent] = useState(0);
|
||||
const [scale, setScale] = useState(1);
|
||||
@@ -164,13 +164,14 @@ function PlaceHolderSign() {
|
||||
const [userList, setUserList] = useState([]);
|
||||
const [isAttchSignerModal, setIsAttchSignerModal] = useState(false);
|
||||
const [isNewContact, setIsNewContact] = useState({ status: false, id: "" });
|
||||
const [owner, setOwner] = useState({});
|
||||
const [docTitle, setDocTitle] = useState("");
|
||||
const isMobile = window.innerWidth < 767;
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
drop: (item, monitor) => addPositionOfSignature(item, monitor),
|
||||
collect: (monitor) => ({ isOver: !!monitor.isOver() })
|
||||
});
|
||||
|
||||
const documentId = docId;
|
||||
useEffect(() => {
|
||||
if (documentId) {
|
||||
@@ -244,6 +245,7 @@ function PlaceHolderSign() {
|
||||
//getting document details
|
||||
const documentData = await contractDocument(documentId);
|
||||
if (documentData && documentData.length > 0) {
|
||||
setDocTitle(documentData?.[0]?.Name);
|
||||
if (documentData[0]?.Placeholders?.length > 0) {
|
||||
const signerNotExist = documentData[0]?.Placeholders.some(
|
||||
(data) => !data.signerObjId && data.Role !== "prefill"
|
||||
@@ -286,7 +288,7 @@ function PlaceHolderSign() {
|
||||
setPdfArrayBuffer(arrayBuffer);
|
||||
setPdfBase64Url(base64Pdf);
|
||||
}
|
||||
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
|
||||
setOwner(documentData?.[0]?.ExtUserPtr);
|
||||
const alreadyPlaceholder = documentData[0]?.SignedUrl;
|
||||
// Check if document is sent for signing
|
||||
if (alreadyPlaceholder) {
|
||||
@@ -457,7 +459,11 @@ function PlaceHolderSign() {
|
||||
documentData === "Error: Something went wrong!" ||
|
||||
(documentData.result && documentData.result.error)
|
||||
) {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
if (documentData?.result?.error?.includes("deleted")) {
|
||||
setHandleError(t("document-deleted"));
|
||||
} else {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
}
|
||||
setIsLoading({ isLoad: false });
|
||||
} else {
|
||||
setHandleError(t("no-data-avaliable"));
|
||||
@@ -525,7 +531,7 @@ function PlaceHolderSign() {
|
||||
scale: containerScale,
|
||||
zIndex: posZIndex,
|
||||
type: dragTypeValue,
|
||||
options: addWidgetOptions(dragTypeValue),
|
||||
options: addWidgetOptions(dragTypeValue, owner),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
Height: widgetHeight / (containerScale * scale)
|
||||
};
|
||||
@@ -556,7 +562,7 @@ function PlaceHolderSign() {
|
||||
scale: containerScale,
|
||||
zIndex: posZIndex,
|
||||
type: dragTypeValue,
|
||||
options: addWidgetOptions(dragTypeValue),
|
||||
options: addWidgetOptions(dragTypeValue, owner),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
Height: widgetHeight / (containerScale * scale)
|
||||
};
|
||||
@@ -863,11 +869,7 @@ function PlaceHolderSign() {
|
||||
scale
|
||||
);
|
||||
const pdfName = generatePdfName(16);
|
||||
const pdfUrl = await convertBase64ToFile(
|
||||
pdfName,
|
||||
pdfBase64,
|
||||
"",
|
||||
);
|
||||
const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64, "");
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const buffer = atob(pdfBase64);
|
||||
SaveFileSize(buffer.length, pdfUrl, tenantId);
|
||||
@@ -879,11 +881,7 @@ function PlaceHolderSign() {
|
||||
} else if (pdfBase64Url) {
|
||||
try {
|
||||
const pdfName = generatePdfName(16);
|
||||
const pdfUrl = await convertBase64ToFile(
|
||||
pdfName,
|
||||
pdfBase64Url,
|
||||
"",
|
||||
);
|
||||
const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
|
||||
return pdfUrl;
|
||||
} catch (err) {
|
||||
console.log("error to convertBase64ToFile in placeholder flow", err);
|
||||
@@ -989,11 +987,7 @@ function PlaceHolderSign() {
|
||||
let pdfUrl;
|
||||
if (isUploadPdf) {
|
||||
const pdfName = generatePdfName(16);
|
||||
pdfUrl = await convertBase64ToFile(
|
||||
pdfName,
|
||||
pdfBase64Url,
|
||||
"",
|
||||
);
|
||||
pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
|
||||
}
|
||||
try {
|
||||
const docCls = new Parse.Object("contracts_Document");
|
||||
@@ -1019,6 +1013,7 @@ function PlaceHolderSign() {
|
||||
const saveDocumentDetails = async () => {
|
||||
setIsUiLoading(true);
|
||||
let signerMail = signersdata.slice();
|
||||
// For "Send in order", only consider the first signer
|
||||
if (pdfDetails?.[0]?.SendinOrder && pdfDetails?.[0]?.SendinOrder === true) {
|
||||
signerMail.splice(1);
|
||||
}
|
||||
@@ -1031,7 +1026,7 @@ function PlaceHolderSign() {
|
||||
objectId: x.objectId
|
||||
};
|
||||
});
|
||||
const addExtraDays = pdfDetails[0]?.TimeToCompleteDays
|
||||
const addExtraDays = pdfDetails?.[0]?.TimeToCompleteDays
|
||||
? pdfDetails[0].TimeToCompleteDays
|
||||
: 15;
|
||||
const currentUser = signersdata.find((x) => x.Email === currentId);
|
||||
@@ -1046,22 +1041,22 @@ function PlaceHolderSign() {
|
||||
} else {
|
||||
setIsCurrUser(currentUser?.objectId ? true : false);
|
||||
}
|
||||
let updateExpiryDate, data;
|
||||
updateExpiryDate = new Date();
|
||||
// Compute expiry date with extra days
|
||||
let updateExpiryDate = new Date();
|
||||
updateExpiryDate.setDate(updateExpiryDate.getDate() + addExtraDays);
|
||||
//filter label widgets after add label widgets data on pdf
|
||||
|
||||
// Filter out prefill roles
|
||||
const filterPrefill = signerPos.filter((data) => data.Role !== "prefill");
|
||||
try {
|
||||
data = {
|
||||
const data = {
|
||||
Name: docTitle || pdfDetails?.[0]?.Name,
|
||||
Placeholders: filterPrefill,
|
||||
SignedUrl: pdfUrl,
|
||||
Signers: signers,
|
||||
SentToOthers: true,
|
||||
SignatureType: pdfDetails?.[0]?.SignatureType
|
||||
SignatureType: pdfDetails?.[0]?.SignatureType,
|
||||
ExpiryDate: { iso: updateExpiryDate, __type: "Date" }
|
||||
};
|
||||
if (updateExpiryDate) {
|
||||
data["ExpiryDate"] = { iso: updateExpiryDate, __type: "Date" };
|
||||
}
|
||||
await axios.put(
|
||||
`${localStorage.getItem(
|
||||
"baseUrl"
|
||||
@@ -1080,6 +1075,11 @@ function PlaceHolderSign() {
|
||||
setIsUiLoading(false);
|
||||
setSignerPos([]);
|
||||
setIsSendAlert({ mssg: "confirm", alert: true });
|
||||
if (docTitle) {
|
||||
const updatedPdfDetails = [...pdfDetails];
|
||||
updatedPdfDetails[0].Name = docTitle;
|
||||
setPdfDetails(updatedPdfDetails);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
@@ -1091,6 +1091,9 @@ function PlaceHolderSign() {
|
||||
|
||||
const copytoclipboard = (text) => {
|
||||
copytoData(text);
|
||||
if (copyUrlRef.current) {
|
||||
copyUrlRef.current.textContent = text; // Update text safely
|
||||
}
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500); // Reset copied state after 1.5 seconds
|
||||
};
|
||||
@@ -1149,8 +1152,7 @@ function PlaceHolderSign() {
|
||||
year: "numeric"
|
||||
});
|
||||
|
||||
let senderEmail =
|
||||
pdfDetails?.[0]?.ExtUserPtr?.Email;
|
||||
let senderEmail = pdfDetails?.[0]?.ExtUserPtr?.Email;
|
||||
let senderPhone = pdfDetails?.[0]?.ExtUserPtr?.Phone;
|
||||
let signerMail = signersdata.slice();
|
||||
|
||||
@@ -1176,16 +1178,11 @@ function PlaceHolderSign() {
|
||||
const orgName = pdfDetails[0]?.ExtUserPtr.Company
|
||||
? pdfDetails[0].ExtUserPtr.Company
|
||||
: "";
|
||||
const senderName =
|
||||
pdfDetails?.[0].ExtUserPtr.Name;
|
||||
const senderName = pdfDetails?.[0].ExtUserPtr.Name;
|
||||
const documentName = `${pdfDetails?.[0].Name}`;
|
||||
let replaceVar;
|
||||
|
||||
if (
|
||||
requestBody &&
|
||||
requestSubject &&
|
||||
isCustomize
|
||||
) {
|
||||
if (requestBody && requestSubject && isCustomize) {
|
||||
const replacedRequestBody = requestBody.replace(/"/g, "'");
|
||||
htmlReqBody =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
|
||||
@@ -1194,6 +1191,7 @@ function PlaceHolderSign() {
|
||||
|
||||
const variables = {
|
||||
document_title: documentName,
|
||||
note: pdfDetails?.[0]?.Note,
|
||||
sender_name: senderName,
|
||||
sender_mail: senderEmail,
|
||||
sender_phone: senderPhone || "",
|
||||
@@ -1209,10 +1207,7 @@ function PlaceHolderSign() {
|
||||
htmlReqBody,
|
||||
variables
|
||||
);
|
||||
} else if (
|
||||
tenantMailTemplate?.body &&
|
||||
tenantMailTemplate?.subject
|
||||
) {
|
||||
} else if (tenantMailTemplate?.body && tenantMailTemplate?.subject) {
|
||||
const mailBody = tenantMailTemplate?.body;
|
||||
const mailSubject = tenantMailTemplate?.subject;
|
||||
const replacedRequestBody = mailBody.replace(/"/g, "'");
|
||||
@@ -1222,6 +1217,7 @@ function PlaceHolderSign() {
|
||||
"</body> </html>";
|
||||
const variables = {
|
||||
document_title: documentName,
|
||||
note: pdfDetails?.[0]?.Note,
|
||||
sender_name: senderName,
|
||||
sender_mail: senderEmail,
|
||||
sender_phone: senderPhone || "",
|
||||
@@ -1236,6 +1232,7 @@ function PlaceHolderSign() {
|
||||
}
|
||||
const mailparam = {
|
||||
senderName: senderName,
|
||||
note: pdfDetails?.[0]?.Note || "",
|
||||
senderMail: senderEmail,
|
||||
title: documentName,
|
||||
organization: orgName,
|
||||
@@ -1243,14 +1240,13 @@ function PlaceHolderSign() {
|
||||
sigingUrl: signPdf
|
||||
};
|
||||
let params = {
|
||||
extUserId: extUserId,
|
||||
extUserId: owner?.objectId,
|
||||
recipient: signerMail[i].Email,
|
||||
subject: replaceVar?.subject
|
||||
? replaceVar?.subject
|
||||
: mailTemplate(mailparam).subject,
|
||||
replyto: senderEmail,
|
||||
from:
|
||||
senderEmail,
|
||||
from: senderEmail,
|
||||
html: replaceVar?.body
|
||||
? replaceVar?.body
|
||||
: mailTemplate(mailparam).body
|
||||
@@ -1265,20 +1261,13 @@ function PlaceHolderSign() {
|
||||
setMailStatus("success");
|
||||
try {
|
||||
let data;
|
||||
if (
|
||||
requestBody &&
|
||||
requestSubject &&
|
||||
isCustomize
|
||||
) {
|
||||
if (requestBody && requestSubject && isCustomize) {
|
||||
data = {
|
||||
RequestBody: htmlReqBody,
|
||||
RequestSubject: requestSubject,
|
||||
SendMail: true
|
||||
};
|
||||
} else if (
|
||||
tenantMailTemplate?.body &&
|
||||
tenantMailTemplate?.subject
|
||||
) {
|
||||
} else if (tenantMailTemplate?.body && tenantMailTemplate?.subject) {
|
||||
data = {
|
||||
RequestBody: tenantMailTemplate?.body,
|
||||
RequestSubject: tenantMailTemplate?.subject,
|
||||
@@ -1557,8 +1546,7 @@ function PlaceHolderSign() {
|
||||
status: defaultdata?.status || "required",
|
||||
hint: defaultdata?.hint || "",
|
||||
defaultValue: defaultdata?.defaultValue || "",
|
||||
validation:
|
||||
{},
|
||||
validation: {},
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
@@ -1568,6 +1556,15 @@ function PlaceHolderSign() {
|
||||
isReadOnly: defaultdata?.isReadOnly || false
|
||||
}
|
||||
};
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata.name,
|
||||
hint: defaultdata?.hint || ""
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...position,
|
||||
@@ -1576,6 +1573,7 @@ function PlaceHolderSign() {
|
||||
name: defaultdata.name,
|
||||
status: defaultdata.status,
|
||||
defaultValue: defaultdata.defaultValue,
|
||||
hint: defaultdata?.hint || "",
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
@@ -1855,6 +1853,10 @@ function PlaceHolderSign() {
|
||||
setPdfBase64Url(urlDetails.base64);
|
||||
};
|
||||
const handleSendDoc = () => {
|
||||
if (docTitle?.length > maxTitleLength) {
|
||||
alert(t("title-length-alert"));
|
||||
return;
|
||||
}
|
||||
setIsAttchSignerModal(false);
|
||||
setCheckTourStatus(true);
|
||||
alertSendEmail();
|
||||
@@ -1917,6 +1919,10 @@ function PlaceHolderSign() {
|
||||
return isAllSigner;
|
||||
};
|
||||
const handleCloseAttachSigner = () => {
|
||||
if (docTitle?.length > maxTitleLength) {
|
||||
alert(t("title-length-alert"));
|
||||
return;
|
||||
}
|
||||
setIsAttchSignerModal(false);
|
||||
};
|
||||
return (
|
||||
@@ -2023,30 +2029,26 @@ function PlaceHolderSign() {
|
||||
{!isCustomize && (
|
||||
<span>{t("placeholder-alert-3")}</span>
|
||||
)}
|
||||
{
|
||||
isCustomize && (
|
||||
<>
|
||||
<EmailBody
|
||||
editorRef={editorRef}
|
||||
requestBody={requestBody}
|
||||
requestSubject={requestSubject}
|
||||
handleOnchangeRequest={
|
||||
handleOnchangeRequest
|
||||
}
|
||||
setRequestSubject={setRequestSubject}
|
||||
/>
|
||||
<div
|
||||
className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary"
|
||||
onClick={() => {
|
||||
setRequestBody(defaultBody);
|
||||
setRequestSubject(defaultSubject);
|
||||
}}
|
||||
>
|
||||
<span>{t("reset-to-default")}</span>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
{isCustomize && (
|
||||
<>
|
||||
<EmailBody
|
||||
editorRef={editorRef}
|
||||
requestBody={requestBody}
|
||||
requestSubject={requestSubject}
|
||||
handleOnchangeRequest={handleOnchangeRequest}
|
||||
setRequestSubject={setRequestSubject}
|
||||
/>
|
||||
<div
|
||||
className="flex justify-end items-center gap-1 mt-2 op-link op-link-primary"
|
||||
onClick={() => {
|
||||
setRequestBody(defaultBody);
|
||||
setRequestSubject(defaultSubject);
|
||||
}}
|
||||
>
|
||||
<span>{t("reset-to-default")}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex flex-row items-center gap-2 md:gap-6 mt-2">
|
||||
<div className="flex flex-row gap-2">
|
||||
<button
|
||||
@@ -2064,16 +2066,14 @@ function PlaceHolderSign() {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{
|
||||
!isCustomize && (
|
||||
<span
|
||||
className="op-link op-link-accent text-sm"
|
||||
onClick={() => setIsCustomize(!isCustomize)}
|
||||
>
|
||||
{t("cutomize-email")}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{!isCustomize && (
|
||||
<span
|
||||
className="op-link op-link-accent text-sm"
|
||||
onClick={() => setIsCustomize(!isCustomize)}
|
||||
>
|
||||
{t("cutomize-email")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@@ -2085,6 +2085,11 @@ function PlaceHolderSign() {
|
||||
<span className="h-[1px] w-[20%] bg-[#ccc]"></span>
|
||||
</div>
|
||||
<div className="my-3">{handleShareList()}</div>
|
||||
<p
|
||||
id="copyUrl"
|
||||
ref={copyUrlRef}
|
||||
className="hidden"
|
||||
></p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -2111,9 +2116,11 @@ function PlaceHolderSign() {
|
||||
<LottieWithLoader />
|
||||
{pdfDetails[0].SendinOrder ? (
|
||||
<p>
|
||||
{t("placeholder-mail-alert", {
|
||||
name: signersdata[0]?.Name
|
||||
})}
|
||||
{isCurrUser
|
||||
? t("placeholder-mail-alert-you")
|
||||
: t("placeholder-mail-alert", {
|
||||
name: signersdata[0]?.Name
|
||||
})}
|
||||
</p>
|
||||
) : (
|
||||
<p>{t("placeholder-alert-4")}</p>
|
||||
@@ -2126,7 +2133,11 @@ function PlaceHolderSign() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="mb-[10px]">
|
||||
<p>{t("placeholder-alert-6")}</p>
|
||||
{mailStatus === "dailyquotareached" ? (
|
||||
<p>{t("daily-quota-reached")}</p>
|
||||
) : (
|
||||
<p>{t("placeholder-alert-6")}</p>
|
||||
)}
|
||||
{isCurrUser && (
|
||||
<p className="mt-1">{t("placeholder-alert-5")}</p>
|
||||
)}
|
||||
@@ -2186,24 +2197,45 @@ function PlaceHolderSign() {
|
||||
</ModalUi>
|
||||
<ModalUi
|
||||
isOpen={isAttchSignerModal}
|
||||
title={t("add-signer")}
|
||||
title={t("create-document")}
|
||||
handleClose={() => handleCloseAttachSigner()}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<div className="h-[100%] px-[20px] py-[10px]">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="doctitle"
|
||||
className="block text-xs font-semibold"
|
||||
>
|
||||
Title
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="doctitle"
|
||||
value={docTitle}
|
||||
onChange={(e) => setDocTitle(e.target.value)}
|
||||
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-[11px] py-[18px]"
|
||||
/>
|
||||
</div>
|
||||
{pdfDetails[0].Placeholders?.some(
|
||||
(x) => !x.signerObjId
|
||||
) && (
|
||||
<>
|
||||
{/* grid grid-cols-1 md:grid-cols-2 */}
|
||||
<div className="min-h-max max-h-[250px] overflow-y-auto">
|
||||
<div className="py-3 px-[10px] op-card border-[1px] border-gray-400 mt-3 md:mx-3 mb-4 bg-base-200 text-base-content flex flex-col gap-2 relative">
|
||||
<div className="py-2 text-base-content flex flex-col gap-2 relative">
|
||||
{forms?.map((field, id) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col"
|
||||
key={field?.value}
|
||||
>
|
||||
<label>{field?.role}</label>
|
||||
<label className="block text-xs font-semibold">
|
||||
{field?.role}
|
||||
</label>
|
||||
<div className="flex justify-between items-center gap-1">
|
||||
<div className="flex-1">
|
||||
<AsyncSelect
|
||||
@@ -2246,7 +2278,7 @@ function PlaceHolderSign() {
|
||||
onClick={(e) =>
|
||||
handleCreateNew(e, field.value)
|
||||
}
|
||||
className="op-btn op-btn-accent op-btn-outline op-btn-sm "
|
||||
className="op-btn op-btn-accent op-btn-outline op-btn-sm"
|
||||
>
|
||||
<i className="fa-light fa-plus"></i>
|
||||
</button>
|
||||
@@ -2256,8 +2288,8 @@ function PlaceHolderSign() {
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-[1px] bg-[#9f9f9f] my-[15px]"></div>
|
||||
<div className="flex mx-4 mb-4 gap-3">
|
||||
<div className="w-full h-[0.5px] bg-[#9f9f9f] mt-[8px] mb-[15px]"></div>
|
||||
<div className="flex mx-2 mb-2 gap-3">
|
||||
<button
|
||||
disabled={handleDisable()}
|
||||
onClick={() => handleSendDoc()}
|
||||
|
||||
@@ -20,10 +20,10 @@ import EditorToolbar, {
|
||||
module2,
|
||||
formats
|
||||
} from "../components/pdf/EditorToolbar";
|
||||
import DateFormatSelector from "../components/shared/fields/DateFormatSelector";
|
||||
|
||||
const Preferences = () => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const editorRef = useRef();
|
||||
const editorRefCom = useRef();
|
||||
@@ -46,6 +46,10 @@ const Preferences = () => {
|
||||
const [activeTab, setactiveTab] = useState(0);
|
||||
const generaltab = { title: t("general"), icon: "fa-light fa-gears" };
|
||||
const [tab, setTab] = useState([generaltab]);
|
||||
const [sendinOrder, setSendinOrder] = useState(true);
|
||||
const [isTourEnabled, setIsTourEnabled] = useState(false);
|
||||
const [dateFormat, setDateFormat] = useState("MM/DD/YYYY");
|
||||
const [is12HourTime, setIs12HourTime] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSignType();
|
||||
@@ -90,10 +94,26 @@ const Preferences = () => {
|
||||
const SignatureType = _getUser?.SignatureType || signatureTypes;
|
||||
setSignatureType(SignatureType);
|
||||
}
|
||||
const sendinorder =
|
||||
_getUser?.SendinOrder !== undefined ? _getUser?.SendinOrder : true;
|
||||
setSendinOrder(sendinorder);
|
||||
const istourenabled =
|
||||
_getUser?.IsTourEnabled !== undefined
|
||||
? _getUser?.IsTourEnabled
|
||||
: true;
|
||||
setIsTourEnabled(istourenabled);
|
||||
const DateFormat =
|
||||
_getUser?.DateFormat !== undefined
|
||||
? _getUser?.DateFormat
|
||||
: "MM/DD/YYYY";
|
||||
setDateFormat(DateFormat);
|
||||
const is12Hr =
|
||||
_getUser?.Is12HourTime !== undefined ? _getUser?.Is12HourTime : false;
|
||||
setIs12HourTime(is12Hr);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("err while getting user details", err);
|
||||
setErrMsg("Something went wrong");
|
||||
setErrMsg(t("something-went-wrong-mssg"));
|
||||
} finally {
|
||||
setIsTopLoader(false);
|
||||
}
|
||||
@@ -142,14 +162,25 @@ const Preferences = () => {
|
||||
params = { ...params, NotifyOnSignatures: isNotifyOnSignatures };
|
||||
}
|
||||
try {
|
||||
params = {
|
||||
...params,
|
||||
SendinOrder: sendinOrder,
|
||||
IsTourEnabled: isTourEnabled,
|
||||
DateFormat: dateFormat,
|
||||
Is12HourTime: is12HourTime
|
||||
};
|
||||
const updateRes = await Parse.Cloud.run("updatepreferences", params);
|
||||
if (updateRes) {
|
||||
setIsAlert({ type: "success", msg: "Saved successfully." });
|
||||
setIsAlert({ type: "success", msg: t("saved-successfully") });
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.NotifyOnSignatures = isNotifyOnSignatures;
|
||||
extUser.SendinOrder = sendinOrder;
|
||||
extUser.IsTourEnabled = isTourEnabled;
|
||||
extUser.DateFormat = dateFormat;
|
||||
extUser.Is12HourTime = is12HourTime;
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
@@ -213,21 +244,23 @@ const Preferences = () => {
|
||||
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));
|
||||
const updateTenant = await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: {
|
||||
CompletionBody: htmlBody,
|
||||
CompletionSubject: completionsubject
|
||||
}
|
||||
});
|
||||
if (updateTenant) {
|
||||
const updateRes = JSON.parse(JSON.stringify(updateTenant));
|
||||
SetCompletionBody(updateRes?.CompletionBody);
|
||||
setCompletionSubject(updateRes?.CompletionSubject);
|
||||
setIsAlert({ type: "success", msg: "Saved successfully." });
|
||||
setIsAlert({ type: "success", msg: t("saved-successfully") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err", err);
|
||||
setIsAlert({ type: "danger", msg: "Something went wrong." });
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
@@ -240,21 +273,23 @@ const Preferences = () => {
|
||||
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));
|
||||
const updateTenant = await Parse.Cloud.run("updatetenant", {
|
||||
tenantId: tenantId,
|
||||
details: {
|
||||
RequestBody: htmlBody,
|
||||
RequestSubject: requestSubject
|
||||
}
|
||||
});
|
||||
if (updateTenant) {
|
||||
const updateRes = JSON.parse(JSON.stringify(updateTenant));
|
||||
setRequestBody(updateRes?.RequestBody);
|
||||
setRequestSubject(updateRes?.RequestSubject);
|
||||
setIsAlert({ type: "success", msg: "Saved successfully." });
|
||||
setIsAlert({ type: "success", msg: t("saved-successfully") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err", err);
|
||||
setIsAlert({ type: "danger", msg: "Something went wrong." });
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
@@ -285,6 +320,9 @@ const Preferences = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleTourInput = () => setIsTourEnabled(!isTourEnabled);
|
||||
const handleSendinOrderInput = () => setSendinOrder(!sendinOrder);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title title={t("Preferences")} />
|
||||
@@ -302,7 +340,7 @@ const Preferences = () => {
|
||||
) : (
|
||||
<div className="relative bg-base-100 text-base-content flex flex-col justify-center shadow-md rounded-box mb-3">
|
||||
{isLoader && (
|
||||
<div className="flex justify-center items-center absolute w-full h-full rounded-box bg-black/30">
|
||||
<div className="flex z-[100] justify-center items-center absolute w-full h-full rounded-box bg-black/30">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
@@ -322,7 +360,9 @@ const Preferences = () => {
|
||||
onClick={() => setactiveTab(ind)}
|
||||
key={ind}
|
||||
role="tab"
|
||||
className={`${activeTab === ind ? "op-tab-active" : ""} op-tab text-xs md:text-base pb-2 md:pb-0 }`}
|
||||
className={`${
|
||||
activeTab === ind ? "op-tab-active" : ""
|
||||
} op-tab text-xs md:text-base pb-2 md:pb-0 }`}
|
||||
>
|
||||
<i className={tabData.icon}></i>
|
||||
<span className="ml-1 md:ml-2">{tabData.title}</span>
|
||||
@@ -332,154 +372,281 @@ const Preferences = () => {
|
||||
</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"
|
||||
>
|
||||
<div className="ml-4 mt-1 mb-2 flex flex-col">
|
||||
<div className="mb-[0.75rem]">
|
||||
<label
|
||||
className="mb-[0.7rem] text-[12px]"
|
||||
htmlFor="signaturetype"
|
||||
>
|
||||
<span className="font-medium text-[14px]">
|
||||
{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>
|
||||
</span>
|
||||
<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]">
|
||||
<p className="font-bold">
|
||||
{t("allowed-signature-types")}
|
||||
</p>
|
||||
<p>{t("allowed-signature-types-help.p1")}</p>
|
||||
<div 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>
|
||||
</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>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<div className="ml-[7px] flex flex-col md:flex-row gap-[10px] mb-[0.7rem]">
|
||||
{signatureType.map((type, i) => (
|
||||
<div
|
||||
className={
|
||||
"flex items-center gap-2 ml-2 mb-1"
|
||||
}
|
||||
key={i}
|
||||
className="flex flex-row gap-[5px] items-center"
|
||||
>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={isNotifyOnSignatures === true}
|
||||
className="mr-[2px] op-checkbox op-checkbox-xs"
|
||||
type="checkbox"
|
||||
name="signaturetype"
|
||||
onChange={() => handleCheckboxChange(i)}
|
||||
checked={type.enabled}
|
||||
/>
|
||||
<div className="text-[13px] font-medium cursor-default capitalize">
|
||||
{t("yes")}
|
||||
<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 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 className="mb-[0.75rem]">
|
||||
<label className="mb-[0.7rem] text-[12px]">
|
||||
<span className="text-[14px] font-medium">
|
||||
{t("notify-on-signatures")}
|
||||
</span>
|
||||
<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]">
|
||||
<p className="font-bold">
|
||||
{t("notify-on-signatures")}
|
||||
</p>
|
||||
<p>{t("notify-on-signatures-help.p1")}</p>
|
||||
<p>{t("notify-on-signatures-help.note")}</p>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className={"flex items-center gap-2 ml-2 mb-1"}>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(true)}
|
||||
checked={isNotifyOnSignatures === true}
|
||||
/>
|
||||
<div className="text-[13px] cursor-default capitalize">
|
||||
{t("yes")}
|
||||
</div>
|
||||
</div>
|
||||
<div className={"flex items-center gap-2 ml-2 mb-1"}>
|
||||
<input
|
||||
className="mr-[2px] op-radio op-radio-xs"
|
||||
type="radio"
|
||||
onChange={() => handleNotifySignChange(false)}
|
||||
checked={isNotifyOnSignatures === false}
|
||||
/>
|
||||
<div className="text-[13px] cursor-default capitalize">
|
||||
{t("no")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<TimezoneSelector
|
||||
timezone={timezone}
|
||||
setTimezone={setTimezone}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<button
|
||||
className="op-btn op-btn-primary"
|
||||
onClick={handleSave}
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<TimezoneSelector
|
||||
timezone={timezone}
|
||||
setTimezone={setTimezone}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<DateFormatSelector
|
||||
timezone={timezone}
|
||||
dateFormat={dateFormat}
|
||||
is12HourTime={is12HourTime}
|
||||
setIs12HourTime={setIs12HourTime}
|
||||
setDateFormat={setDateFormat}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-[0.75rem] text-[12px]">
|
||||
<label className="block mb-[0.7rem]">
|
||||
<span className="text-[14px] font-medium">
|
||||
{t("send-in-order")}
|
||||
</span>
|
||||
<a
|
||||
data-tooltip-id="sendInOrder-tooltip"
|
||||
className="ml-1"
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
<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="sendInOrder-tooltip"
|
||||
className="z-[999]"
|
||||
>
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("send-in-order")}</p>
|
||||
<p>{t("send-in-order-help.p1")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
{t("yes")}:{" "}
|
||||
</span>
|
||||
<span>{t("send-in-order-help.p2")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">{t("no")}: </span>
|
||||
<span>{t("send-in-order-help.p3")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p>{t("send-in-order-help.p4")}</p>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={true}
|
||||
className="op-radio op-radio-xs"
|
||||
name="SendinOrder"
|
||||
checked={sendinOrder}
|
||||
onChange={handleSendinOrderInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={false}
|
||||
name="SendinOrder"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={!sendinOrder}
|
||||
onChange={handleSendinOrderInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
<div className="mb-[0.75rem] text-[12px]">
|
||||
<label className="block mb-[0.7rem]">
|
||||
<span className="text-[14px] font-medium">
|
||||
{t("enable-tour")}
|
||||
</span>
|
||||
<a
|
||||
data-tooltip-id="istourenabled-tooltip"
|
||||
className="ml-1"
|
||||
>
|
||||
<sup>
|
||||
<i className="fa-light fa-question rounded-full border-[#33bbff] text-[#33bbff] text-[13px] border-[1px] py-[1.5px] px-[4px]"></i>
|
||||
</sup>
|
||||
</a>
|
||||
<ReactTooltip
|
||||
id="istourenabled-tooltip"
|
||||
className="z-[999]"
|
||||
>
|
||||
<div className="max-w-[200px] md:max-w-[450px]">
|
||||
<p className="font-bold">{t("enable-tour")}</p>
|
||||
<div className="p-[5px]">
|
||||
<ol className="list-disc">
|
||||
<li>
|
||||
<span className="font-bold">
|
||||
{t("yes")}:{" "}
|
||||
</span>
|
||||
<span>{t("istourenabled-help.p1")}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-bold">{t("no")}: </span>
|
||||
<span>{t("istourenabled-help.p2")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<p>
|
||||
{t("istourenabled-help.p3", {
|
||||
appName: appName
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</ReactTooltip>
|
||||
</label>
|
||||
<div className="flex flex-col md:flex-row md:gap-4">
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={true}
|
||||
className="op-radio op-radio-xs"
|
||||
name="IsTourEnabled"
|
||||
checked={isTourEnabled}
|
||||
onChange={handleTourInput}
|
||||
/>
|
||||
<div className="text-center">{t("yes")}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-2 mb-1">
|
||||
<input
|
||||
type="radio"
|
||||
value={false}
|
||||
name="IsTourEnabled"
|
||||
className="op-radio op-radio-xs"
|
||||
checked={!isTourEnabled}
|
||||
onChange={handleTourInput}
|
||||
/>
|
||||
<div className="text-center">{t("no")}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-[0.75rem]">
|
||||
<button
|
||||
className="op-btn op-btn-primary w-[110px]"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col mx-4 mb-4">
|
||||
<div className="flex flex-col">
|
||||
@@ -496,7 +663,9 @@ const Preferences = () => {
|
||||
{t("subject")}
|
||||
<Tooltip
|
||||
id={"request-sub-tooltip"}
|
||||
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
|
||||
message={`${t(
|
||||
"variables-use"
|
||||
)}: {{sender_name}} {{document_title}}`}
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
@@ -505,7 +674,9 @@ const Preferences = () => {
|
||||
onChange={(e) =>
|
||||
setRequestSubject(e.target.value)
|
||||
}
|
||||
placeholder={`{{sender_name}} ${t("send-to-sign")} {{document_title}}`}
|
||||
placeholder={`{{sender_name}} ${t(
|
||||
"send-to-sign"
|
||||
)} {{document_title}}`}
|
||||
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
|
||||
/>
|
||||
</div>
|
||||
@@ -514,7 +685,9 @@ const Preferences = () => {
|
||||
{t("body")}
|
||||
<Tooltip
|
||||
id={"request-body-tooltip"}
|
||||
message={`${t("variables-use")}: {{sender_name}} {{document_title}}`}
|
||||
message={`${t(
|
||||
"variables-use"
|
||||
)}: {{sender_name}} {{document_title}}`}
|
||||
/>
|
||||
</label>
|
||||
<EditorToolbar containerId="toolbar1" />
|
||||
@@ -560,7 +733,9 @@ const Preferences = () => {
|
||||
{t("subject")}
|
||||
<Tooltip
|
||||
id={"complete-sub-tooltip"}
|
||||
message={`${t("variables-use")}:{{sender_name}} {{document_title}}`}
|
||||
message={`${t(
|
||||
"variables-use"
|
||||
)}:{{sender_name}} {{document_title}}`}
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
@@ -569,7 +744,9 @@ const Preferences = () => {
|
||||
onChange={(e) =>
|
||||
setCompletionSubject(e.target.value)
|
||||
}
|
||||
placeholder={`{{sender_name}} ${t("send-to-sign")} {{document_title}}`}
|
||||
placeholder={`{{sender_name}} ${t(
|
||||
"send-to-sign"
|
||||
)} {{document_title}}`}
|
||||
className="w-full op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs"
|
||||
/>
|
||||
</div>
|
||||
@@ -578,7 +755,9 @@ const Preferences = () => {
|
||||
{t("body")}
|
||||
<Tooltip
|
||||
id={"complete-body-tooltip"}
|
||||
message={`${t("variables-use")}:{{sender_name}} {{document_title}} {{signing_url}}`}
|
||||
message={`${t(
|
||||
"variables-use"
|
||||
)}:{{sender_name}} {{document_title}} {{signing_url}}`}
|
||||
/>
|
||||
</label>
|
||||
<EditorToolbar containerId="toolbar2" />
|
||||
|
||||
@@ -40,7 +40,8 @@ import {
|
||||
convertBase64ToFile,
|
||||
generatePdfName,
|
||||
handleRemoveWidgets,
|
||||
compressedFileSize
|
||||
compressedFileSize,
|
||||
addWidgetSelfsignOptions
|
||||
} from "../constant/Utils";
|
||||
import { useParams } from "react-router";
|
||||
import Tour from "reactour";
|
||||
@@ -66,8 +67,7 @@ import LoaderWithMsg from "../primitives/LoaderWithMsg";
|
||||
function SignYourSelf() {
|
||||
const { t } = useTranslation();
|
||||
const { docId } = useParams();
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
const divRef = useRef(null);
|
||||
const nodeRef = useRef(null);
|
||||
const imageRef = useRef(null);
|
||||
@@ -124,7 +124,6 @@ function SignYourSelf() {
|
||||
const [pdfLoad, setPdfLoad] = useState(false);
|
||||
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
const [extUserId, setExtUserId] = useState("");
|
||||
const [isCompleted, setIsCompleted] = useState(false);
|
||||
const [isCelebration, setIsCelebration] = useState(false);
|
||||
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
|
||||
@@ -146,6 +145,7 @@ function SignYourSelf() {
|
||||
isVisible: false,
|
||||
signId: ""
|
||||
});
|
||||
const [owner, setOwner] = useState({});
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
drop: (item, monitor) => addPositionOfSignature(item, monitor),
|
||||
@@ -206,8 +206,8 @@ function SignYourSelf() {
|
||||
const documentData = await contractDocument(documentId);
|
||||
|
||||
if (documentData && documentData.length > 0) {
|
||||
setOwner(documentData?.[0]?.ExtUserPtr);
|
||||
setPdfDetails(documentData);
|
||||
setExtUserId(documentData[0]?.ExtUserPtr?.objectId);
|
||||
const placeholders =
|
||||
documentData[0]?.Placeholders?.length > 0
|
||||
? documentData[0]?.Placeholders
|
||||
@@ -252,7 +252,11 @@ function SignYourSelf() {
|
||||
documentData === "Error: Something went wrong!" ||
|
||||
(documentData.result && documentData.result.error)
|
||||
) {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
if (documentData?.result?.error?.includes("deleted")) {
|
||||
setHandleError(t("document-deleted"));
|
||||
} else {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
}
|
||||
setIsLoading({ isLoad: false });
|
||||
} else {
|
||||
setHandleError(t("no-data-avaliable"));
|
||||
@@ -324,7 +328,7 @@ function SignYourSelf() {
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Error: error in getDocumentDetails", err);
|
||||
setHandleError("Error: Something went wrong!");
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
setIsLoading({ isLoad: false });
|
||||
}
|
||||
};
|
||||
@@ -347,54 +351,6 @@ function SignYourSelf() {
|
||||
}
|
||||
};
|
||||
|
||||
const addWidgetOptions = (type) => {
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return { name: "signature" };
|
||||
case "stamp":
|
||||
return { name: "stamp" };
|
||||
case "checkbox":
|
||||
return { name: "checkbox" };
|
||||
case textWidget:
|
||||
return { name: "text" };
|
||||
case "initials":
|
||||
return { name: "initials" };
|
||||
case "name":
|
||||
return {
|
||||
name: "name",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
case "company":
|
||||
return {
|
||||
name: "company",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
case "job title":
|
||||
return {
|
||||
name: "job title",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
case "date":
|
||||
return {
|
||||
name: "date",
|
||||
response: getDate(),
|
||||
validation: { format: "MM/dd/yyyy", type: "date-format" }
|
||||
};
|
||||
case "image":
|
||||
return { name: "image" };
|
||||
case "email":
|
||||
return {
|
||||
name: "email",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: { type: "email", pattern: "" }
|
||||
};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
};
|
||||
//function for setting position after drop signature button over pdf
|
||||
const addPositionOfSignature = (item, monitor) => {
|
||||
setCurrWidgetsDetails({});
|
||||
@@ -419,7 +375,7 @@ function SignYourSelf() {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
const getWidth = widgetTypeExist
|
||||
? calculateInitialWidthHeight(dragTypeValue, widgetValue).getWidth
|
||||
? calculateInitialWidthHeight(widgetValue).getWidth
|
||||
: defaultWidthHeight(dragTypeValue).width;
|
||||
const getHeight = defaultWidthHeight(dragTypeValue).height;
|
||||
|
||||
@@ -433,7 +389,7 @@ function SignYourSelf() {
|
||||
scale: containerScale,
|
||||
Width: getWidth,
|
||||
Height: getHeight,
|
||||
options: addWidgetOptions(dragTypeValue)
|
||||
options: addWidgetSelfsignOptions(dragTypeValue, getWidgetValue, owner)
|
||||
};
|
||||
dropData.push(dropObj);
|
||||
} else {
|
||||
@@ -462,7 +418,7 @@ function SignYourSelf() {
|
||||
type: dragTypeValue,
|
||||
Width: getWidth / (containerScale * scale),
|
||||
Height: getHeight / (containerScale * scale),
|
||||
options: addWidgetOptions(dragTypeValue),
|
||||
options: addWidgetSelfsignOptions(dragTypeValue, getWidgetValue, owner),
|
||||
scale: containerScale
|
||||
};
|
||||
dropData.push(dropObj);
|
||||
@@ -564,11 +520,7 @@ function SignYourSelf() {
|
||||
let pdfUrl;
|
||||
if (isUploadPdf) {
|
||||
const pdfName = generatePdfName(16);
|
||||
pdfUrl = await convertBase64ToFile(
|
||||
pdfName,
|
||||
pdfBase64Url,
|
||||
"",
|
||||
);
|
||||
pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
|
||||
}
|
||||
const widgetsType = ["signature", "stamp", "image", "initials"];
|
||||
let updatedXYPosition;
|
||||
@@ -754,10 +706,7 @@ function SignYourSelf() {
|
||||
if (tenantDetails && tenantDetails === "user does not exist!") {
|
||||
alert(t("user-not-exist"));
|
||||
} else {
|
||||
if (
|
||||
tenantDetails?.CompletionBody &&
|
||||
tenantDetails?.CompletionSubject
|
||||
) {
|
||||
if (tenantDetails?.CompletionBody && tenantDetails?.CompletionSubject) {
|
||||
isCustomCompletionMail = true;
|
||||
}
|
||||
}
|
||||
@@ -793,7 +742,7 @@ function SignYourSelf() {
|
||||
pdfFile: base64Url,
|
||||
docId: documentId,
|
||||
isCustomCompletionMail: isCustomCompletionMail,
|
||||
signature: suffixbase64,
|
||||
signature: suffixbase64
|
||||
};
|
||||
const resSignPdf = await Parse.Cloud.run("signPdf", params);
|
||||
if (resSignPdf) {
|
||||
@@ -1263,7 +1212,7 @@ function SignYourSelf() {
|
||||
setPdfBase64Url={setPdfBase64Url}
|
||||
setIsUploadPdf={setIsUploadPdf}
|
||||
pdfArrayBuffer={pdfArrayBuffer}
|
||||
isMergePdfBtn={true}
|
||||
isMergePdfBtn={!pdfDetails?.[0]?.IsCompleted}
|
||||
/>
|
||||
<div className=" w-full md:w-[57%] flex mr-4">
|
||||
<PdfZoom
|
||||
@@ -1371,13 +1320,10 @@ function SignYourSelf() {
|
||||
{/*render email component to send email after finish signature on document */}
|
||||
<EmailComponent
|
||||
isEmail={isEmail}
|
||||
pdfUrl={pdfUrl}
|
||||
setIsEmail={setIsEmail}
|
||||
pdfDetails={pdfDetails}
|
||||
setSuccessEmail={setSuccessEmail}
|
||||
sender={jsonSender}
|
||||
pdfDetails={pdfDetails}
|
||||
setIsAlert={setIsAlert}
|
||||
extUserId={extUserId}
|
||||
setIsDownloadModal={setIsDownloadModal}
|
||||
/>
|
||||
{/* pdf header which contain funish back button */}
|
||||
|
||||
@@ -139,6 +139,7 @@ const TemplatePlaceholder = () => {
|
||||
const [updatedPdfUrl, setUpdatedPdfUrl] = useState("");
|
||||
const [tempSignerId, setTempSignerId] = useState("");
|
||||
const [unSignedWidgetId, setUnSignedWidgetId] = useState("");
|
||||
const [owner, setOwner] = useState({});
|
||||
useEffect(() => {
|
||||
fetchTemplate();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -165,7 +166,6 @@ const TemplatePlaceholder = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [divRef.current, isHeader]);
|
||||
|
||||
|
||||
//function to fetch tenant Details
|
||||
const fetchTenantDetails = async () => {
|
||||
const user = JSON.parse(
|
||||
@@ -215,6 +215,7 @@ const TemplatePlaceholder = () => {
|
||||
: [];
|
||||
|
||||
if (documentData && documentData.length > 0) {
|
||||
setOwner(documentData?.[0]?.ExtUserPtr);
|
||||
const url = documentData[0] && documentData[0]?.URL;
|
||||
if (url) {
|
||||
const arrayBuffer = await convertPdfArrayBuffer(url);
|
||||
@@ -283,14 +284,14 @@ const TemplatePlaceholder = () => {
|
||||
const updatedSigners = documentData[0].Signers.map((x, index) => ({
|
||||
...x,
|
||||
Id: randomId(),
|
||||
Role: "User " + (index + 1)
|
||||
Role: "Role " + (index + 1)
|
||||
}));
|
||||
setSignersData(updatedSigners);
|
||||
setUniqueId(updatedSigners[0].Id);
|
||||
setBlockColor(updatedSigners[0].blockColor);
|
||||
}
|
||||
} else {
|
||||
setRoleName("User 1");
|
||||
setRoleName("Role 1");
|
||||
if (
|
||||
documentData[0].Placeholders &&
|
||||
documentData[0].Placeholders.length > 0
|
||||
@@ -392,7 +393,7 @@ const TemplatePlaceholder = () => {
|
||||
scale: containerScale,
|
||||
zIndex: posZIndex,
|
||||
type: dragTypeValue,
|
||||
options: addWidgetOptions(dragTypeValue),
|
||||
options: addWidgetOptions(dragTypeValue, owner),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
Height: widgetHeight / (containerScale * scale)
|
||||
};
|
||||
@@ -422,7 +423,7 @@ const TemplatePlaceholder = () => {
|
||||
// isMobile: isMobile,
|
||||
zIndex: posZIndex,
|
||||
type: item.text,
|
||||
options: addWidgetOptions(dragTypeValue),
|
||||
options: addWidgetOptions(dragTypeValue, owner),
|
||||
Width: widgetWidth / (containerScale * scale),
|
||||
Height: widgetHeight / (containerScale * scale)
|
||||
};
|
||||
@@ -778,11 +779,7 @@ const TemplatePlaceholder = () => {
|
||||
let pdfUrl;
|
||||
if (isUploadPdf) {
|
||||
const pdfName = generatePdfName(16);
|
||||
pdfUrl = await convertBase64ToFile(
|
||||
pdfName,
|
||||
pdfBase64Url,
|
||||
"",
|
||||
);
|
||||
pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
|
||||
}
|
||||
if (signersdata?.length > 0) {
|
||||
signersdata.forEach((x) => {
|
||||
@@ -849,11 +846,7 @@ const TemplatePlaceholder = () => {
|
||||
scale
|
||||
);
|
||||
const pdfName = generatePdfName(16);
|
||||
const pdfUrl = await convertBase64ToFile(
|
||||
pdfName,
|
||||
pdfBase64,
|
||||
"",
|
||||
);
|
||||
const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64, "");
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
const buffer = atob(pdfBase64);
|
||||
SaveFileSize(buffer.length, pdfUrl, tenantId);
|
||||
@@ -865,11 +858,7 @@ const TemplatePlaceholder = () => {
|
||||
} else if (pdfBase64Url) {
|
||||
try {
|
||||
const pdfName = generatePdfName(16);
|
||||
const pdfUrl = await convertBase64ToFile(
|
||||
pdfName,
|
||||
pdfBase64Url,
|
||||
"",
|
||||
);
|
||||
const pdfUrl = await convertBase64ToFile(pdfName, pdfBase64Url, "");
|
||||
return pdfUrl;
|
||||
} catch (err) {
|
||||
console.log("error to convertBase64ToFile in placeholder flow", err);
|
||||
@@ -881,6 +870,14 @@ const TemplatePlaceholder = () => {
|
||||
};
|
||||
const handleSaveTemplate = async () => {
|
||||
if (signersdata?.length) {
|
||||
const remindOnceInEvery = parseInt(pdfDetails[0]?.RemindOnceInEvery);
|
||||
const TimeToCompleteDays = parseInt(pdfDetails[0]?.TimeToCompleteDays);
|
||||
const AutomaticReminders = pdfDetails[0]?.AutomaticReminders;
|
||||
const reminderCount = TimeToCompleteDays / remindOnceInEvery;
|
||||
if (AutomaticReminders && reminderCount > 15) {
|
||||
alert(t("only-15-reminder-allowed"));
|
||||
return;
|
||||
}
|
||||
setIsLoading({ isLoad: true, message: t("loading-mssg") });
|
||||
setIsSendAlert(false);
|
||||
let signers = [],
|
||||
@@ -1086,7 +1083,7 @@ const TemplatePlaceholder = () => {
|
||||
});
|
||||
setIsCreateDoc(false);
|
||||
} else {
|
||||
setHandleError(t("something-went-wrong-mssg"));
|
||||
setHandleError(t(res.id));
|
||||
setIsCreateDoc(false);
|
||||
}
|
||||
};
|
||||
@@ -1104,7 +1101,7 @@ const TemplatePlaceholder = () => {
|
||||
const Id = randomId();
|
||||
const index = signersdata.length;
|
||||
const obj = {
|
||||
Role: roleName || "User " + count,
|
||||
Role: roleName || "Role " + count,
|
||||
Id: Id,
|
||||
blockColor: color[index]
|
||||
};
|
||||
@@ -1113,7 +1110,7 @@ const TemplatePlaceholder = () => {
|
||||
signerPtr: {},
|
||||
signerObjId: "",
|
||||
blockColor: color[index],
|
||||
Role: roleName || "User " + count,
|
||||
Role: roleName || "Role " + count,
|
||||
Id: Id
|
||||
};
|
||||
|
||||
@@ -1461,8 +1458,7 @@ const TemplatePlaceholder = () => {
|
||||
status: defaultdata?.status || "required",
|
||||
hint: defaultdata?.hint || "",
|
||||
defaultValue: defaultdata?.defaultValue || "",
|
||||
validation:
|
||||
{},
|
||||
validation: {},
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
@@ -1471,6 +1467,15 @@ const TemplatePlaceholder = () => {
|
||||
"black"
|
||||
}
|
||||
};
|
||||
} else if (["signature"].includes(position.type)) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
name: defaultdata.name,
|
||||
hint: defaultdata?.hint || ""
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...position,
|
||||
@@ -1479,6 +1484,7 @@ const TemplatePlaceholder = () => {
|
||||
name: defaultdata.name,
|
||||
status: defaultdata.status,
|
||||
defaultValue: defaultdata.defaultValue,
|
||||
hint: defaultdata?.hint || "",
|
||||
fontSize:
|
||||
fontSize || currWidgetsDetails?.options?.fontSize || 12,
|
||||
fontColor:
|
||||
|
||||
@@ -7,8 +7,7 @@ import Title from "../components/Title";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { emailRegex } from "../constant/const";
|
||||
const UpdateExistUserAdmin = () => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const appName = "OpenSign™";
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [formdata, setFormdata] = useState({ email: "", masterkey: "" });
|
||||
@@ -30,7 +29,7 @@ const UpdateExistUserAdmin = () => {
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err in checkadminexist", err);
|
||||
setErrMsg("Something went wrong.");
|
||||
setErrMsg(t("something-went-wrong-mssg"));
|
||||
} finally {
|
||||
setLoader(false);
|
||||
}
|
||||
|
||||
+188
-197
@@ -10,7 +10,6 @@ import AddUser from "../components/AddUser";
|
||||
import Title from "../components/Title";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const heading = ["Sr.No", "Name", "Email", "Phone", "Role", "Team", "Active"];
|
||||
// const actions = [];
|
||||
const UserList = () => {
|
||||
const { t } = useTranslation();
|
||||
const [userList, setUserList] = useState([]);
|
||||
@@ -114,9 +113,8 @@ const UserList = () => {
|
||||
setUserList(_userRes);
|
||||
} catch (err) {
|
||||
console.log("Err in fetch userlist", err);
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
showAlert("danger", t("something-went-wrong-mssg"));
|
||||
} finally {
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
@@ -173,17 +171,15 @@ const UserList = () => {
|
||||
extUser.id = user.objectId;
|
||||
extUser.set("IsDisabled", !IsDisabled);
|
||||
await extUser.save();
|
||||
setIsAlert({
|
||||
type: !IsDisabled === true ? "danger" : "success",
|
||||
msg:
|
||||
!IsDisabled === true ? t("user-deactivated") : t("user-activated")
|
||||
});
|
||||
showAlert(
|
||||
!IsDisabled === true ? "danger" : "success",
|
||||
!IsDisabled === true ? t("user-deactivated") : t("user-activated")
|
||||
);
|
||||
} catch (err) {
|
||||
setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") });
|
||||
showAlert("danger", t("something-went-wrong-mssg"));
|
||||
console.log("err in disable team", err);
|
||||
} finally {
|
||||
setIsActLoader({});
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -191,6 +187,11 @@ const UserList = () => {
|
||||
setIsActiveModal({ [user.objectId]: true });
|
||||
};
|
||||
|
||||
// `showAlert` handle show/hide alert
|
||||
const showAlert = (type, msg) => {
|
||||
setIsAlert({ type, msg });
|
||||
setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500);
|
||||
};
|
||||
return (
|
||||
<div className="relative">
|
||||
<Title title={isAdmin ? "Users" : "Page not found"} />
|
||||
@@ -205,197 +206,187 @@ const UserList = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{
|
||||
!isLoader && (
|
||||
<>
|
||||
{isAdmin ? (
|
||||
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
|
||||
{isAlert.msg && (
|
||||
<Alert type={isAlert.type}>{isAlert.msg}</Alert>
|
||||
)}
|
||||
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
|
||||
<div className="font-light">
|
||||
{t("report-name.Users")}{" "}
|
||||
<span className="text-xs md:text-[13px] font-normal">
|
||||
<Tooltip message={t("users-from-teams")} />
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleModal("form")}
|
||||
>
|
||||
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-x-auto w-full">
|
||||
<table className="op-table border-collapse w-full">
|
||||
<thead className="text-[14px]">
|
||||
<tr className="border-y-[1px]">
|
||||
{heading?.map((item, index) => (
|
||||
<th key={index} className="px-4 py-2">
|
||||
{t(`report-heading.${item}`)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-[12px]">
|
||||
{userList?.length > 0 && (
|
||||
<>
|
||||
{currentList.map((item, index) => (
|
||||
<tr className="border-y-[1px]" key={index}>
|
||||
{heading.includes("Sr.No") && (
|
||||
<th className="px-4 py-2">
|
||||
{startIndex + index + 1}
|
||||
</th>
|
||||
)}
|
||||
<td className="px-4 py-2 font-semibold">
|
||||
{item?.Name}{" "}
|
||||
</td>
|
||||
<td className="px-4 py-2 ">
|
||||
{item?.Email || "-"}
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
{item?.Phone || "-"}
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
{item?.UserRole?.split("_").pop() || "-"}
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
{formatRow(item.TeamIds)}
|
||||
</td>
|
||||
{item.UserRole !== "contracts_Admin" && (
|
||||
<td className="px-4 py-2 font-semibold">
|
||||
<label className="cursor-pointer relative block items-center mb-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="op-toggle transition-all op-toggle-secondary"
|
||||
checked={item?.IsDisabled !== true}
|
||||
onChange={() => handleToggleBtn(item)}
|
||||
/>
|
||||
</label>
|
||||
{isActiveModal[item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={t("user-status")}
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
{t("are-you-sure")}{" "}
|
||||
{item?.IsDisabled
|
||||
? t("activate")
|
||||
: t("deactivate")}{" "}
|
||||
{t("this-user")}?
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-4 " />
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button
|
||||
onClick={() =>
|
||||
handleToggleSubmit(item)
|
||||
}
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between items-center text-xs font-medium">
|
||||
<div className="op-join flex flex-wrap items-center p-2">
|
||||
{userList.length > recordperPage && (
|
||||
<button
|
||||
onClick={() => paginateBack()}
|
||||
className="op-join-item op-btn op-btn-sm"
|
||||
>
|
||||
{t("prev")}
|
||||
</button>
|
||||
)}
|
||||
{pageNumbers.map((x, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentPage(x)}
|
||||
disabled={x === "..."}
|
||||
className={`${
|
||||
x === currentPage ? "op-btn-active" : ""
|
||||
} op-join-item op-btn op-btn-sm`}
|
||||
>
|
||||
{x}
|
||||
</button>
|
||||
))}
|
||||
{userList.length > recordperPage && (
|
||||
<button
|
||||
onClick={() => paginateFront()}
|
||||
className="op-join-item op-btn op-btn-sm"
|
||||
>
|
||||
{t("next")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{userList?.length <= 0 && (
|
||||
<div
|
||||
className={`${
|
||||
isDashboard ? "h-[317px]" : ""
|
||||
} flex flex-col items-center justify-center w-ful bg-base-100 text-base-content rounded-xl py-4`}
|
||||
>
|
||||
<div className="w-[60px] h-[60px] overflow-hidden">
|
||||
<img
|
||||
className="w-full h-full object-contain"
|
||||
src={pad}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm font-semibold">
|
||||
{t("no-data-avaliable")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isModal.form}
|
||||
title={formHeader}
|
||||
handleClose={() => handleModal("form")}
|
||||
>
|
||||
<AddUser
|
||||
setIsAlert={setIsAlert}
|
||||
handleUserData={handleUserData}
|
||||
closePopup={() => handleModal("form")}
|
||||
setFormHeader={setFormHeader}
|
||||
/>
|
||||
</ModalUi>
|
||||
{!isLoader && (
|
||||
<>
|
||||
{isAdmin ? (
|
||||
<div className="p-2 w-full bg-base-100 text-base-content op-card shadow-lg">
|
||||
{isAlert.msg && <Alert type={isAlert.type}>{isAlert.msg}</Alert>}
|
||||
<div className="flex flex-row items-center justify-between my-2 mx-3 text-[20px] md:text-[23px]">
|
||||
<div className="font-light">
|
||||
{t("report-name.Users")}{" "}
|
||||
<span className="text-xs md:text-[13px] font-normal">
|
||||
<Tooltip message={t("users-from-teams")} />
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
|
||||
<div className="text-center">
|
||||
<h1 className="text-[60px] lg:text-[120px] font-semibold">
|
||||
404
|
||||
</h1>
|
||||
<p className="text-[30px] lg:text-[50px]">
|
||||
{t("page-not-found")}
|
||||
</p>
|
||||
<div className="flex flex-row gap-2 items-center">
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleModal("form")}
|
||||
>
|
||||
<i className="fa-light fa-square-plus text-accent text-[30px] md:text-[40px]"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<table className="op-table border-collapse w-full mb-[50px]">
|
||||
<thead className="text-[14px]">
|
||||
<tr className="border-y-[1px]">
|
||||
{heading?.map((item, index) => (
|
||||
<th key={index} className="px-4 py-2">
|
||||
{t(`report-heading.${item}`)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
{userList?.length > 0 && (
|
||||
<tbody className="text-[12px]">
|
||||
{currentList.map((item, index) => (
|
||||
<tr className="border-y-[1px]" key={index}>
|
||||
{heading.includes("Sr.No") && (
|
||||
<th className="px-4 py-2">
|
||||
{startIndex + index + 1}
|
||||
</th>
|
||||
)}
|
||||
<td className="px-4 py-2 font-semibold">
|
||||
{item?.Name}{" "}
|
||||
</td>
|
||||
<td className="px-4 py-2 ">{item?.Email || "-"}</td>
|
||||
<td className="px-4 py-2">{item?.Phone || "-"}</td>
|
||||
<td className="px-4 py-2">
|
||||
{item?.UserRole?.split("_").pop() || "-"}
|
||||
</td>
|
||||
<td className="px-4 py-2">
|
||||
{formatRow(item.TeamIds)}
|
||||
</td>
|
||||
{item.UserRole !== "contracts_Admin" ? (
|
||||
<td className="px-4 py-2 font-semibold">
|
||||
<label className="cursor-pointer relative block items-center mb-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="op-toggle transition-all op-toggle-secondary"
|
||||
checked={item?.IsDisabled !== true}
|
||||
onChange={() => handleToggleBtn(item)}
|
||||
/>
|
||||
</label>
|
||||
{isActiveModal[item.objectId] && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={t("user-status")}
|
||||
handleClose={handleClose}
|
||||
>
|
||||
<div className="m-[20px]">
|
||||
<div className="text-lg font-normal text-black">
|
||||
{t("are-you-sure")}{" "}
|
||||
{item?.IsDisabled
|
||||
? t("activate")
|
||||
: t("deactivate")}{" "}
|
||||
{t("this-user")}?
|
||||
</div>
|
||||
<hr className="bg-[#ccc] mt-4 " />
|
||||
<div className="flex items-center mt-3 gap-2 text-white">
|
||||
<button
|
||||
onClick={() => handleToggleSubmit(item)}
|
||||
className="op-btn op-btn-primary"
|
||||
>
|
||||
{t("yes")}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleClose}
|
||||
className="op-btn op-btn-secondary"
|
||||
>
|
||||
{t("no")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
</td>
|
||||
) : (
|
||||
<td className="px-4 py-2 font-semibold"></td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
)}
|
||||
</table>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between items-center text-xs font-medium">
|
||||
<div className="op-join flex flex-wrap items-center p-2">
|
||||
{userList.length > recordperPage && (
|
||||
<button
|
||||
onClick={() => paginateBack()}
|
||||
className="op-join-item op-btn op-btn-sm"
|
||||
>
|
||||
{t("prev")}
|
||||
</button>
|
||||
)}
|
||||
{pageNumbers.map((x, i) => (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => setCurrentPage(x)}
|
||||
disabled={x === "..."}
|
||||
className={`${
|
||||
x === currentPage ? "op-btn-active" : ""
|
||||
} op-join-item op-btn op-btn-sm`}
|
||||
>
|
||||
{x}
|
||||
</button>
|
||||
))}
|
||||
{userList.length > recordperPage && (
|
||||
<button
|
||||
onClick={() => paginateFront()}
|
||||
className="op-join-item op-btn op-btn-sm"
|
||||
>
|
||||
{t("next")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{userList?.length <= 0 && (
|
||||
<div
|
||||
className={`${
|
||||
isDashboard ? "h-[317px]" : ""
|
||||
} flex flex-col items-center justify-center w-ful bg-base-100 text-base-content rounded-xl py-4`}
|
||||
>
|
||||
<div className="w-[60px] h-[60px] overflow-hidden">
|
||||
<img
|
||||
className="w-full h-full object-contain"
|
||||
src={pad}
|
||||
alt="img"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-sm font-semibold">
|
||||
{t("no-data-avaliable")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
<ModalUi
|
||||
isOpen={isModal.form}
|
||||
title={formHeader}
|
||||
handleClose={() => handleModal("form")}
|
||||
>
|
||||
<AddUser
|
||||
showAlert={showAlert}
|
||||
handleUserData={handleUserData}
|
||||
closePopup={() => handleModal("form")}
|
||||
setFormHeader={setFormHeader}
|
||||
/>
|
||||
</ModalUi>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-screen w-full bg-base-100 text-base-content rounded-box">
|
||||
<div className="text-center">
|
||||
<h1 className="text-[60px] lg:text-[120px] font-semibold">
|
||||
404
|
||||
</h1>
|
||||
<p className="text-[30px] lg:text-[50px]">
|
||||
{t("page-not-found")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
} from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Navigate, useNavigate } from "react-router";
|
||||
import Parse from "parse";
|
||||
import { SaveFileSize } from "../constant/saveFileSize";
|
||||
@@ -10,10 +7,7 @@ import Title from "../components/Title";
|
||||
import sanitizeFileName from "../primitives/sanitizeFileName";
|
||||
import axios from "axios";
|
||||
import Tooltip from "../primitives/Tooltip";
|
||||
import {
|
||||
getSecureUrl,
|
||||
handleSendOTP,
|
||||
} from "../constant/Utils";
|
||||
import { getSecureUrl, handleSendOTP } from "../constant/Utils";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -121,13 +115,13 @@ function UserProfile() {
|
||||
const updateExtUser = async (obj) => {
|
||||
try {
|
||||
const extData = JSON.parse(localStorage.getItem("Extand_Class"));
|
||||
const ExtUserId = extData[0].objectId;
|
||||
const ExtUserId = extData?.[0]?.objectId;
|
||||
const body = {
|
||||
Phone: obj?.Phone || "",
|
||||
Name: obj.Name,
|
||||
JobTitle: jobTitle,
|
||||
Company: company,
|
||||
Language: obj?.language || "",
|
||||
Language: obj?.language || ""
|
||||
};
|
||||
|
||||
await axios.put(
|
||||
@@ -235,7 +229,7 @@ function UserProfile() {
|
||||
const handleResend = async (e) => {
|
||||
e.preventDefault();
|
||||
setOtpLoader(true);
|
||||
await handleSendOTP();
|
||||
await handleSendOTP(Parse.User.current().getEmail());
|
||||
setOtpLoader(false);
|
||||
alert(t("otp-sent-alert"));
|
||||
};
|
||||
@@ -412,7 +406,7 @@ function UserProfile() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
editmode ? handleSubmit(e) : setEditMode(true);
|
||||
editmode ? handleSubmit(e) : setEditMode(true);
|
||||
}}
|
||||
className="op-btn op-btn-primary w-[100px]"
|
||||
>
|
||||
|
||||
@@ -33,8 +33,9 @@ const AddContact = (props) => {
|
||||
try {
|
||||
const baseURL = localStorage.getItem("baseUrl");
|
||||
const url = `${baseURL}functions/isuserincontactbook`;
|
||||
const token =
|
||||
{ "X-Parse-Session-Token": localStorage.getItem("accesstoken") };
|
||||
const token = {
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId"),
|
||||
@@ -63,18 +64,15 @@ const AddContact = (props) => {
|
||||
)
|
||||
);
|
||||
const userId = user?.objectId || "";
|
||||
const tenantDetails = await getTenantDetails(
|
||||
userId,
|
||||
);
|
||||
const tenantDetails = await getTenantDetails(userId);
|
||||
const tenantId = tenantDetails?.objectId || "";
|
||||
if (tenantId) {
|
||||
try {
|
||||
const baseURL = localStorage.getItem("baseUrl");
|
||||
const url = `${baseURL}functions/savecontact`;
|
||||
const token =
|
||||
{
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
const token = {
|
||||
"X-Parse-Session-Token": localStorage.getItem("accesstoken")
|
||||
};
|
||||
const data = { name, email, phone, tenantId };
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
@@ -137,7 +135,9 @@ const AddContact = (props) => {
|
||||
</div>
|
||||
)}
|
||||
<div className="w-full mx-auto p-[8px]">
|
||||
<div className="text-[14px] font-[700]">{t("add-contact")}</div>
|
||||
{!props?.isDisableTitle && (
|
||||
<div className="text-[14px] font-[700]">{t("add-contact")}</div>
|
||||
)}
|
||||
{isUserExist && (
|
||||
<div className="mb-[0.75rem] flex items-center mt-1">
|
||||
<input
|
||||
|
||||
@@ -11,7 +11,7 @@ const Alert = ({ children, type, className }) => {
|
||||
case "danger":
|
||||
return "op-alert-error";
|
||||
case "warning":
|
||||
return "op-alert-warning";
|
||||
return "op-alert-warning text-black";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from "react";
|
||||
|
||||
const CheckCircle = ({ size = 56, color = "text-green-500" }) => {
|
||||
return (
|
||||
<div className={`flex items-center justify-center ${color}`}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className={`w-${size / 4} h-${size / 4}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
d="M9 12l2 2l4-4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckCircle;
|
||||
@@ -65,19 +65,18 @@ function DownloadPdfZip(props) {
|
||||
throw new Error(`Failed to fetch certificate PDF: ${certificateUrl}`);
|
||||
}
|
||||
const pdf2Blob = await pdf2Response.blob();
|
||||
// Add files to ZIP
|
||||
zip.file(
|
||||
`${sanitizeFileName(pdfName)}_signed_by_${appName}.pdf`,
|
||||
pdf1Blob
|
||||
);
|
||||
zip.file(`Certificate_signed_by_${appName}.pdf`, pdf2Blob);
|
||||
|
||||
// Generate the ZIP and trigger download
|
||||
const zipBlob = await zip.generateAsync({ type: "blob" });
|
||||
saveAs(
|
||||
zipBlob,
|
||||
`${sanitizeFileName(pdfName)}_signed_by_${appName}.zip`
|
||||
);
|
||||
// Add files to ZIP
|
||||
zip.file(
|
||||
`${sanitizeFileName(pdfName)}_signed_by_${appName}.pdf`,
|
||||
pdf1Blob
|
||||
);
|
||||
zip.file(`Certificate_signed_by_${appName}.pdf`, pdf2Blob);
|
||||
// Generate the ZIP and trigger download
|
||||
const zipBlob = await zip.generateAsync({ type: "blob" });
|
||||
saveAs(
|
||||
zipBlob,
|
||||
`${sanitizeFileName(pdfName)}_signed_by_${appName}.zip`
|
||||
);
|
||||
setSelectType(1);
|
||||
props.setIsDownloadModal(false);
|
||||
setIsDownloading("");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
function sanitizeFileName(fileName) {
|
||||
// Remove spaces and invalid characters
|
||||
const file = fileName.replace(/[^a-zA-Z0-9._-]/g, "");
|
||||
const removedot = file.replace(/\.(?=.*\.)/g, "");
|
||||
return removedot.replace(/[^a-zA-Z0-9._-]/g, "");
|
||||
const file = fileName?.replace(/[^a-zA-Z0-9._-]/g, "");
|
||||
const removedot = file?.replace(/\.(?=.*\.)/g, "");
|
||||
return removedot?.replace(/[^a-zA-Z0-9._-]/g, "");
|
||||
}
|
||||
export default sanitizeFileName;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:20
|
||||
FROM node:22.14.0
|
||||
|
||||
# install java
|
||||
RUN wget https://download.oracle.com/java/23/latest/jdk-23_linux-x64_bin.deb \
|
||||
&& dpkg -i jdk-23_linux-x64_bin.deb
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
Binary file not shown.
+52
-139
@@ -5,12 +5,11 @@ import { PDFDocument } from 'pdf-lib';
|
||||
dotenv.config();
|
||||
|
||||
export const cloudServerUrl = 'http://localhost:8080/app';
|
||||
export const appName = process.env.APP_NAME || 'OpenSign™';
|
||||
export function customAPIurl() {
|
||||
const url = new URL(cloudServerUrl);
|
||||
return url.pathname === '/api/app' ? url.origin + '/api' : url.origin;
|
||||
}
|
||||
export const appName = 'OpenSign™';
|
||||
|
||||
export const MAX_NAME_LENGTH = 250;
|
||||
export const MAX_NOTE_LENGTH = 200;
|
||||
export const MAX_DESCRIPTION_LENGTH = 500;
|
||||
export const color = [
|
||||
'#93a3db',
|
||||
'#e6c3db',
|
||||
@@ -53,7 +52,7 @@ export const saveFileUsage = async (size, fileUrl, userId) => {
|
||||
className: '_User',
|
||||
objectId: userId,
|
||||
});
|
||||
const tenant = await tenantQuery.first();
|
||||
const tenant = await tenantQuery.first({ useMasterKey: true });
|
||||
if (tenant) {
|
||||
const tenantPtr = { __type: 'Pointer', className: 'partners_Tenant', objectId: tenant.id };
|
||||
try {
|
||||
@@ -138,131 +137,6 @@ export const updateMailCount = async (extUserId, plan, monthchange) => {
|
||||
}
|
||||
};
|
||||
|
||||
export function formatWidgetOptions(type, options) {
|
||||
const colorsArr = ['red', 'black', 'blue', 'yellow'];
|
||||
const fontSizes = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28];
|
||||
const status = options?.required === true ? 'required' : 'optional' || 'required';
|
||||
const defaultValue = options?.default || '';
|
||||
const values = options?.values || [];
|
||||
const color = options?.color ? options.color : 'black';
|
||||
const fontColor = colorsArr.includes(color) ? color : 'black';
|
||||
const size = options?.fontsize ? parseInt(options.fontsize) : 12;
|
||||
const fontSize = fontSizes.includes(size) ? size : 12;
|
||||
switch (type) {
|
||||
case 'signature':
|
||||
return { name: 'signature', status: 'required' };
|
||||
case 'stamp':
|
||||
return { status: status, name: 'stamp' };
|
||||
case 'initials':
|
||||
return { status: status, name: options.name || 'initials' };
|
||||
case 'image':
|
||||
return { status: status, name: options.name || 'image' };
|
||||
case 'email':
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'email',
|
||||
validation: { type: 'email' },
|
||||
fontColor: fontColor,
|
||||
fontSize: fontSize,
|
||||
};
|
||||
case 'name':
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'name',
|
||||
fontColor: fontColor,
|
||||
fontSize: fontSize,
|
||||
};
|
||||
case 'job title':
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'job title',
|
||||
fontColor: fontColor,
|
||||
fontSize: fontSize,
|
||||
};
|
||||
case 'company':
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'company',
|
||||
fontColor: fontColor,
|
||||
fontSize: fontSize,
|
||||
};
|
||||
case 'date': {
|
||||
let today = new Date();
|
||||
let dd = String(today.getDate()).padStart(2, '0');
|
||||
let mm = String(today.getMonth() + 1).padStart(2, '0'); // January is 0!
|
||||
let yyyy = today.getFullYear();
|
||||
today = dd + '-' + mm + '-' + yyyy;
|
||||
let dateFormat = options?.format;
|
||||
dateFormat = dateFormat.replace(/m/g, 'M');
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'date',
|
||||
response: defaultValue || today,
|
||||
validation: { format: dateFormat || 'dd-MM-yyyy', type: 'date-format' },
|
||||
fontColor: fontColor,
|
||||
fontSize: fontSize,
|
||||
};
|
||||
}
|
||||
case 'textbox':
|
||||
return {
|
||||
status: status,
|
||||
name: 'textbox',
|
||||
defaultValue: defaultValue,
|
||||
hint: options.hint,
|
||||
validation: { type: 'regex', pattern: options?.regularexpression || '/^[a-zA-Z0-9s]+$/' },
|
||||
fontColor: fontColor,
|
||||
fontSize: fontSize,
|
||||
isReadOnly: options?.readonly || false,
|
||||
};
|
||||
case 'checkbox': {
|
||||
const arr = options?.values;
|
||||
let selectedvalues = [];
|
||||
for (const obj of options.selectedvalues) {
|
||||
const index = arr.indexOf(obj);
|
||||
selectedvalues.push(index);
|
||||
}
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'checkbox',
|
||||
values: values,
|
||||
isReadOnly: options?.readonly || false,
|
||||
isHideLabel: options?.hidelabel || false,
|
||||
validation: {
|
||||
minRequiredCount: options?.validation?.minselections || 0,
|
||||
maxRequiredCount: options?.validation?.maxselections || 0,
|
||||
},
|
||||
defaultValue: selectedvalues || [],
|
||||
fontColor: fontColor,
|
||||
fontSize: fontSize,
|
||||
};
|
||||
}
|
||||
case 'radio button': {
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'radio',
|
||||
values: values,
|
||||
isReadOnly: options?.readonly || false,
|
||||
isHideLabel: options?.hidelabel || false,
|
||||
defaultValue: defaultValue,
|
||||
fontColor: fontColor,
|
||||
fontSize: fontSize,
|
||||
};
|
||||
}
|
||||
case 'dropdown':
|
||||
return {
|
||||
status: status,
|
||||
name: options.name || 'dropdown',
|
||||
values: values,
|
||||
defaultValue: defaultValue,
|
||||
fontColor: fontColor,
|
||||
fontSize: fontSize,
|
||||
isReadOnly: options?.readonly || false,
|
||||
};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export function sanitizeFileName(fileName) {
|
||||
// Remove spaces and invalid characters
|
||||
const file = fileName.replace(/[^a-zA-Z0-9._-]/g, '');
|
||||
@@ -275,7 +149,6 @@ export const smtpsecure = process.env.SMTP_PORT && process.env.SMTP_PORT !== '46
|
||||
export const smtpenable =
|
||||
process.env.SMTP_ENABLE && process.env.SMTP_ENABLE.toLowerCase() === 'true' ? true : false;
|
||||
|
||||
|
||||
// `generateId` is used to unique Id for fileAdapter
|
||||
export function generateId(length) {
|
||||
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
@@ -355,11 +228,10 @@ export const flattenPdf = async pdfFile => {
|
||||
export const mailTemplate = param => {
|
||||
const themeColor = '#47a3ad';
|
||||
const subject = `${param.senderName} has requested you to sign "${param.title}"`;
|
||||
const logo =
|
||||
`<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' />`;
|
||||
const AppName = appName;
|
||||
const logo = `<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' />`;
|
||||
|
||||
const opurl =
|
||||
` <a href='www.opensignlabs.com' target=_blank>here</a>`;
|
||||
const opurl = ` <a href='www.opensignlabs.com' target=_blank>here</a>`;
|
||||
|
||||
const body =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background:white;padding-bottom:20px'><div style='padding:10px'>" +
|
||||
@@ -372,15 +244,56 @@ export const mailTemplate = param => {
|
||||
param.senderMail +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td><td></td><td style='color:#626363;font-weight:bold'> " +
|
||||
param.organization +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expire on</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||
param.localExpireDate +
|
||||
"</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Note</td><td></td><td style='color:#626363;font-weight:bold'>" +
|
||||
param.note +
|
||||
"</td></tr><tr><td></td><td></td></tr></table></div> <div style='margin-left:70px'><a target=_blank href=" +
|
||||
param.sigingUrl +
|
||||
"><button style='padding:12px;background-color:#d46b0f;color:white;border:0px;font-weight:bold;margin-top:30px'>Sign here</button></a></div><div style='display:flex;justify-content:center;margin-top:10px'></div></div></div><div><p> This is an automated email from " +
|
||||
appName +
|
||||
AppName +
|
||||
'. For any queries regarding this email, please contact the sender ' +
|
||||
param.senderMail +
|
||||
` directly. If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`;
|
||||
` directly. If you think this email is inappropriate or spam, you may file a complaint with ${AppName}${opurl}.</p></div></div></body></html>`;
|
||||
|
||||
return { subject, body };
|
||||
};
|
||||
|
||||
export const selectFormat = data => {
|
||||
switch (data) {
|
||||
case 'L':
|
||||
return 'MM/dd/yyyy';
|
||||
case 'MM/DD/YYYY':
|
||||
return 'MM/dd/yyyy';
|
||||
case 'DD-MM-YYYY':
|
||||
return 'dd-MM-yyyy';
|
||||
case 'DD/MM/YYYY':
|
||||
return 'dd/MM/yyyy';
|
||||
case 'LL':
|
||||
return 'MMMM dd, yyyy';
|
||||
case 'DD MMM, YYYY':
|
||||
return 'dd MMM, yyyy';
|
||||
case 'YYYY-MM-DD':
|
||||
return 'yyyy-MM-dd';
|
||||
case 'MM-DD-YYYY':
|
||||
return 'MM-dd-yyyy';
|
||||
case 'MM.DD.YYYY':
|
||||
return 'MM.dd.yyyy';
|
||||
case 'MMM DD, YYYY':
|
||||
return 'MMM dd, yyyy';
|
||||
case 'MMMM DD, YYYY':
|
||||
return 'MMMM dd, yyyy';
|
||||
case 'DD MMMM, YYYY':
|
||||
return 'dd MMMM, yyyy';
|
||||
default:
|
||||
return 'MM/dd/yyyy';
|
||||
}
|
||||
};
|
||||
|
||||
export function formatDateTime(date, dateFormat, timeZone, is12Hour) {
|
||||
const zonedDate = toZonedTime(date, timeZone); // Convert date to the given timezone
|
||||
const timeFormat = is12Hour ? 'hh:mm:ss a' : 'HH:mm:ss';
|
||||
return dateFormat
|
||||
? format(zonedDate, `${selectFormat(dateFormat)}, ${timeFormat} 'GMT' XXX`, { timeZone })
|
||||
: formatTimeInTimezone(date, timeZone);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import PDF from './parsefunction/pdf/PDF.js';
|
||||
import sendmailv3 from './parsefunction/sendMailv3.js';
|
||||
import usersignup from './parsefunction/usersignup.js';
|
||||
@@ -13,7 +12,6 @@ import getDrive from './parsefunction/getDrive.js';
|
||||
import getReport from './parsefunction/getReport.js';
|
||||
import TemplateAfterSave from './parsefunction/TemplateAfterSave.js';
|
||||
import GetTemplate from './parsefunction/GetTemplate.js';
|
||||
import callWebhook from './parsefunction/callWebhook.js';
|
||||
import DocumentBeforesave from './parsefunction/DocumentBeforesave.js';
|
||||
import TemplateBeforeSave from './parsefunction/TemplateBeforesave.js';
|
||||
import DocumentBeforeFind from './parsefunction/DocumentAfterFind.js';
|
||||
@@ -22,7 +20,6 @@ import UserAfterFind from './parsefunction/UserAfterFInd.js';
|
||||
import SignatureAfterFind from './parsefunction/SignatureAfterFind.js';
|
||||
import TenantAterFind from './parsefunction/TenantAfterFind.js';
|
||||
import VerifyEmail from './parsefunction/VerifyEmail.js';
|
||||
import encryptedpdf from './parsefunction/encryptedPdf.js';
|
||||
import { getSignedUrl } from './parsefunction/getSignedUrl.js';
|
||||
import createBatchDocs from './parsefunction/createBatchDocs.js';
|
||||
import linkContactToDoc from './parsefunction/linkContactToDoc.js';
|
||||
@@ -51,7 +48,9 @@ import generateCertificatebydocId from './parsefunction/generateCertificatebydoc
|
||||
import fileUpload from './parsefunction/fileUpload.js';
|
||||
import getUserListByOrg from './parsefunction/getUserListByOrg.js';
|
||||
import editContact from './parsefunction/editContact.js';
|
||||
|
||||
import forwardDoc from './parsefunction/ForwardDoc.js';
|
||||
import saveAsTemplate from './parsefunction/saveAsTemplate.js';
|
||||
import updateTenant from './parsefunction/updateTenant.js';
|
||||
|
||||
// This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic.
|
||||
Parse.Cloud.afterSave('contracts_Document', DocumentAftersave);
|
||||
@@ -82,9 +81,7 @@ Parse.Cloud.define('getDocument', getDocument);
|
||||
Parse.Cloud.define('getDrive', getDrive);
|
||||
Parse.Cloud.define('getReport', getReport);
|
||||
Parse.Cloud.define('getTemplate', GetTemplate);
|
||||
Parse.Cloud.define('callwebhook', callWebhook);
|
||||
Parse.Cloud.define('verifyemail', VerifyEmail);
|
||||
Parse.Cloud.define('encryptedpdf', encryptedpdf);
|
||||
Parse.Cloud.define('getsignedurl', getSignedUrl);
|
||||
Parse.Cloud.define('batchdocuments', createBatchDocs);
|
||||
Parse.Cloud.define('linkcontacttodoc', linkContactToDoc);
|
||||
@@ -112,3 +109,6 @@ Parse.Cloud.define('generatecertificate', generateCertificatebydocId);
|
||||
Parse.Cloud.define('fileupload', fileUpload);
|
||||
Parse.Cloud.define('getuserlistbyorg', getUserListByOrg);
|
||||
Parse.Cloud.define('editcontact', editContact);
|
||||
Parse.Cloud.define('forwarddoc', forwardDoc);
|
||||
Parse.Cloud.define('saveastemplate', saveAsTemplate);
|
||||
Parse.Cloud.define('updatetenant', updateTenant);
|
||||
|
||||
@@ -61,7 +61,7 @@ async function addTeamAndOrg(extUser) {
|
||||
|
||||
async function saveUser(userDetails) {
|
||||
const userQuery = new Parse.Query(Parse.User);
|
||||
userQuery.equalTo('username', userDetails.email);
|
||||
userQuery.equalTo('username', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
const userRes = await userQuery.first({ useMasterKey: true });
|
||||
|
||||
if (userRes) {
|
||||
@@ -83,9 +83,9 @@ async function saveUser(userDetails) {
|
||||
return { id: login.objectId, sessionToken: login.sessionToken };
|
||||
} else {
|
||||
const user = new Parse.User();
|
||||
user.set('username', userDetails.email);
|
||||
user.set('username', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
user.set('password', userDetails.password);
|
||||
user.set('email', userDetails.email);
|
||||
user.set('email', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
if (userDetails?.phone) {
|
||||
user.set('phone', userDetails.phone);
|
||||
}
|
||||
@@ -111,7 +111,6 @@ export default async function AddAdmin(request) {
|
||||
if (extUser) {
|
||||
return { message: 'User already exist' };
|
||||
} else {
|
||||
// console.log("role ", role);
|
||||
const partnerQuery = new Parse.Object('partners_Tenant');
|
||||
partnerQuery.set('UserId', {
|
||||
__type: 'Pointer',
|
||||
@@ -123,7 +122,7 @@ export default async function AddAdmin(request) {
|
||||
partnerQuery.set('ContactNumber', userDetails.phone);
|
||||
}
|
||||
partnerQuery.set('TenantName', userDetails.company);
|
||||
partnerQuery.set('EmailAddress', userDetails.email);
|
||||
partnerQuery.set('EmailAddress', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
partnerQuery.set('IsActive', true);
|
||||
partnerQuery.set('CreatedBy', {
|
||||
__type: 'Pointer',
|
||||
@@ -154,7 +153,7 @@ export default async function AddAdmin(request) {
|
||||
objectId: user.id,
|
||||
});
|
||||
newObj.set('UserRole', userDetails.role);
|
||||
newObj.set('Email', userDetails.email);
|
||||
newObj.set('Email', userDetails.email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
newObj.set('Name', userDetails.name);
|
||||
if (userDetails?.phone) {
|
||||
newObj.set('Phone', userDetails?.phone);
|
||||
@@ -177,7 +176,7 @@ export default async function AddAdmin(request) {
|
||||
const extUser = {
|
||||
objectId: extRes.id,
|
||||
Name: userDetails.name,
|
||||
Email: userDetails.email,
|
||||
Email: userDetails.email?.toLowerCase()?.replace(/\s/g, ''),
|
||||
Phone: userDetails?.phone ? userDetails.phone : '',
|
||||
TenantId: { objectId: tenantRes.id },
|
||||
UserId: { objectId: user.id },
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
import { MAX_DESCRIPTION_LENGTH, MAX_NAME_LENGTH, MAX_NOTE_LENGTH } from '../../Utils.js';
|
||||
|
||||
async function DocumentBeforesave(request) {
|
||||
if (!request.original) {
|
||||
const validations = [
|
||||
{ field: 'Name', max: MAX_NAME_LENGTH },
|
||||
{ field: 'Note', max: MAX_NOTE_LENGTH },
|
||||
{ field: 'Description', max: MAX_DESCRIPTION_LENGTH },
|
||||
];
|
||||
|
||||
for (const { field, max } of validations) {
|
||||
const value = request?.object?.get(field);
|
||||
if (value && value.length > max) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.VALIDATION_ERROR,
|
||||
`The "${field}" field must be at most ${max} characters long.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const TimeToCompleteDays = request?.object?.get('TimeToCompleteDays') || 15;
|
||||
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
|
||||
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
|
||||
const reminderCount = TimeToCompleteDays / RemindOnceInEvery;
|
||||
if (AutoReminder && reminderCount > 15) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'only 15 reminder allowed');
|
||||
}
|
||||
}
|
||||
try {
|
||||
// below code is used to update document when user sent document or self signed
|
||||
const document = request.object;
|
||||
@@ -27,7 +54,6 @@ async function DocumentBeforesave(request) {
|
||||
}
|
||||
if (document?.get('Signers') && document.get('Signers').length > 0) {
|
||||
document.set('DocSentAt', new Date());
|
||||
document.save(null, { useMasterKey: true });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import axios from 'axios';
|
||||
import { appName, cloudServerUrl } from '../../Utils.js';
|
||||
|
||||
export default async function forwardDoc(request) {
|
||||
try {
|
||||
if (!request.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'unauthorized.');
|
||||
}
|
||||
const { docId, recipients } = request.params;
|
||||
const isReceipents = recipients?.length > 0 && recipients?.length <= 10;
|
||||
if (docId && isReceipents) {
|
||||
const userPtr = { __type: 'Pointer', className: '_User', objectId: request.user.id };
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
docQuery
|
||||
.equalTo('objectId', docId)
|
||||
.equalTo('CreatedBy', userPtr)
|
||||
.notEqualTo('IsArchive', true)
|
||||
.notEqualTo('IsDeclined', true)
|
||||
.include('Signers')
|
||||
.include('ExtUserPtr')
|
||||
.include('Placeholders.signerPtr')
|
||||
.include('ExtUserPtr.TenantId');
|
||||
const docRes = await docQuery.first({ useMasterKey: true });
|
||||
if (!docRes) {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
|
||||
}
|
||||
const _docRes = docRes?.toJSON();
|
||||
const docName = _docRes.Name;
|
||||
const fileAdapterId = _docRes?.FileAdapterId || '';
|
||||
const extUserId = _docRes?.ExtUserPtr?.objectId;
|
||||
const TenantAppName = appName;
|
||||
const from = _docRes?.ExtUserPtr?.Email;
|
||||
const replyTo = _docRes?.ExtUserPtr?.Email;
|
||||
const senderName = _docRes?.ExtUserPtr?.Name;
|
||||
|
||||
try {
|
||||
let mailRes;
|
||||
for (let i = 0; i < recipients.length; i++) {
|
||||
const logo = `<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>`;
|
||||
const opurl = ` <a href='www.opensignlabs.com' target=_blank>here</a>`;
|
||||
const themeColor = '#47a3ad';
|
||||
|
||||
let params = {
|
||||
extUserId: extUserId,
|
||||
pdfName: docName,
|
||||
url: _docRes?.SignedUrl || '',
|
||||
recipient: recipients[i],
|
||||
subject: `${senderName} has signed the doc - ${docName}`,
|
||||
replyto: replyTo || '',
|
||||
from: from,
|
||||
html:
|
||||
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'><div>` +
|
||||
`${logo}</div><div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document Copy</p></div><div>` +
|
||||
`<p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>${docName}</strong> is attached to this email. Kindly download the document from the attachment.</p>` +
|
||||
`</div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, please contact the sender ${replyTo} directly. ` +
|
||||
`If you think this email is inappropriate or spam, you may file a complaint with ${TenantAppName}${opurl}.</p></div></div></body></html>`,
|
||||
};
|
||||
mailRes = await axios.post(`${cloudServerUrl}/functions/sendmailv3`, params, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Master-Key': process.env.MASTER_KEY,
|
||||
},
|
||||
});
|
||||
}
|
||||
return mailRes.data?.result;
|
||||
} catch (error) {
|
||||
const msg =
|
||||
error?.response?.data?.error ||
|
||||
error?.response?.data ||
|
||||
error?.message ||
|
||||
'Something went wrong.';
|
||||
throw new Parse.Error(400, msg);
|
||||
}
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'please provide parameters.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err in forwardDoc', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,17 @@ export default async function GetLogoByDomain(request) {
|
||||
try {
|
||||
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
|
||||
tenantCreditsQuery.equalTo('Domain', domain);
|
||||
const res = await tenantCreditsQuery.first();
|
||||
const res = await tenantCreditsQuery.first({ useMasterKey: true });
|
||||
if (res) {
|
||||
const updateRes = JSON.parse(JSON.stringify(res));
|
||||
return { logo: updateRes?.Logo, appname: appName, user: 'exist' };
|
||||
return {
|
||||
logo: updateRes?.Logo,
|
||||
appname: appName,
|
||||
user: 'exist',
|
||||
};
|
||||
} else {
|
||||
const tenantCreditsQuery = new Parse.Query('partners_Tenant');
|
||||
const tenantRes = await tenantCreditsQuery.first();
|
||||
const tenantRes = await tenantCreditsQuery.first({ useMasterKey: true });
|
||||
if (tenantRes) {
|
||||
return { logo: '', appname: appName, user: 'exist' };
|
||||
} else {
|
||||
|
||||
@@ -1,112 +1,82 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
cloudServerUrl,
|
||||
} from '../../Utils.js';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
|
||||
export default async function GetTemplate(request) {
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const templateId = request.params.templateId;
|
||||
const ispublic = request.params.ispublic;
|
||||
const sessiontoken = request.headers?.sessiontoken;
|
||||
try {
|
||||
if (!ispublic) {
|
||||
let userEmail;
|
||||
if (sessiontoken) {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Session-Token': sessiontoken,
|
||||
},
|
||||
});
|
||||
userEmail = userRes.data && userRes.data.email;
|
||||
}
|
||||
if (templateId && userEmail) {
|
||||
try {
|
||||
let template = new Parse.Query('contracts_Template');
|
||||
template.equalTo('objectId', templateId);
|
||||
template.include('ExtUserPtr');
|
||||
template.include('Signers');
|
||||
template.include('CreatedBy');
|
||||
template.include('ExtUserPtr.TenantId');
|
||||
template.include('Bcc');
|
||||
const extUserQuery = new Parse.Query('contracts_Users');
|
||||
extUserQuery.equalTo('Email', userEmail);
|
||||
extUserQuery.include('TeamIds');
|
||||
const extUser = await extUserQuery.first({ useMasterKey: true });
|
||||
if (extUser) {
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
|
||||
let teamsArr = [];
|
||||
_extUser?.TeamIds?.forEach(x => (teamsArr = [...teamsArr, ...x.Ancestors]));
|
||||
// Create the first query
|
||||
const sharedWithQuery = new Parse.Query('contracts_Template');
|
||||
sharedWithQuery.containedIn('SharedWith', teamsArr);
|
||||
|
||||
// Create the second query
|
||||
const createdByQuery = new Parse.Query('contracts_Template');
|
||||
createdByQuery.equalTo('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
});
|
||||
template = Parse.Query.or(sharedWithQuery, createdByQuery);
|
||||
template.equalTo('objectId', templateId);
|
||||
template.include('ExtUserPtr');
|
||||
template.include('Signers');
|
||||
template.include('CreatedBy');
|
||||
template.include('ExtUserPtr.TenantId');
|
||||
template.include('Placeholders.signerPtr');
|
||||
template.include('Bcc');
|
||||
}
|
||||
}
|
||||
const res = await template.first({ useMasterKey: true });
|
||||
if (res) {
|
||||
const templateRes = JSON.parse(JSON.stringify(res));
|
||||
delete templateRes?.ExtUserPtr?.TenantId?.FileAdapters;
|
||||
delete templateRes?.ExtUserPtr?.TenantId?.PfxFile;
|
||||
return templateRes;
|
||||
} else {
|
||||
return { error: "You don't have access of this document!" };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
return { error: "You don't have access of this document!" };
|
||||
}
|
||||
} else if (templateId && ispublic) {
|
||||
let userEmail;
|
||||
if (sessiontoken) {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': process.env.APP_ID,
|
||||
'X-Parse-Session-Token': sessiontoken,
|
||||
},
|
||||
});
|
||||
userEmail = userRes.data && userRes.data.email;
|
||||
}
|
||||
if (templateId && userEmail) {
|
||||
try {
|
||||
const template = new Parse.Query('contracts_Template');
|
||||
let template = new Parse.Query('contracts_Template');
|
||||
template.equalTo('objectId', templateId);
|
||||
template.include('ExtUserPtr');
|
||||
template.include('Signers');
|
||||
template.include('CreatedBy');
|
||||
template.include('ExtUserPtr.TenantId');
|
||||
template.include('Bcc');
|
||||
const extUserQuery = new Parse.Query('contracts_Users');
|
||||
extUserQuery.equalTo('Email', userEmail);
|
||||
extUserQuery.include('TeamIds');
|
||||
const extUser = await extUserQuery.first({ useMasterKey: true });
|
||||
if (extUser) {
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
if (_extUser?.TeamIds && _extUser.TeamIds?.length > 0) {
|
||||
let teamsArr = [];
|
||||
_extUser?.TeamIds?.forEach(x => (teamsArr = [...teamsArr, ...x.Ancestors]));
|
||||
// Create the first query
|
||||
const sharedWithQuery = new Parse.Query('contracts_Template');
|
||||
sharedWithQuery.containedIn('SharedWith', teamsArr);
|
||||
|
||||
// Create the second query
|
||||
const createdByQuery = new Parse.Query('contracts_Template');
|
||||
createdByQuery.equalTo('ExtUserPtr', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
});
|
||||
template = Parse.Query.or(sharedWithQuery, createdByQuery);
|
||||
template.equalTo('objectId', templateId);
|
||||
template.include('ExtUserPtr');
|
||||
template.include('Signers');
|
||||
template.include('CreatedBy');
|
||||
template.include('ExtUserPtr.TenantId');
|
||||
template.include('Placeholders.signerPtr');
|
||||
template.include('Bcc');
|
||||
}
|
||||
}
|
||||
const res = await template.first({ useMasterKey: true });
|
||||
// console.log("res ", res)
|
||||
if (res) {
|
||||
const templateRes = JSON.parse(JSON.stringify(res));
|
||||
delete templateRes?.ExtUserPtr?.TenantId?.FileAdapters;
|
||||
delete templateRes?.ExtUserPtr?.TenantId?.PfxFile;
|
||||
return templateRes;
|
||||
} else {
|
||||
return { error: "You don't have access of this document!" };
|
||||
return { error: "You don't have access of this template!" };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
return err;
|
||||
}
|
||||
} else {
|
||||
return { error: 'Please pass required parameters!' };
|
||||
return { error: "You don't have access of this template!" };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
if (err?.response?.data?.code === 209 || err.code == 209) {
|
||||
return { error: 'Invalid session token' };
|
||||
} else {
|
||||
return { error: "You don't have access of this document!" };
|
||||
return { error: "You don't have access of this template!" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import axios from 'axios';
|
||||
export default async function Newsletter(request) {
|
||||
const name = request.params.name;
|
||||
const email = request.params.email;
|
||||
const email = request.params?.email?.toLowerCase()?.replace(/\s/g, '');
|
||||
const domain = request.params.domain;
|
||||
try {
|
||||
const envAppId = process.env.REACT_APP_APPID || 'legadranaxn';
|
||||
const envAppId = process.env.REACT_APP_APPID || 'opensign';
|
||||
const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': envAppId };
|
||||
const envProdServer = process.env.REACT_APP_SERVERURL || 'https://app.opensignlabs.com/api/app';
|
||||
const newsletter = await axios.post(
|
||||
|
||||
@@ -11,7 +11,7 @@ async function getDocument(docId) {
|
||||
query.include('Placeholders');
|
||||
query.notEqualTo('IsArchive', true);
|
||||
const res = await query.first({ useMasterKey: true });
|
||||
const _res = res.toJSON();
|
||||
const _res = res?.toJSON();
|
||||
return _res?.ExtUserPtr?.objectId;
|
||||
} catch (err) {
|
||||
console.log('err ', err);
|
||||
@@ -19,24 +19,24 @@ async function getDocument(docId) {
|
||||
}
|
||||
async function sendMailOTPv1(request) {
|
||||
try {
|
||||
//--for elearning app side
|
||||
let code = Math.floor(1000 + Math.random() * 9000);
|
||||
let email = request.params.email;
|
||||
var TenantId = request.params.TenantId ? request.params.TenantId : undefined;
|
||||
let TenantId = request.params.TenantId ? request.params.TenantId : undefined;
|
||||
const AppName = appName;
|
||||
|
||||
if (email) {
|
||||
const recipient = request.params.email;
|
||||
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
|
||||
try {
|
||||
await Parse.Cloud.sendEmail({
|
||||
from: appName + ' <' + mailsender + '>',
|
||||
sender: AppName + ' <' + mailsender + '>',
|
||||
recipient: recipient,
|
||||
subject: `Your ${appName} OTP`,
|
||||
text: 'This email is a test.',
|
||||
subject: `Your ${AppName} OTP`,
|
||||
text: 'otp 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 style='background-color:red;padding:2px;font-family:system-ui; background-color:#47a3ad;'> <p style='font-size:20px;font-weight:400;color:white;padding-left:20px',>OTP Verification</p></div><div style='padding:20px'><p style='font-family:system-ui;font-size:14px'>Your OTP for ${appName} verification is:</p><p style=' text-decoration: none; font-weight: bolder; color:blue;font-size:45px;margin:20px'>` +
|
||||
`<html><head><meta http-equiv='Content-Type' content='text/html;charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white;'><div style='background-color:red;padding:2px;font-family:system-ui;background-color:#47a3ad;'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px;'>OTP Verification</p></div><div style='padding:20px;'><p style='font-family:system-ui;font-size:14px;'>Your OTP for ${AppName} verification is:</p><p style='text-decoration:none;font-weight:bolder;color:blue;font-size:45px;margin:20px;'>` +
|
||||
code +
|
||||
'</p></div> </div> </div></body></html>',
|
||||
'</p></div></div></div></body></html>',
|
||||
});
|
||||
console.log('OTP sent', code);
|
||||
if (request.params?.docId) {
|
||||
|
||||
@@ -1,4 +1,31 @@
|
||||
import { MAX_DESCRIPTION_LENGTH, MAX_NAME_LENGTH, MAX_NOTE_LENGTH } from '../../Utils.js';
|
||||
|
||||
async function TemplateBeforeSave(request) {
|
||||
if (!request.original) {
|
||||
const validations = [
|
||||
{ field: 'Name', max: MAX_NAME_LENGTH },
|
||||
{ field: 'Note', max: MAX_NOTE_LENGTH },
|
||||
{ field: 'Description', max: MAX_DESCRIPTION_LENGTH },
|
||||
];
|
||||
|
||||
for (const { field, max } of validations) {
|
||||
const value = request.object?.get(field);
|
||||
if (value && value.length > max) {
|
||||
throw new Parse.Error(
|
||||
Parse.Error.VALIDATION_ERROR,
|
||||
`The "${field}" field must be at most ${max} characters long.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const TimeToCompleteDays = request.object.get('TimeToCompleteDays') || 15;
|
||||
const RemindOnceInEvery = request?.object?.get('RemindOnceInEvery') || 5;
|
||||
const AutoReminder = request?.object?.get('AutomaticReminders') || false;
|
||||
const reminderCount = TimeToCompleteDays / RemindOnceInEvery;
|
||||
if (AutoReminder && reminderCount > 15) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_QUERY, 'only 15 reminder allowed');
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (!request.original) {
|
||||
// below code is used to update template when user sent template or self signed
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
export default async function callWebhook(request) {
|
||||
const event = request.params.event;
|
||||
const body = request.params.body;
|
||||
const docId = body.objectId;
|
||||
const contactId = request.params.contactId;
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
try {
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
docQuery.include('ExtUserPtr.TenantId');
|
||||
const docRes = await docQuery.get(docId, { useMasterKey: true });
|
||||
const isEnableOTP = docRes?.get('IsEnableOTP') || false;
|
||||
let userId;
|
||||
if (isEnableOTP) {
|
||||
const userRes = await axios.get(serverUrl + '/users/me', {
|
||||
headers: {
|
||||
'X-Parse-Application-Id': appId,
|
||||
'X-Parse-Session-Token': request.headers['sessiontoken'],
|
||||
},
|
||||
});
|
||||
userId = userRes.data && userRes.data.objectId;
|
||||
}
|
||||
if (!isEnableOTP || userId) {
|
||||
if (event === 'viewed' && contactId) {
|
||||
if (docRes) {
|
||||
const _docRes = docRes.toJSON();
|
||||
const userPtr = {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Contactbook',
|
||||
objectId: contactId,
|
||||
};
|
||||
const date = new Date().toISOString();
|
||||
const obj = {
|
||||
UserPtr: userPtr,
|
||||
SignedUrl: _docRes.SignedUrl,
|
||||
Activity: 'Viewed',
|
||||
ipAddress: request.headers['x-real-ip'],
|
||||
ViewedOn: date,
|
||||
};
|
||||
const isUserExist = _docRes?.AuditTrail?.some(
|
||||
x => x.UserPtr.objectId === contactId && x?.ViewedOn
|
||||
);
|
||||
if (!isUserExist) {
|
||||
const updateDoc = new Parse.Object('contracts_Document');
|
||||
updateDoc.id = docRes.id;
|
||||
if (_docRes?.AuditTrail && _docRes?.AuditTrail?.length > 0) {
|
||||
updateDoc.set('AuditTrail', [..._docRes?.AuditTrail, obj]);
|
||||
} else {
|
||||
updateDoc.set('AuditTrail', [obj]);
|
||||
}
|
||||
await updateDoc.save(null, { useMasterKey: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
const extendcls = new Parse.Query('contracts_Users');
|
||||
extendcls.equalTo('objectId', docRes.get('ExtUserPtr')?.id);
|
||||
// extendcls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
|
||||
const resExt = await extendcls.first({ useMasterKey: true });
|
||||
if (resExt) {
|
||||
const extUser = JSON.parse(JSON.stringify(resExt));
|
||||
if (extUser?.Webhook) {
|
||||
const params = { event: event, ...body };
|
||||
await axios
|
||||
.post(extUser?.Webhook, params, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.then(res => {
|
||||
try {
|
||||
const webhook = new Parse.Object('contracts_Webhook');
|
||||
webhook.set('Log', res?.status);
|
||||
webhook.set('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userId,
|
||||
});
|
||||
webhook.save(null, { useMasterKey: true });
|
||||
} catch (err) {
|
||||
console.log('err save in contracts_Webhook', err.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('Err send data to webhook', err.message);
|
||||
try {
|
||||
const webhook = new Parse.Object('contracts_Webhook');
|
||||
webhook.set('Log', err?.status);
|
||||
webhook.set('UserId', {
|
||||
__type: 'Pointer',
|
||||
className: '_User',
|
||||
objectId: userId,
|
||||
});
|
||||
webhook.save(null, { useMasterKey: true });
|
||||
} catch (err) {
|
||||
console.log('err save in contracts_Webhook', err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
return { message: 'webhook called!' };
|
||||
}
|
||||
} else {
|
||||
return { message: 'User not found!' };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err in callwebhook', err);
|
||||
return { message: 'Something went wrong!' };
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ export default async function createBatchContact(req) {
|
||||
TenantId: { __type: 'Pointer', className: 'partners_Tenant', objectId: x.TenantId },
|
||||
CreatedBy: { __type: 'Pointer', className: '_User', objectId: req.user.id },
|
||||
Name: x.Name,
|
||||
Email: x.Email,
|
||||
Email: x.Email?.toLowerCase()?.replace(/\s/g, ''),
|
||||
IsDeleted: false,
|
||||
IsImported: true,
|
||||
...(x?.Phone ? { Phone: `${x?.Phone}` } : {}),
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
cloudServerUrl,
|
||||
replaceMailVaribles,
|
||||
} from '../../Utils.js';
|
||||
import { cloudServerUrl, replaceMailVaribles } from '../../Utils.js';
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const appId = process.env.APP_ID;
|
||||
async function deductcount(
|
||||
docsCount,
|
||||
extUserId,
|
||||
) {
|
||||
async function deductcount(docsCount, extUserId) {
|
||||
try {
|
||||
const extCls = new Parse.Object('contracts_Users');
|
||||
extCls.id = extUserId;
|
||||
@@ -18,9 +12,9 @@ async function deductcount(
|
||||
console.log('Err in deduct in quick send', err);
|
||||
}
|
||||
}
|
||||
async function sendMail(document) {
|
||||
async function sendMail(document, publicUrl) {
|
||||
//sessionToken
|
||||
const baseUrl = new URL(process.env.PUBLIC_URL);
|
||||
const baseUrl = new URL(publicUrl); //process.env.PUBLIC_URL
|
||||
|
||||
// console.log("pdfDetails", pdfDetails);
|
||||
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
|
||||
@@ -33,10 +27,8 @@ async function sendMail(document) {
|
||||
year: 'numeric',
|
||||
});
|
||||
let signerMail = document.Placeholders;
|
||||
const senderName =
|
||||
document.ExtUserPtr.Name;
|
||||
const senderEmail =
|
||||
document.ExtUserPtr.Email;
|
||||
const senderName = document.ExtUserPtr.Name;
|
||||
const senderEmail = document.ExtUserPtr.Email;
|
||||
|
||||
if (document.SendinOrder) {
|
||||
signerMail = signerMail.slice();
|
||||
@@ -70,6 +62,7 @@ async function sendMail(document) {
|
||||
'</body></html>';
|
||||
const variables = {
|
||||
document_title: document?.Name,
|
||||
note: document?.Note || '',
|
||||
sender_name: senderName,
|
||||
sender_mail: senderEmail,
|
||||
sender_phone: senderObj?.Phone || '',
|
||||
@@ -83,6 +76,7 @@ async function sendMail(document) {
|
||||
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
|
||||
}
|
||||
const mailparam = {
|
||||
note: document?.Note || '',
|
||||
senderName: senderName,
|
||||
senderMail: senderEmail,
|
||||
title: document.Name,
|
||||
@@ -94,8 +88,7 @@ async function sendMail(document) {
|
||||
extUserId: document.ExtUserPtr.objectId,
|
||||
recipient: objectId ? existSigner?.Email : signerMail[i].email,
|
||||
subject: replaceVar?.subject ? replaceVar?.subject : mailTemplate(mailparam).subject,
|
||||
from:
|
||||
document.ExtUserPtr.Email,
|
||||
from: document.ExtUserPtr.Email,
|
||||
replyto: senderEmail || '',
|
||||
html: replaceVar?.body ? replaceVar?.body : mailTemplate(mailparam).body,
|
||||
};
|
||||
@@ -108,13 +101,7 @@ async function sendMail(document) {
|
||||
}
|
||||
}
|
||||
}
|
||||
async function batchQuery(
|
||||
userId,
|
||||
Documents,
|
||||
Ip,
|
||||
parseConfig,
|
||||
type
|
||||
) {
|
||||
async function batchQuery(userId, Documents, Ip, parseConfig, type, publicUrl) {
|
||||
const extCls = new Parse.Query('contracts_Users');
|
||||
extCls.equalTo('UserId', {
|
||||
__type: 'Pointer',
|
||||
@@ -195,26 +182,35 @@ async function batchQuery(
|
||||
...(x?.RedirectUrl ? { RedirectUrl: x?.RedirectUrl } : {}),
|
||||
...(mailBody ? { RequestBody: mailBody } : {}),
|
||||
...(mailSubject ? { RequestSubject: mailSubject } : {}),
|
||||
...(x?.objectId
|
||||
? {
|
||||
TemplateId: {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Template',
|
||||
objectId: x?.objectId,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
});
|
||||
// console.log('requests ', requests);
|
||||
if (requests?.length > 0) {
|
||||
const newrequests = [requests?.[0]];
|
||||
const response = await axios.post('batch', { requests: newrequests }, parseConfig);
|
||||
// Handle the batch query response
|
||||
// console.log('Batch query response:', response.data);
|
||||
if (response.data && response.data.length > 0) {
|
||||
const document = Documents?.[0];
|
||||
const updateDocuments = {
|
||||
...document,
|
||||
objectId: response.data[0]?.success?.objectId,
|
||||
createdAt: response.data[0]?.success?.createdAt,
|
||||
};
|
||||
deductcount(response.data.length, resExt.id);
|
||||
sendMail(updateDocuments); //sessionToken
|
||||
return 'success';
|
||||
}
|
||||
if (requests?.length > 0) {
|
||||
const newrequests = [requests?.[0]];
|
||||
const response = await axios.post('batch', { requests: newrequests }, parseConfig);
|
||||
// Handle the batch query response
|
||||
// console.log('Batch query response:', response.data);
|
||||
if (response.data && response.data.length > 0) {
|
||||
const document = Documents?.[0];
|
||||
const updateDocuments = {
|
||||
...document,
|
||||
objectId: response.data[0]?.success?.objectId,
|
||||
createdAt: response.data[0]?.success?.createdAt,
|
||||
};
|
||||
deductcount(response.data.length, resExt.id);
|
||||
sendMail(updateDocuments, publicUrl); //sessionToken
|
||||
return 'success';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const code = error?.response?.data?.code || error?.response?.status || error?.code || 400;
|
||||
@@ -236,6 +232,8 @@ export default async function createBatchDocs(request) {
|
||||
const type = request.headers?.type || 'quicksend';
|
||||
const Documents = JSON.parse(strDocuments);
|
||||
const Ip = request?.headers?.['x-real-ip'] || '';
|
||||
// Access the host from the headers
|
||||
const publicUrl = request.headers.public_url;
|
||||
const parseConfig = {
|
||||
baseURL: serverUrl,
|
||||
headers: {
|
||||
@@ -246,9 +244,8 @@ export default async function createBatchDocs(request) {
|
||||
};
|
||||
try {
|
||||
if (request?.user) {
|
||||
return await batchQuery(request.user.id, Documents, Ip, parseConfig, '', type);
|
||||
}
|
||||
else {
|
||||
return await batchQuery(request.user.id, Documents, Ip, parseConfig, '', type, publicUrl);
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -15,7 +15,7 @@ export default async function editContact(request) {
|
||||
const query = new Parse.Query('contracts_Contactbook');
|
||||
query.equalTo('CreatedBy', createdBy);
|
||||
query.notEqualTo('IsDeleted', true);
|
||||
query.equalTo('Email', email);
|
||||
query.equalTo('Email', email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
const isContactExist = await query.first({ useMasterKey: true });
|
||||
if (isContactExist) {
|
||||
throw new Parse.Error(Parse.Error.DUPLICATE_VALUE, 'Contact already exists.');
|
||||
@@ -25,7 +25,7 @@ export default async function editContact(request) {
|
||||
if (phone) {
|
||||
contactQuery.set('Phone', phone);
|
||||
}
|
||||
contactQuery.set('Email', email);
|
||||
contactQuery.set('Email', email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
contactQuery.set('UserRole', 'contracts_Guest');
|
||||
contactQuery.set('IsDeleted', false);
|
||||
contactQuery.set('TenantId', {
|
||||
@@ -37,9 +37,9 @@ export default async function editContact(request) {
|
||||
const _users = Parse.Object.extend('User');
|
||||
const _user = new _users();
|
||||
_user.set('name', name);
|
||||
_user.set('username', email);
|
||||
_user.set('email', email);
|
||||
_user.set('password', email);
|
||||
_user.set('username', email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
_user.set('email', email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
_user.set('password', email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
if (phone) {
|
||||
_user.set('phone', phone);
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import { PostHog } from 'posthog-node';
|
||||
const ph_project_api_key = process.env.PH_PROJECT_API_KEY;
|
||||
const client = ph_project_api_key ? new PostHog(ph_project_api_key) : '';
|
||||
export default async function encryptedpdf(request) {
|
||||
const email = request.params.email;
|
||||
if (client) {
|
||||
client?.capture({
|
||||
distinctId: email,
|
||||
event: 'encrypted_pdf_error',
|
||||
properties: { response_code: 200 },
|
||||
});
|
||||
}
|
||||
return { message: 'success' };
|
||||
}
|
||||
@@ -12,21 +12,18 @@ const eSignName = 'OpenSign';
|
||||
const eSigncontact = 'hello@opensignlabs.com';
|
||||
|
||||
// `uploadFile` is used to create url in from pdfFile
|
||||
async function uploadFile(
|
||||
pdfName,
|
||||
filepath,
|
||||
) {
|
||||
async function uploadFile(pdfName, filepath) {
|
||||
try {
|
||||
const filedata = fs.readFileSync(filepath);
|
||||
let fileUrl;
|
||||
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
const fileRes = getSecureUrl(file.url());
|
||||
fileUrl = fileRes.url;
|
||||
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
const fileRes = getSecureUrl(file.url());
|
||||
fileUrl = fileRes.url;
|
||||
return { imageUrl: fileUrl };
|
||||
} catch (err) {
|
||||
console.log('Err ', err);
|
||||
// `unlinkCertificate` is used to remove exported signed pdf file from exports folder
|
||||
// `unlinkCertificate` is used to remove exported certificate file from exports folder
|
||||
unlinkCertificate(filepath);
|
||||
}
|
||||
}
|
||||
@@ -86,10 +83,7 @@ export default async function generateCertificatebydocId(req) {
|
||||
|
||||
//below is used to save signed certificate in exports folder
|
||||
fs.writeFileSync(certificatePath, signedCertificate);
|
||||
const file = await uploadFile(
|
||||
'certificate.pdf',
|
||||
certificatePath,
|
||||
);
|
||||
const file = await uploadFile('certificate.pdf', certificatePath);
|
||||
const updateDoc = new Parse.Object('contracts_Document');
|
||||
updateDoc.id = doc.objectId;
|
||||
updateDoc.set('CertificateUrl', file.imageUrl);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
cloudServerUrl,
|
||||
} from '../../Utils.js';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
export default async function getDocument(request) {
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const docId = request.params.docId;
|
||||
@@ -47,13 +45,12 @@ export default async function getDocument(request) {
|
||||
console.log('err user in not authenticated', err);
|
||||
return { error: "You don't have access of this document!" };
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return { error: "You don't have access of this document!" };
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return { error: "You don't have access of this document!" };
|
||||
return { error: "document deleted or you don't have access." };
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err', err);
|
||||
|
||||
@@ -52,7 +52,8 @@ const saveRoleContact = async contact => {
|
||||
// `linkContactToDoc` cloud function is used to create contact, add this contact in contracts_Guest role and
|
||||
// save contact pointer in placeholder, signers and ACL of Document
|
||||
export default async function linkContactToDoc(req) {
|
||||
const email = req.params.email;
|
||||
const requestemail = req.params?.email;
|
||||
const email = requestemail?.toLowerCase()?.replace(/\s/g, '');
|
||||
const docId = req.params.docId;
|
||||
const name = req.params.name;
|
||||
const phone = req.params.phone;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { exec } from 'child_process';
|
||||
|
||||
export default function DigitalSign(pdf, pfx, details) {
|
||||
const signcmd = `java -jar PDFDigitalSigner.jar "${pdf}" "${pfx.name}" "${pfx.passphrase}" "${details.name}" "${details.location}" "${details.reason}"`;
|
||||
// const signcmd = `java -jar PDFDigitalSigner.jar "${pdf}" keystore.pfx opensign "${details.name}" "${details.location}" "${details.reason}"`;
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(signcmd, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`Error: ${error.message}`);
|
||||
}
|
||||
if (stderr) {
|
||||
reject(`stderr: ${stderr}`);
|
||||
}
|
||||
// Resolve the promise with the output
|
||||
resolve(stdout.trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function retryAsync(fn, args = [], retries = 3, delay = 2000) {
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
try {
|
||||
const response = await fn(...args); // Try executing the async function
|
||||
if (response) return { response, attempt }; // Stop retrying if response is received
|
||||
} catch (error) {
|
||||
if (attempt < retries) {
|
||||
console.log(`Attempt ${attempt} failed. Retrying in ${delay / 1000} seconds...\n`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay)); // Wait before retrying
|
||||
} else {
|
||||
return { error, attempt };
|
||||
// throw new Error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||
import { PDFDocument, rgb } from 'pdf-lib';
|
||||
import fs from 'node:fs';
|
||||
import fontkit from '@pdf-lib/fontkit';
|
||||
import { formatTimeInTimezone } from '../../../Utils.js';
|
||||
import { formatDateTime } from '../../../Utils.js';
|
||||
|
||||
export default async function GenerateCertificate(docDetails) {
|
||||
const timezone = docDetails?.ExtUserPtr?.Timezone || '';
|
||||
const Is12Hr = docDetails?.ExtUserPtr?.Is12HourTime || false;
|
||||
const DateFormat = docDetails?.ExtUserPtr?.DateFormat || 'MM/DD/YYYY';
|
||||
const pdfDoc = await PDFDocument.create();
|
||||
// `fontBytes` is used to embed custom font in pdf
|
||||
const fontBytes = fs.readFileSync('./font/times.ttf'); //
|
||||
@@ -20,23 +22,27 @@ export default async function GenerateCertificate(docDetails) {
|
||||
const titleColor = rgb(0, 0.2, 0.4); //rgb(0, 0.53, 0.71);
|
||||
const titleUnderline = rgb(0, 0.2, 0.4); // rgb(0.12, 0.12, 0.12);
|
||||
const title = 25;
|
||||
const subtitle = 20;
|
||||
const text = 14;
|
||||
const subtitle = 16;
|
||||
const text = 13;
|
||||
const signertext = 13;
|
||||
const timeText = 11;
|
||||
const textKeyColor = rgb(0.12, 0.12, 0.12);
|
||||
const textValueColor = rgb(0.3, 0.3, 0.3);
|
||||
const completedAt = docDetails?.completedAt ? new Date(docDetails?.completedAt) : new Date();
|
||||
const completedAtperTimezone = formatTimeInTimezone(completedAt, timezone);
|
||||
const completedAtperTimezone = formatDateTime(completedAt, DateFormat, timezone, Is12Hr);
|
||||
const completedUTCtime = completedAtperTimezone;
|
||||
const signersCount = docDetails?.Signers?.length || 1;
|
||||
const generateAt = docDetails?.completedAt ? new Date(docDetails?.completedAt) : new Date();
|
||||
const generatedAtperTimezone = formatTimeInTimezone(generateAt, timezone);
|
||||
const generatedAtperTimezone = formatDateTime(generateAt, DateFormat, timezone, Is12Hr);
|
||||
const generatedUTCTime = generatedAtperTimezone;
|
||||
const generatedOn = 'Generated On ' + generatedUTCTime;
|
||||
const textWidth = timesRomanFont.widthOfTextAtSize(generatedOn, 12);
|
||||
const margin = 30;
|
||||
const maxX = width - margin - textWidth; // Ensures text stays inside the border with 30px margin
|
||||
const OriginIp = docDetails?.OriginIp || '';
|
||||
const company = docDetails?.ExtUserPtr?.Company || '';
|
||||
const createdAt = docDetails?.DocSentAt?.iso || docDetails.createdAt;
|
||||
const createdAtperTimezone = formatTimeInTimezone(createdAt, timezone);
|
||||
const createdAtperTimezone = formatDateTime(createdAt, DateFormat, timezone, Is12Hr);
|
||||
const IsEnableOTP = docDetails?.IsEnableOTP || false;
|
||||
const filteredaudit = docDetails?.AuditTrail?.filter(x => x?.UserPtr?.objectId);
|
||||
const auditTrail =
|
||||
@@ -81,7 +87,7 @@ export default async function GenerateCertificate(docDetails) {
|
||||
});
|
||||
|
||||
page.drawText(generatedOn, {
|
||||
x: 320,
|
||||
x: Math.max(startX, maxX), // Adjusts dynamically 320
|
||||
y: 810,
|
||||
size: 12,
|
||||
font: timesRomanFont,
|
||||
@@ -90,13 +96,13 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText('Certificate of Completion', {
|
||||
x: 160,
|
||||
y: 750,
|
||||
y: 755,
|
||||
size: title,
|
||||
font: timesRomanFont,
|
||||
color: titleColor,
|
||||
});
|
||||
|
||||
const underlineY = 740;
|
||||
const underlineY = 745;
|
||||
page.drawLine({
|
||||
start: { x: 30, y: underlineY },
|
||||
end: { x: width - 30, y: underlineY },
|
||||
@@ -106,7 +112,7 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText('Summary', {
|
||||
x: 30,
|
||||
y: 710,
|
||||
y: 727,
|
||||
size: subtitle,
|
||||
font: timesRomanFont,
|
||||
color: titleColor,
|
||||
@@ -114,15 +120,15 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText('Document Id :', {
|
||||
x: 30,
|
||||
y: 685,
|
||||
y: 710,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(docDetails.objectId, {
|
||||
x: 115,
|
||||
y: 685,
|
||||
x: 110,
|
||||
y: 710,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
@@ -130,15 +136,15 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText('Document Name :', {
|
||||
x: 30,
|
||||
y: 665,
|
||||
y: 690,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(docDetails?.Name, {
|
||||
x: 140,
|
||||
y: 665,
|
||||
x: 130,
|
||||
y: 690,
|
||||
size: docDetails?.Name?.length >= 78 ? 12 : text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
@@ -146,52 +152,52 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText('Organization :', {
|
||||
x: 30,
|
||||
y: 645,
|
||||
y: 670,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(company, {
|
||||
x: 115,
|
||||
y: 645,
|
||||
x: 110,
|
||||
y: 670,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Created on :', {
|
||||
x: 30,
|
||||
y: 625,
|
||||
y: 650,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(`${createdAtperTimezone}`, {
|
||||
x: 105,
|
||||
y: 625,
|
||||
x: 97,
|
||||
y: 650,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Completed on :', {
|
||||
x: 30,
|
||||
y: 605,
|
||||
y: 630,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(`${completedUTCtime}`, {
|
||||
x: 125,
|
||||
y: 605,
|
||||
x: 115,
|
||||
y: 630,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Signers :', {
|
||||
x: 30,
|
||||
y: 585,
|
||||
y: 610,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
@@ -199,73 +205,76 @@ export default async function GenerateCertificate(docDetails) {
|
||||
|
||||
page.drawText(`${signersCount}`, {
|
||||
x: 80,
|
||||
y: 585,
|
||||
y: 610,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Document originator', {
|
||||
x: 30,
|
||||
y: 565,
|
||||
y: 590,
|
||||
size: 17,
|
||||
font: timesRomanFont,
|
||||
color: titleColor,
|
||||
});
|
||||
page.drawText('Name :', {
|
||||
x: 60,
|
||||
y: 545,
|
||||
y: 573,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText(ownerName, {
|
||||
x: 105,
|
||||
y: 545,
|
||||
y: 573,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('Email :', {
|
||||
x: 60,
|
||||
y: 525,
|
||||
y: 553,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText(ownerEmail, {
|
||||
x: 105,
|
||||
y: 525,
|
||||
y: 553,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
page.drawText('IP address :', {
|
||||
x: 60,
|
||||
y: 505,
|
||||
y: 533,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText(`${OriginIp}`, {
|
||||
x: 130,
|
||||
y: 505,
|
||||
x: 125,
|
||||
y: 533,
|
||||
size: text,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawLine({
|
||||
start: { x: 30, y: 495 },
|
||||
end: { x: width - 30, y: 495 },
|
||||
start: { x: 30, y: 527 },
|
||||
end: { x: width - 30, y: 527 },
|
||||
color: rgb(0.12, 0.12, 0.12),
|
||||
thickness: 0.5,
|
||||
});
|
||||
let yPosition1 = 475;
|
||||
let yPosition2 = 455;
|
||||
let yPosition3 = 435;
|
||||
let yPosition4 = 415;
|
||||
let yPosition5 = 395;
|
||||
let yPosition6 = 360;
|
||||
let yPosition1 = 512;
|
||||
let yPosition2 = 498;
|
||||
let yPosition3 = 478;
|
||||
let yPosition4 = 458;
|
||||
let yPosition5 = 438;
|
||||
let yPosition6 = 418;
|
||||
let yPosition7 = 398;
|
||||
let yPosition8 = 363;
|
||||
|
||||
auditTrail.slice(0, 3).forEach(async (x, i) => {
|
||||
const embedPng = x.Signature ? await pdfDoc.embedPng(x.Signature) : '';
|
||||
page.drawText(`Signer ${i + 1}`, {
|
||||
@@ -278,7 +287,7 @@ export default async function GenerateCertificate(docDetails) {
|
||||
page.drawText('Name :', {
|
||||
x: 30,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
@@ -286,32 +295,32 @@ export default async function GenerateCertificate(docDetails) {
|
||||
page.drawText(x?.Name, {
|
||||
x: 75,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('Viewed on :', {
|
||||
x: half + 45,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
//new Date(x.ViewedOn).toUTCString()
|
||||
page.drawText(`${formatTimeInTimezone(x.ViewedOn, timezone)}`, {
|
||||
x: half + 102,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
if (IsEnableOTP) {
|
||||
page.drawText('Security level :', {
|
||||
x: half + 120,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText('Email, OTP Auth', {
|
||||
x: half + 190,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
}
|
||||
|
||||
page.drawText('Email :', {
|
||||
x: 30,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
@@ -319,70 +328,70 @@ export default async function GenerateCertificate(docDetails) {
|
||||
page.drawText(x?.Email, {
|
||||
x: 75,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('Viewed on :', {
|
||||
x: 30,
|
||||
y: yPosition4,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(`${formatDateTime(x.ViewedOn, DateFormat, timezone, Is12Hr)}`, {
|
||||
x: 97,
|
||||
y: yPosition4,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('Signed on :', {
|
||||
x: half + 45,
|
||||
y: yPosition3 + 5,
|
||||
size: timeText,
|
||||
x: 30,
|
||||
y: yPosition5,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
// new Date(x.SignedOn).toUTCString()
|
||||
page.drawText(`${formatTimeInTimezone(x.SignedOn, timezone)}`, {
|
||||
x: half + 98,
|
||||
y: yPosition3 + 5,
|
||||
size: timeText,
|
||||
page.drawText(`${formatDateTime(x.SignedOn, DateFormat, timezone, Is12Hr)}`, {
|
||||
x: 95,
|
||||
y: yPosition5,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
page.drawText('IP address :', {
|
||||
x: 30,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
y: yPosition6,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawText(x?.ipAddress, {
|
||||
x: 100,
|
||||
y: yPosition4,
|
||||
size: 13,
|
||||
x: 95,
|
||||
y: yPosition6,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
if (IsEnableOTP) {
|
||||
page.drawText('Security level :', {
|
||||
x: half + 45,
|
||||
y: yPosition4 + 10,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
page.drawText('Email, OTP Auth', {
|
||||
x: half + 115,
|
||||
y: yPosition4 + 10,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
}
|
||||
|
||||
page.drawText('Signature :', {
|
||||
x: 30,
|
||||
y: yPosition5,
|
||||
size: text,
|
||||
y: yPosition7,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
page.drawRectangle({
|
||||
x: 98,
|
||||
y: yPosition5 - 30,
|
||||
y: yPosition7 - 30,
|
||||
width: 104,
|
||||
height: 44,
|
||||
borderColor: rgb(0.22, 0.18, 0.47),
|
||||
@@ -391,24 +400,26 @@ export default async function GenerateCertificate(docDetails) {
|
||||
if (embedPng) {
|
||||
page.drawImage(embedPng, {
|
||||
x: 100,
|
||||
y: yPosition5 - 27,
|
||||
y: yPosition7 - 27,
|
||||
width: 100,
|
||||
height: 40,
|
||||
});
|
||||
}
|
||||
page.drawLine({
|
||||
start: { x: 30, y: yPosition6 },
|
||||
end: { x: width - 30, y: yPosition6 },
|
||||
start: { x: 30, y: yPosition8 },
|
||||
end: { x: width - 30, y: yPosition8 },
|
||||
color: rgb(0.12, 0.12, 0.12),
|
||||
thickness: 0.5,
|
||||
});
|
||||
|
||||
yPosition1 = yPosition6 - 20;
|
||||
yPosition1 = yPosition8 - 20;
|
||||
yPosition2 = yPosition1 - 20;
|
||||
yPosition3 = yPosition2 - 20;
|
||||
yPosition4 = yPosition3 - 20;
|
||||
yPosition5 = yPosition4 - 20;
|
||||
yPosition6 = yPosition6 - 140;
|
||||
yPosition6 = yPosition5 - 20;
|
||||
yPosition7 = yPosition6 - 20;
|
||||
yPosition8 = yPosition8 - 174;
|
||||
});
|
||||
|
||||
if (auditTrail.length > 3) {
|
||||
@@ -418,7 +429,7 @@ export default async function GenerateCertificate(docDetails) {
|
||||
const embedPng = x.Signature ? await pdfDoc.embedPng(x.Signature) : '';
|
||||
|
||||
// Calculate remaining space on current page
|
||||
const remainingSpace = yPosition6;
|
||||
const remainingSpace = yPosition8;
|
||||
|
||||
// If there's not enough space for the next entry, create a new page
|
||||
if (remainingSpace < 90) {
|
||||
@@ -438,7 +449,10 @@ export default async function GenerateCertificate(docDetails) {
|
||||
yPosition3 = yPosition2 - 20;
|
||||
yPosition4 = yPosition3 - 20;
|
||||
yPosition5 = yPosition4 - 20;
|
||||
yPosition6 = currentPage.getHeight() - 160;
|
||||
yPosition5 = yPosition4 - 20;
|
||||
yPosition6 = yPosition5 - 20;
|
||||
yPosition7 = yPosition6 - 20;
|
||||
yPosition8 = currentPage.getHeight() - 190;
|
||||
}
|
||||
|
||||
currentPage.drawText(`Signer ${4 + i}`, {
|
||||
@@ -451,7 +465,7 @@ export default async function GenerateCertificate(docDetails) {
|
||||
currentPage.drawText('Name :', {
|
||||
x: 30,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
@@ -459,32 +473,32 @@ export default async function GenerateCertificate(docDetails) {
|
||||
currentPage.drawText(x?.Name, {
|
||||
x: 75,
|
||||
y: yPosition2,
|
||||
size: text,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
currentPage.drawText('Viewed on :', {
|
||||
x: half + 45,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
// new Date(x.ViewedOn).toUTCString()
|
||||
currentPage.drawText(`${formatTimeInTimezone(x.ViewedOn, timezone)}`, {
|
||||
x: half + 102,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
if (IsEnableOTP) {
|
||||
currentPage.drawText('Security level :', {
|
||||
x: half + 120,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
currentPage.drawText(`Email, OTP Auth`, {
|
||||
x: half + 190,
|
||||
y: yPosition2,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
}
|
||||
|
||||
currentPage.drawText('Email :', {
|
||||
x: 30,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
@@ -492,70 +506,68 @@ export default async function GenerateCertificate(docDetails) {
|
||||
currentPage.drawText(x?.Email, {
|
||||
x: 75,
|
||||
y: yPosition3,
|
||||
size: text,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
currentPage.drawText('Signed on :', {
|
||||
x: half + 45,
|
||||
y: yPosition3 + 5,
|
||||
size: timeText,
|
||||
currentPage.drawText('Viewed on :', {
|
||||
x: 30,
|
||||
y: yPosition4,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
// new Date(x.SignedOn).toUTCString()
|
||||
currentPage.drawText(`${formatTimeInTimezone(x.SignedOn, timezone)}`, {
|
||||
x: half + 98,
|
||||
y: yPosition3 + 5,
|
||||
size: timeText,
|
||||
currentPage.drawText(`${formatDateTime(x.ViewedOn, DateFormat, timezone, Is12Hr)}`, {
|
||||
x: 97,
|
||||
y: yPosition4,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
currentPage.drawText('Signed on :', {
|
||||
x: 30,
|
||||
y: yPosition5,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(`${formatDateTime(x.SignedOn, DateFormat, timezone, Is12Hr)}`, {
|
||||
x: 95,
|
||||
y: yPosition5,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
currentPage.drawText('IP address :', {
|
||||
x: 30,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
y: yPosition6,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
|
||||
currentPage.drawText(x?.ipAddress, {
|
||||
x: 100,
|
||||
y: yPosition4,
|
||||
size: text,
|
||||
y: yPosition6,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
|
||||
if (IsEnableOTP) {
|
||||
currentPage.drawText('Security level :', {
|
||||
x: half + 45,
|
||||
y: yPosition4 + 10,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
currentPage.drawText(`Email, OTP Auth`, {
|
||||
x: half + 115,
|
||||
y: yPosition4 + 10,
|
||||
size: timeText,
|
||||
font: timesRomanFont,
|
||||
color: textValueColor,
|
||||
});
|
||||
}
|
||||
currentPage.drawText('Signature :', {
|
||||
x: 30,
|
||||
y: yPosition5,
|
||||
size: text,
|
||||
y: yPosition7,
|
||||
size: signertext,
|
||||
font: timesRomanFont,
|
||||
color: textKeyColor,
|
||||
});
|
||||
currentPage.drawRectangle({
|
||||
x: 98,
|
||||
y: yPosition5 - 27,
|
||||
y: yPosition7 - 27,
|
||||
width: 104,
|
||||
height: 44,
|
||||
borderColor: rgb(0.22, 0.18, 0.47),
|
||||
@@ -564,26 +576,28 @@ export default async function GenerateCertificate(docDetails) {
|
||||
if (embedPng) {
|
||||
currentPage.drawImage(embedPng, {
|
||||
x: 100,
|
||||
y: yPosition5 - 25,
|
||||
y: yPosition7 - 25,
|
||||
width: 100,
|
||||
height: 40,
|
||||
});
|
||||
}
|
||||
|
||||
currentPage.drawLine({
|
||||
start: { x: 30, y: yPosition6 },
|
||||
end: { x: width - 30, y: yPosition6 },
|
||||
start: { x: 30, y: yPosition8 },
|
||||
end: { x: width - 30, y: yPosition8 },
|
||||
color: rgb(0.12, 0.12, 0.12),
|
||||
thickness: 0.5,
|
||||
});
|
||||
|
||||
// Update y positions for the next entry
|
||||
yPosition1 = yPosition6 - 20;
|
||||
yPosition1 = yPosition8 - 20;
|
||||
yPosition2 = yPosition1 - 20;
|
||||
yPosition3 = yPosition2 - 20;
|
||||
yPosition4 = yPosition3 - 20;
|
||||
yPosition5 = yPosition4 - 20;
|
||||
yPosition6 = yPosition6 - 140;
|
||||
yPosition6 = yPosition5 - 20;
|
||||
yPosition7 = yPosition6 - 20;
|
||||
yPosition8 = yPosition8 - 174;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import fs from 'node:fs';
|
||||
import axios from 'axios';
|
||||
import { SignPdf } from '@signpdf/signpdf';
|
||||
import { P12Signer } from '@signpdf/signer-p12';
|
||||
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
|
||||
import { PDFDocument } from 'pdf-lib';
|
||||
import {
|
||||
cloudServerUrl,
|
||||
@@ -12,24 +9,26 @@ import {
|
||||
appName,
|
||||
} from '../../../Utils.js';
|
||||
import GenerateCertificate from './GenerateCertificate.js';
|
||||
import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib';
|
||||
import { Placeholder } from './Placeholder.js';
|
||||
import { SignPdf } from '@signpdf/signpdf';
|
||||
import { P12Signer } from '@signpdf/signer-p12';
|
||||
|
||||
const serverUrl = cloudServerUrl; // process.env.SERVER_URL;
|
||||
const APPID = process.env.APP_ID;
|
||||
const masterKEY = process.env.MASTER_KEY;
|
||||
const eSignName = 'OpenSign';
|
||||
const eSigncontact = 'hello@opensignlabs.com';
|
||||
|
||||
// `updateDoc` is used to create url in from pdfFile
|
||||
async function uploadFile(
|
||||
pdfName,
|
||||
filepath,
|
||||
) {
|
||||
async function uploadFile(pdfName, filepath) {
|
||||
try {
|
||||
const filedata = fs.readFileSync(filepath);
|
||||
let fileUrl;
|
||||
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
const fileRes = getSecureUrl(file.url());
|
||||
fileUrl = fileRes.url;
|
||||
const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
|
||||
await file.save({ useMasterKey: true });
|
||||
const fileRes = getSecureUrl(file.url());
|
||||
fileUrl = fileRes.url;
|
||||
|
||||
return { imageUrl: fileUrl };
|
||||
} catch (err) {
|
||||
@@ -91,8 +90,12 @@ async function updateDoc(docId, url, userId, ipAddress, data, className, sign) {
|
||||
}
|
||||
|
||||
// `sendNotifyMail` is used to send notification mail of signer signed the document
|
||||
async function sendNotifyMail(doc, signUser, mailProvider) {
|
||||
async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
|
||||
try {
|
||||
const TenantAppName = appName;
|
||||
const logo =
|
||||
"<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>";
|
||||
const opurl = ` <a href=www.opensignlabs.com target=_blank>here</a>`;
|
||||
const auditTrailCount = doc?.AuditTrail?.filter(x => x.Activity === 'Signed')?.length || 0;
|
||||
const signersCount = doc?.Placeholders?.length;
|
||||
const remaingsign = signersCount - auditTrailCount;
|
||||
@@ -103,22 +106,18 @@ async function sendNotifyMail(doc, signUser, mailProvider) {
|
||||
const creatorEmail = doc.ExtUserPtr.Email;
|
||||
const signerName = signUser.Name;
|
||||
const signerEmail = signUser.Email;
|
||||
const viewDocUrl = `${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
|
||||
const logo =
|
||||
`<div><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px' /></div>`;
|
||||
const opurl =
|
||||
` <a href=www.opensignlabs.com target=_blank>here</a>`;
|
||||
const viewDocUrl = `${publicUrl}/recipientSignPdf/${doc.objectId}`; // ` ${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
|
||||
const subject = `Document "${pdfName}" has been signed by ${signerName}`;
|
||||
const body =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
|
||||
`${logo}<div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed by ${signerName}</p>` +
|
||||
`<div>${logo}</div><div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed by ${signerName}</p>` +
|
||||
`</div><div style='padding:20px;font-family:system-ui;font-size:14px'><p>Dear ${creatorName},</p><p>${pdfName} has been signed by ${signerName} "${signerEmail}" successfully</p>` +
|
||||
`<p><a href=${viewDocUrl} target=_blank>View Document</a></p></div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, ` +
|
||||
`please contact the sender ${creatorEmail} directly. If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}.</p></div></div></body></html>`;
|
||||
`<p><a href=${viewDocUrl} target=_blank>View Document</a></p></div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, ` +
|
||||
`please contact the sender ${creatorEmail} directly. If you think this email is inappropriate or spam, you may file a complaint with ${TenantAppName}${opurl}.</p></div></div></body></html>`;
|
||||
|
||||
const params = {
|
||||
extUserId: sender.objectId,
|
||||
from: appName,
|
||||
from: TenantAppName,
|
||||
recipient: creatorEmail,
|
||||
subject: subject,
|
||||
pdfName: pdfName,
|
||||
@@ -144,6 +143,10 @@ async function sendCompletedMail(obj) {
|
||||
const doc = obj.doc;
|
||||
const sender = obj.doc.ExtUserPtr;
|
||||
const pdfName = doc.Name;
|
||||
const TenantAppName = appName;
|
||||
const logo =
|
||||
"<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>";
|
||||
const opurl = ` <a href=www.opensignlabs.com target=_blank>here</a>`;
|
||||
let signersMail;
|
||||
if (doc?.Signers?.length > 0) {
|
||||
const isOwnerExistsinSigners = doc?.Signers?.find(x => x.Email === sender.Email);
|
||||
@@ -154,17 +157,13 @@ async function sendCompletedMail(obj) {
|
||||
signersMail = sender.Email;
|
||||
}
|
||||
const recipient = signersMail;
|
||||
const logo =
|
||||
`<div><img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/></div>`;
|
||||
const opurl =
|
||||
` <a href=www.opensignlabs.com target=_blank>here</a>.</p></div></div></body></html>`;
|
||||
let subject = `Document "${pdfName}" has been signed by all parties`;
|
||||
let body =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
|
||||
`${logo}<div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed successfully</p></div><div>` +
|
||||
`<div>${logo}</div><div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed successfully</p></div><div>` +
|
||||
`<p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document <b>"${pdfName}"</b>. Kindly download the document from the attachment.</p>` +
|
||||
`</div></div><div><p>This is an automated email from ${appName}. For any queries regarding this email, please contact the sender ${sender.Email} directly.` +
|
||||
`If you think this email is inappropriate or spam, you may file a complaint with ${appName}${opurl}`;
|
||||
`</div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, please contact the sender ${sender.Email} directly.` +
|
||||
`If you think this email is inappropriate or spam, you may file a complaint with ${TenantAppName}${opurl}.</p></div></div></body></html>`;
|
||||
|
||||
if (obj?.isCustomMail) {
|
||||
const tenant = sender?.TenantId;
|
||||
@@ -181,7 +180,7 @@ async function sendCompletedMail(obj) {
|
||||
className: '_User',
|
||||
objectId: userId,
|
||||
});
|
||||
const tenantRes = await tenantQuery.first();
|
||||
const tenantRes = await tenantQuery.first({ useMasterKey: true });
|
||||
if (tenantRes) {
|
||||
const _tenantRes = JSON.parse(JSON.stringify(tenantRes));
|
||||
subject = _tenantRes?.CompletionSubject || '';
|
||||
@@ -202,8 +201,7 @@ async function sendCompletedMail(obj) {
|
||||
|
||||
const variables = {
|
||||
document_title: pdfName,
|
||||
sender_name:
|
||||
sender.Name,
|
||||
sender_name: sender.Name,
|
||||
sender_mail: doc?.SenderMail || sender.Email,
|
||||
sender_phone: sender?.Phone || '',
|
||||
receiver_name: sender.Name,
|
||||
@@ -216,49 +214,45 @@ async function sendCompletedMail(obj) {
|
||||
subject = replaceVar.subject;
|
||||
body = replaceVar.body;
|
||||
}
|
||||
const Bcc = doc?.Bcc?.length > 0 ? doc.Bcc.map(x => x.Email) : '';
|
||||
const Bcc = doc?.Bcc?.length > 0 ? doc.Bcc.map(x => x.Email) : [];
|
||||
const updatedBcc = doc?.SenderMail ? [...Bcc, doc?.SenderMail] : Bcc;
|
||||
const params = {
|
||||
extUserId: sender.objectId,
|
||||
url: url,
|
||||
from:
|
||||
appName,
|
||||
replyto:
|
||||
doc?.ExtUserPtr?.Email ||
|
||||
'',
|
||||
from: TenantAppName,
|
||||
replyto: doc?.ExtUserPtr?.Email || '',
|
||||
recipient: recipient,
|
||||
subject: subject,
|
||||
pdfName: pdfName,
|
||||
html: body,
|
||||
mailProvider: obj.mailProvider,
|
||||
bcc: Bcc,
|
||||
certificatePath: `./exports/certificate_${doc.objectId}.pdf`,
|
||||
bcc: updatedBcc?.length > 0 ? updatedBcc : '',
|
||||
certificatePath: `./exports/signed_certificate_${doc.objectId}.pdf`,
|
||||
filename: obj?.filename,
|
||||
};
|
||||
const res = await axios.post(serverUrl + '/functions/sendmailv3', params, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
},
|
||||
});
|
||||
try {
|
||||
const res = await axios.post(serverUrl + '/functions/sendmailv3', params, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Parse-Application-Id': APPID,
|
||||
'X-Parse-Master-Key': masterKEY,
|
||||
},
|
||||
});
|
||||
// console.log('res', res.data.result);
|
||||
if (res.data?.result?.status !== 'success') {
|
||||
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
||||
}
|
||||
} catch (err) {
|
||||
fs.unlinkSync(`./exports/signed_certificate_${doc.objectId}.pdf`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// `sendMailsaveCertifcate` is used send completion mail and update complete status of document
|
||||
async function sendMailsaveCertifcate(
|
||||
doc,
|
||||
P12Buffer,
|
||||
isCustomMail,
|
||||
mailProvider,
|
||||
filename,
|
||||
) {
|
||||
async function sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, filename) {
|
||||
const certificate = await GenerateCertificate(doc);
|
||||
const certificatePdf = await PDFDocument.load(certificate);
|
||||
let passphrase = process.env.PASS_PHRASE;
|
||||
if (doc?.ExtUserPtr?.TenantId?.PfxFile?.password) {
|
||||
passphrase = doc?.ExtUserPtr?.TenantId?.PfxFile?.password;
|
||||
}
|
||||
const p12 = new P12Signer(P12Buffer, { passphrase: passphrase || null });
|
||||
const P12Buffer = fs.readFileSync(pfx.name);
|
||||
const p12 = new P12Signer(P12Buffer, { passphrase: pfx.passphrase || null });
|
||||
// `pdflibAddPlaceholder` is used to add code of only digitial sign in certificate
|
||||
pdflibAddPlaceholder({
|
||||
pdfDoc: certificatePdf,
|
||||
@@ -274,13 +268,11 @@ async function sendMailsaveCertifcate(
|
||||
const certificateOBJ = new SignPdf();
|
||||
// `signedCertificate` is used to sign certificate digitally
|
||||
const signedCertificate = await certificateOBJ.sign(CertificateBuffer, p12);
|
||||
const certificatePath = `./exports/certificate_${doc.objectId}.pdf`;
|
||||
const certificatePath = `./exports/signed_certificate_${doc.objectId}.pdf`;
|
||||
|
||||
//below is used to save signed certificate in exports folder
|
||||
fs.writeFileSync(certificatePath, signedCertificate);
|
||||
const file = await uploadFile(
|
||||
'certificate.pdf',
|
||||
certificatePath,
|
||||
);
|
||||
const file = await uploadFile('certificate.pdf', certificatePath);
|
||||
const body = { CertificateUrl: file.imageUrl };
|
||||
await axios.put(serverUrl + '/classes/contracts_Document/' + doc.objectId, body, {
|
||||
headers: {
|
||||
@@ -296,6 +288,7 @@ async function sendMailsaveCertifcate(
|
||||
sendCompletedMail({ isCustomMail, doc, mailProvider, filename });
|
||||
}
|
||||
saveFileUsage(CertificateBuffer.length, file.imageUrl, doc?.CreatedBy?.objectId);
|
||||
fs.unlinkSync(pfx.name);
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -304,13 +297,16 @@ async function sendMailsaveCertifcate(
|
||||
* @returns if success {status, data} else {status, message}
|
||||
*/
|
||||
async function PDF(req) {
|
||||
const docId = req.params.docId;
|
||||
const randomNumber = Math.floor(Math.random() * 5000);
|
||||
const pfxname = `keystore_${randomNumber}.pfx`;
|
||||
try {
|
||||
const userIP = req.headers['x-real-ip']; // client IPaddress
|
||||
const docId = req.params.docId;
|
||||
const reqUserId = req.params.userId;
|
||||
const isCustomMail = req.params.isCustomCompletionMail || false;
|
||||
const mailProvider = req.params.mailProvider || '';
|
||||
const sign = req.params.signature || '';
|
||||
const publicUrl = req.headers.public_url;
|
||||
// below bode is used to get info of docId
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
docQuery.include('ExtUserPtr,Signers,ExtUserPtr.TenantId,Bcc');
|
||||
@@ -354,9 +350,9 @@ async function PDF(req) {
|
||||
pfxFile = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.base64;
|
||||
passphrase = _resDoc?.ExtUserPtr?.TenantId?.PfxFile?.password;
|
||||
}
|
||||
// const P12Buffer = fs.readFileSync();
|
||||
const pfx = { name: pfxname, passphrase: passphrase };
|
||||
const P12Buffer = Buffer.from(pfxFile, 'base64');
|
||||
const p12Cert = new P12Signer(P12Buffer, { passphrase: passphrase || null });
|
||||
fs.writeFileSync(pfxname, P12Buffer);
|
||||
const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId };
|
||||
const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP };
|
||||
let updateAuditTrail;
|
||||
@@ -375,12 +371,12 @@ async function PDF(req) {
|
||||
} else {
|
||||
isCompleted = true;
|
||||
}
|
||||
const randomNumber = Math.floor(Math.random() * 5000);
|
||||
// below regex is used to replace all word with "_" except A to Z, a to z, numbers
|
||||
const docName = _resDoc?.Name?.replace(/[^a-zA-Z0-9._-]/g, '_')?.toLowerCase();
|
||||
const filename = docName?.length > 100 ? docName?.slice(0, 100) : docName;
|
||||
const name = `signed_${filename}_${randomNumber}.pdf`;
|
||||
const filePath = `./exports/${name}`;
|
||||
const name = `${filename}_${randomNumber}.pdf`;
|
||||
let filePath = `./exports/${name}`;
|
||||
let signedFilePath = `./exports/signed_${name}`;
|
||||
let pdfSize = PdfBuffer.length;
|
||||
if (isCompleted) {
|
||||
const signersName = _resDoc.Signers?.map(x => x.Name + ' <' + x.Email + '>');
|
||||
@@ -394,6 +390,8 @@ async function PDF(req) {
|
||||
form.updateFieldAppearances();
|
||||
// Flattens the form, converting all form fields into non-editable, static content
|
||||
form.flatten();
|
||||
const p12Cert = new P12Signer(P12Buffer, { passphrase: passphrase || null });
|
||||
signedFilePath = `./exports/signed_${name}`;
|
||||
Placeholder({
|
||||
pdfDoc: pdfDoc,
|
||||
reason: `Digitally signed by ${eSignName} for ${reason}`,
|
||||
@@ -410,19 +408,18 @@ async function PDF(req) {
|
||||
const signedDocs = await OBJ.sign(PdfBuffer, p12Cert);
|
||||
|
||||
//`saveUrl` is used to save signed pdf in exports folder
|
||||
fs.writeFileSync(filePath, signedDocs);
|
||||
fs.writeFileSync(signedFilePath, signedDocs);
|
||||
pdfSize = signedDocs.length;
|
||||
console.log(`✅ PDF digitally signed created: ${signedFilePath} \n`);
|
||||
} else {
|
||||
//`saveUrl` is used to save signed pdf in exports folder
|
||||
fs.writeFileSync(filePath, PdfBuffer);
|
||||
fs.writeFileSync(signedFilePath, PdfBuffer);
|
||||
pdfSize = PdfBuffer.length;
|
||||
console.log(`New Signed PDF created called: ${signedFilePath}`);
|
||||
}
|
||||
|
||||
// `uploadFile` is used to upload pdf to aws s3 and get it's url
|
||||
const data = await uploadFile(
|
||||
name,
|
||||
filePath,
|
||||
);
|
||||
const data = await uploadFile(`signed_${name}`, signedFilePath);
|
||||
|
||||
if (data && data.imageUrl) {
|
||||
// `axios` is used to update signed pdf url in contracts_Document classes for given DocId
|
||||
@@ -435,21 +432,17 @@ async function PDF(req) {
|
||||
className, // className based on flow
|
||||
sign // sign base64
|
||||
);
|
||||
sendNotifyMail(_resDoc, signUser, mailProvider);
|
||||
sendNotifyMail(_resDoc, signUser, mailProvider, publicUrl);
|
||||
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
|
||||
if (updatedDoc && updatedDoc.isCompleted) {
|
||||
const doc = { ..._resDoc, AuditTrail: updatedDoc.AuditTrail, SignedUrl: data.imageUrl };
|
||||
sendMailsaveCertifcate(
|
||||
doc,
|
||||
P12Buffer,
|
||||
isCustomMail,
|
||||
mailProvider,
|
||||
name,
|
||||
);
|
||||
sendMailsaveCertifcate(doc, pfx, isCustomMail, mailProvider, `signed_${name}`);
|
||||
} else {
|
||||
fs.unlinkSync(pfxname);
|
||||
}
|
||||
// `fs.unlinkSync` is used to remove exported signed pdf file from exports folder
|
||||
fs.unlinkSync(filePath);
|
||||
console.log(`New Signed PDF created called: ${filePath}`);
|
||||
fs.unlinkSync(signedFilePath);
|
||||
// console.log(`New Signed PDF created called: ${filePath}`);
|
||||
if (updatedDoc.message === 'success') {
|
||||
return { status: 'success', data: data.imageUrl };
|
||||
} else {
|
||||
@@ -465,6 +458,15 @@ async function PDF(req) {
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err in signpdf', err);
|
||||
const body = { DebugginLog: err?.message };
|
||||
try {
|
||||
await axios.put(serverUrl + '/classes/contracts_Document/' + docId, body, {
|
||||
headers: { 'X-Parse-Application-Id': APPID, 'X-Parse-Master-Key': masterKEY },
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('err in saving debugginglog', err);
|
||||
}
|
||||
fs.unlinkSync(pfxname);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ export default function reportJson(id, userId) {
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'IsSignyourself',
|
||||
'TemplateId',
|
||||
],
|
||||
};
|
||||
|
||||
// Need your sign report
|
||||
case '4Hhwbp482K':
|
||||
return {
|
||||
@@ -61,6 +61,7 @@ export default function reportJson(id, userId) {
|
||||
'AuditTrail',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
'ExpiryDate',
|
||||
],
|
||||
};
|
||||
@@ -94,6 +95,7 @@ export default function reportJson(id, userId) {
|
||||
'SendMail',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
],
|
||||
};
|
||||
// completed documents report
|
||||
@@ -140,6 +142,7 @@ export default function reportJson(id, userId) {
|
||||
'Placeholders',
|
||||
'IsSignyourself',
|
||||
'IsCompleted',
|
||||
'TemplateId',
|
||||
],
|
||||
};
|
||||
// declined documents report
|
||||
@@ -164,6 +167,7 @@ export default function reportJson(id, userId) {
|
||||
'Placeholders',
|
||||
'DeclineReason',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
],
|
||||
};
|
||||
// Expired Documents report
|
||||
@@ -190,6 +194,7 @@ export default function reportJson(id, userId) {
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
'ExpiryDate',
|
||||
],
|
||||
};
|
||||
@@ -221,6 +226,7 @@ export default function reportJson(id, userId) {
|
||||
'ExpiryDate',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
],
|
||||
};
|
||||
// Recent signature requests report show on dashboard
|
||||
@@ -254,6 +260,7 @@ export default function reportJson(id, userId) {
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
'ExpiryDate',
|
||||
],
|
||||
};
|
||||
@@ -279,6 +286,7 @@ export default function reportJson(id, userId) {
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'TemplateId',
|
||||
],
|
||||
};
|
||||
// contact book report
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
const randomId = () => Math.floor(1000 + Math.random() * 9000);
|
||||
export default async function saveAsTemplate(request) {
|
||||
const docId = request.params.docId;
|
||||
const Ip = request?.headers?.['x-real-ip'] || '';
|
||||
|
||||
if (!request.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'user is not authenticated.');
|
||||
}
|
||||
try {
|
||||
const docQuery = new Parse.Query('contracts_Document');
|
||||
docQuery.equalTo('objectId', docId);
|
||||
docQuery.equalTo('CreatedBy', request.user);
|
||||
docQuery.include('ExtUserPtr');
|
||||
docQuery.include('ExtUserPtr.TenantId');
|
||||
docQuery.notEqualTo('IsArchive', true);
|
||||
const docRes = await docQuery.first({ useMasterKey: true });
|
||||
if (docRes) {
|
||||
const _docRes = docRes?.toJSON();
|
||||
const templateCls = new Parse.Object('contracts_Template');
|
||||
templateCls.set('URL', _docRes?.URL);
|
||||
templateCls.set('Name', _docRes?.Name);
|
||||
templateCls.set('Note', _docRes?.Note);
|
||||
templateCls.set('Description', _docRes?.Description);
|
||||
templateCls.set('OriginIp', Ip);
|
||||
templateCls.set('SendinOrder', _docRes?.SendinOrder || false);
|
||||
templateCls.set('AutomaticReminders', _docRes?.AutomaticReminders || false);
|
||||
templateCls.set('ExtUserPtr', _docRes?.ExtUserPtr);
|
||||
templateCls.set('CreatedBy', _docRes?.CreatedBy);
|
||||
templateCls.set('IsEnableOTP', _docRes?.IsEnableOTP === true ? true : false);
|
||||
templateCls.set('IsTourEnabled', _docRes?.IsTourEnabled === true ? true : false);
|
||||
templateCls.set('AllowModifications', _docRes?.AllowModifications || false);
|
||||
templateCls.set('EmailSenderName', _docRes?.EmailSenderName);
|
||||
templateCls.set('SenderName', _docRes?.SenderName);
|
||||
templateCls.set('SenderMail', _docRes?.SenderMail);
|
||||
templateCls.set('FileAdapterId', _docRes?.FileAdapterId);
|
||||
templateCls.set('RequestBody', _docRes?.RequestBody);
|
||||
templateCls.set('RequestSubject', _docRes?.RequestSubject);
|
||||
templateCls.set('NextReminderDate', _docRes?.NextReminderDate);
|
||||
templateCls.set('RedirectUrl', _docRes?.RedirectUrl);
|
||||
templateCls.set(
|
||||
'NotifyOnSignatures',
|
||||
_docRes?.NotifyOnSignatures !== undefined ? _docRes?.NotifyOnSignatures : false
|
||||
);
|
||||
templateCls.set(
|
||||
'TimeToCompleteDays',
|
||||
_docRes?.TimeToCompleteDays ? parseInt(_docRes?.TimeToCompleteDays) : 15
|
||||
);
|
||||
if (_docRes?.RemindOnceInEvery) {
|
||||
templateCls.set('RemindOnceInEvery', parseInt(_docRes?.RemindOnceInEvery));
|
||||
}
|
||||
|
||||
if (_docRes?.Placeholders?.length > 0) {
|
||||
if (_docRes?.IsSignyourself) {
|
||||
const placeHolders = {
|
||||
signerObjId: '',
|
||||
signerPtr: {},
|
||||
Id: randomId(),
|
||||
blockColor: '#93a3db',
|
||||
Role: 'Role 1',
|
||||
email: '',
|
||||
placeHolder: _docRes?.Placeholders,
|
||||
};
|
||||
templateCls.set('Placeholders', [placeHolders]);
|
||||
} else {
|
||||
const placeHolders = _docRes?.Placeholders?.map((x, i) => {
|
||||
const email = x.email ? { email: '' } : {};
|
||||
return { ...x, signerObjId: '', signerPtr: {}, Role: 'Role ' + (i + 1), ...email };
|
||||
});
|
||||
templateCls.set('Placeholders', placeHolders);
|
||||
}
|
||||
}
|
||||
if (_docRes?.SignatureType?.length > 0) {
|
||||
templateCls.set('SignatureType', _docRes?.SignatureType);
|
||||
}
|
||||
if (_docRes?.Bcc?.length > 0) {
|
||||
templateCls.set('Bcc', _docRes?.Bcc);
|
||||
}
|
||||
const res = await templateCls.save(null, { useMasterKey: true });
|
||||
return res;
|
||||
} else {
|
||||
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'document not found.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Err in save as template', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
export default async function savecontact(request) {
|
||||
const name = request.params.name;
|
||||
const phone = request.params.phone;
|
||||
const email = request.params.email;
|
||||
const requestemail = request.params?.email;
|
||||
const email = requestemail?.toLowerCase()?.replace(/\s/g, '');
|
||||
const tenantId = request.params.tenantId;
|
||||
|
||||
if (request.user) {
|
||||
|
||||
@@ -35,7 +35,8 @@ const makeEmail = async (
|
||||
bcc,
|
||||
filename,
|
||||
certificatePath,
|
||||
replyto
|
||||
replyto,
|
||||
testPdf
|
||||
) => {
|
||||
const publicUrl = new URL(process.env.SERVER_URL);
|
||||
const htmlContent = html;
|
||||
@@ -46,7 +47,7 @@ const makeEmail = async (
|
||||
let str;
|
||||
if (url) {
|
||||
let attachments;
|
||||
let Pdf = fs.createWriteStream('test.pdf');
|
||||
let Pdf = fs.createWriteStream(testPdf);
|
||||
const writeToLocalDisk = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const isSecure =
|
||||
@@ -161,6 +162,8 @@ export default async function sendMailGmailProvider(_extRes, template) {
|
||||
// Construct email message
|
||||
const from = sender || _extRes.Email || 'me';
|
||||
const to = receiver;
|
||||
const randomNumber = Math.floor(Math.random() * 5000);
|
||||
const testPdf = `test_${randomNumber}.pdf`;
|
||||
const email = await makeEmail(
|
||||
to,
|
||||
from,
|
||||
@@ -171,7 +174,8 @@ export default async function sendMailGmailProvider(_extRes, template) {
|
||||
bcc,
|
||||
filename,
|
||||
certificatePath,
|
||||
replyto
|
||||
replyto,
|
||||
testPdf
|
||||
);
|
||||
// Update Gmail client with new access token
|
||||
const newGmail = createGmailClient(access_token);
|
||||
@@ -189,6 +193,13 @@ export default async function sendMailGmailProvider(_extRes, template) {
|
||||
console.log('Err in unlink certificate sendmailgmail provider');
|
||||
}
|
||||
}
|
||||
if (fs.existsSync(testPdf)) {
|
||||
try {
|
||||
fs.unlinkSync(testPdf);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink pdf sendmailv3');
|
||||
}
|
||||
}
|
||||
return { code: 200, message: 'Email sent successfully' };
|
||||
} catch (error) {
|
||||
console.error('Error sending email:', error);
|
||||
|
||||
@@ -29,113 +29,99 @@ async function sendMailProvider(req, plan, monthchange) {
|
||||
}
|
||||
}
|
||||
if (req.params.url) {
|
||||
let Pdf = fs.createWriteStream('test.pdf');
|
||||
const writeToLocalDisk = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const isSecure =
|
||||
new URL(req.params.url)?.protocol === 'https:' &&
|
||||
new URL(req.params.url)?.hostname !== 'localhost';
|
||||
if (isSecure) {
|
||||
https
|
||||
.get(req.params.url, async function (response) {
|
||||
response.pipe(Pdf);
|
||||
response.on('end', () => resolve('success'));
|
||||
})
|
||||
.on('error', e => {
|
||||
console.error(`error: ${e.message}`);
|
||||
resolve('error');
|
||||
});
|
||||
} else {
|
||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // Disable SSL validation
|
||||
axios
|
||||
.get(req.params.url, { responseType: 'stream', httpsAgent })
|
||||
.then(response => {
|
||||
response.data.pipe(Pdf);
|
||||
Pdf.on('finish', () => resolve('success'));
|
||||
Pdf.on('error', () => resolve('error'));
|
||||
})
|
||||
.catch(e => {
|
||||
console.log('error', e.message);
|
||||
resolve('error');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
// `writeToLocalDisk` is used to create pdf file from doc url
|
||||
const ress = await writeToLocalDisk();
|
||||
if (ress) {
|
||||
function readTolocal() {
|
||||
const randomNumber = Math.floor(Math.random() * 5000);
|
||||
const testPdf = `test_${randomNumber}.pdf`;
|
||||
try {
|
||||
let Pdf = fs.createWriteStream(testPdf);
|
||||
const writeToLocalDisk = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
let PdfBuffer = fs.readFileSync(Pdf.path);
|
||||
resolve(PdfBuffer);
|
||||
}, 100);
|
||||
const isSecure =
|
||||
new URL(req.params.url)?.protocol === 'https:' &&
|
||||
new URL(req.params.url)?.hostname !== 'localhost';
|
||||
if (isSecure) {
|
||||
https
|
||||
.get(req.params.url, async function (response) {
|
||||
response.pipe(Pdf);
|
||||
response.on('end', () => resolve('success'));
|
||||
})
|
||||
.on('error', e => {
|
||||
console.error(`error: ${e.message}`);
|
||||
resolve('error');
|
||||
});
|
||||
} else {
|
||||
const httpsAgent = new https.Agent({ rejectUnauthorized: false }); // Disable SSL validation
|
||||
axios
|
||||
.get(req.params.url, { responseType: 'stream', httpsAgent })
|
||||
.then(response => {
|
||||
response.data.pipe(Pdf);
|
||||
Pdf.on('finish', () => resolve('success'));
|
||||
Pdf.on('error', () => resolve('error'));
|
||||
})
|
||||
.catch(e => {
|
||||
console.log('error', e.message);
|
||||
resolve('error');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// `PdfBuffer` used to create buffer from pdf file
|
||||
let PdfBuffer = await readTolocal();
|
||||
const pdfName = req.params.pdfName && `${req.params.pdfName}.pdf`;
|
||||
const filename = req.params.filename;
|
||||
const file = {
|
||||
filename: filename || pdfName || 'exported.pdf',
|
||||
content: smtpenable ? PdfBuffer : undefined,
|
||||
data: smtpenable ? undefined : PdfBuffer,
|
||||
};
|
||||
// `writeToLocalDisk` is used to create pdf file from doc url
|
||||
const ress = await writeToLocalDisk();
|
||||
if (ress) {
|
||||
function readTolocal() {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
let PdfBuffer = fs.readFileSync(Pdf.path);
|
||||
resolve(PdfBuffer);
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
// `PdfBuffer` used to create buffer from pdf file
|
||||
let PdfBuffer = await readTolocal();
|
||||
const pdfName = req.params.pdfName && `${req.params.pdfName}.pdf`;
|
||||
const filename = req.params.filename;
|
||||
const file = {
|
||||
filename: filename || pdfName || 'exported.pdf',
|
||||
content: smtpenable ? PdfBuffer : undefined,
|
||||
data: smtpenable ? undefined : PdfBuffer,
|
||||
};
|
||||
|
||||
let attachment;
|
||||
const certificatePath = req.params.certificatePath || `./exports/certificate.pdf`;
|
||||
if (fs.existsSync(certificatePath)) {
|
||||
try {
|
||||
// `certificateBuffer` used to create buffer from pdf file
|
||||
const certificateBuffer = fs.readFileSync(certificatePath);
|
||||
const certificate = {
|
||||
filename: 'certificate.pdf',
|
||||
content: smtpenable ? certificateBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
|
||||
data: smtpenable ? undefined : certificateBuffer,
|
||||
};
|
||||
attachment = [file, certificate];
|
||||
} catch (err) {
|
||||
let attachment;
|
||||
const certificatePath = req.params.certificatePath || `./exports/certificate.pdf`;
|
||||
if (fs.existsSync(certificatePath)) {
|
||||
try {
|
||||
// `certificateBuffer` used to create buffer from pdf file
|
||||
const certificateBuffer = fs.readFileSync(certificatePath);
|
||||
const certificate = {
|
||||
filename: 'certificate.pdf',
|
||||
content: smtpenable ? certificateBuffer : undefined, //fs.readFileSync('./exports/exported_file_1223.pdf'),
|
||||
data: smtpenable ? undefined : certificateBuffer,
|
||||
};
|
||||
attachment = [file, certificate];
|
||||
} catch (err) {
|
||||
attachment = [file];
|
||||
console.log('Err in read certificate sendmailv3', err);
|
||||
}
|
||||
} else {
|
||||
attachment = [file];
|
||||
console.log('Err in read certificate sendmailv3', err);
|
||||
}
|
||||
} else {
|
||||
attachment = [file];
|
||||
}
|
||||
const from = req.params.from || '';
|
||||
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
|
||||
const replyto = req.params?.replyto || '';
|
||||
const messageParams = {
|
||||
from: from + ' <' + mailsender + '>',
|
||||
to: req.params.recipient,
|
||||
subject: req.params.subject,
|
||||
text: req.params.text || 'mail',
|
||||
html: req.params.html || '',
|
||||
attachments: smtpenable ? attachment : undefined,
|
||||
attachment: smtpenable ? undefined : attachment,
|
||||
bcc: req.params.bcc ? req.params.bcc : undefined,
|
||||
replyTo: replyto ? replyto : undefined,
|
||||
};
|
||||
if (transporterSMTP) {
|
||||
const res = await transporterSMTP.sendMail(messageParams);
|
||||
console.log('smtp transporter res: ', res?.response);
|
||||
if (!res.err) {
|
||||
if (req.params?.extUserId) {
|
||||
await updateMailCount(req.params.extUserId, plan, monthchange);
|
||||
}
|
||||
if (fs.existsSync(certificatePath)) {
|
||||
try {
|
||||
fs.unlinkSync(certificatePath);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink certificate sendmailv3');
|
||||
}
|
||||
}
|
||||
return { status: 'success' };
|
||||
}
|
||||
} else {
|
||||
if (mailgunApiKey) {
|
||||
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
|
||||
console.log('mailgun res: ', res?.status);
|
||||
if (res.status === 200) {
|
||||
const from = req.params.from || '';
|
||||
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
|
||||
const replyto = req.params?.replyto || '';
|
||||
const messageParams = {
|
||||
from: from + ' <' + mailsender + '>',
|
||||
to: req.params.recipient,
|
||||
subject: req.params.subject,
|
||||
text: req.params.text || 'mail',
|
||||
html: req.params.html || '',
|
||||
attachments: smtpenable ? attachment : undefined,
|
||||
attachment: smtpenable ? undefined : attachment,
|
||||
bcc: req.params.bcc ? req.params.bcc : undefined,
|
||||
replyTo: replyto ? replyto : undefined,
|
||||
};
|
||||
if (transporterSMTP) {
|
||||
const res = await transporterSMTP.sendMail(messageParams);
|
||||
console.log('smtp transporter res: ', res?.response);
|
||||
if (!res.err) {
|
||||
if (req.params?.extUserId) {
|
||||
await updateMailCount(req.params.extUserId, plan, monthchange);
|
||||
}
|
||||
@@ -146,19 +132,70 @@ async function sendMailProvider(req, plan, monthchange) {
|
||||
console.log('Err in unlink certificate sendmailv3');
|
||||
}
|
||||
}
|
||||
if (fs.existsSync(testPdf)) {
|
||||
try {
|
||||
fs.unlinkSync(testPdf);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink pdf sendmailv3');
|
||||
}
|
||||
}
|
||||
return { status: 'success' };
|
||||
}
|
||||
} else {
|
||||
if (fs.existsSync(certificatePath)) {
|
||||
try {
|
||||
fs.unlinkSync(certificatePath);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink certificate sendmailv3');
|
||||
if (mailgunApiKey) {
|
||||
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
|
||||
console.log('mailgun res: ', res?.status);
|
||||
if (res.status === 200) {
|
||||
if (req.params?.extUserId) {
|
||||
await updateMailCount(req.params.extUserId, plan, monthchange);
|
||||
}
|
||||
if (fs.existsSync(certificatePath)) {
|
||||
try {
|
||||
fs.unlinkSync(certificatePath);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink certificate sendmailv3');
|
||||
}
|
||||
}
|
||||
if (fs.existsSync(testPdf)) {
|
||||
try {
|
||||
fs.unlinkSync(testPdf);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink pdf sendmailv3');
|
||||
}
|
||||
}
|
||||
return { status: 'success' };
|
||||
}
|
||||
} else {
|
||||
if (fs.existsSync(certificatePath)) {
|
||||
try {
|
||||
fs.unlinkSync(certificatePath);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink certificate sendmailv3');
|
||||
}
|
||||
}
|
||||
if (fs.existsSync(testPdf)) {
|
||||
try {
|
||||
fs.unlinkSync(testPdf);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink pdf sendmailv3');
|
||||
}
|
||||
}
|
||||
return { status: 'error' };
|
||||
}
|
||||
return { status: 'error' };
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('err in sendmailv3', err);
|
||||
if (fs.existsSync(testPdf)) {
|
||||
try {
|
||||
fs.unlinkSync(testPdf);
|
||||
} catch (err) {
|
||||
console.log('Err in unlink pdf sendmailv3');
|
||||
}
|
||||
}
|
||||
if (err) {
|
||||
return { status: 'error' };
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const from = req.params.from || '';
|
||||
@@ -207,8 +244,8 @@ async function sendMailProvider(req, plan, monthchange) {
|
||||
}
|
||||
|
||||
async function sendmailv3(req) {
|
||||
const nonCustomMail = await sendMailProvider(req);
|
||||
return nonCustomMail;
|
||||
const nonCustomMail = await sendMailProvider(req);
|
||||
return nonCustomMail;
|
||||
}
|
||||
|
||||
export default sendmailv3;
|
||||
|
||||
@@ -42,6 +42,18 @@ export default async function updatePreferences(request) {
|
||||
newOrg.set('SignatureType', SignatureType);
|
||||
}
|
||||
}
|
||||
if (request.params.SendinOrder !== undefined) {
|
||||
newOrg.set('SendinOrder', request.params.SendinOrder);
|
||||
}
|
||||
if (request.params.IsTourEnabled !== undefined) {
|
||||
newOrg.set('IsTourEnabled', request.params.IsTourEnabled);
|
||||
}
|
||||
if (request.params.DateFormat) {
|
||||
newOrg.set('DateFormat', request.params.DateFormat);
|
||||
}
|
||||
if (request.params.Is12HourTime !== undefined) {
|
||||
newOrg.set('Is12HourTime', request.params.Is12HourTime);
|
||||
}
|
||||
const updateUserRes = await newOrg.save(null, { useMasterKey: true });
|
||||
if (updateUserRes) {
|
||||
const _updateUserRes = JSON.parse(JSON.stringify(updateUserRes));
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
export default async function updateTenant(request) {
|
||||
const { tenantId, details } = request.params;
|
||||
|
||||
if (!tenantId || !details) {
|
||||
throw new Parse.Error(400, 'Missing tenantId or details.');
|
||||
}
|
||||
|
||||
if (!request.user) {
|
||||
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'unauthorized');
|
||||
}
|
||||
try {
|
||||
const tenant = new Parse.Object('partners_Tenant');
|
||||
tenant.id = tenantId;
|
||||
// Update tenant details
|
||||
Object.keys(details).forEach(key => {
|
||||
tenant.set(key, details?.[key]);
|
||||
});
|
||||
|
||||
const tenantRes = await tenant.save(null, { useMasterKey: true });
|
||||
if (tenantRes) {
|
||||
const res = JSON.parse(JSON.stringify(tenantRes));
|
||||
return res;
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import {
|
||||
cloudServerUrl,
|
||||
} from '../../Utils.js';
|
||||
import { cloudServerUrl } from '../../Utils.js';
|
||||
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
|
||||
const APPID = process.env.APP_ID;
|
||||
const masterKEY = process.env.MASTER_KEY;
|
||||
@@ -32,7 +30,7 @@ async function saveUser(userDetails) {
|
||||
const user = new Parse.User();
|
||||
user.set('username', userDetails.email);
|
||||
user.set('password', userDetails.password);
|
||||
user.set('email', userDetails.email);
|
||||
user.set('email', userDetails?.email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
if (userDetails?.phone) {
|
||||
user.set('phone', userDetails.phone);
|
||||
}
|
||||
@@ -73,7 +71,7 @@ export default async function usersignup(request) {
|
||||
partnerQuery.set('ContactNumber', userDetails.phone);
|
||||
}
|
||||
partnerQuery.set('TenantName', userDetails.company);
|
||||
partnerQuery.set('EmailAddress', userDetails.email);
|
||||
partnerQuery.set('EmailAddress', userDetails?.email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
partnerQuery.set('IsActive', true);
|
||||
partnerQuery.set('CreatedBy', {
|
||||
__type: 'Pointer',
|
||||
@@ -105,7 +103,7 @@ export default async function usersignup(request) {
|
||||
objectId: user.id,
|
||||
});
|
||||
newObj.set('UserRole', userDetails.role);
|
||||
newObj.set('Email', userDetails.email);
|
||||
newObj.set('Email', userDetails?.email?.toLowerCase()?.replace(/\s/g, ''));
|
||||
newObj.set('Name', userDetails.name);
|
||||
if (userDetails?.phone) {
|
||||
newObj.set('Phone', userDetails?.phone);
|
||||
@@ -131,4 +129,3 @@ export default async function usersignup(request) {
|
||||
console.log('Err ', err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
*
|
||||
* @param {Parse} Parse
|
||||
*/
|
||||
exports.up = async Parse => {
|
||||
const className = 'contracts_Document';
|
||||
const schema = new Parse.Schema(className);
|
||||
schema.addPointer('TemplateId', 'contracts_Template');
|
||||
return schema.update();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Parse} Parse
|
||||
*/
|
||||
exports.down = async Parse => {
|
||||
const className = 'contracts_Document';
|
||||
const schema = new Parse.Schema(className);
|
||||
schema.deleteField('TemplateId');
|
||||
return schema.update();
|
||||
};
|
||||
Binary file not shown.
@@ -15,7 +15,6 @@ import AWS from 'aws-sdk';
|
||||
import { app as customRoute } from './cloud/customRoute/customApp.js';
|
||||
import { exec } from 'child_process';
|
||||
import { createTransport } from 'nodemailer';
|
||||
import { PostHog } from 'posthog-node';
|
||||
import { appName, cloudServerUrl, smtpenable, smtpsecure, useLocal } from './Utils.js';
|
||||
import { SSOAuth } from './auth/authadapter.js';
|
||||
import createContactIndex from './migrationdb/createContactIndex.js';
|
||||
@@ -161,6 +160,8 @@ app.use(express.json({ limit: '50mb' }));
|
||||
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
app.use(function (req, res, next) {
|
||||
req.headers['x-real-ip'] = getUserIP(req);
|
||||
const publicUrl = req?.protocol + '://' + req?.get('host');
|
||||
req.headers['public_url'] = publicUrl; // process.env.PUBLIC_URL
|
||||
next();
|
||||
});
|
||||
function getUserIP(request) {
|
||||
@@ -175,16 +176,6 @@ function getUserIP(request) {
|
||||
return request.socket.remoteAddress;
|
||||
}
|
||||
}
|
||||
app.use(function (req, res, next) {
|
||||
const ph_project_api_key = process.env.PH_PROJECT_API_KEY;
|
||||
try {
|
||||
req.posthog = new PostHog(ph_project_api_key);
|
||||
} catch (err) {
|
||||
// console.log('Err', err);
|
||||
req.posthog = '';
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(async function (req, res, next) {
|
||||
const isFilePath = req.path.includes('files') || false;
|
||||
@@ -225,7 +216,6 @@ if (!process.env.TESTING) {
|
||||
// Mount your custom express app
|
||||
app.use('/', customRoute);
|
||||
|
||||
|
||||
// Parse Server plays nicely with the rest of your web routes
|
||||
app.get('/', function (req, res) {
|
||||
res.status(200).send('opensign-server is running !!!');
|
||||
|
||||
Generated
+2352
-2099
File diff suppressed because it is too large
Load Diff
@@ -18,26 +18,26 @@
|
||||
"watch": "nodemon index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.741.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.741.0",
|
||||
"@aws-sdk/client-s3": "^3.775.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.775.0",
|
||||
"@parse/fs-files-adapter": "^3.0.0",
|
||||
"@parse/s3-files-adapter": "^4.1.0",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@signpdf/placeholder-pdf-lib": "^3.2.4",
|
||||
"@signpdf/signer-p12": "^3.2.4",
|
||||
"@signpdf/signpdf": "^3.2.4",
|
||||
"@signpdf/signpdf": "^3.2.5",
|
||||
"aws-sdk": "^2.1692.0",
|
||||
"axios": "^1.7.9",
|
||||
"axios": "^1.8.4",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.1",
|
||||
"form-data": "^4.0.1",
|
||||
"form-data": "^4.0.2",
|
||||
"generate-api-key": "^1.0.2",
|
||||
"googleapis": "^144.0.0",
|
||||
"googleapis": "^148.0.0",
|
||||
"mailgun.js": "^11.1.0",
|
||||
"mongodb": "^6.13.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mongodb": "^6.15.0",
|
||||
"multer": "^1.4.5-lts.2",
|
||||
"multer-s3": "^3.0.1",
|
||||
"node-forge": "^1.3.1",
|
||||
"nodemailer": "^6.10.0",
|
||||
@@ -46,18 +46,18 @@
|
||||
"parse-server": "^7.4.0",
|
||||
"parse-server-api-mail-adapter": "^4.1.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"posthog-node": "^4.5.0",
|
||||
"ws": "^8.18.0"
|
||||
"posthog-node": "^4.10.2",
|
||||
"ws": "^8.18.1"
|
||||
},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.26.5",
|
||||
"eslint": "^9.20.0",
|
||||
"@babel/eslint-parser": "^7.27.0",
|
||||
"eslint": "^9.23.0",
|
||||
"jasmine": "^5.6.0",
|
||||
"mongodb-runner": "^5.7.1",
|
||||
"mongodb-runner": "^5.8.0",
|
||||
"nodemon": "^3.1.9",
|
||||
"nyc": "^17.1.0",
|
||||
"prettier": "^3.5.0"
|
||||
"prettier": "^3.5.3"
|
||||
},
|
||||
"overrides": {
|
||||
"ws": "$ws",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+2
-1
@@ -11,7 +11,8 @@ services:
|
||||
env_file: .env.prod
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- SERVER_URL=${HOST_URL:-https://localhost:3001}/app
|
||||
- SERVER_URL=${HOST_URL:-https://localhost:3001}/api/app
|
||||
- PUBLIC_URL=${HOST_URL:-https://localhost:3001}
|
||||
networks:
|
||||
- app-network
|
||||
mongo:
|
||||
|
||||
Reference in New Issue
Block a user