mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-09-03 16:28:56 +02:00
Compare commits
@@ -73,12 +73,28 @@ Welcome to OpenSign, the premier open source docusign alternative - document e-s
|
||||
|
||||
---
|
||||
|
||||
### Installation
|
||||
### Deploy
|
||||
|
||||
Note: The default MongoDB instance used in deployment is not persistant and will be cleared on every restart. To retain your data, configure and supply your own MongoDB connection URL.
|
||||
|
||||
#### DigitalOcean
|
||||
[](https://cloud.digitalocean.com/apps/new?repo=https://github.com/OpenSignLabs/Deploy-OpenSign-to-Digital-Ocean/tree/main&refcode=30db1c901ab0)
|
||||
|
||||
#### Docker
|
||||
The simplest way to install OpenSign on your own server is using official docker images by running the following command -
|
||||
|
||||
**Command for linux/MacOS**
|
||||
```
|
||||
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
|
||||
```
|
||||
**Command for Windows (Powershell)**
|
||||
```
|
||||
$env:HOST_URL="https://opensign.yourdomain.com"; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml -OutFile docker-compose.yml; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile -OutFile Caddyfile; Invoke-WebRequest -Uri https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev -OutFile .env.local_dev; Rename-Item -Path .env.local_dev -NewName .env.prod; docker compose up --force-recreate
|
||||
```
|
||||
**Command for Windows (CMD/Terminal)**
|
||||
```
|
||||
set HOST_URL=https://opensign.yourdomain.com && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/docker-compose.yml && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/Caddyfile && curl -O https://raw.githubusercontent.com/OpenSignLabs/OpenSign/main/.env.local_dev && rename .env.local_dev .env.prod && docker compose up --force-recreate
|
||||
```
|
||||
Make sure that you have `Docker` and `git` installed before you run this command -
|
||||
|
||||
Please refer to the [Installation Guide](https://docs.opensignlabs.com/docs/self-host/docker/run-locally/) for detailed instructions on how to install OpenSign on your system.
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:18
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package.json and package-lock.json first to leverage Docker cache
|
||||
COPY ./package*.json ./
|
||||
|
||||
# Install application dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy the current directory contents into the container
|
||||
COPY ./ .
|
||||
COPY ./.husky .
|
||||
|
||||
# Make port 3000 available to the world outside this container
|
||||
EXPOSE 3000
|
||||
|
||||
# Define environment variables if needed
|
||||
# ENV NODE_ENV production
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT npm run start
|
||||
|
||||
@@ -13,6 +13,10 @@ RUN npm install
|
||||
# Copy the current directory contents into the container
|
||||
COPY apps/OpenSign/ .
|
||||
COPY apps/OpenSign/.husky .
|
||||
COPY apps/OpenSign/entrypoint.sh .
|
||||
|
||||
# make the entrypoint.sh file executable
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
# Define environment variables if needed
|
||||
ENV NODE_ENV=production
|
||||
@@ -20,8 +24,13 @@ ENV GENERATE_SOURCEMAP=false
|
||||
# build
|
||||
RUN npm run build
|
||||
|
||||
# Inject env.js loader into index.html
|
||||
RUN sed -i '/<head>/a\<script src="/env.js"></script>' build/index.html
|
||||
|
||||
# Make port 3000 available to the world outside this container
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
|
||||
# Run the application
|
||||
CMD ["npm", "start"]
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
|
||||
ENV_FILE=./build/env.js
|
||||
DOTENV_FILE=./.env.prod # ✅ use .env.prod
|
||||
|
||||
echo "Generating runtime env file at $ENV_FILE..."
|
||||
|
||||
echo "window.RUNTIME_ENV = {" > $ENV_FILE
|
||||
|
||||
# List of keys to include
|
||||
RUNTIME_KEYS="REACT_APP_SERVERURL"
|
||||
|
||||
for key in $RUNTIME_KEYS; do
|
||||
# First check docker env (-e), fallback to .env file
|
||||
value=$(printenv "$key")
|
||||
|
||||
if [ -z "$value" ] && [ -f "$DOTENV_FILE" ]; then
|
||||
# fallback: read from .env
|
||||
value=$(grep "^$key=" "$DOTENV_FILE" | cut -d '=' -f2- | tr -d '\r\n' | sed 's/"/\\"/g')
|
||||
else
|
||||
value=$(echo "$value" | sed 's/"/\\"/g')
|
||||
fi
|
||||
|
||||
echo " $key: \"$value\"," >> $ENV_FILE
|
||||
done
|
||||
|
||||
echo "};" >> $ENV_FILE
|
||||
|
||||
exec "$@"
|
||||
Generated
+5115
-17524
File diff suppressed because it is too large
Load Diff
+23
-39
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "open_sign",
|
||||
"version": "0.1.0",
|
||||
"version": "2.21.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^0.8.2",
|
||||
"@imgly/background-removal": "^1.6.0",
|
||||
"@lottiefiles/dotlottie-react": "^0.13.5",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
"@radix-ui/themes": "^3.1.6",
|
||||
"@reduxjs/toolkit": "^2.7.0",
|
||||
"@radix-ui/themes": "^3.2.1",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"axios": "^1.9.0",
|
||||
"css-minimizer-webpack-plugin": "^7.0.2",
|
||||
"date-fns-tz": "^3.2.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"i18next": "^23.16.8",
|
||||
@@ -18,60 +18,52 @@
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"moment": "^2.30.1",
|
||||
"nth-check": "^2.1.1",
|
||||
"parse": "^6.1.1",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pkijs": "^3.0.8",
|
||||
"print-js": "^1.6.0",
|
||||
"radix-ui": "^1.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.10.9",
|
||||
"prismjs": "^1.30.0",
|
||||
"radix-ui": "^1.4.2",
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-cookie": "^7.2.2",
|
||||
"react-datepicker": "^7.6.0",
|
||||
"react-datepicker": "^8.3.0",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dnd-multi-backend": "^9.0.0",
|
||||
"react-dnd-touch-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-gtm-module": "^2.0.11",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.5.0",
|
||||
"react-i18next": "^15.5.1",
|
||||
"react-konva": "^18.2.10",
|
||||
"react-pdf": "^9.2.1",
|
||||
"react-quill-new": "^3.4.6",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-rnd": "^10.5.2",
|
||||
"react-router": "^7.5.3",
|
||||
"react-scripts": "^5.0.1",
|
||||
"react-router": "^7.6.0",
|
||||
"react-scrollbars-custom": "^4.1.1",
|
||||
"react-select": "^5.10.1",
|
||||
"react-signature-canvas": "^1.0.7",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"react-signature-canvas": "^1.1.0-alpha.2",
|
||||
"react-timezone-select": "^3.2.8",
|
||||
"react-tooltip": "^5.28.1",
|
||||
"react-web-share": "^2.0.2",
|
||||
"reactour": "^1.19.4",
|
||||
"redux": "^5.0.1",
|
||||
"redux-thunk": "^3.1.0",
|
||||
"regex-parser": "^2.3.1",
|
||||
"serve": "^14.2.4",
|
||||
"styled-components": "^5.3.0",
|
||||
"web-vitals": "^4.2.4",
|
||||
"ws": "^8.18.2",
|
||||
"styled-components": "^5.3.11",
|
||||
"web-vitals": "^5.0.1",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
||||
},
|
||||
"scripts": {
|
||||
"build-template-win": "vite build --config vite.public-template.config.js",
|
||||
"build-template": "NODE_OPTIONS=\"--max-old-space-size=8192\" vite build --config vite.public-template.config.js",
|
||||
"build": "npm run version && react-scripts build",
|
||||
"build-template-watch": "NODE_OPTIONS=\"--max-old-space-size=8192\" vite build --config vite.public-template.config.js --watch",
|
||||
"build": "npm run version && NODE_OPTIONS=\"--max-old-space-size=8192\" vite build",
|
||||
"start-dev": "vite",
|
||||
"dev": "vite",
|
||||
"preview": "vite preview",
|
||||
"start": "serve -s build",
|
||||
"version": "curl -s https://api.github.com/repos/opensignlabs/opensign/releases/latest | grep '\"tag_name\":' | awk -F '\"' '{print $4}' > ./public/version.txt",
|
||||
"version-win": "powershell -Command \"Invoke-RestMethod -Uri 'https://api.github.com/repos/opensignlabs/opensign/releases/latest' | Select-Object -ExpandProperty tag_name | Out-File -FilePath ./public/version.txt\"",
|
||||
"build-win": "npm run version-win && react-scripts build",
|
||||
"build-win": "npm run version-win && vite build",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"release": "standard-version",
|
||||
@@ -102,13 +94,13 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.1",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@babel/preset-react": "^7.27.1",
|
||||
"@babel/runtime-corejs2": "^7.27.1",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@testing-library/user-event": "^14.6.1",
|
||||
"@types/react": "^18.3.21",
|
||||
"@types/react": "^18.3.22",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"@vitejs/plugin-react-swc": "^3.9.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
@@ -118,27 +110,19 @@
|
||||
"css-loader": "^7.1.2",
|
||||
"daisyui": "^4.12.24",
|
||||
"dotenv": "^16.5.0",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"lint-staged": "^15.5.1",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"lint-staged": "^16.0.0",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-quick": "^4.1.1",
|
||||
"rollup-plugin-node-polyfills": "^0.2.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"terser-webpack-plugin": "^5.3.14",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-svgr": "^4.3.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.1.3",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"overrides": {
|
||||
"nth-check": "$nth-check",
|
||||
"ws": "$ws"
|
||||
"vitest": "^3.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || 22"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"header-news": "Neue Funktion: Benutzer des Teams-Plans können jetzt ihre eigenen AWS S3-Buckets für die Dateispeicherung integrieren",
|
||||
"header-news-btn": "Jetzt einrichten",
|
||||
"sandbox-news": "Dies ist eine Sandbox-Umgebung. Bitte nicht für produktive Zwecke verwenden.",
|
||||
"create-account": "Konto erstellen",
|
||||
"login": "Anmelden",
|
||||
"language": "Sprache",
|
||||
@@ -39,8 +40,10 @@
|
||||
"save": "Speichern",
|
||||
"cancel": "Abbrechen",
|
||||
"upgrade-now": "Jetzt upgraden",
|
||||
"contact-now": "Jetzt kontaktieren",
|
||||
"upgrade-to": "Upgrade zu",
|
||||
"plan": "Plan",
|
||||
"subscription-renew-warning": "Ihr Abonnement läuft in {{remainingDays}} Tagen ab. Bitte verlängern Sie Ihr Abonnement.",
|
||||
"subscribe-card-teamplan": "Entfesseln Sie die volle Kraft der Zusammenarbeit! Erstellen Sie unbegrenzt Organisationen, Teams und Hierarchien. Teilen Sie Vorlagen nahtlos zwischen Teams und weisen Sie benutzerdefinierte Benutzerrollen zu. Optimieren Sie Ihren Workflow noch heute!",
|
||||
"subscribe-card-plan": "Entsperren Sie Premium-Funktionen ab nur {{premiumPrice}}/Monat. Genießen Sie eine verbesserte Leistung und zahlen Sie nur {{addonPrice}} pro zusätzlichem Credit nach den enthaltenen Premium-Credits.",
|
||||
"user-name-limit-char": "Um einen Benutzernamen mit weniger als 8 Zeichen zu haben, abonnieren Sie bitte.",
|
||||
@@ -142,6 +145,7 @@
|
||||
"Quick send": "Schnell senden",
|
||||
"Edit": "Bearbeiten",
|
||||
"Share with team": "Mit Team teilen",
|
||||
"Share with user": "Mit Kollegen teilen",
|
||||
"Share": "Teilen",
|
||||
"View": "Ansehen",
|
||||
"option": "Option",
|
||||
@@ -245,6 +249,7 @@
|
||||
"API": "API",
|
||||
"api-token": "API-Token",
|
||||
"regenerate-token": "Live-Token neu generieren",
|
||||
"remove-background": "Hintergrund entfernen",
|
||||
"generate-token": "Live-Token generieren",
|
||||
"view-docs": "Dokumentation ansehen",
|
||||
"generate-token-alert": "Sind Sie sicher, dass Sie das Token neu generieren möchten? Das alte Token wird ablaufen.",
|
||||
@@ -478,6 +483,7 @@
|
||||
"document-alert": "Dokument-Warnung",
|
||||
"owner-subscription-expired": "Das Abonnement des Besitzers ist abgelaufen.",
|
||||
"subscription-expired": "Abonnement abgelaufen",
|
||||
"owner-doesnt-have-paid-plan": "Der Inhaber hat keinen kostenpflichtigen Plan.",
|
||||
"alert-message": "Warnmeldung",
|
||||
"document-decline": "Dokument ablehnen",
|
||||
"decline-alert-1": "Sind Sie sicher, dass Sie dieses Dokument ablehnen möchten?",
|
||||
@@ -670,7 +676,7 @@
|
||||
"public-template-mssg-1": "Um OpenSign in Ihr React- oder Next.js-Projekt zu integrieren, führen Sie einfach den folgenden Befehl aus:",
|
||||
"public-template-mssg-2": "Stellen Sie sicher, dass npm oder yarn in Ihrem Projekt eingerichtet ist. Wenn Sie Yarn verwenden, können Sie npm install durch yarn add @opensign/react ersetzen.",
|
||||
"public-template-mssg-3": "Benötigen Sie weitere Details oder Beispiele?",
|
||||
"public-template-mssg-4": "Besuchen Sie die",
|
||||
"public-template-mssg-4": "Besuchen Sie die ",
|
||||
"public-template-mssg-5": " npm für die neuesten Updates, detaillierte Dokumentationen und Versionshistorie.",
|
||||
"public-template-mssg-6": "Bevor Sie diesen Code-Schnipsel verwenden können, müssen Sie diese Vorlage öffentlich machen.",
|
||||
"public-template-mssg-7": "Bevor Sie einen öffentlichen Link generieren können, müssen Sie diese Vorlage öffentlich machen.",
|
||||
@@ -971,5 +977,50 @@
|
||||
"kiosk-sign": "Kiosk-Unterschrift",
|
||||
"dont-have-access-to-template": "Das template wurde gelöscht oder Sie haben keinen Zugriff. Bitte kontaktieren Sie den Absender.",
|
||||
"kiosk-info": "Kiosk Modus ermöglicht es Ihnen, persönliche Unterschriften schnell und effizient zu erfassen. Ideal für Messen, Veranstaltungen oder Laufkundschaft, bei denen alle Unterzeichner physisch anwesend sind. ",
|
||||
"learn-more": "Mehr erfahren"
|
||||
"learn-more": "Mehr erfahren",
|
||||
"finish-mssg": "Sind Sie sicher, dass Sie das Dokument abschließen möchten?",
|
||||
"review": "Überprüfen",
|
||||
"next-field": "Nächstes Feld",
|
||||
"required-mssg": "{{leftRequiredWidget}} von {{totalWidget}} Feldern übrig",
|
||||
"verify-document-signature": "Dokumentensignatur überprüfen",
|
||||
"select-pdf-document": "PDF-Dokument auswählen",
|
||||
"selected-file": "Ausgewählte Datei",
|
||||
"verify-signature": "Signatur überprüfen",
|
||||
"verification-status": "Überprüfungsstatus",
|
||||
"verification-in-progress": "Überprüfung läuft...",
|
||||
"verification-results-will-appear-here": "Überprüfungsergebnisse werden hier angezeigt",
|
||||
"please-select-pdf": "Bitte wählen Sie eine gültige PDF-Datei aus",
|
||||
"please-select-file-to-verify": "Bitte wählen Sie eine Datei zur Überprüfung aus",
|
||||
"no-signature-found": "Keine Signatur im Dokument gefunden",
|
||||
"error-verifying-pdf": "Fehler beim Überprüfen der PDF",
|
||||
"signature-valid-basic": "Signatur ist gültig",
|
||||
"signature-invalid-basic": "Signatur ist ungültig",
|
||||
"all-signatures-verified-convincing": "Dokument überprüft: Alle Signaturen wurden erfolgreich validiert.",
|
||||
"some-signatures-invalid-basic": "Einige Signaturen sind ungültig",
|
||||
"no-signatures-processed": "Keine Signaturen verarbeitet",
|
||||
"unnamed-signature-field": "Unbenanntes Signaturfeld",
|
||||
"error-processing-signature": "Fehler beim Verarbeiten der Signatur",
|
||||
"signer-info-not-available": "Signaturinformationen nicht verfügbar",
|
||||
"cert-validity-not-checked": "Gültigkeit des Zertifikats nicht geprüft",
|
||||
"valid": "Gültig",
|
||||
"expired-or-not-yet-valid": "Abgelaufen oder noch nicht gültig",
|
||||
"valid-from": "Gültig von",
|
||||
"to": "bis",
|
||||
"signer": "Unterzeichner",
|
||||
"issuer": "Aussteller",
|
||||
"not-available": "Nicht verfügbar",
|
||||
"not-performed": "Nicht durchgeführt",
|
||||
"missing-acrofield-dict": "Fehlendes Acrofield-Wörterbuch",
|
||||
"signature-dictionary-not-found-or-invalid": "Signaturwörterbuch nicht gefunden oder ungültig",
|
||||
"missing-or-invalid-byterange": "Fehlender oder ungültiger ByteRange",
|
||||
"missing-or-invalid-contents": "Fehlender oder ungültiger Inhalt",
|
||||
"missing-signature-contents": "Fehlender Signaturinhalt",
|
||||
"invalid-signature-hex-format": "Ungültiges Signatur-Hex-Format",
|
||||
"unsupported-signature-format-not-signeddata": "Nicht unterstütztes Signaturformat - nicht SignedData",
|
||||
"signer-certificate-not-found": "Unterzeichnerzertifikat nicht gefunden",
|
||||
"no-certificates-in-signature": "Keine Zertifikate in der Signatur",
|
||||
"no-signer-info-in-pkcs7": "Keine Signaturinformationen in PKCS#7",
|
||||
"could-not-parse-signer-info": "Signaturinformationen konnten nicht analysiert werden",
|
||||
"not-calculated": "Nicht berechnet",
|
||||
"not-found-in-signature": "Nicht in Signatur gefunden"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"header-news": "New feature: Protect your account with Two-Factor Authentication (2FA) and enjoy the future of login with Passkeys — no passwords needed.",
|
||||
"header-news-btn": "Setup now",
|
||||
"sandbox-news": "This is a sandbox environment. Please do not use it for production purposes.",
|
||||
"create-account": "Create account",
|
||||
"login": "Login",
|
||||
"language": "Language",
|
||||
@@ -39,8 +40,10 @@
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"upgrade-now": "Upgrade now",
|
||||
"contact-now": "Contact now",
|
||||
"upgrade-to": "Upgrade to",
|
||||
"plan": "Plan",
|
||||
"subscription-renew-warning": "Your subscription will expire in {{remainingDays}} days. Please renew your subscription.",
|
||||
"subscribe-card-teamplan": "Unlock the full power of collaboration! Create unlimited organizations, teams, and hierarchies. Share templates seamlessly across teams and assign custom user roles. Elevate your workflow today!",
|
||||
"subscribe-card-plan": "Unlock premium features starting at just {{premiumPrice}}/month. Enjoy enhanced performance and only {{addonPrice}} per additional credit after your included premium credits.",
|
||||
"user-name-limit-char": "To have a username less than 8 character please subscribe",
|
||||
@@ -142,6 +145,7 @@
|
||||
"Quick send": "Quick send",
|
||||
"Edit": "Edit",
|
||||
"Share with team": "Share with team",
|
||||
"Share with user": "Share with colleague",
|
||||
"Share": "Share",
|
||||
"View": "View",
|
||||
"option": "Option",
|
||||
@@ -245,6 +249,7 @@
|
||||
"API": "API",
|
||||
"api-token": "API token",
|
||||
"regenerate-token": "Regenerate live token",
|
||||
"remove-background": "Remove Background",
|
||||
"generate-token": "Generate live token",
|
||||
"view-docs": "View docs",
|
||||
"generate-token-alert": "Are you sure you want to regenerate token it will expire old token?",
|
||||
@@ -478,6 +483,7 @@
|
||||
"document-alert": "Document alert",
|
||||
"owner-subscription-expired": "Owner's subscription has expired.",
|
||||
"subscription-expired": "Subscription Expired",
|
||||
"owner-doesnt-have-paid-plan": "Owner doesn't have paid plan.",
|
||||
"alert-message": "Alert message",
|
||||
"document-decline": "Document decline",
|
||||
"decline-alert-1": "Are you sure want to decline this document ?",
|
||||
@@ -670,7 +676,7 @@
|
||||
"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-3": "Need more details or examples?",
|
||||
"public-template-mssg-4": "Visit the",
|
||||
"public-template-mssg-4": "Visit the ",
|
||||
"public-template-mssg-5": " npm for the latest updates, detailed documentation, and version history.",
|
||||
"public-template-mssg-6": "Before you can use this code snippet, you must make this template public.",
|
||||
"public-template-mssg-7": "Before you can generate a public link you must make this template public.",
|
||||
@@ -971,5 +977,50 @@
|
||||
"kiosk-sign": "Kiosk Sign",
|
||||
"dont-have-access-to-template": "The template has been deleted or you don't have access. Please contact the sender.",
|
||||
"kiosk-info": "Kiosk Mode lets you collect in-person signatures quickly and efficiently. Ideal for trade shows, events, or walk-in scenarios where all signers are physically present. ",
|
||||
"learn-more": "Learn more"
|
||||
"learn-more": "Learn more",
|
||||
"finish-mssg":" Are you sure you want to finish the document ?",
|
||||
"review":"Review",
|
||||
"next-field":"Next Field",
|
||||
"required-mssg":"{{leftRequiredWidget}} of {{totalWidget}} fields left",
|
||||
"verify-document-signature": "Verify Document Signature",
|
||||
"select-pdf-document": "Select PDF Document",
|
||||
"selected-file": "Selected file",
|
||||
"verify-signature": "Verify Signature",
|
||||
"verification-status": "Verification Status",
|
||||
"verification-in-progress": "Verification in progress...",
|
||||
"verification-results-will-appear-here": "Verification results will appear here",
|
||||
"please-select-pdf": "Please select a valid PDF file",
|
||||
"please-select-file-to-verify": "Please select a file to verify",
|
||||
"no-signature-found": "No signature found in the document",
|
||||
"error-verifying-pdf": "Error verifying PDF",
|
||||
"signature-valid-basic": "Signature is valid",
|
||||
"signature-invalid-basic": "Signature is invalid",
|
||||
"all-signatures-verified-convincing": "Document Verified: All signatures have been successfully validated.",
|
||||
"some-signatures-invalid-basic": "Some signatures are invalid",
|
||||
"no-signatures-processed": "No signatures were processed",
|
||||
"unnamed-signature-field": "Unnamed Signature Field",
|
||||
"error-processing-signature": "Error processing signature",
|
||||
"signer-info-not-available": "Signer information not available",
|
||||
"cert-validity-not-checked": "Certificate validity not checked",
|
||||
"valid": "Valid",
|
||||
"expired-or-not-yet-valid": "Expired or not yet valid",
|
||||
"valid-from": "Valid from",
|
||||
"to": "to",
|
||||
"signer": "Signer",
|
||||
"issuer": "Issuer",
|
||||
"not-available": "Not available",
|
||||
"not-performed": "Not performed",
|
||||
"missing-acrofield-dict": "Missing acrofield dictionary",
|
||||
"signature-dictionary-not-found-or-invalid": "Signature dictionary not found or invalid",
|
||||
"missing-or-invalid-byterange": "Missing or invalid ByteRange",
|
||||
"missing-or-invalid-contents": "Missing or invalid Contents",
|
||||
"missing-signature-contents": "Missing signature contents",
|
||||
"invalid-signature-hex-format": "Invalid signature hex format",
|
||||
"unsupported-signature-format-not-signeddata": "Unsupported signature format - not SignedData",
|
||||
"signer-certificate-not-found": "Signer certificate not found",
|
||||
"no-certificates-in-signature": "No certificates in signature",
|
||||
"no-signer-info-in-pkcs7": "No signer info in PKCS#7",
|
||||
"could-not-parse-signer-info": "Could not parse signer info",
|
||||
"not-calculated": "Not calculated",
|
||||
"not-found-in-signature": "Not found in signature"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"header-news": "Nueva característica: los usuarios del plan Teams ahora pueden integrar sus propios depósitos de AWS S3 para el almacenamiento de archivos",
|
||||
"header-news-btn": "Configurar ahora",
|
||||
"sandbox-news": "Este es un entorno sandbox. Por favor, no lo utilice con fines de producción.",
|
||||
"create-account": "Crear cuenta",
|
||||
"login": "Iniciar sesión",
|
||||
"language": "Idioma",
|
||||
@@ -39,8 +40,10 @@
|
||||
"save": "Guardar",
|
||||
"cancel": "Cancelar",
|
||||
"upgrade-now": "Mejorar ahora",
|
||||
"contact-now": "Contactar ahora",
|
||||
"upgrade-to": "Mejorar a",
|
||||
"plan": "Plan",
|
||||
"subscription-renew-warning": "Su suscripción vencerá en {{remainingDays}} días. Por favor, renueve su suscripción.",
|
||||
"subscribe-card-teamplan": "¡Libera todo el poder de la colaboración! Crea organizaciones, equipos y jerarquías ilimitadas. Comparte plantillas sin problemas entre equipos y asigna funciones de usuario personalizadas. ¡Mejora tu flujo de trabajo hoy mismo!",
|
||||
"subscribe-card-plan": "Desbloquea funciones premium desde solo {{premiumPrice}}/mes. Disfruta de un rendimiento mejorado y solo {{addonPrice}} por crédito adicional después de tus créditos premium incluidos.",
|
||||
"user-name-limit-char": "Para tener un nombre de usuario menor a 8 caracteres por favor suscríbete",
|
||||
@@ -142,6 +145,7 @@
|
||||
"Quick send": "Envío rápido",
|
||||
"Edit": "Editar",
|
||||
"Share with team": "Compartir con el equipo",
|
||||
"Share with user": "Compartir con un colega",
|
||||
"Share": "Compartir",
|
||||
"View": "Ver",
|
||||
"option": "Opción",
|
||||
@@ -246,6 +250,7 @@
|
||||
"API": "API",
|
||||
"api-token": "Token API",
|
||||
"regenerate-token": "Regenerar token activo",
|
||||
"remove-background": "Eliminar fondo",
|
||||
"generate-token": "Generar token activo",
|
||||
"view-docs": "Ver documentación",
|
||||
"generate-token-alert": "¿En definitiva quieres regenerar el token? Esto expirará el token antiguo.",
|
||||
@@ -477,6 +482,7 @@
|
||||
"mail-not-delivered": "correo no entregado",
|
||||
"document-alert": "Alerta de documento",
|
||||
"owner-subscription-expired": "La suscripción del propietario ha expirado.",
|
||||
"owner-doesnt-have-paid-plan": "El propietario no tiene un plan de pago.",
|
||||
"subscription-expired": "Suscripción expirada",
|
||||
"alert-message": "Mensaje de alerta",
|
||||
"document-decline": "Rechazar documento",
|
||||
@@ -670,7 +676,7 @@
|
||||
"public-template-mssg-1": "Para integrar OpenSign a tu proyecto React o Next.js, simplemente ejecuta los siguientes comandos:",
|
||||
"public-template-mssg-2": "Asegúrate de tener «npm» o «yarn» configurado en tu proyecto. Si estás usando «yarn», puedes reemplazar «npm install» con «yarn add @opensign/react».",
|
||||
"public-template-mssg-3": "¿Necesitas más detalles o ejemplos?",
|
||||
"public-template-mssg-4": "Visita la",
|
||||
"public-template-mssg-4": "Visita la ",
|
||||
"public-template-mssg-5": " «npm» para las últimas actualizaciones, documentación detallada e historial de versiones.",
|
||||
"public-template-mssg-6": "Antes de que puedas usar este fragmento de código, debes convertir esta plantilla en pública.",
|
||||
"public-template-mssg-7": "Antes de poder generar un enlace público, debes hacer que esta plantilla sea pública.",
|
||||
@@ -971,5 +977,50 @@
|
||||
"kiosk-sign": "Firma en quiosco",
|
||||
"dont-have-access-to-template": "El template ha sido eliminado o no tiene acceso. Por favor, contacte al remitente.",
|
||||
"kiosk-info": "El Modo Kiosco le permite recopilar firmas en persona de forma rápida y eficiente. Ideal para ferias, eventos o situaciones con personas que firman en el lugar. ",
|
||||
"learn-more": "Más información"
|
||||
"learn-more": "Más información",
|
||||
"finish-mssg": "¿Está seguro de que desea finalizar el documento?",
|
||||
"review": "Revisar",
|
||||
"next-field": "Siguiente campo",
|
||||
"required-mssg": "{{leftRequiredWidget}} de {{totalWidget}} campos restantes",
|
||||
"verify-document-signature": "Verificar firma del documento",
|
||||
"select-pdf-document": "Seleccionar documento PDF",
|
||||
"selected-file": "Archivo seleccionado",
|
||||
"verify-signature": "Verificar firma",
|
||||
"verification-status": "Estado de verificación",
|
||||
"verification-in-progress": "Verificación en curso...",
|
||||
"verification-results-will-appear-here": "Los resultados de la verificación aparecerán aquí",
|
||||
"please-select-pdf": "Por favor, seleccione un archivo PDF válido",
|
||||
"please-select-file-to-verify": "Por favor, seleccione un archivo para verificar",
|
||||
"no-signature-found": "No se encontró ninguna firma en el documento",
|
||||
"error-verifying-pdf": "Error al verificar el PDF",
|
||||
"signature-valid-basic": "La firma es válida",
|
||||
"signature-invalid-basic": "La firma no es válida",
|
||||
"all-signatures-verified-convincing": "Documento verificado: Todas las firmas han sido validadas exitosamente.",
|
||||
"some-signatures-invalid-basic": "Algunas firmas no son válidas",
|
||||
"no-signatures-processed": "No se procesaron firmas",
|
||||
"unnamed-signature-field": "Campo de firma sin nombre",
|
||||
"error-processing-signature": "Error al procesar la firma",
|
||||
"signer-info-not-available": "Información del firmante no disponible",
|
||||
"cert-validity-not-checked": "Validez del certificado no verificada",
|
||||
"valid": "Válido",
|
||||
"expired-or-not-yet-valid": "Caducado o aún no válido",
|
||||
"valid-from": "Válido desde",
|
||||
"to": "hasta",
|
||||
"signer": "Firmante",
|
||||
"issuer": "Emisor",
|
||||
"not-available": "No disponible",
|
||||
"not-performed": "No realizado",
|
||||
"missing-acrofield-dict": "Falta el diccionario Acrofield",
|
||||
"signature-dictionary-not-found-or-invalid": "Diccionario de firmas no encontrado o inválido",
|
||||
"missing-or-invalid-byterange": "ByteRange faltante o inválido",
|
||||
"missing-or-invalid-contents": "Contenido faltante o inválido",
|
||||
"missing-signature-contents": "Falta el contenido de la firma",
|
||||
"invalid-signature-hex-format": "Formato hexadecimal de firma inválido",
|
||||
"unsupported-signature-format-not-signeddata": "Formato de firma no compatible - no SignedData",
|
||||
"signer-certificate-not-found": "Certificado del firmante no encontrado",
|
||||
"no-certificates-in-signature": "No hay certificados en la firma",
|
||||
"no-signer-info-in-pkcs7": "No hay información del firmante en PKCS#7",
|
||||
"could-not-parse-signer-info": "No se pudo analizar la información del firmante",
|
||||
"not-calculated": "No calculado",
|
||||
"not-found-in-signature": "No encontrado en la firma"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"header-news": "Nouvelle fonctionnalité : les utilisateurs du forfait Teams peuvent désormais intégrer leurs propres compartiments AWS S3 pour le stockage de fichiers",
|
||||
"header-news-btn": "Configurer maintenant",
|
||||
"sandbox-news": "Ceci est un environnement sandbox. Veuillez ne pas l'utiliser à des fins de production.",
|
||||
"create-account": "Créer un compte",
|
||||
"login": "Se Connecter",
|
||||
"language": "Langue",
|
||||
@@ -39,9 +40,11 @@
|
||||
"save": "Sauvegarder",
|
||||
"cancel": "Annuler",
|
||||
"upgrade-now": "Mettre à jour maintenant",
|
||||
"contact-now": "Contacter maintenant",
|
||||
"upgrade-to": "Mettre à niveau vers",
|
||||
"pro": "PRO",
|
||||
"plan": "Offre",
|
||||
"subscription-renew-warning": "Votre abonnement expirera dans {{remainingDays}} jours. Veuillez renouveler votre abonnement.",
|
||||
"subscribe-card-teamplan": "Libérez toute la puissance de la collaboration ! Créez un nombre illimité d'organisations, d'équipes et de hiérarchies. Partagez des modèles de manière transparente entre les équipes et attribuez des rôles d'utilisateur personnalisés. Améliorez votre flux de travail dès aujourd'hui !",
|
||||
"subscribe-card-plan": "Débloquez des fonctionnalités premium à partir de seulement {{premiumPrice}}/mois. Bénéficiez de performances améliorées et de seulement {{addonPrice}} par crédit supplémentaire après vos crédits premium inclus.",
|
||||
"user-name-limit-char": "Pour avoir un nom d'utilisateur de moins de 8 caractères s'il vous plaît s'abonner",
|
||||
@@ -163,6 +166,7 @@
|
||||
"Quick send": "Envoi rapide",
|
||||
"Edit": "Modifier",
|
||||
"Share with team": "Partager avec l'équipe",
|
||||
"Share with user": "Partager avec un collègue",
|
||||
"Share": "Partager",
|
||||
"View": "Voir",
|
||||
"option": "Option",
|
||||
@@ -245,6 +249,7 @@
|
||||
"API": "API",
|
||||
"api-token": "Jeton API",
|
||||
"regenerate-token": "Régénérer en direct jeton",
|
||||
"remove-background": "Supprimer l'arrière-plan",
|
||||
"generate-token": "Générer en direct jeton",
|
||||
"view-docs": "Afficher les documents",
|
||||
"generate-token-alert": "Êtes-vous sûr de vouloir régénérer le jeton, votre ancien jeton sera supprimer?",
|
||||
@@ -477,6 +482,7 @@
|
||||
"mail-not-delivered": "Courrier non distribué",
|
||||
"document-alert": "Alerte document",
|
||||
"owner-subscription-expired": "L'abonnement du propriétaire a expiré.",
|
||||
"owner-doesnt-have-paid-plan": "Le propriétaire n'a pas de plan payant.",
|
||||
"subscription-expired": "Abonnement expiré",
|
||||
"alert-message": "Message d'alerte",
|
||||
"document-decline": "Document-refusé",
|
||||
@@ -670,7 +676,7 @@
|
||||
"public-template-mssg-1": "Pour intégrer OpenSign dans votre projet React ou Next.js, exécutez simplement la commande suivante :",
|
||||
"public-template-mssg-2": "Assurez-vous que npm ou Yarn est configuré dans votre projet. Si vous utilisez Yarn, vous pouvez remplacer npm install par Yarn Add @opensign/react.",
|
||||
"public-template-mssg-3": "Besoin de plus de détails ou d'exemples ?",
|
||||
"public-template-mssg-4": "Visitez le",
|
||||
"public-template-mssg-4": "Visitez le ",
|
||||
"public-template-mssg-5": "npm pour les dernières mises à jour, une documentation détaillée et l'historique des versions.",
|
||||
"public-template-mssg-6": "Avant de pouvoir utiliser cet extrait de code, vous devez rendre ce modèle public.",
|
||||
"public-template-mssg-7": "Avant de pouvoir générer un lien public, vous devez rendre ce modèle public.",
|
||||
@@ -971,5 +977,50 @@
|
||||
"kiosk-sign": "Signature sur kiosque",
|
||||
"dont-have-access-to-template": "Le template a été supprimé ou vous n'y avez pas accès. Veuillez contacter l'expéditeur.",
|
||||
"kiosk-info": "Le Mode Kiosque vous permet de recueillir des signatures en personne rapidement et efficacement. Idéal pour les salons, événements ou situations où tous les signataires sont physiquement présents. ",
|
||||
"learn-more": "En savoir plus"
|
||||
"learn-more": "En savoir plus",
|
||||
"finish-mssg": "Êtes-vous sûr de vouloir terminer le document ?",
|
||||
"review": "Revoir",
|
||||
"next-field": "Champ suivant",
|
||||
"required-mssg":"{{leftRequiredWidget}} champs sur {{totalWidget}} restants",
|
||||
"verify-document-signature": "Vérifier la signature du document",
|
||||
"select-pdf-document": "Sélectionner le document PDF",
|
||||
"selected-file": "Fichier sélectionné",
|
||||
"verify-signature": "Vérifier la signature",
|
||||
"verification-status": "État de la vérification",
|
||||
"verification-in-progress": "Vérification en cours...",
|
||||
"verification-results-will-appear-here": "Les résultats de la vérification apparaîtront ici",
|
||||
"please-select-pdf": "Veuillez sélectionner un fichier PDF valide",
|
||||
"please-select-file-to-verify": "Veuillez sélectionner un fichier à vérifier",
|
||||
"no-signature-found": "Aucune signature trouvée dans le document",
|
||||
"error-verifying-pdf": "Erreur lors de la vérification du PDF",
|
||||
"signature-valid-basic": "La signature est valide",
|
||||
"signature-invalid-basic": "La signature est invalide",
|
||||
"all-signatures-verified-convincing": "Document vérifié : Toutes les signatures ont été validées avec succès.",
|
||||
"some-signatures-invalid-basic": "Certaines signatures sont invalides",
|
||||
"no-signatures-processed": "Aucune signature traitée",
|
||||
"unnamed-signature-field": "Champ de signature sans nom",
|
||||
"error-processing-signature": "Erreur lors du traitement de la signature",
|
||||
"signer-info-not-available": "Informations sur le signataire non disponibles",
|
||||
"cert-validity-not-checked": "Validité du certificat non vérifiée",
|
||||
"valid": "Valide",
|
||||
"expired-or-not-yet-valid": "Expiré ou pas encore valide",
|
||||
"valid-from": "Valide du",
|
||||
"to": "au",
|
||||
"signer": "Signataire",
|
||||
"issuer": "Émetteur",
|
||||
"not-available": "Non disponible",
|
||||
"not-performed": "Non effectué",
|
||||
"missing-acrofield-dict": "Dictionnaire Acrofield manquant",
|
||||
"signature-dictionary-not-found-or-invalid": "Dictionnaire de signatures introuvable ou invalide",
|
||||
"missing-or-invalid-byterange": "ByteRange manquant ou invalide",
|
||||
"missing-or-invalid-contents": "Contenu manquant ou invalide",
|
||||
"missing-signature-contents": "Contenu de la signature manquant",
|
||||
"invalid-signature-hex-format": "Format hexadécimal de signature invalide",
|
||||
"unsupported-signature-format-not-signeddata": "Format de signature non pris en charge - pas SignedData",
|
||||
"signer-certificate-not-found": "Certificat du signataire introuvable",
|
||||
"no-certificates-in-signature": "Aucun certificat dans la signature",
|
||||
"no-signer-info-in-pkcs7": "Aucune information sur le signataire dans PKCS#7",
|
||||
"could-not-parse-signer-info": "Impossible d'analyser les informations sur le signataire",
|
||||
"not-calculated": "Non calculé",
|
||||
"not-found-in-signature": "Introuvable dans la signature"
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"header-news": "Nuova funzionalità: Gli utenti del piano Teams possono ora integrare i propri bucket AWS S3 per l'archiviazione dei file",
|
||||
"header-news-btn": "Configura Ora",
|
||||
"sandbox-news": "Questo è un ambiente sandbox. Si prega di non utilizzarlo per scopi di produzione.",
|
||||
"create-account": "Crea Account",
|
||||
"login": "Accedi",
|
||||
"language": "Lingua",
|
||||
@@ -39,8 +40,10 @@
|
||||
"save": "Salva",
|
||||
"cancel": "Annulla",
|
||||
"upgrade-now": "Aggiorna ora",
|
||||
"contact-now": "Contatta ora",
|
||||
"upgrade-to": "Aggiorna a",
|
||||
"plan": "Piano",
|
||||
"subscription-renew-warning": "Il tuo abbonamento scadrà tra {{remainingDays}} giorni. Ti preghiamo di rinnovarlo.",
|
||||
"subscribe-card-teamplan": "Sblocca tutto il potenziale della collaborazione! Crea organizzazioni, team e gerarchie illimitati. Condividi modelli senza problemi tra i team e assegna ruoli personalizzati agli utenti. Migliora il tuo flusso di lavoro oggi stesso!",
|
||||
"subscribe-card-plan": "Sblocca le funzionalità premium a partire da soli {{premiumPrice}}/mese. Approfitta di prestazioni migliorate e paga solo {{addonPrice}} per ogni credito aggiuntivo dopo quelli inclusi.",
|
||||
"user-name-limit-char": "Per un nome utente con meno di 8 caratteri, abbonati",
|
||||
@@ -142,6 +145,7 @@
|
||||
"Quick send": "Invio rapido",
|
||||
"Edit": "Modifica",
|
||||
"Share with team": "Condividi con il team",
|
||||
"Share with user": "Condividi con un collega",
|
||||
"Share": "Condividi",
|
||||
"View": "Visualizza",
|
||||
"option": "Opzione",
|
||||
@@ -245,6 +249,7 @@
|
||||
"API": "API",
|
||||
"api-token": "Token API",
|
||||
"regenerate-token": "Rigenera token live",
|
||||
"remove-background": "Rimuovi sfondo",
|
||||
"generate-token": "Genera token live",
|
||||
"view-docs": "Visualizza documenti",
|
||||
"generate-token-alert": "Sei sicuro di voler rigenerare il token? Questo invaliderà il vecchio token.",
|
||||
@@ -477,6 +482,7 @@
|
||||
"mail-not-delivered": "Mail non consegnata",
|
||||
"document-alert": "Avviso Documento",
|
||||
"owner-subscription-expired": "L'abbonamento del proprietario è scaduto.",
|
||||
"owner-doesnt-have-paid-plan": "Il proprietario non ha un piano a pagamento.",
|
||||
"subscription-expired": "Abbonamento Scaduto",
|
||||
"alert-message": "Messaggio di avviso",
|
||||
"document-decline": "Documento rifiutato",
|
||||
@@ -670,7 +676,7 @@
|
||||
"public-template-mssg-1": "Per integrare OpenSign nel tuo progetto React o Next.js, esegui semplicemente il seguente comando:",
|
||||
"public-template-mssg-2": "Assicurati di avere npm o yarn configurato nel tuo progetto. Se usi Yarn, puoi sostituire npm install con yarn add @opensign/react.",
|
||||
"public-template-mssg-3": "Hai bisogno di maggiori dettagli o esempi?",
|
||||
"public-template-mssg-4": "Visita il",
|
||||
"public-template-mssg-4": "Visita il ",
|
||||
"public-template-mssg-5": " npm per gli aggiornamenti più recenti, documentazione dettagliata e cronologia delle versioni.",
|
||||
"public-template-mssg-6": "Prima di poter utilizzare questo frammento di codice, devi rendere questo modello pubblico.",
|
||||
"public-template-mssg-7": "Prima di poter generare un link pubblico, devi rendere questo modello pubblico.",
|
||||
@@ -971,5 +977,50 @@
|
||||
"kiosk-sign": "Firma su chiosco",
|
||||
"dont-have-access-to-template": "Il template è stato eliminato o non hai accesso. Si prega di contattare il mittente.",
|
||||
"kiosk-info": "La Modalità Kiosk consente di raccogliere firme in presenza in modo rapido ed efficiente. Ideale per fiere, eventi o situazioni con firmatari fisicamente presenti. ",
|
||||
"learn-more": "Scopri di più"
|
||||
"learn-more": "Scopri di più",
|
||||
"finish-mssg": "Sei sicuro di voler completare il documento?",
|
||||
"review": "Rivedere",
|
||||
"next-field": "Campo successivo",
|
||||
"required-mssg":"{{leftRequiredWidget}} di {{totalWidget}} campi rimanenti",
|
||||
"verify-document-signature": "Verifica firma documento",
|
||||
"select-pdf-document": "Seleziona documento PDF",
|
||||
"selected-file": "File selezionato",
|
||||
"verify-signature": "Verifica firma",
|
||||
"verification-status": "Stato verifica",
|
||||
"verification-in-progress": "Verifica in corso...",
|
||||
"verification-results-will-appear-here": "I risultati della verifica appariranno qui",
|
||||
"please-select-pdf": "Seleziona un file PDF valido",
|
||||
"please-select-file-to-verify": "Seleziona un file da verificare",
|
||||
"no-signature-found": "Nessuna firma trovata nel documento",
|
||||
"error-verifying-pdf": "Errore durante la verifica del PDF",
|
||||
"signature-valid-basic": "La firma è valida",
|
||||
"signature-invalid-basic": "La firma non è valida",
|
||||
"all-signatures-verified-convincing": "Documento Verificato: Tutte le firme sono state validate con successo.",
|
||||
"some-signatures-invalid-basic": "Alcune firme non sono valide",
|
||||
"no-signatures-processed": "Nessuna firma elaborata",
|
||||
"unnamed-signature-field": "Campo firma senza nome",
|
||||
"error-processing-signature": "Errore durante l'elaborazione della firma",
|
||||
"signer-info-not-available": "Informazioni firmatario non disponibili",
|
||||
"cert-validity-not-checked": "Validità certificato non verificata",
|
||||
"valid": "Valido",
|
||||
"expired-or-not-yet-valid": "Scaduto o non ancora valido",
|
||||
"valid-from": "Valido dal",
|
||||
"to": "al",
|
||||
"signer": "Firmatario",
|
||||
"issuer": "Emittente",
|
||||
"not-available": "Non disponibile",
|
||||
"not-performed": "Non eseguito",
|
||||
"missing-acrofield-dict": "Dizionario Acrofield mancante",
|
||||
"signature-dictionary-not-found-or-invalid": "Dizionario firme non trovato o non valido",
|
||||
"missing-or-invalid-byterange": "ByteRange mancante o non valido",
|
||||
"missing-or-invalid-contents": "Contenuto mancante o non valido",
|
||||
"missing-signature-contents": "Contenuto firma mancante",
|
||||
"invalid-signature-hex-format": "Formato esadecimale firma non valido",
|
||||
"unsupported-signature-format-not-signeddata": "Formato firma non supportato - non SignedData",
|
||||
"signer-certificate-not-found": "Certificato firmatario non trovato",
|
||||
"no-certificates-in-signature": "Nessun certificato nella firma",
|
||||
"no-signer-info-in-pkcs7": "Nessuna informazione firmatario in PKCS#7",
|
||||
"could-not-parse-signer-info": "Impossibile analizzare le informazioni del firmatario",
|
||||
"not-calculated": "Non calcolato",
|
||||
"not-found-in-signature": "Non trovato nella firma"
|
||||
}
|
||||
|
||||
Binary file not shown.
+15
-11
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, lazy } from "react";
|
||||
import { useState, useEffect, lazy } from "react";
|
||||
import { Routes, Route, BrowserRouter } from "react-router";
|
||||
import { pdfjs } from "react-pdf";
|
||||
import Form from "./pages/Form";
|
||||
@@ -30,7 +30,7 @@ const AddAdmin = lazy(() => import("./pages/AddAdmin"));
|
||||
const UpdateExistUserAdmin = lazy(() => import("./pages/UpdateExistUserAdmin"));
|
||||
const Preferences = lazy(() => import("./pages/Preferences"));
|
||||
const Login = lazy(() => import("./pages/Login"));
|
||||
|
||||
const VerifyDocument = lazy(() => import("./pages/VerifyDocument"));
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/legacy/build/pdf.worker.min.mjs`;
|
||||
const AppLoader = () => {
|
||||
return (
|
||||
@@ -106,10 +106,10 @@ function App() {
|
||||
element={<LazyPage Page={GuestLogin} />}
|
||||
/>
|
||||
<Route path="/debugpdf" element={<LazyPage Page={DebugPdf} />} />
|
||||
<Route
|
||||
path="/forgetpassword"
|
||||
element={<LazyPage Page={ForgetPassword} />}
|
||||
/>
|
||||
<Route
|
||||
path="/forgetpassword"
|
||||
element={<LazyPage Page={ForgetPassword} />}
|
||||
/>
|
||||
<Route
|
||||
element={
|
||||
<ValidateSession>
|
||||
@@ -117,10 +117,10 @@ function App() {
|
||||
</ValidateSession>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path="/changepassword"
|
||||
element={<LazyPage Page={ChangePassword} />}
|
||||
/>
|
||||
<Route
|
||||
path="/changepassword"
|
||||
element={<LazyPage Page={ChangePassword} />}
|
||||
/>
|
||||
<Route path="/form/:id" element={<Form />} />
|
||||
<Route path="/report/:id" element={<Report />} />
|
||||
<Route path="/dashboard/:id" element={<Dashboard />} />
|
||||
@@ -165,7 +165,11 @@ function App() {
|
||||
path="/recipientSignPdf/:docId"
|
||||
element={<PdfRequestFiles />}
|
||||
/>
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route path="/users" element={<UserList />} />
|
||||
<Route
|
||||
path="/verify-document"
|
||||
element={<LazyPage Page={VerifyDocument} />}
|
||||
/>
|
||||
<Route
|
||||
path="/preferences"
|
||||
element={<LazyPage Page={Preferences} />}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import dp from "../assets/images/dp.png";
|
||||
import FullScreenButton from "./FullScreenButton";
|
||||
import { useNavigate } from "react-router";
|
||||
@@ -172,15 +172,26 @@ const Header = ({ showSidebar, setIsMenu, isConsole }) => {
|
||||
<i className="fa-light fa-user"></i> {t("profile")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i>{" "}
|
||||
{t("change-password")}
|
||||
</span>
|
||||
</li>
|
||||
<li
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
navigate("/changepassword");
|
||||
navigate("/verify-document");
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<i className="fa-light fa-lock"></i>{" "}
|
||||
{t("change-password")}
|
||||
<i className="fa-light fa-check-square"></i>{" "}
|
||||
{t("verify-document")}
|
||||
</span>
|
||||
</li>
|
||||
</>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import React from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
function Title({ title, drive }) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import "../../styles/opensigndrive.css";
|
||||
import axios from "axios";
|
||||
import * as ContextMenu from "@radix-ui/react-context-menu";
|
||||
import { ContextMenu } from "radix-ui";
|
||||
import { useNavigate } from "react-router";
|
||||
import Table from "react-bootstrap/Table";
|
||||
import * as HoverCard from "@radix-ui/react-hover-card";
|
||||
import { HoverCard } from "radix-ui";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import FolderModal from "../shared/fields/FolderModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -12,19 +12,32 @@ function AgreementSign(props) {
|
||||
<div className="op-modal op-modal-open absolute z-[448]">
|
||||
<div className="w-[95%] md:w-[60%] lg:w-[40%] op-modal-box overflow-y-auto hide-scrollbar text-sm p-4">
|
||||
<div className="flex flex-row items-center">
|
||||
<input
|
||||
data-tut="IsAgree"
|
||||
className="mr-3 op-checkbox op-checkbox-m"
|
||||
type="checkbox"
|
||||
value={isChecked}
|
||||
onChange={(e) => {
|
||||
setIsChecked(e.target.checked);
|
||||
if (e.target.checked) {
|
||||
props.setIsAgreeTour(false);
|
||||
}
|
||||
props.showFirstWidget();
|
||||
}}
|
||||
/>
|
||||
<label className="inline-flex justify-center items-center cursor-pointer mb-0">
|
||||
{/* 1) This div becomes the “fake” checkbox */}
|
||||
<div
|
||||
data-tut="IsAgree"
|
||||
className={`w-6 h-6 border-2 mr-3 rounded-full flex text-center items-center justify-center ${isChecked ? "op-border-primary" : "border-red-500"}`}
|
||||
>
|
||||
{isChecked ? (
|
||||
<span className="op-text-primary text-sm font-bold">✓</span>
|
||||
) : (
|
||||
<span className="text-red-500 text-sm font-bold">X</span>
|
||||
)}
|
||||
</div>
|
||||
{/* 2) Visually hide the native checkbox but keep it in the DOM */}
|
||||
<input
|
||||
className="sr-only"
|
||||
type="checkbox"
|
||||
checked={isChecked}
|
||||
onChange={(e) => {
|
||||
setIsChecked(e.target.checked);
|
||||
if (e.target.checked) {
|
||||
props.setIsAgreeTour(false);
|
||||
}
|
||||
props.showFirstWidget();
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
<div className="text-[11px] md:text-base">
|
||||
<span>{t("agree-p1")}</span>
|
||||
<span
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector } from "react-redux";
|
||||
function DefaultSignature(props) {
|
||||
const { t } = useTranslation();
|
||||
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
|
||||
const myInitial = useSelector((state) => state.widget.myInitial)
|
||||
const tabName = ["my-signature", "my-initials"];
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const confirmToaddDefaultSign = (type) => {
|
||||
@@ -69,15 +72,15 @@ function DefaultSignature(props) {
|
||||
<img
|
||||
alt="signature"
|
||||
className="w-full h-full object-contain"
|
||||
src={props?.defaultSignImg}
|
||||
src={defaultSignImg}
|
||||
/>
|
||||
) : (
|
||||
activeTab === 1 &&
|
||||
(props?.myInitial ? (
|
||||
(myInitial ? (
|
||||
<img
|
||||
alt="signature"
|
||||
className="w-full h-full object-contain"
|
||||
src={props?.myInitial}
|
||||
src={myInitial}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex justify-center items-center h-full">
|
||||
@@ -97,7 +100,7 @@ function DefaultSignature(props) {
|
||||
disabled={
|
||||
activeTab === 0 && !props?.isDefault
|
||||
? true
|
||||
: activeTab === 1 && !props.myInitial
|
||||
: activeTab === 1 && !myInitial
|
||||
? true
|
||||
: false
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ function DropdownWidgetOption(props) {
|
||||
]);
|
||||
const [minCount, setMinCount] = useState(0);
|
||||
const [maxCount, setMaxCount] = useState(0);
|
||||
const [dropdownName, setDropdownName] = useState(props.type);
|
||||
const [dropdownName, setDropdownName] = useState();
|
||||
const [isReadOnly, setIsReadOnly] = useState(false);
|
||||
const [isHideLabel, setIsHideLabel] = useState(false);
|
||||
const [status, setStatus] = useState("required");
|
||||
@@ -22,7 +22,7 @@ function DropdownWidgetOption(props) {
|
||||
|
||||
const resetState = () => {
|
||||
setDropdownOptionList(["option-1", "option-2"]);
|
||||
setDropdownName(props.type);
|
||||
setDropdownName(props.currWidgetsDetails?.options?.name || props.type);
|
||||
setIsReadOnly(false);
|
||||
setIsHideLabel(false);
|
||||
setMinCount(0);
|
||||
@@ -30,11 +30,10 @@ function DropdownWidgetOption(props) {
|
||||
setDefaultCheckbox([]);
|
||||
setDefaultValue("");
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
props.currWidgetsDetails?.options?.name &&
|
||||
props.currWidgetsDetails?.options?.values
|
||||
props.currWidgetsDetails?.options?.values?.length > 0
|
||||
) {
|
||||
setDropdownName(props.currWidgetsDetails?.options?.name);
|
||||
setDropdownOptionList(props.currWidgetsDetails?.options?.values);
|
||||
@@ -116,16 +115,7 @@ function DropdownWidgetOption(props) {
|
||||
defaultData,
|
||||
isHideLabel
|
||||
);
|
||||
// props.setShowDropdown(false);
|
||||
setDropdownOptionList(["option-1", "option-2"]);
|
||||
setDropdownName(props.type);
|
||||
// props.setCurrWidgetsDetails({});
|
||||
setIsReadOnly(false);
|
||||
setIsHideLabel(false);
|
||||
setMinCount(0);
|
||||
setMaxCount(0);
|
||||
setDefaultCheckbox([]);
|
||||
setDefaultValue("");
|
||||
resetState();
|
||||
};
|
||||
|
||||
|
||||
@@ -137,7 +127,6 @@ function DropdownWidgetOption(props) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalUi isOpen={props.showDropdown} title={props.title} showClose={false}>
|
||||
<div className="h-full p-[15px] text-base-content">
|
||||
@@ -156,7 +145,6 @@ function DropdownWidgetOption(props) {
|
||||
onInvalid={(e) => e.target.setCustomValidity(t("input-required"))}
|
||||
onInput={(e) => e.target.setCustomValidity("")}
|
||||
required
|
||||
defaultValue={dropdownName}
|
||||
value={dropdownName}
|
||||
onChange={(e) => setDropdownName(e.target.value)}
|
||||
className="op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-xs"
|
||||
@@ -166,7 +154,7 @@ function DropdownWidgetOption(props) {
|
||||
{t("options")}
|
||||
</label>
|
||||
<div className="flex flex-col">
|
||||
{dropdownOptionList.map((option, index) => (
|
||||
{dropdownOptionList?.map((option, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex flex-row mb-[5px] items-center"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, {
|
||||
import {
|
||||
useState,
|
||||
useRef,
|
||||
} from "react";
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
handleToPrint
|
||||
} from "../../constant/Utils";
|
||||
import "../../styles/signature.css";
|
||||
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
|
||||
import { DropdownMenu } from "radix-ui";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import Loader from "../../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -276,6 +276,20 @@ function Header(props) {
|
||||
className="bg-white shadow-md rounded-md px-3 py-2"
|
||||
sideOffset={5}
|
||||
>
|
||||
{props?.setIsEditTemplate && (
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() => props?.setIsEditTemplate(true)}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<i
|
||||
className="fa-light fa-gear mr-[3px]"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
<span className="font-[500]">{t("Edit")}</span>
|
||||
</div>
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
<DropdownMenu.Item
|
||||
className="DropdownMenuItem"
|
||||
onClick={() =>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import BorderResize from "./BorderResize";
|
||||
import PlaceholderBorder from "./PlaceholderBorder";
|
||||
import { Rnd } from "react-rnd";
|
||||
import {
|
||||
changeDateToMomentFormat,
|
||||
@@ -21,6 +20,9 @@ import moment from "moment";
|
||||
import "../../styles/opensigndrive.css";
|
||||
import ModalUi from "../../primitives/ModalUi";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { setIsShowModal } from "../../redux/reducers/widgetSlice";
|
||||
import { themeColor } from "../../constant/const";
|
||||
|
||||
const selectFormat = (data) => {
|
||||
switch (data) {
|
||||
@@ -72,31 +74,24 @@ const getDefaultDate = (dateStr, format) => {
|
||||
};
|
||||
|
||||
function Placeholder(props) {
|
||||
//'isTouchDevice' is used to detect whether a device has a touchscreen or is mouse-based
|
||||
const isTouchDevice = navigator.maxTouchPoints > 0;
|
||||
const { t } = useTranslation();
|
||||
const [placeholderBorder, setPlaceholderBorder] = useState({ w: 0, h: 0 });
|
||||
const [isDraggingEnabled, setDraggingEnabled] = useState(true);
|
||||
const dispatch = useDispatch();
|
||||
const widgetData =
|
||||
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
||||
const [isDateModal, setIsDateModal] = useState(false);
|
||||
const [containerScale, setContainerScale] = useState();
|
||||
const holdTimeout = useRef(null);
|
||||
const startTime = useRef(null); // Track when the user starts holdings
|
||||
const [isDisableDragging, setIsDisableDragging] = useState(true);
|
||||
const [selectDate, setSelectDate] = useState({});
|
||||
const [dateFormat, setDateFormat] = useState([]);
|
||||
const [clickonWidget, setClickonWidget] = useState({});
|
||||
const [startDate, setStartDate] = useState(
|
||||
props?.pos?.options?.response
|
||||
? getDefaultDate(
|
||||
props?.pos?.options?.response,
|
||||
props.pos?.options?.validation?.format
|
||||
)
|
||||
: new Date()
|
||||
);
|
||||
const [getCheckboxRenderWidth, setGetCheckboxRenderWidth] = useState({
|
||||
width: null,
|
||||
height: null
|
||||
});
|
||||
const startDate = props?.pos?.options?.response
|
||||
? getDefaultDate(
|
||||
props?.pos?.options?.response,
|
||||
props.pos?.options?.validation?.format
|
||||
)
|
||||
: new Date();
|
||||
|
||||
useEffect(() => {
|
||||
const getPdfPageWidth = props.pdfOriginalWH.find(
|
||||
(data) => data.pageNumber === props.pageNumber
|
||||
@@ -117,33 +112,16 @@ function Placeholder(props) {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const updateWidth = () => {
|
||||
const rndElement = document.getElementById(props.pos.key);
|
||||
if (rndElement) {
|
||||
const { width, height } = rndElement.getBoundingClientRect();
|
||||
setGetCheckboxRenderWidth({ width: width, height: height });
|
||||
}
|
||||
};
|
||||
if (props?.pos?.type === "date") {
|
||||
const isDateChange = true;
|
||||
const dateObj = {
|
||||
date: startDate,
|
||||
format: getDefaultFormat(props.pos?.options?.validation?.format)
|
||||
};
|
||||
handleSaveDate(dateObj, isDateChange); //function to save date and format in local array
|
||||
}
|
||||
}, [widgetData]);
|
||||
|
||||
// Delay to ensure rendering is complete
|
||||
const timer = setTimeout(updateWidth, 0);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [props.pos]);
|
||||
useEffect(() => {
|
||||
const onOutsideClick = () => {
|
||||
if (!isDraggingEnabled) {
|
||||
setDraggingEnabled(true);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("click", onOutsideClick);
|
||||
|
||||
return () => {
|
||||
// Cleanup the event listener when the component unmounts
|
||||
document.removeEventListener("click", onOutsideClick);
|
||||
};
|
||||
}, [isDraggingEnabled]);
|
||||
//function change format array list with selected date and format
|
||||
const changeDateFormat = () => {
|
||||
const updateDate = [];
|
||||
@@ -175,30 +153,18 @@ function Placeholder(props) {
|
||||
|
||||
//`handleWidgetIdandPopup` is used to set current widget id and open relative popup
|
||||
const handleWidgetIdandPopup = async () => {
|
||||
if (props.setSelectWidgetId) {
|
||||
props.setSelectWidgetId(props.pos.key);
|
||||
}
|
||||
|
||||
const widgetTypeExist = [
|
||||
textInputWidget,
|
||||
"checkbox",
|
||||
"name",
|
||||
"company",
|
||||
"job title",
|
||||
"date",
|
||||
"email",
|
||||
textWidget
|
||||
].includes(props.pos.type);
|
||||
|
||||
if (widgetTypeExist) {
|
||||
setDraggingEnabled(false);
|
||||
}
|
||||
if (props.isOpenSignPad && !props.isDragging) {
|
||||
//'props.isOpenSignPad' variable is used to check flow to open signature pad for finish document
|
||||
if (
|
||||
props.isOpenSignPad &&
|
||||
!props.isDragging &&
|
||||
!props.pos.options?.isReadOnly &&
|
||||
props.pos.type !== textWidget
|
||||
) {
|
||||
if (props?.ispublicTemplate) {
|
||||
props.handleUserDetails();
|
||||
} else {
|
||||
if (props?.isNeedSign) {
|
||||
//funcion is used to heightlight widgets on top if two widgets on overlap
|
||||
//funcion is used to highlight widgets on top when click any widget if two widgets on overlap
|
||||
const getCurrentSignerPos = props.xyPosition.find(
|
||||
(x) => x.Id === props.uniqueId
|
||||
);
|
||||
@@ -212,23 +178,14 @@ function Placeholder(props) {
|
||||
);
|
||||
props.setXyPosition(updatesignerPos);
|
||||
}
|
||||
if (
|
||||
["signature", "stamp", "image", "initials"].includes(props.pos.type)
|
||||
) {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
}
|
||||
if (props.pos.type === "initials") {
|
||||
props.setIsInitial(true);
|
||||
}
|
||||
dispatch(setIsShowModal({ [props.pos.key]: true }));
|
||||
}
|
||||
} else if (
|
||||
props.isPlaceholder &&
|
||||
!props.isDragging &&
|
||||
props.pos.type !== textWidget
|
||||
) {
|
||||
if (props.pos.key === props.selectWidgetId) {
|
||||
if (props.pos.key === props?.currWidgetsDetails?.key) {
|
||||
props.handleLinkUser(props.data.Id);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPosition.findIndex(
|
||||
@@ -236,31 +193,17 @@ function Placeholder(props) {
|
||||
);
|
||||
props.setIsSelectId(checkIndex || 0);
|
||||
}
|
||||
} else if (!props.pos.type) {
|
||||
if (
|
||||
!props.pos.type &&
|
||||
props.isNeedSign &&
|
||||
props.data.signerObjId === props.signerObjId
|
||||
) {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
} else if (
|
||||
(props.isNeedSign && props.pos.type === "signature") ||
|
||||
props.pos.type === "stamp"
|
||||
) {
|
||||
props.setIsSignPad(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setIsStamp(props.pos.isStamp);
|
||||
} else if (props.isNeedSign && props.pos.type === "dropdown") {
|
||||
props.setSignKey(props.pos.key);
|
||||
}
|
||||
//handle prefill 'text widget' click then save previos in tem variable after save or close button again assign current selected userId
|
||||
} else if (props.pos.type === textWidget) {
|
||||
dispatch(setIsShowModal({ [props.pos.key]: true }));
|
||||
props.setTempSignerId(props?.uniqueId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
}
|
||||
};
|
||||
|
||||
const widgetClickHandler = () => {
|
||||
//The else condition opens the signature pad if it's a request signature flow and the user clicking is identified as a signer.
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
|
||||
//condition to check in request signing flow user click on agree or not
|
||||
if (props?.data?.signerObjId === props?.signerObjId && !props.isDragging) {
|
||||
if (!props.isAgree && !props.isSelfSign) {
|
||||
props.setIsAgreeTour && props.setIsAgreeTour(true);
|
||||
@@ -272,7 +215,10 @@ function Placeholder(props) {
|
||||
handleWidgetIdandPopup();
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnClickPlaceholder = () => {
|
||||
//'props.isDragging' variable is used to checking if user take any widget and try to drag then in that case
|
||||
//onclick event call. so to prevent onclick and do not open unecessary modal open.
|
||||
//condition only for request signing flow and self signing flow then apply one click copy sign url of previous drawn signature
|
||||
if (props.isApplyAll) {
|
||||
props.setRequestSignTour && props.setRequestSignTour(true);
|
||||
@@ -342,12 +288,9 @@ function Placeholder(props) {
|
||||
} else {
|
||||
widgetClickHandler();
|
||||
}
|
||||
// }
|
||||
// }
|
||||
} else {
|
||||
//The else condition is used to handle the case when the user clicks on a widget and open signature pad to draw sign
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails(props.pos);
|
||||
props.setWidgetType(props.pos.type);
|
||||
handleWidgetIdandPopup();
|
||||
}
|
||||
};
|
||||
@@ -391,8 +334,6 @@ function Placeholder(props) {
|
||||
props.setTempSignerId(props.uniqueId);
|
||||
props.setUniqueId(props?.data?.Id);
|
||||
}
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setWidgetType(props.pos.type);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
};
|
||||
//function to set required state value onclick on widget's copy icon
|
||||
@@ -425,7 +366,7 @@ function Placeholder(props) {
|
||||
)
|
||||
) {
|
||||
props.setIsPageCopy(true);
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
} else {
|
||||
//function to create new widget next to just widget
|
||||
handleCopyNextToWidget(
|
||||
@@ -439,18 +380,6 @@ function Placeholder(props) {
|
||||
}
|
||||
};
|
||||
|
||||
//function to save date and format after seleted new date in response field and after finish document it should be emebed new selected date instead of current date
|
||||
useEffect(() => {
|
||||
if (props.pos.type === "date") {
|
||||
const isDateChange = true;
|
||||
const dateObj = {
|
||||
date: startDate,
|
||||
format: getDefaultFormat(props.pos?.options?.validation?.format)
|
||||
};
|
||||
handleSaveDate(dateObj, isDateChange); //function to save date and format in local array
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [startDate]);
|
||||
//function to save date and format on local array onchange date and onclick format
|
||||
const handleSaveDate = (data, isDateChange) => {
|
||||
let updateDate = data.date;
|
||||
@@ -476,8 +405,6 @@ function Placeholder(props) {
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
data?.format,
|
||||
null,
|
||||
null,
|
||||
props.fontSize || props.pos?.options?.fontSize || 12,
|
||||
props.fontColor || props.pos?.options?.fontColor || "black"
|
||||
);
|
||||
@@ -586,7 +513,7 @@ function Placeholder(props) {
|
||||
e.stopPropagation();
|
||||
setClickonWidget(props.pos);
|
||||
if (props.data) {
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPosition.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
@@ -599,7 +526,7 @@ function Placeholder(props) {
|
||||
e.stopPropagation();
|
||||
setIsDateModal(!isDateModal);
|
||||
if (props.data) {
|
||||
props.setSignKey(props.pos.key);
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
props.setUniqueId(props.data.Id);
|
||||
const checkIndex = props.xyPosition.findIndex(
|
||||
(data) => data.Id === props.data.Id
|
||||
@@ -636,7 +563,6 @@ function Placeholder(props) {
|
||||
//condition for signyour-self flow
|
||||
else {
|
||||
props.handleDeleteSign(props.pos.key);
|
||||
props.setIsStamp(false);
|
||||
}
|
||||
}}
|
||||
//for mobile and tablet touch event
|
||||
@@ -649,7 +575,6 @@ function Placeholder(props) {
|
||||
//condition for signyour-self flow
|
||||
else {
|
||||
props.handleDeleteSign(props.pos.key);
|
||||
props.setIsStamp(false);
|
||||
}
|
||||
}}
|
||||
style={{ color: "#188ae2", right: "-8px", top: "-18px" }}
|
||||
@@ -744,43 +669,7 @@ function Placeholder(props) {
|
||||
return "all-scroll";
|
||||
}
|
||||
};
|
||||
const handleDragging = () => {
|
||||
//condition for request signing flow
|
||||
if (props.isNeedSign) {
|
||||
//enable dragging functionality only if isAlllowModify true on tab that widget and hold 1sec
|
||||
if (
|
||||
props.isAlllowModify &&
|
||||
props?.assignedWidgetId.includes(props.pos.key) &&
|
||||
props.data?.signerObjId === props.signerObjId
|
||||
) {
|
||||
//if 'isTouchDevice' then handle dragging functionality conditionaly
|
||||
if (isTouchDevice) {
|
||||
return isDisableDragging;
|
||||
} else {
|
||||
//no need to handle dragging functionality it auto enable and working or mouse click devices
|
||||
return false;
|
||||
}
|
||||
} else if (
|
||||
//condition when 'isAlllowModify' is true and user add new widgets then handle dragging functionality like signyourself flow
|
||||
props.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key) &&
|
||||
props.data?.signerObjId === props.signerObjId
|
||||
) {
|
||||
return !isDraggingEnabled;
|
||||
} else {
|
||||
//if 'isAlllowModify' is false then disbale dragging functionality
|
||||
return true;
|
||||
}
|
||||
} //dragging enable in placeholder,template and not text widget flow
|
||||
else if (props.isPlaceholder && ![textWidget].includes(props.pos.type)) {
|
||||
return false;
|
||||
} //dragging depend on 'isDraggingEnabled' variable in self sign and signyourself flow
|
||||
else if (isTouchDevice) {
|
||||
return !isDraggingEnabled;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
//function to handle widget background color
|
||||
const handleBackground = () => {
|
||||
if (props.data) {
|
||||
@@ -812,42 +701,38 @@ function Placeholder(props) {
|
||||
}
|
||||
if (!props.isNeedSign || props.isAlllowModify) handleOnClickPlaceholder();
|
||||
};
|
||||
const handleTouchStart = () => {
|
||||
clearTimeout(holdTimeout.current); // Ensure no previous timeouts are running
|
||||
startTime.current = Date.now(); // Store touch start time
|
||||
|
||||
holdTimeout.current = setTimeout(() => {
|
||||
//handlle vibration and tab any widget and hold for 1 sec then show border outside widget and then user can able to drag
|
||||
if (isDisableDragging) {
|
||||
if (
|
||||
props.isNeedSign &&
|
||||
props.isAlllowModify &&
|
||||
props?.assignedWidgetId.includes(props.pos.key)
|
||||
) {
|
||||
try {
|
||||
navigator.vibrate(200); // Vibrate for 200ms
|
||||
} catch (e) {
|
||||
console.log("error in navigator.vibrate", e);
|
||||
}
|
||||
setIsDisableDragging(false);
|
||||
props.setSelectWidgetId(props.pos.key);
|
||||
} else if (!props.isNeedSign) {
|
||||
setIsDisableDragging(false);
|
||||
}
|
||||
}
|
||||
}, 1000); // Hold for 1 second before vibrating
|
||||
};
|
||||
const fontSize = calculateFont(props.pos.options?.fontSize);
|
||||
const fontColor = props.pos.options?.fontColor || "black";
|
||||
|
||||
const handleDragging = () => {
|
||||
//condition for request signing flow
|
||||
if (props.isNeedSign) {
|
||||
//enable dragging functionality only if isAlllowModify true on tab that widget and hold 1sec
|
||||
if (
|
||||
props.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key) &&
|
||||
props.data?.signerObjId === props.signerObjId
|
||||
) {
|
||||
return false;
|
||||
} else {
|
||||
//if 'isAlllowModify' is false then disbale dragging functionality
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{/* Check if a text widget (prefill type) exists. Once the user enters a value and clicks outside or the widget becomes non-selectable, it should appear as plain text (just like embedded text in a document). When the user clicks on the text again, it should become editable. */}
|
||||
{props.pos?.options?.response &&
|
||||
props.pos.key !== props.selectWidgetId &&
|
||||
props.pos.key !== props?.currWidgetsDetails?.key &&
|
||||
props.pos.type === textWidget ? (
|
||||
<span
|
||||
onClick={() => {
|
||||
props.setSelectWidgetId && props.setSelectWidgetId(props.pos.key);
|
||||
props.setCurrWidgetsDetails &&
|
||||
props.setCurrWidgetsDetails(props.pos);
|
||||
}}
|
||||
style={{
|
||||
fontFamily: "Arial, sans-serif",
|
||||
@@ -896,7 +781,7 @@ function Placeholder(props) {
|
||||
: false
|
||||
: props.pos.type !== radioButtonWidget &&
|
||||
props.pos.type !== "checkbox" &&
|
||||
props.pos.key === props.selectWidgetId &&
|
||||
props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
true,
|
||||
bottomLeft: false,
|
||||
topLeft: false
|
||||
@@ -909,7 +794,7 @@ function Placeholder(props) {
|
||||
cursor: getCursor(),
|
||||
zIndex:
|
||||
props.pos.type === "date"
|
||||
? props.pos.key === props.selectWidgetId
|
||||
? props.pos.key === props?.currWidgetsDetails?.key
|
||||
? 99 + 1
|
||||
: 99
|
||||
: props?.pos?.zIndex
|
||||
@@ -920,7 +805,6 @@ function Placeholder(props) {
|
||||
background: handleBackground()
|
||||
}}
|
||||
onDrag={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.handleTabDrag && props.handleTabDrag(props.pos.key);
|
||||
}}
|
||||
size={{
|
||||
@@ -935,10 +819,12 @@ function Placeholder(props) {
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself)
|
||||
}}
|
||||
minHeight={calculateFont(props.pos.options?.fontSize, true)}
|
||||
minHeight={
|
||||
props.pos.type !== "checkbox" &&
|
||||
calculateFont(props.pos.options?.fontSize, true)
|
||||
}
|
||||
maxHeight="auto"
|
||||
onResizeStart={() => {
|
||||
setDraggingEnabled(true);
|
||||
props.setIsResize && props.setIsResize(true);
|
||||
}}
|
||||
onResizeStop={(e, direction, ref) => {
|
||||
@@ -958,9 +844,7 @@ function Placeholder(props) {
|
||||
props.isResize
|
||||
);
|
||||
}}
|
||||
disableDragging={handleDragging()}
|
||||
onDragStop={(event, dragElement) => {
|
||||
setIsDisableDragging(true);
|
||||
props.handleStop &&
|
||||
props.handleStop(
|
||||
event,
|
||||
@@ -973,17 +857,9 @@ function Placeholder(props) {
|
||||
x: xPos(props.pos, props.isSignYourself),
|
||||
y: yPos(props.pos, props.isSignYourself)
|
||||
}}
|
||||
onResize={(e, direction, ref) => {
|
||||
setPlaceholderBorder({
|
||||
w: ref.offsetWidth / (props.scale * containerScale),
|
||||
h: ref.offsetHeight / (props.scale * containerScale)
|
||||
});
|
||||
}}
|
||||
// onClick={() =>
|
||||
// !props.isResize && !isMobile && handleOnClickPlaceholder()
|
||||
// }
|
||||
disableDragging={handleDragging()}
|
||||
>
|
||||
{props.pos.key === props.selectWidgetId &&
|
||||
{props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
((props.isShowBorder &&
|
||||
![radioButtonWidget, "checkbox"].includes(props.pos.type)) ||
|
||||
(props?.isAlllowModify &&
|
||||
@@ -1010,59 +886,44 @@ function Placeholder(props) {
|
||||
)
|
||||
) : (
|
||||
![radioButtonWidget, "checkbox"].includes(props.pos.type) &&
|
||||
props.pos.key === props.selectWidgetId && <BorderResize />
|
||||
props.pos.key === props?.currWidgetsDetails?.key && <BorderResize />
|
||||
)}
|
||||
|
||||
{/* 1- Show a border if props.pos.key === props.selectWidgetId, indicating the current user's selected widget.
|
||||
2- If props.isShowBorder is true, display borders for all widgets.
|
||||
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show borders:
|
||||
1- When isAlllowModify is true, show borders.
|
||||
2- Do not display border for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
|
||||
*/}
|
||||
{props.pos.key === props.selectWidgetId &&
|
||||
(props.isShowBorder ||
|
||||
!isDisableDragging ||
|
||||
(props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key))) && (
|
||||
<PlaceholderBorder
|
||||
setDraggingEnabled={setDraggingEnabled}
|
||||
pos={props.pos}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
getCheckboxRenderWidth={getCheckboxRenderWidth}
|
||||
scale={props.scale}
|
||||
containerScale={containerScale}
|
||||
placeholderBorder={placeholderBorder}
|
||||
/>
|
||||
)}
|
||||
{/* 1- Show a ouline if props.pos.key === props?.currWidgetsDetails?.key, indicating the current user's selected widget.
|
||||
2- If props.isShowBorder is true, display ouline for all widgets.
|
||||
3- Use the combination of props?.isAlllowModify and !props?.assignedWidgetId.includes(props.pos.key) to determine when to show ouline:
|
||||
3.1- When isAlllowModify is true, show ouline.
|
||||
3.2- Do not display ouline for widgets already assigned (props.assignedWidgetId.includes(props.pos.key) is true).
|
||||
*/}
|
||||
<div
|
||||
className="flex items-stretch justify-center"
|
||||
className={`${
|
||||
props.pos.key === props?.currWidgetsDetails?.key &&
|
||||
(props.isShowBorder ||
|
||||
(props?.isAlllowModify &&
|
||||
!props?.assignedWidgetId.includes(props.pos.key)))
|
||||
? "outline-[0.3px] outline-dashed outline-offset-[10px]"
|
||||
: ""
|
||||
} flex items-stretch justify-center`}
|
||||
style={{
|
||||
outlineColor: themeColor,
|
||||
left: xPos(props.pos, props.isSignYourself),
|
||||
top: yPos(props.pos, props.isSignYourself),
|
||||
width:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posWidth(props.pos, props.isSignYourself),
|
||||
height:
|
||||
props.pos.type === radioButtonWidget ||
|
||||
props.pos.type === "checkbox"
|
||||
? "auto"
|
||||
: props.posHeight(props.pos, props.isSignYourself),
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
zIndex: "10"
|
||||
}}
|
||||
onTouchEnd={() => handleTouchEnd()}
|
||||
onTouchEnd={() => handleOnClickPlaceholder()}
|
||||
onClick={() => handleOnClickPlaceholder()}
|
||||
onTouchStart={() => handleTouchStart()}
|
||||
>
|
||||
{props.pos.key === props.selectWidgetId && <PlaceholderIcon />}
|
||||
{props.pos.key === props?.currWidgetsDetails?.key && (
|
||||
<PlaceholderIcon />
|
||||
)}
|
||||
<PlaceholderType
|
||||
pos={props.pos}
|
||||
xyPosition={props.xyPosition}
|
||||
index={props.index}
|
||||
setXyPosition={props.setXyPosition}
|
||||
data={props.data}
|
||||
setSignKey={props.setSignKey}
|
||||
isShowDropdown={props?.isShowDropdown}
|
||||
isPlaceholder={props.isPlaceholder}
|
||||
isSignYourself={props.isSignYourself}
|
||||
@@ -1073,8 +934,6 @@ function Placeholder(props) {
|
||||
isNeedSign={props.isNeedSign}
|
||||
setSelectDate={setSelectDate}
|
||||
selectDate={selectDate}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setStartDate={setStartDate}
|
||||
startDate={startDate}
|
||||
handleSaveDate={handleSaveDate}
|
||||
xPos={props.xPos}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import React from "react";
|
||||
import { themeColor } from "../../constant/const";
|
||||
import {
|
||||
defaultWidthHeight,
|
||||
isMobile,
|
||||
radioButtonWidget,
|
||||
resizeBorderExtraWidth,
|
||||
textWidget
|
||||
} from "../../constant/Utils";
|
||||
function PlaceholderBorder(props) {
|
||||
const getResizeBorderExtraWidth = resizeBorderExtraWidth();
|
||||
const defaultWidth = defaultWidthHeight(props.pos.type).width;
|
||||
const defaultHeight = defaultWidthHeight(props.pos.type).height;
|
||||
const width = () => {
|
||||
const getWidth =
|
||||
props.placeholderBorder.w || props.pos.Width || defaultWidth;
|
||||
return (
|
||||
getWidth * props.scale * props.containerScale + getResizeBorderExtraWidth
|
||||
);
|
||||
};
|
||||
const height = () => {
|
||||
const getHeight =
|
||||
props.placeholderBorder.h || props.pos.Height || defaultHeight;
|
||||
|
||||
return (
|
||||
getHeight * props.scale * props.containerScale + getResizeBorderExtraWidth
|
||||
);
|
||||
};
|
||||
|
||||
const handleMinWidth = () => {
|
||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
||||
return props.getCheckboxRenderWidth.width + getResizeBorderExtraWidth;
|
||||
} else {
|
||||
return width();
|
||||
}
|
||||
};
|
||||
const handleMinHeight = () => {
|
||||
if (props.pos.type === "checkbox" || props.pos.type === radioButtonWidget) {
|
||||
return props.getCheckboxRenderWidth.height + getResizeBorderExtraWidth;
|
||||
} else {
|
||||
return height();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => !isMobile && props?.setDraggingEnabled(false)}
|
||||
onTouchEnd={() =>
|
||||
props.pos.type === textWidget && props?.setDraggingEnabled(false)
|
||||
}
|
||||
className="absolute inline-block w-[14px] h-[14px] border-[0.2px] overflow-hidden border-dashed"
|
||||
style={{
|
||||
borderColor: themeColor,
|
||||
minWidth: handleMinWidth() || 0,
|
||||
minHeight: handleMinHeight() || 0
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlaceholderBorder;
|
||||
@@ -2,19 +2,17 @@ import React, { useEffect, useState, forwardRef, useRef } from "react";
|
||||
import {
|
||||
getMonth,
|
||||
getYear,
|
||||
onChangeHeightOfTextArea,
|
||||
onChangeInput,
|
||||
radioButtonWidget,
|
||||
range,
|
||||
textInputWidget,
|
||||
textWidget,
|
||||
widgetDataValue
|
||||
months,
|
||||
years,
|
||||
selectCheckbox,
|
||||
checkRegularExpress
|
||||
} from "../../constant/Utils";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import "../../styles/signature.css";
|
||||
import RegexParser from "regex-parser";
|
||||
import { emailRegex } from "../../constant/const";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const textWidgetCls =
|
||||
"w-full h-full md:min-w-full md:min-h-full z-[999] text-[12px] rounded-[2px] border-[1px] border-[#007bff] overflow-hidden resize-none outline-none text-base-content item-center whitespace-pre-wrap bg-white";
|
||||
@@ -25,32 +23,19 @@ const widgetCls =
|
||||
function PlaceholderType(props) {
|
||||
const { t } = useTranslation();
|
||||
const type = props?.pos?.type;
|
||||
const iswidgetEnable =
|
||||
props.isSignYourself ||
|
||||
((props.isSelfSign || props.isNeedSign) &&
|
||||
props.data?.signerObjId === props.signerObjId);
|
||||
const widgetData =
|
||||
props.pos?.options?.defaultValue || props.pos?.options?.response;
|
||||
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 [widgetValue, setwidgetValue] = 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";
|
||||
const months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"
|
||||
];
|
||||
const textWidgetStyle = {
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
@@ -61,89 +46,30 @@ function PlaceholderType(props) {
|
||||
display: "flex",
|
||||
height: "100%"
|
||||
};
|
||||
const validateExpression = (regexValidation) => {
|
||||
if (textValue && regexValidation) {
|
||||
let regexObject = regexValidation;
|
||||
if (props.pos?.options?.validation?.type === "regex") {
|
||||
regexObject = RegexParser(regexValidation);
|
||||
}
|
||||
// new RegExp(regexValidation);
|
||||
let isValidate = regexObject.test(textValue);
|
||||
if (!isValidate) {
|
||||
props?.setValidateAlert(true);
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
const validateType = props.pos?.options?.validation?.type;
|
||||
let regexValidation;
|
||||
if (validateType && validateType !== "text") {
|
||||
switch (validateType) {
|
||||
case "email":
|
||||
regexValidation = emailRegex;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
case "number":
|
||||
regexValidation = /^[0-9\s]*$/;
|
||||
validateExpression(regexValidation);
|
||||
break;
|
||||
default:
|
||||
regexValidation = props.pos?.options?.validation?.pattern || "";
|
||||
validateExpression(regexValidation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTextValid = (e) => {
|
||||
const textInput = e.target.value;
|
||||
setTextValue(textInput);
|
||||
};
|
||||
function checkRegularExpress(validateType) {
|
||||
switch (validateType) {
|
||||
case "email":
|
||||
setValidatePlaceholder("demo@gmail.com");
|
||||
break;
|
||||
case "number":
|
||||
setValidatePlaceholder("12345");
|
||||
break;
|
||||
case "text":
|
||||
setValidatePlaceholder("please enter text");
|
||||
break;
|
||||
default:
|
||||
setValidatePlaceholder("please enter value");
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (type && type === "checkbox" && props.isNeedSign) {
|
||||
const isDefaultValue = props.pos.options?.defaultValue;
|
||||
if (isDefaultValue) {
|
||||
setSelectedCheckbox(isDefaultValue);
|
||||
if (type !== "date") {
|
||||
if (type && type === "checkbox") {
|
||||
setSelectedCheckbox(
|
||||
props?.pos?.options?.response ||
|
||||
props?.pos?.options?.defaultValue ||
|
||||
[]
|
||||
);
|
||||
} else {
|
||||
if (widgetData) {
|
||||
setwidgetValue(widgetData);
|
||||
}
|
||||
}
|
||||
if (props.pos?.options?.hint) {
|
||||
setHint(props.pos?.options.hint);
|
||||
} else if (props.pos?.options?.validation?.type) {
|
||||
checkRegularExpress(props.pos?.options?.validation?.type, setHint);
|
||||
} else {
|
||||
setHint(props.pos?.type);
|
||||
}
|
||||
} else if (props.pos?.options?.hint) {
|
||||
setValidatePlaceholder(props.pos?.options.hint);
|
||||
} else if (props.pos?.options?.validation?.type) {
|
||||
checkRegularExpress(props.pos?.options?.validation?.type);
|
||||
}
|
||||
setTextValue(
|
||||
props.pos?.options?.response
|
||||
? props.pos?.options?.response
|
||||
: props.pos?.options?.defaultValue
|
||||
? props.pos?.options?.defaultValue
|
||||
: ""
|
||||
);
|
||||
setSelectOption(
|
||||
props.pos?.options?.response
|
||||
? props.pos?.options?.response
|
||||
: props.pos?.options?.defaultValue
|
||||
? props.pos?.options?.defaultValue
|
||||
: ""
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
}, [props.pos]);
|
||||
const ExampleCustomInput = forwardRef(({ value, onClick }, ref) => (
|
||||
<div
|
||||
style={{
|
||||
@@ -160,53 +86,10 @@ function PlaceholderType(props) {
|
||||
</div>
|
||||
));
|
||||
ExampleCustomInput.displayName = "ExampleCustomInput";
|
||||
useEffect(() => {
|
||||
if (
|
||||
["name", "email", "job title", "company", textInputWidget].includes(
|
||||
type
|
||||
) &&
|
||||
props.isNeedSign &&
|
||||
props.data?.signerObjId === props.signerObjId
|
||||
) {
|
||||
if (widgetData) {
|
||||
setTextValue(widgetData);
|
||||
}
|
||||
}
|
||||
|
||||
if (props.pos?.options?.hint) {
|
||||
setHint(props.pos?.options.hint);
|
||||
} else {
|
||||
setHint(props.pos?.type);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [type, widgetData]);
|
||||
//function for show checked checkbox
|
||||
const selectCheckbox = (ind) => {
|
||||
const res = props.pos.options?.response;
|
||||
const defaultCheck = props.pos.options?.defaultValue;
|
||||
if (res && res?.length > 0) {
|
||||
const isSelectIndex = res.indexOf(ind);
|
||||
if (isSelectIndex > -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// }
|
||||
} else if (defaultCheck) {
|
||||
const isSelectIndex = defaultCheck.indexOf(ind);
|
||||
if (isSelectIndex > -1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleRadioCheck = (data) => {
|
||||
const defaultData = props.pos.options?.defaultValue;
|
||||
if (textValue === data) {
|
||||
if (widgetValue === data) {
|
||||
return true;
|
||||
} else if (defaultData === data) {
|
||||
return true;
|
||||
@@ -215,90 +98,6 @@ function PlaceholderType(props) {
|
||||
}
|
||||
};
|
||||
|
||||
//function for set checked and unchecked value of checkbox
|
||||
const handleCheckboxValue = (isChecked, ind) => {
|
||||
let updateSelectedCheckbox = [],
|
||||
checkedList;
|
||||
let isDefaultValue, isDefaultEmpty;
|
||||
if (type === "checkbox") {
|
||||
updateSelectedCheckbox = selectedCheckbox ? selectedCheckbox : [];
|
||||
|
||||
if (isChecked) {
|
||||
updateSelectedCheckbox.push(ind);
|
||||
setSelectedCheckbox(updateSelectedCheckbox);
|
||||
} else {
|
||||
checkedList = selectedCheckbox.filter((data) => data !== ind);
|
||||
setSelectedCheckbox(checkedList);
|
||||
}
|
||||
if (props.isNeedSign) {
|
||||
isDefaultValue = props.pos.options?.defaultValue;
|
||||
}
|
||||
if (isDefaultValue && isDefaultValue.length > 0) {
|
||||
isDefaultEmpty = true;
|
||||
}
|
||||
onChangeInput(
|
||||
checkedList ? checkedList : updateSelectedCheckbox,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
null,
|
||||
isDefaultEmpty
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
//function to handle select radio widget and set value seletced by user
|
||||
const handleCheckRadio = (isChecked, data) => {
|
||||
let isDefaultValue,
|
||||
isDefaultEmpty,
|
||||
isRadio = true;
|
||||
if (props.isNeedSign) {
|
||||
isDefaultValue = props.pos.options?.defaultValue;
|
||||
}
|
||||
if (isDefaultValue) {
|
||||
isDefaultEmpty = true;
|
||||
}
|
||||
if (isChecked) {
|
||||
setTextValue(data);
|
||||
} else {
|
||||
setTextValue("");
|
||||
}
|
||||
onChangeInput(
|
||||
data,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data.Id,
|
||||
false,
|
||||
null,
|
||||
isDefaultEmpty,
|
||||
isRadio
|
||||
);
|
||||
};
|
||||
//function to set onchange date
|
||||
const handleOnDateChange = (date) => {
|
||||
props.setStartDate(date);
|
||||
};
|
||||
//handle height on enter press in text area
|
||||
const handleEnterPress = (e) => {
|
||||
const height = 18;
|
||||
if (e.key === "Enter") {
|
||||
//function to save height of text area
|
||||
onChangeHeightOfTextArea(
|
||||
height,
|
||||
props.pos.type,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data?.Id
|
||||
);
|
||||
}
|
||||
};
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return props.pos.SignUrl ? (
|
||||
@@ -319,9 +118,7 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
{hint || widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -345,9 +142,7 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
{hint || widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -357,97 +152,59 @@ function PlaceholderType(props) {
|
||||
<div style={{ zIndex: props.isSignYourself && "99" }}>
|
||||
{props.pos.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className="select-none-cls flex items-center text-center gap-0.5"
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ width: fontSize, height: fontSize }}
|
||||
className={`${
|
||||
ind === 0 ? "mt-0" : "mt-[5px]"
|
||||
} flex justify-center op-checkbox rounded-[1px] `}
|
||||
onBlur={handleInputBlur}
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
type="checkbox"
|
||||
checked={selectCheckbox(ind)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
if (!props.isPlaceholder) {
|
||||
const maxRequired =
|
||||
props.pos.options?.validation?.maxRequiredCount;
|
||||
const maxCountInt =
|
||||
maxRequired && parseInt(maxRequired);
|
||||
|
||||
if (maxCountInt > 0) {
|
||||
if (
|
||||
selectedCheckbox &&
|
||||
selectedCheckbox?.length <= maxCountInt - 1
|
||||
) {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
} else {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleCheckboxValue(e.target.checked, ind);
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className={`mb-0 flex items-center gap-1 ${
|
||||
ind > 0 ? "mt-[3px]" : "mt-[0px]"
|
||||
}`}
|
||||
>
|
||||
<input
|
||||
id={`checkbox-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize
|
||||
}}
|
||||
className="op-checkbox rounded-[1px]"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label
|
||||
htmlFor={`checkbox-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className="text-xs mb-0 text-center"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
type="checkbox"
|
||||
readOnly
|
||||
checked={!!selectCheckbox(ind, selectedCheckbox)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
case textInputWidget:
|
||||
return props.isSignYourself ||
|
||||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
return props.isSignYourself || iswidgetEnable ? (
|
||||
<textarea
|
||||
ref={inputRef}
|
||||
placeholder={validatePlaceholder || t("widgets-name.text")}
|
||||
placeholder={hint || t("widgets-name.text")}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
setTextValue(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
value={widgetValue}
|
||||
className={`${
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId
|
||||
? "select-none"
|
||||
: "" + textWidgetCls
|
||||
: textWidgetCls
|
||||
}`}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
background: props.data?.blockColor
|
||||
background: props.data?.blockColor,
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
readOnly
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
@@ -457,70 +214,16 @@ function PlaceholderType(props) {
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{textValue || widgetTypeTranslation}</span>
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "dropdown":
|
||||
return props.data?.signerObjId === props.signerObjId ? (
|
||||
<select
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className={`${
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
? " disabled:bg-inherit select-none "
|
||||
: "" + `${selectWidgetCls} text-[12px] bg-inherit`
|
||||
}`}
|
||||
id="myDropdown"
|
||||
value={selectOption}
|
||||
onChange={(e) => {
|
||||
setSelectOption(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
>
|
||||
{/* Default/Title option */}
|
||||
<option
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
value=""
|
||||
disabled
|
||||
hidden
|
||||
>
|
||||
{props?.pos?.options?.name}
|
||||
</option>
|
||||
|
||||
{props.pos?.options?.values?.map((data, ind) => {
|
||||
return (
|
||||
<option
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
key={ind}
|
||||
value={data}
|
||||
>
|
||||
{data}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
) : (
|
||||
return (
|
||||
<div
|
||||
style={textWidgetStyle}
|
||||
className="select-none-cls flex justify-between items-center"
|
||||
>
|
||||
{props.pos?.options?.name
|
||||
? props.pos.options.name
|
||||
: widgetTypeTranslation}
|
||||
{widgetData || hint || widgetTypeTranslation}
|
||||
<i className="fa-light fa-circle-chevron-down mr-1 "></i>
|
||||
</div>
|
||||
);
|
||||
@@ -543,111 +246,76 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium text-center"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
{hint || widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "name":
|
||||
return props.isSignYourself ||
|
||||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
const isDefault = false;
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data?.Id,
|
||||
isDefault
|
||||
);
|
||||
}}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex h-full select-none-cls" style={textWidgetStyle}>
|
||||
<span>{widgetTypeTranslation}</span>
|
||||
<span> {props.pos?.options?.hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "company":
|
||||
return props.isSignYourself ||
|
||||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{widgetTypeTranslation}</span>
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "job title":
|
||||
return props.isSignYourself ||
|
||||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
cols="50"
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{widgetTypeTranslation}</span>
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case "date":
|
||||
return props.isSignYourself ||
|
||||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
return iswidgetEnable ? (
|
||||
<DatePicker
|
||||
renderCustomHeader={({ date, changeYear, changeMonth }) => (
|
||||
<div className="flex justify-start ml-2 ">
|
||||
@@ -677,15 +345,10 @@ function PlaceholderType(props) {
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
disabled={
|
||||
props.isPlaceholder ||
|
||||
(props.isNeedSign && props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
onBlur={handleInputBlur}
|
||||
disabled={true}
|
||||
closeOnScroll={true}
|
||||
className={`${selectWidgetCls} outline-[#007bff]`}
|
||||
selected={props?.startDate}
|
||||
onChange={(date) => handleOnDateChange(date)}
|
||||
popperPlacement="top-end"
|
||||
customInput={<ExampleCustomInput />}
|
||||
dateFormat={
|
||||
@@ -729,52 +392,32 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium text-center"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
{hint || widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
case "email":
|
||||
return props.isSignYourself ||
|
||||
(props.isSelfSign && props.data?.signerObjId === props.signerObjId) ||
|
||||
(props.isNeedSign && props.data?.signerObjId === props.signerObjId) ? (
|
||||
return iswidgetEnable ? (
|
||||
<textarea
|
||||
readOnly
|
||||
ref={inputRef}
|
||||
placeholder={hint || widgetTypeTranslation}
|
||||
rows={1}
|
||||
onKeyDown={(e) => {
|
||||
// Prevent new line on Enter key press
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
handleTextValid(e);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
fontFamily: "Arial, sans-serif"
|
||||
fontFamily: "Arial, sans-serif",
|
||||
pointerEvents: "none"
|
||||
}}
|
||||
disabled
|
||||
cols="1"
|
||||
/>
|
||||
) : (
|
||||
<div style={textWidgetStyle} className="select-none-cls">
|
||||
<span>{widgetTypeTranslation}</span>
|
||||
<span>{hint || widgetTypeTranslation}</span>
|
||||
</div>
|
||||
);
|
||||
case radioButtonWidget:
|
||||
@@ -782,40 +425,39 @@ function PlaceholderType(props) {
|
||||
<div>
|
||||
{props.pos.options?.values.map((data, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
className="select-none-cls flex items-center text-center gap-0.5"
|
||||
>
|
||||
<input
|
||||
id={`radio-${props.pos.key + ind}`}
|
||||
<div key={ind} className="select-none-cls pointer-events-none">
|
||||
<label
|
||||
htmlFor={`radio-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize,
|
||||
marginTop: ind > 0 ? "10px" : "0px"
|
||||
fontSize: fontSize,
|
||||
color: fontColor,
|
||||
marginTop: ind > 0 ? "5px" : "0px"
|
||||
}}
|
||||
className={`flex justify-center op-radio`}
|
||||
type="radio"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
checked={handleRadioCheck(data)}
|
||||
onChange={(e) => {
|
||||
if (!props.isPlaceholder) {
|
||||
handleCheckRadio(e.target.checked, data);
|
||||
className="text-xs mb-0 flex items-center gap-1 "
|
||||
>
|
||||
<input
|
||||
readOnly
|
||||
id={`radio-${props.pos.key + ind}`}
|
||||
style={{
|
||||
width: fontSize,
|
||||
height: fontSize,
|
||||
lineHeight: 2
|
||||
}}
|
||||
className={`op-radio rounded-full border- border-black appearance-none bg-white inline-block align-middle relative ${
|
||||
handleRadioCheck(data) ? "checked-radio" : ""
|
||||
}`}
|
||||
type="radio"
|
||||
disabled={
|
||||
props.isNeedSign &&
|
||||
(props.pos.options?.isReadOnly ||
|
||||
props.data?.signerObjId !== props.signerObjId)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<label
|
||||
htmlFor={`radio-${props.pos.key + ind}`}
|
||||
style={{ fontSize: fontSize, color: fontColor }}
|
||||
className="text-xs mb-0"
|
||||
>
|
||||
{data}
|
||||
</label>
|
||||
)}
|
||||
checked={handleRadioCheck(data)}
|
||||
/>
|
||||
{!props.pos.options?.isHideLabel && (
|
||||
<span className="leading-none">{data}</span>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
@@ -824,23 +466,10 @@ function PlaceholderType(props) {
|
||||
case textWidget:
|
||||
return (
|
||||
<textarea
|
||||
readOnly
|
||||
placeholder={t("widgets-name.text")}
|
||||
rows={1}
|
||||
onKeyDown={handleEnterPress}
|
||||
value={textValue}
|
||||
onBlur={handleInputBlur}
|
||||
onChange={(e) => {
|
||||
setTextValue(e.target.value);
|
||||
onChangeInput(
|
||||
e.target.value,
|
||||
props.pos.key,
|
||||
props.xyPosition,
|
||||
props.index,
|
||||
props.setXyPosition,
|
||||
props.data && props.data?.Id,
|
||||
false
|
||||
);
|
||||
}}
|
||||
value={widgetValue}
|
||||
className={textWidgetCls}
|
||||
style={{
|
||||
fontFamily: "Arial, sans-serif",
|
||||
@@ -872,9 +501,7 @@ function PlaceholderType(props) {
|
||||
}}
|
||||
className="font-medium"
|
||||
>
|
||||
{props.isNeedSign
|
||||
? props.pos?.options?.hint || widgetTypeTranslation
|
||||
: widgetTypeTranslation}
|
||||
{hint || widgetTypeTranslation}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -95,15 +95,12 @@ function RenderPdf(props) {
|
||||
return (
|
||||
<React.Fragment key={key}>
|
||||
{placeData.pageNumber === props.pageNumber &&
|
||||
placeData.pos.map((pos) => {
|
||||
placeData.pos.map((pos, ind) => {
|
||||
return (
|
||||
pos && (
|
||||
<React.Fragment key={pos.key}>
|
||||
<React.Fragment key={ind}>
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setSignKey={props.setSignKey}
|
||||
setIsSignPad={props.setIsSignPad}
|
||||
setIsStamp={props.setIsStamp}
|
||||
handleSignYourselfImageResize={handleImageResize}
|
||||
index={props.pageNumber}
|
||||
xyPosition={props.signerPos}
|
||||
@@ -121,11 +118,7 @@ function RenderPdf(props) {
|
||||
posHeight={posHeight}
|
||||
isDragging={props.isDragging}
|
||||
pdfDetails={props.pdfDetails}
|
||||
setIsInitial={props.setIsInitial}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
unSignedWidgetId={props.unSignedWidgetId}
|
||||
setSelectWidgetId={props.setSelectWidgetId}
|
||||
selectWidgetId={props.selectWidgetId}
|
||||
setCurrWidgetsDetails={props.setCurrWidgetsDetails}
|
||||
uniqueId={props.uniqueId}
|
||||
scale={props.scale}
|
||||
@@ -139,7 +132,6 @@ function RenderPdf(props) {
|
||||
isAgree={props.isAgree}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
setWidgetType={props.setWidgetType}
|
||||
setUniqueId={props.setUniqueId}
|
||||
setIsSelectId={props.setIsSelectId}
|
||||
handleDeleteSign={props.handleDeleteSign}
|
||||
@@ -156,6 +148,8 @@ function RenderPdf(props) {
|
||||
setFontColor={props.setFontColor}
|
||||
setRequestSignTour={props.setRequestSignTour}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={props?.currWidgetsDetails}
|
||||
setTempSignerId={props.setTempSignerId}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
@@ -241,11 +235,9 @@ function RenderPdf(props) {
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
setSignKey={props.setSignKey}
|
||||
handleDeleteSign={
|
||||
props.handleDeleteSign
|
||||
}
|
||||
setIsStamp={props.setIsStamp}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
@@ -270,18 +262,11 @@ function RenderPdf(props) {
|
||||
posHeight={posHeight}
|
||||
isDragging={props.isDragging}
|
||||
setIsValidate={props.setIsValidate}
|
||||
setWidgetType={props.setWidgetType}
|
||||
setIsRadio={props.setIsRadio}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={
|
||||
props.setSelectWidgetId
|
||||
}
|
||||
selectWidgetId={
|
||||
props.selectWidgetId
|
||||
}
|
||||
handleNameModal={
|
||||
props.handleNameModal
|
||||
}
|
||||
@@ -309,6 +294,9 @@ function RenderPdf(props) {
|
||||
calculateFontsize={
|
||||
calculateFontsize
|
||||
}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -331,9 +319,7 @@ function RenderPdf(props) {
|
||||
key={id}
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
setSignKey={props.setSignKey}
|
||||
handleDeleteSign={props.handleDeleteSign}
|
||||
setIsStamp={props.setIsStamp}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
@@ -343,19 +329,13 @@ function RenderPdf(props) {
|
||||
xyPosition={props.xyPosition}
|
||||
setXyPosition={props.setXyPosition}
|
||||
containerWH={props.containerWH}
|
||||
setIsSignPad={props.setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={props.pdfDetails[0]}
|
||||
isDragging={props.isDragging}
|
||||
setIsInitial={props.setIsInitial}
|
||||
setWidgetType={props.setWidgetType}
|
||||
setSelectWidgetId={props.setSelectWidgetId}
|
||||
selectWidgetId={props.selectWidgetId}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
@@ -374,6 +354,9 @@ function RenderPdf(props) {
|
||||
isFreeResize={false}
|
||||
isOpenSignPad={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@@ -388,7 +371,7 @@ function RenderPdf(props) {
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
onClick={() =>
|
||||
props.setSelectWidgetId && props.setSelectWidgetId("")
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
@@ -454,11 +437,9 @@ function RenderPdf(props) {
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
setSignKey={props.setSignKey}
|
||||
handleDeleteSign={
|
||||
props.handleDeleteSign
|
||||
}
|
||||
setIsStamp={props.setIsStamp}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={props.handleStop}
|
||||
handleSignYourselfImageResize={
|
||||
@@ -483,18 +464,11 @@ function RenderPdf(props) {
|
||||
posHeight={posHeight}
|
||||
isDragging={props.isDragging}
|
||||
setIsValidate={props.setIsValidate}
|
||||
setWidgetType={props.setWidgetType}
|
||||
setIsRadio={props.setIsRadio}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
setSelectWidgetId={
|
||||
props.setSelectWidgetId
|
||||
}
|
||||
selectWidgetId={
|
||||
props.selectWidgetId
|
||||
}
|
||||
handleNameModal={
|
||||
props.handleNameModal
|
||||
}
|
||||
@@ -522,6 +496,9 @@ function RenderPdf(props) {
|
||||
calculateFontsize={
|
||||
calculateFontsize
|
||||
}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -544,9 +521,7 @@ function RenderPdf(props) {
|
||||
<Placeholder
|
||||
pos={pos}
|
||||
setIsPageCopy={props.setIsPageCopy}
|
||||
setSignKey={props.setSignKey}
|
||||
handleDeleteSign={props.handleDeleteSign}
|
||||
setIsStamp={props.setIsStamp}
|
||||
handleTabDrag={props.handleTabDrag}
|
||||
handleStop={(event, dragElement) =>
|
||||
props.handleStop(
|
||||
@@ -561,19 +536,13 @@ function RenderPdf(props) {
|
||||
index={props.index}
|
||||
xyPosition={props.xyPosition}
|
||||
setXyPosition={props.setXyPosition}
|
||||
setIsSignPad={props.setIsSignPad}
|
||||
isShowBorder={true}
|
||||
isSignYourself={true}
|
||||
posWidth={posWidth}
|
||||
posHeight={posHeight}
|
||||
pdfDetails={props.pdfDetails[0]}
|
||||
isDragging={props.isDragging}
|
||||
setIsInitial={props.setIsInitial}
|
||||
setWidgetType={props.setWidgetType}
|
||||
setSelectWidgetId={props.setSelectWidgetId}
|
||||
selectWidgetId={props.selectWidgetId}
|
||||
setIsCheckbox={props.setIsCheckbox}
|
||||
setValidateAlert={props.setValidateAlert}
|
||||
setCurrWidgetsDetails={
|
||||
props.setCurrWidgetsDetails
|
||||
}
|
||||
@@ -593,6 +562,9 @@ function RenderPdf(props) {
|
||||
isFreeResize={false}
|
||||
isOpenSignPad={true}
|
||||
calculateFontsize={calculateFontsize}
|
||||
currWidgetsDetails={
|
||||
props?.currWidgetsDetails
|
||||
}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -608,7 +580,7 @@ function RenderPdf(props) {
|
||||
loading={t("loading-doc")}
|
||||
onLoadSuccess={props.pageDetails}
|
||||
onClick={() =>
|
||||
props.setSelectWidgetId && props.setSelectWidgetId("")
|
||||
props.setCurrWidgetsDetails && props.setCurrWidgetsDetails({})
|
||||
}
|
||||
file={pdfDataBase64}
|
||||
>
|
||||
|
||||
@@ -9,7 +9,8 @@ function SelectLanguage(props) {
|
||||
{ value: "es", text: "Española" }, //spanish
|
||||
{ value: "fr", text: "Français" }, //french
|
||||
{ value: "it", text: "Italiano" }, //italian
|
||||
{ value: "de", text: "Deutsch" } //german
|
||||
{ value: "de", text: "Deutsch" }, //german
|
||||
{ value: "hi", text: "हिन्दी" } //hindi
|
||||
];
|
||||
const defaultLanguage = i18next.language || "en";
|
||||
const [lang, setLang] = useState(defaultLanguage);
|
||||
|
||||
@@ -1,854 +0,0 @@
|
||||
import React, { useRef, useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SignatureCanvas from "react-signature-canvas";
|
||||
import Parse from "parse";
|
||||
import {
|
||||
generateTitleFromFilename,
|
||||
getBase64FromUrl,
|
||||
getSecureUrl
|
||||
} from "../../constant/Utils";
|
||||
import sanitizeFileName from "../../primitives/sanitizeFileName";
|
||||
import { SaveFileSize } from "../../constant/saveFileSize";
|
||||
import Loader from "../../primitives/Loader";
|
||||
|
||||
function SignPad(props) {
|
||||
const { t } = useTranslation();
|
||||
const [penColor, setPenColor] = useState("blue");
|
||||
const allColor = ["blue", "red", "black"];
|
||||
const canvasRef = useRef(null);
|
||||
const [isDefaultSign, setIsDefaultSign] = useState(false);
|
||||
const [isTab, setIsTab] = useState("");
|
||||
const [isSignImg, setIsSignImg] = useState("");
|
||||
const [textWidth, setTextWidth] = useState(0);
|
||||
const [textHeight, setTextHeight] = useState(0);
|
||||
const [signatureType, setSignatureType] = useState("");
|
||||
const [isSignTypes, setIsSignTypes] = useState(true);
|
||||
const [typedSignature, setTypedSignature] = useState("");
|
||||
const fontOptions = [
|
||||
{ value: "Fasthand" },
|
||||
{ value: "Dancing Script" },
|
||||
{ value: "Cedarville Cursive" },
|
||||
{ value: "Delicious Handrawn" }
|
||||
// Add more font options as needed
|
||||
];
|
||||
const [fontSelect, setFontSelect] = useState(fontOptions[0].value);
|
||||
const [isSavedSign, setIsSavedSign] = useState(false);
|
||||
const [isLoader, setIsLoader] = useState(false);
|
||||
const accesstoken = localStorage.getItem("accesstoken") || "";
|
||||
const senderUser = localStorage.getItem(
|
||||
`Parse/${localStorage.getItem("parseAppId")}/currentUser`
|
||||
);
|
||||
const jsonSender = senderUser && JSON.parse(senderUser);
|
||||
const currentUserName = jsonSender && jsonSender?.name;
|
||||
useEffect(() => {
|
||||
handleTab();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.signatureTypes]);
|
||||
function handleTab() {
|
||||
const signtypes = props?.signatureTypes || [];
|
||||
const defaultIndex = signtypes?.findIndex(
|
||||
(x) =>
|
||||
x.name === "default" &&
|
||||
x.enabled === true &&
|
||||
props.defaultSign &&
|
||||
(props?.currWidgetsDetails?.type || props.widgetType) !== "image" &&
|
||||
(props?.currWidgetsDetails?.type || props.widgetType) !== "stamp"
|
||||
);
|
||||
const getIndex =
|
||||
defaultIndex !== -1 // Check if the default index exists
|
||||
? defaultIndex // If found, use it
|
||||
: signtypes?.findIndex((x) => x.enabled === true);
|
||||
|
||||
if (getIndex !== -1) {
|
||||
setIsSignTypes(true);
|
||||
const tab = props?.signatureTypes[getIndex].name;
|
||||
if (tab === "draw") {
|
||||
setIsTab("draw");
|
||||
setSignatureType("draw");
|
||||
} else if (tab === "upload") {
|
||||
props?.setIsImageSelect(true);
|
||||
setIsTab("uploadImage");
|
||||
} else if (tab === "typed") {
|
||||
setIsTab("type");
|
||||
} else if (tab === "default") {
|
||||
if (
|
||||
(props?.isInitial && props?.myInitial) ||
|
||||
(!props?.isInitial && props?.defaultSign)
|
||||
) {
|
||||
setIsDefaultSign(true);
|
||||
setIsTab("mysignature");
|
||||
} else {
|
||||
setIsTab("draw");
|
||||
}
|
||||
} else {
|
||||
setIsTab(true);
|
||||
}
|
||||
} else {
|
||||
setIsSignTypes(false);
|
||||
}
|
||||
}
|
||||
function isTabEnabled(tabName) {
|
||||
const isEnabled = props?.signatureTypes.find(
|
||||
(x) => x.name === tabName
|
||||
)?.enabled;
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
//function for clear signature image
|
||||
const handleClear = () => {
|
||||
if (isTab === "draw") {
|
||||
if (canvasRef.current) {
|
||||
canvasRef.current.clear();
|
||||
} else if (props?.isStamp) {
|
||||
props?.setImage("");
|
||||
}
|
||||
setIsSignImg("");
|
||||
} else if (isTab === "uploadImage") {
|
||||
props?.setImage("");
|
||||
}
|
||||
};
|
||||
//function for set signature url
|
||||
const handleSignatureChange = (data) => {
|
||||
props?.setSignature(data);
|
||||
setIsSignImg(data);
|
||||
};
|
||||
function base64StringtoFile(base64String, filename) {
|
||||
let arr = base64String.split(","),
|
||||
// type of uploaded image
|
||||
mime = arr[0].match(/:(.*?);/)[1],
|
||||
// decode base64
|
||||
bstr = atob(arr[1]),
|
||||
n = bstr.length,
|
||||
u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
const ext = mime.split("/").pop();
|
||||
const name = `${filename}.${ext}`;
|
||||
return new File([u8arr], name, { type: mime });
|
||||
}
|
||||
|
||||
const uploadFile = async (file) => {
|
||||
try {
|
||||
const parseFile = new Parse.File(file.name, file);
|
||||
const response = await parseFile.save();
|
||||
if (response?.url()) {
|
||||
const fileRes = await getSecureUrl(response.url());
|
||||
if (fileRes.url) {
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
SaveFileSize(file.size, fileRes.url, tenantId);
|
||||
return fileRes?.url;
|
||||
} else {
|
||||
alert(`${t("something-went-wrong-mssg")}`);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
alert(`${t("something-went-wrong-mssg")}`);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("sign upload err", err);
|
||||
alert(`${err.message}`);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// `handlesavesign` is used to save signaute, initials, stamp as a default
|
||||
const handleSaveSign = async () => {
|
||||
if (isSignImg || props?.image?.src) {
|
||||
setIsLoader(true);
|
||||
try {
|
||||
const User = Parse?.User?.current();
|
||||
const sanitizename = generateTitleFromFilename(User?.get("name"));
|
||||
const replaceSpace = sanitizeFileName(sanitizename);
|
||||
let file;
|
||||
if (isSignImg) {
|
||||
file = base64StringtoFile(isSignImg, `${replaceSpace}__sign`);
|
||||
} else {
|
||||
file = base64StringtoFile(props?.image?.src, `${replaceSpace}__sign`);
|
||||
}
|
||||
const imageUrl = await uploadFile(file);
|
||||
const userId = {
|
||||
__type: "Pointer",
|
||||
className: "_User",
|
||||
objectId: User?.id
|
||||
};
|
||||
if (imageUrl) {
|
||||
// below code is used to save or update default signaute, initials, stamp
|
||||
try {
|
||||
const signCls = new Parse.Object("contracts_Signature");
|
||||
if (props?.saveSignCheckbox?.signId) {
|
||||
signCls.id = props.saveSignCheckbox.signId;
|
||||
}
|
||||
if (
|
||||
props.currWidgetsDetails?.type === "initials" ||
|
||||
props?.widgetType === "initials"
|
||||
) {
|
||||
signCls.set("Initials", imageUrl);
|
||||
} else if (
|
||||
props.currWidgetsDetails?.type === "signature" ||
|
||||
props?.widgetType === "signature"
|
||||
) {
|
||||
signCls.set("ImageURL", imageUrl);
|
||||
}
|
||||
signCls.set("UserId", userId);
|
||||
const signRes = await signCls.save();
|
||||
if (signRes) {
|
||||
props.saveSignCheckbox.signId;
|
||||
props.setSaveSignCheckbox((prev) => ({
|
||||
...prev,
|
||||
signId: signRes?.id
|
||||
}));
|
||||
const _signRes = JSON.parse(JSON.stringify(signRes));
|
||||
if (
|
||||
props.currWidgetsDetails?.type === "signature" ||
|
||||
props?.widgetType === "signature"
|
||||
) {
|
||||
const defaultSign = await getBase64FromUrl(
|
||||
_signRes?.ImageURL,
|
||||
true
|
||||
);
|
||||
props.setDefaultSignImg(defaultSign);
|
||||
} else if (
|
||||
props.currWidgetsDetails?.type === "initials" ||
|
||||
props?.widgetType === "initials"
|
||||
) {
|
||||
const defaultInitials = await getBase64FromUrl(
|
||||
_signRes?.Initials,
|
||||
true
|
||||
);
|
||||
props.setMyInitial(defaultInitials);
|
||||
}
|
||||
alert(t("saved-successfully"));
|
||||
}
|
||||
return signRes;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
alert(`${err.message}`);
|
||||
} finally {
|
||||
setIsLoader(false);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("Err while saving signature", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveBtn = async () => {
|
||||
if (accesstoken && isSavedSign) {
|
||||
await handleSaveSign();
|
||||
resetToDefault();
|
||||
} else {
|
||||
resetToDefault();
|
||||
}
|
||||
};
|
||||
const resetToDefault = () => {
|
||||
props?.setCurrWidgetsDetails({});
|
||||
if (!props?.image) {
|
||||
if (isTab === "mysignature") {
|
||||
setIsSignImg("");
|
||||
if (props?.isInitial) {
|
||||
props?.onSaveSign(signatureType, "initials");
|
||||
} else {
|
||||
props?.onSaveSign(null, "default");
|
||||
}
|
||||
} else {
|
||||
if (isTab === "type") {
|
||||
setIsSignImg("");
|
||||
props?.onSaveSign(
|
||||
null,
|
||||
false,
|
||||
!props?.isInitial && textWidth > 150 ? 150 : textWidth,
|
||||
!props?.isInitial && textHeight > 35 ? 35 : textHeight,
|
||||
typedSignature
|
||||
);
|
||||
} else {
|
||||
setIsSignImg("");
|
||||
canvasRef.current.clear();
|
||||
props?.onSaveSign(signatureType);
|
||||
}
|
||||
}
|
||||
setPenColor("blue");
|
||||
} else {
|
||||
setIsSignImg("");
|
||||
props?.onSaveImage(signatureType);
|
||||
}
|
||||
props?.setIsSignPad(false);
|
||||
props?.setIsInitial && props?.setIsInitial(false);
|
||||
props?.setIsImageSelect(false);
|
||||
setIsDefaultSign(false);
|
||||
props?.setImage();
|
||||
handleTab();
|
||||
props?.setIsStamp(false);
|
||||
};
|
||||
//save button component
|
||||
const SaveBtn = () => {
|
||||
return (
|
||||
<div>
|
||||
{(isTab === "draw" || isTab === "uploadImage") && (
|
||||
<button
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost mr-1 mt-[2px]"
|
||||
onClick={() => handleClear()}
|
||||
>
|
||||
{t("clear")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleSaveBtn()}
|
||||
type="button"
|
||||
className={`${
|
||||
isSignImg ||
|
||||
props?.image ||
|
||||
isDefaultSign ||
|
||||
textWidth ||
|
||||
props.isAutoSign
|
||||
? ""
|
||||
: "pointer-events-none"
|
||||
} op-btn op-btn-primary shadow-lg`}
|
||||
disabled={
|
||||
(isTab === "draw" && isSignImg) ||
|
||||
(isTab === "image" && props?.image) ||
|
||||
(isTab === "mysignature" && isDefaultSign) ||
|
||||
(isTab === "type" && typedSignature) ||
|
||||
props.isAutoSign
|
||||
? false
|
||||
: props?.image
|
||||
? false
|
||||
: true
|
||||
}
|
||||
>
|
||||
{t("save")}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const autoSignAll = () => {
|
||||
return (
|
||||
<label className="cursor-pointer flex items-center mb-[6px] text-center text-[11px] md:text-base">
|
||||
<input
|
||||
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
|
||||
type="checkbox"
|
||||
value={props.isAutoSign}
|
||||
onChange={(e) => {
|
||||
props.setIsAutoSign(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
{t("auto-sign-mssg")}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
//useEffect for set already draw or save signature url/text url of signature text type and draw type for initial type and signature type widgets
|
||||
useEffect(() => {
|
||||
if (props?.currWidgetsDetails && canvasRef.current && props.isSignPad) {
|
||||
const isWidgetType = props?.currWidgetsDetails?.type;
|
||||
const signatureType = props?.currWidgetsDetails?.signatureType;
|
||||
const url = props?.currWidgetsDetails?.SignUrl;
|
||||
//checking widget type and draw type signature url
|
||||
if (props?.isInitial) {
|
||||
if (isWidgetType === "initials" && signatureType === "draw" && url) {
|
||||
canvasRef.current.fromDataURL(url);
|
||||
}
|
||||
} else if (
|
||||
isWidgetType === "signature" &&
|
||||
signatureType === "draw" &&
|
||||
url
|
||||
) {
|
||||
canvasRef.current.fromDataURL(url);
|
||||
}
|
||||
|
||||
const trimmedName = currentUserName && currentUserName?.trim();
|
||||
const firstCharacter = trimmedName?.charAt(0);
|
||||
const userName = props?.isInitial ? firstCharacter : currentUserName;
|
||||
const signatureValue = props?.currWidgetsDetails?.typeSignature;
|
||||
setTypedSignature(signatureValue || userName || "");
|
||||
setFontSelect("Fasthand");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.isSignPad]);
|
||||
useEffect(() => {
|
||||
const loadFont = async () => {
|
||||
try {
|
||||
await document.fonts.load(`20px ${fontSelect}`);
|
||||
const selectFontSTyle = fontOptions.find(
|
||||
(font) => font.value === fontSelect
|
||||
);
|
||||
setFontSelect(selectFontSTyle?.value || fontOptions[0].value);
|
||||
} catch (error) {
|
||||
console.error("Error loading font:", error);
|
||||
}
|
||||
};
|
||||
|
||||
loadFont();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fontSelect]);
|
||||
useEffect(() => {
|
||||
// Load the default signature after the component mounts
|
||||
if (canvasRef.current) {
|
||||
canvasRef.current.fromDataURL(isSignImg);
|
||||
}
|
||||
if (isTab === "type") {
|
||||
const trimmedName = typedSignature
|
||||
? typedSignature?.trim()
|
||||
: currentUserName?.trim();
|
||||
const firstCharacter = trimmedName?.charAt(0);
|
||||
const userName = props?.isInitial ? firstCharacter : typedSignature;
|
||||
const signatureValue = props?.currWidgetsDetails?.typeSignature;
|
||||
setTypedSignature(signatureValue || userName || "");
|
||||
convertToImg(fontSelect, userName);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isTab]);
|
||||
//function for convert input text value in image
|
||||
const convertToImg = async (fontStyle, text, color) => {
|
||||
//get text content to convert in image
|
||||
const textContent = text;
|
||||
const fontfamily = fontStyle
|
||||
? fontStyle
|
||||
: fontSelect
|
||||
? fontSelect
|
||||
: "Fasthand";
|
||||
const fontSizeValue = "40px";
|
||||
//creating span for getting text content width
|
||||
const span = document.createElement("span");
|
||||
span.textContent = textContent;
|
||||
span.style.font = `${fontSizeValue} ${fontfamily}`; // here put your text size and font family
|
||||
span.style.color = color ? color : penColor;
|
||||
span.style.display = "hidden";
|
||||
document.body.appendChild(span); // Replace 'container' with the ID of the container element
|
||||
|
||||
//create canvas to render text in canvas and convert in image
|
||||
const canvasElement = document.createElement("canvas");
|
||||
// Draw the text content on the canvas
|
||||
const ctx = canvasElement.getContext("2d");
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
const addExtraWidth = props?.isInitial ? 10 : 50;
|
||||
const width = span.offsetWidth + addExtraWidth;
|
||||
const height = span.offsetHeight;
|
||||
setTextWidth(width);
|
||||
setTextHeight(height);
|
||||
const font = span.style["font"];
|
||||
// Set the canvas dimensions to match the span
|
||||
canvasElement.width = width * pixelRatio;
|
||||
canvasElement.height = height * pixelRatio;
|
||||
|
||||
// You can customize text styles if needed
|
||||
ctx.font = font;
|
||||
ctx.fillStyle = color ? color : penColor; // Set the text color
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.scale(pixelRatio, pixelRatio);
|
||||
// Draw the content of the span onto the canvas
|
||||
ctx.fillText(span.textContent, width / 2, height / 2); // Adjust the x,y-coordinate as needed
|
||||
//remove span tag
|
||||
document.body.removeChild(span);
|
||||
// Convert the canvas to image data
|
||||
const dataUrl = canvasElement.toDataURL("image/png");
|
||||
props?.setSignature(dataUrl);
|
||||
};
|
||||
const PenColorComponent = (props) => {
|
||||
return (
|
||||
<div className="flex flex-row items-center m-[5px] gap-2">
|
||||
{allColor.map((data, key) => {
|
||||
return (
|
||||
<i
|
||||
key={key}
|
||||
onClick={() => {
|
||||
props?.convertToImg &&
|
||||
props?.convertToImg(fontSelect, typedSignature, data);
|
||||
setPenColor(allColor[key]);
|
||||
}}
|
||||
className={`border-b-[2px] ${key === 0 && penColor === "blue" ? "border-blue-600" : key === 1 && penColor === "red" ? "border-red-500" : key === 2 && penColor === "black" ? "border-black" : "border-white"} text-[${data}] text-[16px] fa-light fa-pen-nib`}
|
||||
></i>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// `handleCancelBtn` function trigger when user click on cross button
|
||||
const handleCancelBtn = () => {
|
||||
setPenColor("blue");
|
||||
props?.setIsSignPad(false);
|
||||
props?.setIsInitial && props?.setIsInitial(false);
|
||||
props?.setIsImageSelect(false);
|
||||
setIsDefaultSign(false);
|
||||
props?.setImage();
|
||||
handleTab();
|
||||
props?.setIsStamp(false);
|
||||
};
|
||||
|
||||
const savesigncheckbox = (
|
||||
<label className="cursor-pointer flex items-center mb-0 text-center text-[11px] md:text-base">
|
||||
<input
|
||||
className="mr-2 md:mr-3 op-checkbox op-checkbox-xs md:op-checkbox-sm"
|
||||
type="checkbox"
|
||||
checked={isSavedSign}
|
||||
onChange={(e) => setIsSavedSign(e.target.checked)}
|
||||
/>
|
||||
Save {props?.currWidgetsDetails?.type || props?.widgetType}
|
||||
</label>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
{props?.isSignPad && (
|
||||
<div className="op-modal op-modal-open">
|
||||
<div className="op-modal-box px-[13px] pt-2 pb-0">
|
||||
{isLoader && (
|
||||
<div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50">
|
||||
<Loader />
|
||||
</div>
|
||||
)}
|
||||
{isSignTypes ? (
|
||||
<>
|
||||
<div className="flex justify-between text-base-content items-center">
|
||||
<div className="text-[1.2rem]">
|
||||
<div className="flex flex-row justify-between mt-[3px]">
|
||||
<div className="flex flex-row justify-between gap-[5px] md:gap-[8px] text-[11px] md:text-base">
|
||||
{props?.isStamp ? (
|
||||
<span className="text-base-content font-bold text-lg">
|
||||
{props?.widgetType === "image" ||
|
||||
props?.currWidgetsDetails?.type === "image"
|
||||
? t("upload-image")
|
||||
: t("upload-stamp-image")}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
{!props?.isInitial &&
|
||||
props?.defaultSign &&
|
||||
isTabEnabled("default") ? (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(true);
|
||||
props?.setIsImageSelect(true);
|
||||
setIsTab("mysignature");
|
||||
setSignatureType("");
|
||||
props?.setImage();
|
||||
}}
|
||||
className={`${
|
||||
isTab === "mysignature"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("my-signature")}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
props?.isInitial &&
|
||||
props?.myInitial &&
|
||||
isTabEnabled("default") && (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(true);
|
||||
props?.setIsImageSelect(true);
|
||||
setIsTab("mysignature");
|
||||
setSignatureType("");
|
||||
props?.setImage();
|
||||
}}
|
||||
className={`${
|
||||
isTab === "mysignature"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("my-initials")}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{isTabEnabled("draw") && (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(false);
|
||||
props?.setIsImageSelect(false);
|
||||
setIsTab("draw");
|
||||
props?.setImage();
|
||||
if (isSignImg) {
|
||||
props?.setSignature(isSignImg);
|
||||
}
|
||||
}}
|
||||
className={`${
|
||||
isTab === "draw"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("draw")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{isTabEnabled("upload") && (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(false);
|
||||
props?.setIsImageSelect(true);
|
||||
setIsTab("uploadImage");
|
||||
setSignatureType("");
|
||||
}}
|
||||
className={`${
|
||||
isTab === "uploadImage"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("upload-image")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{isTabEnabled("typed") && (
|
||||
<div>
|
||||
<span
|
||||
onClick={() => {
|
||||
setIsDefaultSign(false);
|
||||
props?.setIsImageSelect(false);
|
||||
setIsTab("type");
|
||||
setSignatureType("");
|
||||
props?.setImage();
|
||||
}}
|
||||
className={`${
|
||||
isTab === "type"
|
||||
? "op-link-primary"
|
||||
: "no-underline"
|
||||
} op-link underline-offset-8 ml-[2px]`}
|
||||
>
|
||||
{t("type")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="text-[1.5rem] cursor-pointer"
|
||||
onClick={handleCancelBtn}
|
||||
>
|
||||
×
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-[20px] h-full">
|
||||
{isDefaultSign ? (
|
||||
<>
|
||||
{!props?.isInitial &&
|
||||
props?.defaultSign &&
|
||||
isTabEnabled("default") && (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className={`${props?.isInitial ? "intialSignatureCanvas" : "signatureCanvas"} bg-white border-[1.3px] border-[#007bff] flex flex-col justify-center items-center mb-[6px] cursor-pointer`}
|
||||
>
|
||||
<img
|
||||
alt="stamp img"
|
||||
className="w-full h-full object-contain bg-white"
|
||||
draggable="false"
|
||||
src={
|
||||
props?.isInitial
|
||||
? props?.myInitial
|
||||
: props?.defaultSign
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{props.setIsAutoSign && autoSignAll()}
|
||||
<div className="flex justify-end">
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{props?.isInitial &&
|
||||
props?.myInitial &&
|
||||
isTabEnabled("default") && (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className={`${props?.isInitial ? "intialSignatureCanvas" : "signatureCanvas"} bg-white border-[1.3px] border-[#007bff] flex flex-col justify-center items-center mb-[6px] cursor-pointer`}
|
||||
>
|
||||
<img
|
||||
alt="stamp img"
|
||||
className="w-full h-full object-contain bg-white"
|
||||
draggable="false"
|
||||
src={
|
||||
props?.isInitial
|
||||
? props?.myInitial
|
||||
: props?.defaultSign
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{props.setIsAutoSign && autoSignAll()}
|
||||
<div className="flex justify-end">
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
) : props?.isImageSelect || props?.isStamp ? (
|
||||
!props?.image ? (
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className={`${props?.isInitial ? "intialSignatureCanvas" : "signatureCanvas"} bg-white border-[1.3px] border-[#007bff] flex flex-col justify-center items-center mb-[6px] cursor-pointer`}
|
||||
onClick={() => props?.imageRef.current.click()}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
onChange={props?.onImageChange}
|
||||
className="filetype"
|
||||
accept="image/png,image/jpeg"
|
||||
ref={props?.imageRef}
|
||||
hidden
|
||||
/>
|
||||
<i className="fa-light fa-cloud-upload-alt uploadImgLogo"></i>
|
||||
<div className="text-[10px]">{t("upload")}</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<div
|
||||
className={`${props?.isInitial ? "intialSignatureCanvas" : "signatureCanvas"} bg-white border-[1.3px] border-[#007bff] mb-[6px] overflow-hidden`}
|
||||
>
|
||||
<img
|
||||
alt="print img"
|
||||
ref={props?.imageRef}
|
||||
src={props?.image.src}
|
||||
draggable="false"
|
||||
className="object-contain h-full w-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{props.setIsAutoSign && autoSignAll()}
|
||||
<div className="flex justify-end">
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
) : isTab === "type" ? (
|
||||
<div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="mr-[5px] text-[12px]">
|
||||
{props?.isInitial
|
||||
? t("initial-teb")
|
||||
: t("signature-tab")}
|
||||
:
|
||||
</span>
|
||||
<input
|
||||
maxLength={props?.isInitial ? 3 : 30}
|
||||
style={{ fontFamily: fontSelect, color: penColor }}
|
||||
type="text"
|
||||
className="ml-1 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content w-full text-[20px]"
|
||||
placeholder={
|
||||
props?.isInitial
|
||||
? t("initial-type")
|
||||
: t("signature-type")
|
||||
}
|
||||
value={typedSignature}
|
||||
onChange={(e) => {
|
||||
setTypedSignature(e.target.value);
|
||||
convertToImg(fontSelect, e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-[1px] border-[#d6d3d3] mt-[10px] ml-[5px]">
|
||||
{fontOptions.map((font, ind) => {
|
||||
return (
|
||||
<div
|
||||
key={ind}
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
fontFamily: font.value,
|
||||
backgroundColor:
|
||||
fontSelect === font.value &&
|
||||
"rgb(206 225 247)"
|
||||
}}
|
||||
onClick={() => {
|
||||
setFontSelect(font.value);
|
||||
convertToImg(font.value, typedSignature);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="py-[5px] px-[10px] text-[20px]"
|
||||
style={{ color: penColor }}
|
||||
>
|
||||
{typedSignature
|
||||
? typedSignature
|
||||
: "Your signature"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-between mt-[10px]">
|
||||
{props.setIsAutoSign && autoSignAll()}
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent convertToImg={convertToImg} />
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex justify-center">
|
||||
<SignatureCanvas
|
||||
ref={canvasRef}
|
||||
penColor={penColor}
|
||||
canvasProps={{
|
||||
className: `${props?.isInitial ? "intialSignatureCanvas" : "signatureCanvas"} border-[1.3px] border-[#007bff]`
|
||||
}}
|
||||
onEnd={() =>
|
||||
handleSignatureChange(
|
||||
canvasRef.current?.toDataURL()
|
||||
)
|
||||
}
|
||||
dotSize={1}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between mt-[10px]">
|
||||
{props.setIsAutoSign && autoSignAll()}
|
||||
{accesstoken &&
|
||||
props?.saveSignCheckbox?.isVisible &&
|
||||
savesigncheckbox}
|
||||
<div className="flex flex-row justify-between mt-[10px]">
|
||||
<PenColorComponent />
|
||||
<SaveBtn />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div>
|
||||
<div className="relative flex flex-row items-center justify-between">
|
||||
<div className="text-base-content font-bold text-lg">
|
||||
Signature
|
||||
</div>
|
||||
<div
|
||||
className="text-[1.5rem] cursor-pointer"
|
||||
onClick={handleCancelBtn}
|
||||
>
|
||||
×
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-3 mb-6 mt-3">
|
||||
<p>{t("at-least-one-signature-type")}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignPad;
|
||||
@@ -241,6 +241,7 @@ function WidgetComponent(props) {
|
||||
handleDivClick={props.handleDivClick}
|
||||
handleMouseLeave={props.handleMouseLeave}
|
||||
signRef={signRef}
|
||||
addPositionOfSignature={props.addPositionOfSignature}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,17 +25,28 @@ const WidgetNameModal = (props) => {
|
||||
const statusArr = ["Required", "Optional"];
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
|
||||
const handleHint = () => {
|
||||
const type = props.defaultdata?.type;
|
||||
|
||||
if (type === "signature") {
|
||||
return "Draw signature";
|
||||
} else if (type === "stamp" || type === "image") {
|
||||
return `Upload ${type}`;
|
||||
} else if (type === "initials") {
|
||||
return "Draw initial";
|
||||
} else if (type === textInputWidget) {
|
||||
return "Enter text";
|
||||
} else {
|
||||
return `Enter ${type}`;
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (props.defaultdata) {
|
||||
setFormdata({
|
||||
name: props.defaultdata?.options?.name || "",
|
||||
defaultValue: props.defaultdata?.options?.defaultValue || "",
|
||||
status: props.defaultdata?.options?.status || "required",
|
||||
hint:
|
||||
props.defaultdata?.options?.hint ||
|
||||
(props.defaultdata?.type === textInputWidget
|
||||
? "Enter text"
|
||||
: `Enter ${props.defaultdata?.options?.name}`),
|
||||
hint: props.defaultdata?.options?.hint || handleHint(),
|
||||
textvalidate:
|
||||
props.defaultdata?.options?.validation?.type === "regex"
|
||||
? props.defaultdata?.options?.validation?.pattern
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,19 @@ import React, { useEffect, useState } from "react";
|
||||
import AsyncSelect from "react-select/async";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import axios from "axios";
|
||||
import { handleUnlinkSigner } from "../../../constant/Utils";
|
||||
|
||||
const SelectSigners = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
signerPos,
|
||||
setSignerPos,
|
||||
signersData,
|
||||
setSignersData,
|
||||
uniqueId,
|
||||
isRemove,
|
||||
handleAddUser
|
||||
} = props;
|
||||
const [userList, setUserList] = useState([]);
|
||||
const [selected, setSelected] = useState();
|
||||
const [userData, setUserData] = useState({});
|
||||
@@ -30,7 +40,7 @@ const SelectSigners = (props) => {
|
||||
//checking if user select no signer option from dropdown
|
||||
if (item) {
|
||||
//checking selected signer is already assign to the document or not
|
||||
const alreadyAssign = props.signersData.some(
|
||||
const alreadyAssign = signersData.some(
|
||||
(item2) => item2.objectId === item.value
|
||||
);
|
||||
if (alreadyAssign) {
|
||||
@@ -49,7 +59,7 @@ const SelectSigners = (props) => {
|
||||
};
|
||||
const handleAdd = () => {
|
||||
if (userData && userData.objectId) {
|
||||
props.details(userData);
|
||||
handleAddUser(userData);
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
@@ -60,7 +70,13 @@ const SelectSigners = (props) => {
|
||||
};
|
||||
//function to use remove signer from assigned widgets in create template flow
|
||||
const handleRemove = () => {
|
||||
props.handleUnlinkSigner();
|
||||
handleUnlinkSigner(
|
||||
signerPos,
|
||||
setSignerPos,
|
||||
signersData,
|
||||
setSignersData,
|
||||
uniqueId
|
||||
);
|
||||
if (props.closePopup) {
|
||||
props.closePopup();
|
||||
}
|
||||
@@ -161,7 +177,7 @@ const SelectSigners = (props) => {
|
||||
<button className="op-btn op-btn-primary" onClick={() => handleAdd()}>
|
||||
{t("submit")}
|
||||
</button>
|
||||
{props.isExistSigner && props.handleUnlinkSigner && (
|
||||
{props.isExistSigner && isRemove && (
|
||||
<button
|
||||
className="op-btn op-btn-accent op-btn-outline"
|
||||
onClick={() => handleRemove()}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { PDFDocument, rgb, degrees } from "pdf-lib";
|
||||
import Parse from "parse";
|
||||
import { appInfo } from "./appinfo";
|
||||
@@ -21,7 +20,38 @@ export const isTabAndMobile = window.innerWidth < 1023;
|
||||
export const textInputWidget = "text input";
|
||||
export const textWidget = "text";
|
||||
export const radioButtonWidget = "radio button";
|
||||
export function getEnv() {
|
||||
return window?.RUNTIME_ENV || {};
|
||||
}
|
||||
|
||||
//function for create list of year for date widget
|
||||
export const range = (start, end, step) => {
|
||||
const range = [];
|
||||
for (let i = start; i <= end; i += step) {
|
||||
range.push(i);
|
||||
}
|
||||
return range;
|
||||
};
|
||||
//function for get year
|
||||
export const getYear = (date) => {
|
||||
const newYear = new Date(date).getFullYear();
|
||||
return newYear;
|
||||
};
|
||||
export const years = range(1950, getYear(new Date()) + 16, 1);
|
||||
export const months = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December"
|
||||
];
|
||||
export const fileasbytes = async (filepath) => {
|
||||
const response = await fetch(filepath); // Adjust the path accordingly
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
@@ -304,56 +334,73 @@ export const changeDateToMomentFormat = (format) => {
|
||||
}
|
||||
};
|
||||
export const addWidgetOptions = (type, signer, widgetValue) => {
|
||||
const defaultOpt = { name: type, status: "required" };
|
||||
const status = { status: "required" };
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return defaultOpt;
|
||||
return { ...status, name: "Signature" };
|
||||
case "stamp":
|
||||
return defaultOpt;
|
||||
return { ...status, name: "Upload stamp" };
|
||||
case "checkbox":
|
||||
return {
|
||||
...defaultOpt,
|
||||
options: { isReadOnly: false, isHideLabel: false }
|
||||
...status,
|
||||
name: "Checkbox",
|
||||
isReadOnly: false,
|
||||
isHideLabel: false
|
||||
};
|
||||
case textInputWidget:
|
||||
return { ...defaultOpt, isReadOnly: false };
|
||||
return { ...status, name: "Text", isReadOnly: false };
|
||||
case "initials":
|
||||
return defaultOpt;
|
||||
return { ...status, name: "Initials" };
|
||||
case "name":
|
||||
return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
|
||||
return {
|
||||
...status,
|
||||
name: "Name",
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
case "company":
|
||||
return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
|
||||
return {
|
||||
...status,
|
||||
name: "Company",
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
case "job title":
|
||||
return { ...defaultOpt, defaultValue: widgetValue ? widgetValue : "" };
|
||||
return {
|
||||
...status,
|
||||
name: "Job title",
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
case "date": {
|
||||
const dateFormat = signer?.DateFormat
|
||||
? selectFormat(signer?.DateFormat)
|
||||
: "MM/dd/yyyy";
|
||||
return {
|
||||
...defaultOpt,
|
||||
...status,
|
||||
name: "Date",
|
||||
response: getDate(signer?.DateFormat),
|
||||
validation: { format: dateFormat, type: "date-format" }
|
||||
};
|
||||
}
|
||||
case "image":
|
||||
return defaultOpt;
|
||||
return { ...status, name: "Upload image" };
|
||||
case "email":
|
||||
return {
|
||||
...defaultOpt,
|
||||
...status,
|
||||
name: "Email",
|
||||
validation: { type: "email", pattern: "" },
|
||||
defaultValue: widgetValue ? widgetValue : ""
|
||||
};
|
||||
case "dropdown":
|
||||
return defaultOpt;
|
||||
return { ...status, name: "Dropdown" };
|
||||
case radioButtonWidget:
|
||||
return {
|
||||
...defaultOpt,
|
||||
...status,
|
||||
name: "Radio button",
|
||||
values: [],
|
||||
isReadOnly: false,
|
||||
isHideLabel: false
|
||||
};
|
||||
case textWidget:
|
||||
return defaultOpt;
|
||||
return { ...status, name: "Text" };
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
@@ -362,30 +409,30 @@ export const addWidgetOptions = (type, signer, widgetValue) => {
|
||||
export const addWidgetSelfsignOptions = (type, getWidgetValue, owner) => {
|
||||
switch (type) {
|
||||
case "signature":
|
||||
return { name: "signature" };
|
||||
return { name: "Signature" };
|
||||
case "stamp":
|
||||
return { name: "stamp" };
|
||||
return { name: "Upload stamp" };
|
||||
case "checkbox":
|
||||
return { name: "checkbox" };
|
||||
return { name: "Checkbox" };
|
||||
case textWidget:
|
||||
return { name: "text" };
|
||||
return { name: "Text" };
|
||||
case "initials":
|
||||
return { name: "initials" };
|
||||
return { name: "Initials" };
|
||||
case "name":
|
||||
return {
|
||||
name: "name",
|
||||
name: "Name",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
case "company":
|
||||
return {
|
||||
name: "company",
|
||||
name: "Company",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
case "job title":
|
||||
return {
|
||||
name: "job title",
|
||||
name: "Job title",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: { type: "text", pattern: "" }
|
||||
};
|
||||
@@ -394,16 +441,16 @@ export const addWidgetSelfsignOptions = (type, getWidgetValue, owner) => {
|
||||
? selectFormat(owner?.DateFormat)
|
||||
: "MM/dd/yyyy";
|
||||
return {
|
||||
name: "date",
|
||||
name: "Date",
|
||||
response: getDate(owner?.DateFormat),
|
||||
validation: { format: dateFormat, type: "date-format" }
|
||||
};
|
||||
}
|
||||
case "image":
|
||||
return { name: "image" };
|
||||
return { name: "Upload image" };
|
||||
case "email":
|
||||
return {
|
||||
name: "email",
|
||||
name: "Email",
|
||||
defaultValue: getWidgetValue(type),
|
||||
validation: { type: "email", pattern: "" }
|
||||
};
|
||||
@@ -464,10 +511,6 @@ export const defaultWidthHeight = (type) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const resizeBorderExtraWidth = () => {
|
||||
return 20;
|
||||
};
|
||||
|
||||
export async function getBase64FromUrl(url, autosign) {
|
||||
const data = await fetch(url);
|
||||
const blob = await data.blob();
|
||||
@@ -640,10 +683,14 @@ export const signPdfFun = async (
|
||||
};
|
||||
|
||||
export const randomId = () => {
|
||||
const randomBytes = crypto.getRandomValues(new Uint16Array(1));
|
||||
const randomValue = randomBytes[0];
|
||||
const randomDigit = 1000 + (randomValue % 9000);
|
||||
return randomDigit;
|
||||
// 1. Grab a cryptographically-secure 32-bit random value
|
||||
const randomBytes = crypto.getRandomValues(new Uint32Array(1));
|
||||
const raw = randomBytes[0]; // 0 … 4 294 967 295
|
||||
|
||||
// 2. Collapse into a 90 000 000-wide band (0…89 999 999), then shift to 10 000 000…99 999 999
|
||||
const eightDigit = 10_000_000 + (raw % 90_000_000);
|
||||
|
||||
return eightDigit;
|
||||
};
|
||||
|
||||
export const createDocument = async (
|
||||
@@ -815,8 +862,6 @@ export const onChangeInput = (
|
||||
userId,
|
||||
initial,
|
||||
dateFormat,
|
||||
isDefaultEmpty,
|
||||
isRadio,
|
||||
fontSize,
|
||||
fontColor
|
||||
) => {
|
||||
@@ -853,22 +898,13 @@ export const onChangeInput = (
|
||||
}
|
||||
}
|
||||
};
|
||||
} else if (isDefaultEmpty) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
response: value,
|
||||
defaultValue: isRadio ? "" : []
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
...position.options,
|
||||
response: value,
|
||||
defaultValue:""
|
||||
defaultValue: ""
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -915,7 +951,8 @@ export const onChangeInput = (
|
||||
...positionData,
|
||||
options: {
|
||||
...positionData.options,
|
||||
response: value
|
||||
response: value,
|
||||
defaultValue: ""
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1078,7 +1115,7 @@ export const addInitialData = (signerPos, setXyPosition, value, userId) => {
|
||||
};
|
||||
|
||||
//function for embed document id
|
||||
export const embedDocId = async (pdfDoc, documentId, allPages) => {
|
||||
export const embedDocId = async (pdfOriginalWH, pdfDoc, documentId) => {
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
// `fontBytes` is used to embed custom font in pdf
|
||||
@@ -1087,19 +1124,21 @@ export const embedDocId = async (pdfDoc, documentId, allPages) => {
|
||||
);
|
||||
pdfDoc.registerFontkit(fontkit);
|
||||
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
|
||||
for (let i = 0; i < allPages; i++) {
|
||||
//pdfOriginalWH contained all pdf's pages width and height
|
||||
for (let i = 0; i < pdfOriginalWH?.length; i++) {
|
||||
const fontSize = 10;
|
||||
const textContent =
|
||||
documentId && `${appName} DocumentId: ${documentId} `;
|
||||
const pages = pdfDoc.getPages();
|
||||
const page = pages[i];
|
||||
const getSize = pdfOriginalWH[i];
|
||||
try {
|
||||
const getObj = compensateRotation(
|
||||
page.getRotation().angle,
|
||||
10,
|
||||
5,
|
||||
1,
|
||||
page.getSize(),
|
||||
getSize,
|
||||
fontSize,
|
||||
rgb(0.5, 0.5, 0.5),
|
||||
font,
|
||||
@@ -1196,7 +1235,6 @@ export function onSaveSign(
|
||||
return updatedArray;
|
||||
} //condition when user edit signature/initial then updated signature apply all existing drawn signatures
|
||||
else if (isApplyAll) {
|
||||
// console.log("signatureImg",signatureImg)
|
||||
const updatedArray = updateXYposition.map((page) => ({
|
||||
...page,
|
||||
pos: page.pos.map(
|
||||
@@ -1502,7 +1540,13 @@ const getWidgetsFontColor = (type) => {
|
||||
}
|
||||
};
|
||||
//function for embed multiple signature using pdf-lib
|
||||
export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
export const multiSignEmbed = async (
|
||||
pdfOriginalWH,
|
||||
widgets,
|
||||
pdfDoc,
|
||||
signyourself,
|
||||
scale
|
||||
) => {
|
||||
// `fontBytes` is used to embed custom font in pdf
|
||||
const fontBytes = await fileasbytes(
|
||||
"https://cdn.opensignlabs.com/webfonts/times.ttf"
|
||||
@@ -1511,6 +1555,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
|
||||
let hasError = false;
|
||||
for (let item of widgets) {
|
||||
//pdfOriginalWH contained all pdf's pages width and height
|
||||
//'getSize' is used to get particular pdf's page width and height
|
||||
const getSize = pdfOriginalWH.find(
|
||||
(page) => page?.pageNumber === item?.pageNumber
|
||||
);
|
||||
if (hasError) break; // Stop the outer loop if an error occurred
|
||||
const typeExist = item.pos.some((data) => data?.type);
|
||||
let updateItem;
|
||||
@@ -1630,10 +1679,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
].includes(position.type);
|
||||
if (position.type === "checkbox") {
|
||||
let checkboxGapFromTop, isCheck;
|
||||
let y = yPos(position);
|
||||
const optionsFontSize = fontSize || 13;
|
||||
const checkboxSize = fontSize;
|
||||
const checkboxTextGapFromLeft = fontSize + 5 || 22;
|
||||
let y = yPos(position) + 2;
|
||||
//calculate checkbox size to draw on pdf
|
||||
const checkboxSize = fontSize - 1;
|
||||
//calculate gap between checkbox and options
|
||||
const checkboxTextGapFromLeft = fontSize + 5;
|
||||
if (position?.options?.values.length > 0) {
|
||||
position?.options?.values.forEach((item, ind) => {
|
||||
const checkboxRandomId = "checkbox" + randomId();
|
||||
@@ -1645,13 +1695,11 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
} else if (position?.options?.defaultValue) {
|
||||
isCheck = position?.options?.defaultValue?.includes(ind);
|
||||
}
|
||||
|
||||
const checkbox = form.createCheckBox(checkboxRandomId);
|
||||
|
||||
if (ind > 0) {
|
||||
y = y + checkboxGapFromTop;
|
||||
} else {
|
||||
checkboxGapFromTop = fontSize + 5 || 26;
|
||||
checkboxGapFromTop = fontSize + 3.2;
|
||||
}
|
||||
|
||||
if (!position?.options?.isHideLabel) {
|
||||
@@ -1659,10 +1707,10 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
const optionsPosition = compensateRotation(
|
||||
page.getRotation().angle,
|
||||
xPos(position) + checkboxTextGapFromLeft,
|
||||
y,
|
||||
y - 3,
|
||||
1,
|
||||
page.getSize(),
|
||||
optionsFontSize,
|
||||
getSize,
|
||||
fontSize,
|
||||
updateColorInRgb,
|
||||
font,
|
||||
page
|
||||
@@ -1675,7 +1723,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
width: checkboxSize,
|
||||
height: checkboxSize
|
||||
};
|
||||
checkboxObj = getWidgetPosition(page, checkboxObj, 1);
|
||||
checkboxObj = getWidgetPosition(page, checkboxObj, 1, getSize);
|
||||
checkbox.addToPage(page, checkboxObj);
|
||||
|
||||
//applied which checkbox should be checked
|
||||
@@ -1745,7 +1793,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
: NewbreakTextIntoLines(textContent, fixedWidth);
|
||||
// Set initial y-coordinate for the first line
|
||||
let x = xPos(position);
|
||||
let y = yPos(position);
|
||||
let y = yPos(position) - 4;
|
||||
// Embed each line on the page
|
||||
for (const line of lines) {
|
||||
const textPosition = compensateRotation(
|
||||
@@ -1753,7 +1801,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
x,
|
||||
y,
|
||||
1,
|
||||
page.getSize(),
|
||||
getSize,
|
||||
fontSize,
|
||||
updateColorInRgb,
|
||||
font,
|
||||
@@ -1787,7 +1835,12 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
width: widgetWidth,
|
||||
height: widgetHeight
|
||||
};
|
||||
const dropdownOption = getWidgetPosition(page, dropdownObj, 1);
|
||||
const dropdownOption = getWidgetPosition(
|
||||
page,
|
||||
dropdownObj,
|
||||
1,
|
||||
getSize
|
||||
);
|
||||
const dropdownSelected = { ...dropdownOption, font: font };
|
||||
dropdown.defaultUpdateAppearances(font);
|
||||
dropdown.addToPage(page, dropdownSelected);
|
||||
@@ -1795,28 +1848,33 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
} else if (position.type === radioButtonWidget) {
|
||||
const radioRandomId = "radio" + randomId();
|
||||
const radioGroup = form.createRadioGroup(radioRandomId);
|
||||
let radioOptionGapFromTop;
|
||||
const optionsFontSize = fontSize || 13;
|
||||
const radioTextGapFromLeft = fontSize + 5 || 20;
|
||||
//draw radio button on document and options if hide label is enable
|
||||
let radioButtonFromTop;
|
||||
//getting radio buttons options text font size
|
||||
const optionsFontSize = fontSize;
|
||||
//calculate value of gap between radio button and options
|
||||
const radioTextGapFromLeft = fontSize + 6;
|
||||
//getting radio button font size
|
||||
const radioSize = fontSize;
|
||||
//getting position of radio widget in y direction
|
||||
let y = yPos(position);
|
||||
//on the basic of option's length create radio button and message
|
||||
if (position?.options?.values.length > 0) {
|
||||
position?.options?.values.forEach((item, ind) => {
|
||||
if (ind > 0) {
|
||||
y = y + radioOptionGapFromTop;
|
||||
y = y + radioButtonFromTop;
|
||||
} else {
|
||||
radioOptionGapFromTop = fontSize + 10 || 25;
|
||||
radioButtonFromTop = fontSize + 6;
|
||||
}
|
||||
if (!position?.options?.isHideLabel) {
|
||||
// below line of code is used to embed label with radio button in pdf
|
||||
|
||||
const optionsPosition = compensateRotation(
|
||||
page.getRotation().angle,
|
||||
xPos(position) + radioTextGapFromLeft,
|
||||
y,
|
||||
y - 2,
|
||||
1,
|
||||
page.getSize(),
|
||||
optionsFontSize,
|
||||
getSize,
|
||||
fontSize,
|
||||
updateColorInRgb,
|
||||
font,
|
||||
page
|
||||
@@ -1825,13 +1883,13 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
page.drawText(item, optionsPosition);
|
||||
}
|
||||
let radioObj = {
|
||||
x: xPos(position),
|
||||
x: xPos(position) + 2,
|
||||
y: y,
|
||||
width: radioSize,
|
||||
height: radioSize
|
||||
};
|
||||
|
||||
radioObj = getWidgetPosition(page, radioObj, 1);
|
||||
radioObj = getWidgetPosition(page, radioObj, 1, getSize);
|
||||
radioGroup.addOptionToPage(item, page, radioObj);
|
||||
});
|
||||
}
|
||||
@@ -1849,7 +1907,7 @@ export const multiSignEmbed = async (widgets, pdfDoc, signyourself, scale) => {
|
||||
height: widgetHeight
|
||||
};
|
||||
|
||||
const imageOptions = getWidgetPosition(page, signature, 1);
|
||||
const imageOptions = getWidgetPosition(page, signature, 1, getSize);
|
||||
page.drawImage(img, imageOptions);
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -2040,26 +2098,12 @@ export const addDefaultSignatureImg = (xyPosition, defaultSignImg, type) => {
|
||||
return xyDefaultPos;
|
||||
};
|
||||
|
||||
//function for create list of year for date widget
|
||||
export const range = (start, end, step) => {
|
||||
const range = [];
|
||||
for (let i = start; i <= end; i += step) {
|
||||
range.push(i);
|
||||
}
|
||||
return range;
|
||||
};
|
||||
//function for get month
|
||||
export const getMonth = (date) => {
|
||||
const newMonth = new Date(date).getMonth();
|
||||
return newMonth;
|
||||
};
|
||||
|
||||
//function for get year
|
||||
export const getYear = (date) => {
|
||||
const newYear = new Date(date).getFullYear();
|
||||
return newYear;
|
||||
};
|
||||
|
||||
//function to create/copy widget next to already dropped widget
|
||||
export const handleCopyNextToWidget = (
|
||||
position,
|
||||
@@ -2590,7 +2634,7 @@ function compensateRotation(
|
||||
}
|
||||
|
||||
// `getWidgetPosition` is used to calulcate position of image type widget like x, y, width, height for pdf-lib
|
||||
function getWidgetPosition(page, image, sizeRatio) {
|
||||
function getWidgetPosition(page, image, sizeRatio, getSize) {
|
||||
let pageWidth;
|
||||
// pageHeight;
|
||||
if ([90, 270].includes(page.getRotation().angle)) {
|
||||
@@ -2614,7 +2658,7 @@ function getWidgetPosition(page, image, sizeRatio) {
|
||||
imageX,
|
||||
imageYFromTop,
|
||||
1,
|
||||
page.getSize(),
|
||||
getSize,
|
||||
imageHeight
|
||||
);
|
||||
|
||||
@@ -3045,7 +3089,7 @@ export const mailTemplate = (param) => {
|
||||
"</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 +
|
||||
param.signingUrl +
|
||||
"><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 +
|
||||
". For any queries regarding this email, please contact the sender " +
|
||||
@@ -3081,9 +3125,9 @@ export const updateDateWidgetsRes = (documentData, signerId, journey) => {
|
||||
if (item?.signerObjId === signerId) {
|
||||
return {
|
||||
...item,
|
||||
placeHolder: item.placeHolder.map((ph) => ({
|
||||
placeHolder: item?.placeHolder?.map((ph) => ({
|
||||
...ph,
|
||||
pos: ph.pos.map((widget) => {
|
||||
pos: ph?.pos?.map((widget) => {
|
||||
// only for date widgets *and* missing response
|
||||
if (widget.type === "date" && !widget.options.response) {
|
||||
return {
|
||||
@@ -3117,3 +3161,69 @@ export const updateDateWidgetsRes = (documentData, signerId, journey) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
//function for show checked checkbox
|
||||
export const selectCheckbox = (ind, selectedCheckbox) => {
|
||||
if (selectedCheckbox && selectedCheckbox?.length > 0) {
|
||||
const isCheck = selectedCheckbox?.some((data) => data === ind);
|
||||
return isCheck || false;
|
||||
}
|
||||
};
|
||||
export const checkRegularExpress = (validateType, setValidatePlaceholder) => {
|
||||
switch (validateType) {
|
||||
case "email":
|
||||
setValidatePlaceholder("demo@gmail.com");
|
||||
break;
|
||||
case "number":
|
||||
setValidatePlaceholder("12345");
|
||||
break;
|
||||
case "text":
|
||||
setValidatePlaceholder("please enter text");
|
||||
break;
|
||||
default:
|
||||
setValidatePlaceholder("please enter value");
|
||||
}
|
||||
};
|
||||
//function to use unlink signer from widgets
|
||||
export const handleUnlinkSigner = (
|
||||
signerPos,
|
||||
setSignerPos,
|
||||
signersdata,
|
||||
setSignersData,
|
||||
uniqueId
|
||||
) => {
|
||||
//remove existing signer's details from 'signerPos' array
|
||||
const updatePlaceHolder = signerPos.map((x) => {
|
||||
if (x.Id === uniqueId) {
|
||||
return { ...x, signerPtr: {}, signerObjId: "" };
|
||||
}
|
||||
return { ...x };
|
||||
});
|
||||
setSignerPos(updatePlaceHolder);
|
||||
//remove existing signer's details from 'signersdata' array and keep role and id
|
||||
const updateSigner = signersdata.map((item) => {
|
||||
if (item.Id == uniqueId) {
|
||||
return { Role: item.Role, Id: item.Id, blockColor: item.blockColor };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setSignersData(updateSigner);
|
||||
};
|
||||
//function is used to get pdf original width and height
|
||||
export const getOriginalWH = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
//get total page number
|
||||
const totalPages = pdf?.numPages;
|
||||
//according to page number get all pdf's pages width and height
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
try {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const width = getPage?.view[2];
|
||||
const height = getPage?.view[3];
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
} catch (e) {
|
||||
console.log(`Error getting page ${index + 1} of PDF: ${e.message}`);
|
||||
}
|
||||
}
|
||||
return pdfWHObj;
|
||||
};
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import logo from "../assets/images/logo.png";
|
||||
import { getEnv } from "./Utils";
|
||||
|
||||
export function serverUrl_fn() {
|
||||
let baseUrl = process.env.REACT_APP_SERVERURL
|
||||
? process.env.REACT_APP_SERVERURL
|
||||
: window.location.origin + "/api/app";
|
||||
const env = getEnv();
|
||||
const serverurl = env?.REACT_APP_SERVERURL
|
||||
? env.REACT_APP_SERVERURL // env.REACT_APP_SERVERURL is used for prod
|
||||
: process.env.REACT_APP_SERVERURL; // process.env.REACT_APP_SERVERURL is used for dev (locally)
|
||||
let baseUrl = serverurl ? serverurl : window.location.origin + "/api/app";
|
||||
return baseUrl;
|
||||
}
|
||||
export const appInfo = {
|
||||
|
||||
@@ -25,7 +25,7 @@ i18n
|
||||
interpolation: {
|
||||
escapeValue: false // Not needed for react as it escapes by default
|
||||
},
|
||||
whitelist: ["en", "es", "fr", "it", "de"] // List of allowed languages
|
||||
whitelist: ["en", "es", "fr", "it", "de", "hi"] // List of allowed languages
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
||||
@@ -17,6 +17,9 @@ body {
|
||||
scrollbar-width: none;
|
||||
/* Firefox */
|
||||
}
|
||||
.react-datepicker-popper {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 766px) {
|
||||
.reactour__close {
|
||||
|
||||
@@ -4,7 +4,6 @@ import "./index.css";
|
||||
import App from "./App";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "./redux/store";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { TouchBackend } from "react-dnd-touch-backend";
|
||||
import {
|
||||
@@ -19,7 +18,8 @@ import "./polyfills";
|
||||
import { serverUrl_fn } from "./constant/appinfo";
|
||||
import "./i18n";
|
||||
|
||||
const appId = import.meta.env.VITE_APPID || process.env.REACT_APP_APPID || "opensign";
|
||||
const appId =
|
||||
import.meta.env.VITE_APPID || process.env.REACT_APP_APPID || "opensign";
|
||||
const serverUrl = serverUrl_fn();
|
||||
Parse.initialize(appId);
|
||||
Parse.serverURL = serverUrl;
|
||||
@@ -56,12 +56,10 @@ const generatePreview = (props) => {
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(
|
||||
<CookiesProvider defaultSetOptions={{ path: "/" }}>
|
||||
<Provider store={store}>
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Preview>{generatePreview}</Preview>
|
||||
<App />
|
||||
</DndProvider>
|
||||
</Provider>
|
||||
</CookiesProvider>
|
||||
<Provider store={store}>
|
||||
<DndProvider options={HTML5toTouch}>
|
||||
<Preview>{generatePreview}</Preview>
|
||||
<App />
|
||||
</DndProvider>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
@@ -533,9 +533,9 @@ export default function reportJson(id) {
|
||||
btnId: "1873",
|
||||
btnLabel: "Share with team",
|
||||
hoverLabel: "Share with team",
|
||||
btnIcon: "fa-light fa-share-nodes",
|
||||
btnIcon: "fa-light fa-user-group",
|
||||
redirectUrl: "",
|
||||
action: "sharewith"
|
||||
action: "sharewithteam"
|
||||
});
|
||||
return newItem;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
const userssetting = [
|
||||
{
|
||||
icon: "fa-light fa-users fa-fw",
|
||||
title: "Users",
|
||||
target: "_self",
|
||||
pageType: "",
|
||||
description: "",
|
||||
objectId: "users"
|
||||
}
|
||||
];
|
||||
export const subSetting = [
|
||||
{
|
||||
icon: "fa-light fa-sliders",
|
||||
@@ -7,14 +17,7 @@ export const subSetting = [
|
||||
description: "",
|
||||
objectId: "preferences"
|
||||
},
|
||||
{
|
||||
icon: "fa-light fa-users fa-fw",
|
||||
title: "Users",
|
||||
target: "_self",
|
||||
pageType: "",
|
||||
description: "",
|
||||
objectId: "users"
|
||||
}
|
||||
...userssetting
|
||||
];
|
||||
|
||||
const sidebarList = [
|
||||
@@ -65,7 +68,7 @@ const sidebarList = [
|
||||
pageType: "report",
|
||||
description: "",
|
||||
objectId: "6TeaPr321t"
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,13 +3,12 @@ import Header from "../components/Header";
|
||||
import Footer from "../components/Footer";
|
||||
import Sidebar from "../components/sidebar/Sidebar";
|
||||
import { useWindowSize } from "../hook/useWindowSize";
|
||||
import Tour from "reactour";
|
||||
import Tour from "../primitives/Tour";
|
||||
import axios from "axios";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Parse from "parse";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import { useNavigate, useLocation, Outlet } from "react-router";
|
||||
import { useCookies } from "react-cookie";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { showHeader } from "../redux/reducers/showHeader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -30,7 +29,6 @@ const HomeLayout = () => {
|
||||
const [isTour, setIsTour] = useState(false);
|
||||
const [tourStatusArr, setTourStatusArr] = useState([]);
|
||||
const [tourConfigs, setTourConfigs] = useState([]);
|
||||
const [, setCookie] = useCookies(["accesstoken", "main_Domain"]);
|
||||
|
||||
const tenantId = localStorage.getItem("TenantId");
|
||||
|
||||
@@ -64,38 +62,11 @@ const HomeLayout = () => {
|
||||
setIsUserValid(false);
|
||||
}
|
||||
})();
|
||||
saveCookies();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [tenantId]);
|
||||
//function to use save data in cookies storage
|
||||
const saveCookies = () => {
|
||||
const main_Domain = window.location.origin;
|
||||
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
|
||||
const updateDomain = domainName.substring(indexOfFirstDot); //.opensignlabs.com
|
||||
const serverUrl = localStorage.getItem("baseUrl");
|
||||
const parseAppId = localStorage.getItem("parseAppId");
|
||||
setCookie("accesstoken", localStorage.getItem("accesstoken"), {
|
||||
secure: true,
|
||||
domain: updateDomain
|
||||
});
|
||||
setCookie("main_Domain", main_Domain, {
|
||||
secure: true,
|
||||
domain: updateDomain
|
||||
});
|
||||
setCookie("server_url", serverUrl, {
|
||||
secure: true,
|
||||
domain: updateDomain
|
||||
});
|
||||
setCookie("parse_app_id", parseAppId, {
|
||||
secure: true,
|
||||
domain: updateDomain
|
||||
});
|
||||
};
|
||||
|
||||
const showSidebar = () => {
|
||||
setIsOpen((value) => !value);
|
||||
dispatch(showHeader(!isOpen));
|
||||
@@ -120,12 +91,7 @@ const HomeLayout = () => {
|
||||
// const resArr = arr;
|
||||
const resArr = arr.map((obj, index) => {
|
||||
if (arr.length - 1 === index) {
|
||||
return {
|
||||
...obj
|
||||
// actions: () => {
|
||||
// setIsCloseBtn(true);
|
||||
// },
|
||||
};
|
||||
return { ...obj };
|
||||
} else {
|
||||
return {
|
||||
...obj,
|
||||
@@ -137,27 +103,23 @@ const HomeLayout = () => {
|
||||
});
|
||||
setTourConfigs([
|
||||
{
|
||||
selector: '[data-tut="reactourFirst"]',
|
||||
selector: '[data-tut="nonpresentmask"]',
|
||||
content: t("tour-mssg.home-layout-1"),
|
||||
position: "top"
|
||||
// style: { backgroundColor: "#abd4d2" },
|
||||
position: "center",
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="tourbutton"]',
|
||||
content: t("tour-mssg.home-layout-2"),
|
||||
position: "top"
|
||||
// style: { backgroundColor: "#abd4d2" },
|
||||
},
|
||||
...resArr,
|
||||
{
|
||||
selector: '[data-tut="reactourLast"]',
|
||||
selector: '[data-tut="nonpresentmask"]',
|
||||
content: t("tour-mssg.home-layout-3", { appName }),
|
||||
position: "top"
|
||||
// style: { backgroundColor: "#abd4d2" },
|
||||
position: "center",
|
||||
}
|
||||
]);
|
||||
checkTourStatus();
|
||||
// console.log("resArr ", resArr);
|
||||
}
|
||||
};
|
||||
const closeTour = async () => {
|
||||
|
||||
@@ -455,7 +455,7 @@ const Forms = (props) => {
|
||||
setBcc([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
setFormData({
|
||||
@@ -534,7 +534,7 @@ const Forms = (props) => {
|
||||
setBcc([]);
|
||||
setFolder({ ObjectId: "", Name: "" });
|
||||
const notifySign =
|
||||
extUserData?.NotifyOnSignatures
|
||||
extUserData?.NotifyOnSignatures !== undefined
|
||||
? extUserData?.NotifyOnSignatures
|
||||
: true;
|
||||
let obj = {
|
||||
|
||||
@@ -68,6 +68,7 @@ function Login() {
|
||||
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") {
|
||||
@@ -82,11 +83,11 @@ function Login() {
|
||||
} else {
|
||||
setImage(appInfo?.applogo || undefined);
|
||||
}
|
||||
dispatch(fetchAppInfo());
|
||||
if (localStorage.getItem("accesstoken")) {
|
||||
setState({ ...state, loading: true });
|
||||
GetLoginData();
|
||||
}
|
||||
dispatch(fetchAppInfo());
|
||||
};
|
||||
const handleChange = (event) => {
|
||||
let { name, value } = event.target;
|
||||
@@ -96,20 +97,15 @@ function Login() {
|
||||
setState({ ...state, [name]: value });
|
||||
};
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
localStorage.removeItem("accesstoken");
|
||||
event.preventDefault();
|
||||
const handleLogin = async (
|
||||
) => {
|
||||
const email = state?.email
|
||||
const password = state?.password
|
||||
|
||||
if (!emailRegex.test(state.email)) {
|
||||
alert("Please enter a valid email address.");
|
||||
return;
|
||||
}
|
||||
|
||||
const { email, password } = state;
|
||||
if (!email || !password) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem("accesstoken");
|
||||
try {
|
||||
setState({ ...state, loading: true });
|
||||
localStorage.setItem("appLogo", appInfo.applogo);
|
||||
@@ -132,6 +128,14 @@ function Login() {
|
||||
showToast("danger", "Invalid username/password or region");
|
||||
}
|
||||
};
|
||||
const handleLoginBtn = async (event) => {
|
||||
event.preventDefault();
|
||||
if (!emailRegex.test(state.email)) {
|
||||
alert("Please enter a valid email address.");
|
||||
return;
|
||||
}
|
||||
await handleLogin();
|
||||
};
|
||||
|
||||
const setThirdpartyLoader = (value) => {
|
||||
setState({ ...state, thirdpartyLoader: value });
|
||||
@@ -275,7 +279,6 @@ function Login() {
|
||||
const userInformation = JSON.parse(
|
||||
localStorage.getItem("UserInformation")
|
||||
);
|
||||
// console.log("payload ", payload);
|
||||
if (payload && payload.sessionToken) {
|
||||
const params = {
|
||||
userDetails: {
|
||||
@@ -289,7 +292,6 @@ function Login() {
|
||||
}
|
||||
};
|
||||
const userSignUp = await Parse.Cloud.run("usersignup", params);
|
||||
// console.log("userSignUp ", userSignUp);
|
||||
if (userSignUp && userSignUp.sessionToken) {
|
||||
const LocalUserDetails = {
|
||||
name: userInformation.name,
|
||||
@@ -430,7 +432,7 @@ function Login() {
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-2">
|
||||
<div>
|
||||
<form onSubmit={handleSubmit} aria-label="Login Form">
|
||||
<form onSubmit={handleLoginBtn} aria-label="Login Form">
|
||||
<h1 className="text-[30px] mt-6">{t("welcome")}</h1>
|
||||
<fieldset>
|
||||
<legend className="text-[12px] text-[#878787]">
|
||||
@@ -488,15 +490,14 @@ function Login() {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="relative mt-1">
|
||||
<NavLink
|
||||
to="/forgetpassword"
|
||||
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
|
||||
>
|
||||
{t("forgot-password")}
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="relative mt-1">
|
||||
<NavLink
|
||||
to="/forgetpassword"
|
||||
className="text-[13px] op-link op-link-primary underline-offset-1 focus:outline-none ml-1"
|
||||
>
|
||||
{t("forgot-password")}
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-center text-xs font-bold mt-2">
|
||||
|
||||
@@ -11,7 +11,7 @@ import Title from "../components/Title";
|
||||
import Parse from "parse";
|
||||
import ModalUi from "../primitives/ModalUi";
|
||||
import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
import Tour from "reactour";
|
||||
import Tour from "../primitives/Tour";
|
||||
import axios from "axios";
|
||||
import Loader from "../primitives/Loader";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import "../styles/signature.css";
|
||||
import Parse from "parse";
|
||||
import axios from "axios";
|
||||
import { DndProvider, useDrop } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import SignPad from "../components/pdf/SignPad";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import Tour from "reactour";
|
||||
import Tour from "../primitives/Tour";
|
||||
import Confetti from "react-confetti";
|
||||
import moment from "moment";
|
||||
import {
|
||||
setSaveSignCheckbox,
|
||||
setMyInitial,
|
||||
setDefaultSignImg,
|
||||
resetWidgetState
|
||||
} from "../redux/reducers/widgetSlice.js";
|
||||
import {
|
||||
contractDocument,
|
||||
multiSignEmbed,
|
||||
embedDocId,
|
||||
pdfNewWidthFun,
|
||||
signPdfFun,
|
||||
onSaveSign,
|
||||
onSaveImage,
|
||||
addDefaultSignatureImg,
|
||||
radioButtonWidget,
|
||||
replaceMailVaribles,
|
||||
@@ -40,16 +44,15 @@ import {
|
||||
defaultWidthHeight,
|
||||
addWidgetOptions,
|
||||
textWidget,
|
||||
compressedFileSize,
|
||||
mailTemplate,
|
||||
updateDateWidgetsRes,
|
||||
widgetDataValue
|
||||
widgetDataValue,
|
||||
getOriginalWH
|
||||
} from "../constant/Utils";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import Title from "../components/Title";
|
||||
import DefaultSignature from "../components/pdf/DefaultSignature";
|
||||
import { useSelector } from "react-redux";
|
||||
import SignerListComponent from "../components/pdf/SignerListComponent";
|
||||
import PdfZoom from "../components/pdf/PdfZoom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -65,29 +68,28 @@ import AgreementSign from "../components/pdf/AgreementSign";
|
||||
import WidgetComponent from "../components/pdf/WidgetComponent";
|
||||
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
|
||||
import TextFontSetting from "../components/pdf/TextFontSetting";
|
||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal.jsx";
|
||||
|
||||
function PdfRequestFiles(
|
||||
) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const isShowModal = useSelector((state) => state.widget.isShowModal);
|
||||
const saveSignCheckbox = useSelector(
|
||||
(state) => state.widget.saveSignCheckbox
|
||||
);
|
||||
const defaultSignImg = useSelector((state) => state.widget.defaultSignImg);
|
||||
const myInitial = useSelector((state) => state.widget.myInitial);
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const [pdfDetails, setPdfDetails] = useState([]);
|
||||
const [signedSigners, setSignedSigners] = useState([]);
|
||||
const [unsignedSigners, setUnSignedSigners] = useState([]);
|
||||
const [isSignPad, setIsSignPad] = useState(false);
|
||||
const [pdfUrl, setPdfUrl] = useState();
|
||||
const [allPages, setAllPages] = useState(null);
|
||||
const numPages = 1;
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const [image, setImage] = useState(null);
|
||||
const [isImageSelect, setIsImageSelect] = useState(false);
|
||||
const [signature, setSignature] = useState();
|
||||
const [isStamp, setIsStamp] = useState(false);
|
||||
const [signKey, setSignKey] = useState();
|
||||
const [imgWH, setImgWH] = useState({});
|
||||
const imageRef = useRef(null);
|
||||
const [handleError, setHandleError] = useState();
|
||||
const [selectWidgetId, setSelectWidgetId] = useState("");
|
||||
const [isCelebration, setIsCelebration] = useState(false);
|
||||
const [requestSignTour, setRequestSignTour] = useState(true);
|
||||
const [tourStatus, setTourStatus] = useState([]);
|
||||
@@ -95,7 +97,6 @@ function PdfRequestFiles(
|
||||
isLoad: true,
|
||||
message: t("loading-mssg")
|
||||
});
|
||||
const [defaultSignImg, setDefaultSignImg] = useState();
|
||||
const [isDocId, setIsDocId] = useState(false);
|
||||
const [pdfNewWidth, setPdfNewWidth] = useState();
|
||||
const [pdfOriginalWH, setPdfOriginalWH] = useState([]);
|
||||
@@ -111,6 +112,9 @@ function PdfRequestFiles(
|
||||
const [signerUserId, setSignerUserId] = useState();
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
const [isDownloading, setIsDownloading] = useState("");
|
||||
// tempSignerId is used to temporarily store the currently selected signer's unique ID, When editing a text widget, it automatically attaches a prefill user, and since prefill users are not shown in the signer list, the selected signer from before editing would be lost. To handle this, we store the currently selected signer's unique ID in tempSignerId before entering the text widget edit mode. Once the text widget settings are completed,
|
||||
// we restore the original selected signer by setting tempSignerId back to uniqueId.This ensures that the correct signer remains selected and visible in the UI even after interacting with a prefill-only widget like the text widget.
|
||||
const [tempSignerId, setTempSignerId] = useState("");
|
||||
const [defaultSignAlert, setDefaultSignAlert] = useState({
|
||||
isShow: false,
|
||||
alertMessage: ""
|
||||
@@ -119,14 +123,11 @@ function PdfRequestFiles(
|
||||
isCertificate: false,
|
||||
isModal: false
|
||||
});
|
||||
const [myInitial, setMyInitial] = useState("");
|
||||
const [isInitial, setIsInitial] = useState(false);
|
||||
const [pdfLoad, setPdfLoad] = useState(false);
|
||||
const [isSigned, setIsSigned] = useState(false);
|
||||
const [isExpired, setIsExpired] = useState(false);
|
||||
const [alreadySign, setAlreadySign] = useState(false);
|
||||
const [containerWH, setContainerWH] = useState({});
|
||||
const [validateAlert, setValidateAlert] = useState(false);
|
||||
const [widgetsTour, setWidgetsTour] = useState(false);
|
||||
const [minRequiredCount, setminRequiredCount] = useState();
|
||||
const [sendInOrder, setSendInOrder] = useState(false);
|
||||
@@ -148,20 +149,14 @@ function PdfRequestFiles(
|
||||
const [isredirectCanceled, setIsredirectCanceled] = useState(true);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [dragKey, setDragKey] = useState();
|
||||
const [isAutoSign, setIsAutoSign] = useState(false);
|
||||
const [signBtnPosition, setSignBtnPosition] = useState([]);
|
||||
const [xySignature, setXYSignature] = useState({});
|
||||
const [zIndex, setZIndex] = useState(1);
|
||||
const [fontSize, setFontSize] = useState();
|
||||
const [fontColor, setFontColor] = useState();
|
||||
const [widgetType, setWidgetType] = useState("");
|
||||
const [isTextSetting, setIsTextSetting] = useState(false);
|
||||
const [isPageCopy, setIsPageCopy] = useState(false);
|
||||
const [assignedWidgetId, setAssignedWidgetId] = useState([]);
|
||||
const [saveSignCheckbox, setSaveSignCheckbox] = useState({
|
||||
isVisible: false,
|
||||
signId: ""
|
||||
});
|
||||
const [showSignPagenumber, setShowSignPagenumber] = useState([]);
|
||||
const [owner, setOwner] = useState({});
|
||||
const [, drop] = useDrop({
|
||||
@@ -195,6 +190,7 @@ function PdfRequestFiles(
|
||||
}
|
||||
let getDocumentId = getDocId || documentId;
|
||||
useEffect(() => {
|
||||
dispatch(resetWidgetState([]));
|
||||
if (getDocumentId) {
|
||||
setDocumentId(getDocumentId);
|
||||
getDocumentDetails(getDocumentId);
|
||||
@@ -264,7 +260,6 @@ function PdfRequestFiles(
|
||||
docId,
|
||||
isNextUser,
|
||||
isSuccessPage = false,
|
||||
publiccontactId
|
||||
) => {
|
||||
try {
|
||||
let isUpdatedSubscribe;
|
||||
@@ -274,7 +269,9 @@ function PdfRequestFiles(
|
||||
const jsonSender = JSON.parse(senderUser);
|
||||
const contactId = jsonSender?.objectId
|
||||
? ""
|
||||
: contactBookId || publiccontactId || signerObjectId || "";
|
||||
: contactBookId ||
|
||||
signerObjectId ||
|
||||
"";
|
||||
const tenantSignTypes = await fetchTenantDetails(contactId);
|
||||
// `currUserId` will be contactId or extUserId
|
||||
let currUserId;
|
||||
@@ -322,7 +319,9 @@ function PdfRequestFiles(
|
||||
|
||||
currUserId = getCurrentSigner?.objectId
|
||||
? getCurrentSigner.objectId
|
||||
: contactBookId || publiccontactId || signerObjectId || ""; //signerObjectId is contactBookId refer from public template flow
|
||||
: contactBookId ||
|
||||
signerObjectId ||
|
||||
""; //signerObjectId is contactBookId refer from public template flow
|
||||
if (currUserId) {
|
||||
setSignerObjectId(currUserId);
|
||||
}
|
||||
@@ -477,21 +476,28 @@ function PdfRequestFiles(
|
||||
} else {
|
||||
setRequestSignTour(false);
|
||||
}
|
||||
setSaveSignCheckbox((prev) => ({ ...prev, isVisible: true }));
|
||||
dispatch(
|
||||
setSaveSignCheckbox({
|
||||
...saveSignCheckbox,
|
||||
isVisible: true
|
||||
})
|
||||
);
|
||||
//function to get default signatur of current user from `contracts_Signature` class
|
||||
const defaultSignRes = await getDefaultSignature(
|
||||
jsonSender?.objectId
|
||||
);
|
||||
if (defaultSignRes?.status === "success") {
|
||||
setSaveSignCheckbox((prev) => ({
|
||||
...prev,
|
||||
isVisible: true,
|
||||
signId: defaultSignRes?.res?.id
|
||||
}));
|
||||
dispatch(
|
||||
setSaveSignCheckbox({
|
||||
...saveSignCheckbox,
|
||||
isVisible: true,
|
||||
signId: defaultSignRes?.res?.id
|
||||
})
|
||||
);
|
||||
const sign = defaultSignRes?.res?.defaultSignature || "";
|
||||
const initials = defaultSignRes?.res?.defaultInitial || "";
|
||||
setDefaultSignImg(sign);
|
||||
setMyInitial(initials);
|
||||
dispatch(setDefaultSignImg(sign));
|
||||
dispatch(setMyInitial(initials));
|
||||
}
|
||||
} else if (res?.length === 0) {
|
||||
const res = await contactBook(currUserId);
|
||||
@@ -581,9 +587,12 @@ function PdfRequestFiles(
|
||||
}
|
||||
};
|
||||
//function for embed signature or image url in pdf
|
||||
async function embedWidgetsData(publiccontactId, publicDocId) {
|
||||
let contactId = publiccontactId || signerObjectId;
|
||||
let docId = publicDocId || documentId;
|
||||
async function embedWidgetsData(
|
||||
) {
|
||||
let contactId =
|
||||
signerObjectId;
|
||||
let docId =
|
||||
documentId;
|
||||
const addExtraDays = pdfDetails[0]?.TimeToCompleteDays
|
||||
? pdfDetails[0].TimeToCompleteDays
|
||||
: 15;
|
||||
@@ -808,11 +817,13 @@ function PdfRequestFiles(
|
||||
//embed document's object id to all pages in pdf document
|
||||
if (!HeaderDocId) {
|
||||
if (!isDocId) {
|
||||
await embedDocId(pdfDoc, docId, allPages);
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
await embedDocId(pdfOriginalWH, pdfDoc, docId);
|
||||
}
|
||||
}
|
||||
//embed multi signature in pdf
|
||||
//embed all widgets in document
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
widgets,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -854,7 +865,10 @@ function PdfRequestFiles(
|
||||
const user = usermail?.Email
|
||||
? usermail
|
||||
: pdfDetails?.[0]?.Signers[newIndex];
|
||||
if (sendmail !== "false" && sendInOrder) {
|
||||
if (
|
||||
sendmail !== "false" &&
|
||||
sendInOrder
|
||||
) {
|
||||
const requestBody =
|
||||
updatedDoc.updatedPdfDetails?.[0]?.RequestBody;
|
||||
const requestSubject =
|
||||
@@ -908,7 +922,7 @@ function PdfRequestFiles(
|
||||
const htmlReqBody =
|
||||
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body>" +
|
||||
replacedRequestBody +
|
||||
"</body> </html>";
|
||||
"</body></html>";
|
||||
|
||||
const variables = {
|
||||
document_title: documentName,
|
||||
@@ -921,7 +935,7 @@ function PdfRequestFiles(
|
||||
receiver_phone: user?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
replaceVar = replaceMailVaribles(
|
||||
requestSubject,
|
||||
@@ -936,7 +950,7 @@ function PdfRequestFiles(
|
||||
title: documentName,
|
||||
organization: orgName,
|
||||
localExpireDate: localExpireDate,
|
||||
sigingUrl: signPdf
|
||||
signingUrl: signPdf
|
||||
};
|
||||
let params = {
|
||||
replyto: senderEmail || "",
|
||||
@@ -1004,7 +1018,6 @@ function PdfRequestFiles(
|
||||
}
|
||||
}
|
||||
}
|
||||
setIsSignPad(false);
|
||||
} else {
|
||||
setIsAlert({
|
||||
isShow: true,
|
||||
@@ -1023,31 +1036,9 @@ function PdfRequestFiles(
|
||||
}
|
||||
|
||||
const handleSignPdf = async () => {
|
||||
if (props.templateId) {
|
||||
const params = {
|
||||
...contact,
|
||||
templateid: pdfDetails[0]?.objectId,
|
||||
role: pdfDetails[0]?.PublicRole[0]
|
||||
};
|
||||
const linkRes = await axios.post(
|
||||
`${localStorage.getItem(
|
||||
"baseUrl"
|
||||
)}/functions/publicuserlinkcontacttodoc`,
|
||||
params,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Parse-Application-Id": localStorage.getItem("parseAppId")
|
||||
}
|
||||
}
|
||||
);
|
||||
const contactId = linkRes.data.result?.contactId;
|
||||
const docId = linkRes.data?.result?.docId;
|
||||
await embedWidgetsData(contactId, docId);
|
||||
} else {
|
||||
await embedWidgetsData();
|
||||
}
|
||||
};
|
||||
|
||||
//function for save x and y position and show signature tab on that position
|
||||
const handleTabDrag = (key) => {
|
||||
setDragKey(key);
|
||||
@@ -1130,7 +1121,7 @@ function PdfRequestFiles(
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const updateSignPos = getPosData.map((position) => {
|
||||
if (position.key === signKey) {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
@@ -1161,7 +1152,6 @@ function PdfRequestFiles(
|
||||
return obj;
|
||||
});
|
||||
setSignerPos(newUpdateSigner);
|
||||
|
||||
setFontSize();
|
||||
setFontColor();
|
||||
handleTextSettingModal(false);
|
||||
@@ -1185,14 +1175,7 @@ function PdfRequestFiles(
|
||||
];
|
||||
//function for get pdf page details
|
||||
const pageDetails = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
const totalPages = pdf.numPages; // Get the total number of pages
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const scale = 1;
|
||||
const { width, height } = getPage.getViewport({ scale });
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
}
|
||||
const pdfWHObj = await getOriginalWH(pdf);
|
||||
setPdfOriginalWH(pdfWHObj);
|
||||
setPdfLoad(true);
|
||||
};
|
||||
@@ -1200,127 +1183,6 @@ function PdfRequestFiles(
|
||||
function changePage(offset) {
|
||||
setPageNumber((prevPageNumber) => prevPageNumber + offset);
|
||||
}
|
||||
|
||||
//function for image upload or update
|
||||
const onImageChange = (event) => {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const file = event.target.files[0];
|
||||
compressedFileSize(file, setImgWH, setImage);
|
||||
}
|
||||
};
|
||||
//function for upload stamp image
|
||||
const saveImage = () => {
|
||||
const widgetsType = currWidgetsDetails?.type;
|
||||
//get current signers placeholder position data
|
||||
const currentSigner = signerPos.filter(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
//get current pagenumber placeholder index
|
||||
const getIndex = currentSigner[0].placeHolder.findIndex((object) => {
|
||||
return object.pageNumber === pageNumber;
|
||||
});
|
||||
//get current signer placeholder position data
|
||||
const placeholderPosition = currentSigner[0].placeHolder;
|
||||
//`isApplyAll` is used when user edit stamp then updated signature apply all existing drawn signatures
|
||||
const isApplyAll = true;
|
||||
//function of save image and get updated position with image url
|
||||
const getUpdatePosition = onSaveImage(
|
||||
placeholderPosition,
|
||||
getIndex,
|
||||
signKey,
|
||||
imgWH,
|
||||
image,
|
||||
isAutoSign,
|
||||
widgetsType,
|
||||
isApplyAll
|
||||
);
|
||||
|
||||
//replace updated placeholder position with old data
|
||||
placeholderPosition.splice(
|
||||
0,
|
||||
placeholderPosition.length,
|
||||
...getUpdatePosition
|
||||
);
|
||||
//get current signers placeholder position data index number in array
|
||||
const indexofSigner = signerPos.findIndex((object) => {
|
||||
return object.signerObjId === signerObjectId;
|
||||
});
|
||||
//update current signers data with new placeholder position array data
|
||||
setSignerPos((prevState) => {
|
||||
const newState = [...prevState]; // Create a copy of the state
|
||||
newState.splice(indexofSigner, 1, ...currentSigner); // Modify the copy
|
||||
return newState; // Update the state with the modified copy
|
||||
});
|
||||
setIsAutoSign(false);
|
||||
};
|
||||
//function for save button to save signature or image url
|
||||
const saveSign = (type, isDefaultSign, width, height, typedSignature) => {
|
||||
const widgetsType = currWidgetsDetails?.type;
|
||||
const isTypeText = width && height ? true : false;
|
||||
const signatureImg = isDefaultSign
|
||||
? isDefaultSign === "initials"
|
||||
? myInitial
|
||||
: defaultSignImg
|
||||
: signature;
|
||||
let imgWH = { width: width ? width : "", height: height ? height : "" };
|
||||
setIsSignPad(false);
|
||||
setIsImageSelect(false);
|
||||
setImage();
|
||||
|
||||
//get current signers placeholder position data
|
||||
const currentSigner = signerPos.filter(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
//get current pagenumber placeholder index
|
||||
const getIndex = currentSigner[0].placeHolder.findIndex((object) => {
|
||||
return object.pageNumber === pageNumber;
|
||||
});
|
||||
|
||||
//set default signature image width and height
|
||||
if (isDefaultSign) {
|
||||
const img = new Image();
|
||||
img.src = defaultSignImg;
|
||||
if (img.complete) {
|
||||
imgWH = { width: img.width, height: img.height };
|
||||
}
|
||||
}
|
||||
//get current signer placeholder position data
|
||||
const placeholderPosition = currentSigner[0].placeHolder;
|
||||
//`isApplyAll` is used when user edit signature/initial then updated signature apply all existing drawn signatures
|
||||
const isApplyAll = true;
|
||||
//function of save signature image and get updated position with signature image url
|
||||
const getUpdatePosition = onSaveSign(
|
||||
type,
|
||||
placeholderPosition,
|
||||
getIndex,
|
||||
signKey,
|
||||
signatureImg,
|
||||
imgWH,
|
||||
isDefaultSign,
|
||||
isTypeText,
|
||||
typedSignature,
|
||||
isAutoSign,
|
||||
widgetsType,
|
||||
isApplyAll
|
||||
);
|
||||
const updateSignerData = currentSigner.map((obj) => {
|
||||
if (obj.signerObjId === signerObjectId) {
|
||||
return { ...obj, placeHolder: getUpdatePosition };
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
|
||||
const index = signerPos.findIndex(
|
||||
(data) => data.signerObjId === signerObjectId
|
||||
);
|
||||
setSignerPos((prevState) => {
|
||||
const newState = [...prevState];
|
||||
newState.splice(index, 1, ...updateSignerData);
|
||||
return newState;
|
||||
});
|
||||
|
||||
setIsAutoSign(false);
|
||||
};
|
||||
//function for set decline true on press decline button
|
||||
const declineDoc = async (reason) => {
|
||||
const senderUser = localStorage.getItem(
|
||||
@@ -1692,7 +1554,8 @@ function PdfRequestFiles(
|
||||
pageNumber,
|
||||
containerWH
|
||||
);
|
||||
let dropData = [];
|
||||
let dropData = [],
|
||||
dropObj;
|
||||
let placeHolder;
|
||||
const dragTypeValue = item?.text ? item.text : monitor.type;
|
||||
const widgetWidth =
|
||||
@@ -1704,11 +1567,15 @@ function PdfRequestFiles(
|
||||
const widgetValue = widgetDataValue(dragTypeValue, parseUser);
|
||||
//adding and updating drop position in array when user drop signature button in div
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
|
||||
const divWidth = divRef.current.getBoundingClientRect().width;
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
const dropObj = {
|
||||
// Compute the pixel‐space center within the PDF viewport:
|
||||
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
|
||||
const xPosition_Final = centerX_Pixels / (containerScale * scale);
|
||||
dropObj = {
|
||||
//onclick put placeholder center on pdf
|
||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
||||
xPosition: xPosition_Final,
|
||||
yPosition: widgetHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
@@ -1734,7 +1601,7 @@ function PdfRequestFiles(
|
||||
const y = offset.y - containerRect.top;
|
||||
const getXPosition = signBtnPosition[0] ? x - signBtnPosition[0].xPos : x;
|
||||
const getYPosition = signBtnPosition[0] ? y - signBtnPosition[0].yPos : y;
|
||||
const dropObj = {
|
||||
dropObj = {
|
||||
xPosition: getXPosition / (containerScale * scale),
|
||||
yPosition: getYPosition / (containerScale * scale),
|
||||
isStamp:
|
||||
@@ -1750,7 +1617,6 @@ function PdfRequestFiles(
|
||||
dropData.push(dropObj);
|
||||
placeHolder = { pageNumber: pageNumber, pos: dropData };
|
||||
}
|
||||
setSelectWidgetId(key);
|
||||
if (uniqueId) {
|
||||
let filterSignerPos, currentPagePosition;
|
||||
filterSignerPos = signerPos.find((data) => data.Id === uniqueId);
|
||||
@@ -1799,9 +1665,7 @@ function PdfRequestFiles(
|
||||
setFontSize(12);
|
||||
setFontColor("black");
|
||||
}
|
||||
setWidgetType(dragTypeValue);
|
||||
setSignKey(key);
|
||||
setCurrWidgetsDetails({});
|
||||
setCurrWidgetsDetails(dropObj);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1892,6 +1756,7 @@ function PdfRequestFiles(
|
||||
setShowSignPagenumber(sortedPagenumber);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DndProvider backend={HTML5Backend}>
|
||||
<Title
|
||||
@@ -2074,10 +1939,12 @@ function PdfRequestFiles(
|
||||
setXyPosition={setSignerPos}
|
||||
allPages={allPages}
|
||||
pageNumber={pageNumber}
|
||||
signKey={signKey}
|
||||
signKey={currWidgetsDetails?.key}
|
||||
Id={uniqueId}
|
||||
widgetType={widgetType}
|
||||
widgetType={currWidgetsDetails?.type}
|
||||
setUniqueId={setUniqueId}
|
||||
tempSignerId={tempSignerId}
|
||||
setTempSignerId={setTempSignerId}
|
||||
/>
|
||||
<div className=" w-full md:w-[95%] ">
|
||||
{/* this modal is used show this document is already sign */}
|
||||
@@ -2214,37 +2081,6 @@ function PdfRequestFiles(
|
||||
)}
|
||||
</div>
|
||||
</ModalUi>
|
||||
{/* this component is used for signature pad modal */}
|
||||
{currentSigner && isSignPad && (
|
||||
<SignPad
|
||||
saveSignCheckbox={saveSignCheckbox}
|
||||
setSaveSignCheckbox={setSaveSignCheckbox}
|
||||
signatureTypes={signatureType}
|
||||
isSignPad={isSignPad}
|
||||
isStamp={isStamp}
|
||||
setIsImageSelect={setIsImageSelect}
|
||||
setIsSignPad={setIsSignPad}
|
||||
setImage={setImage}
|
||||
isImageSelect={isImageSelect}
|
||||
imageRef={imageRef}
|
||||
onImageChange={onImageChange}
|
||||
setSignature={setSignature}
|
||||
image={image}
|
||||
onSaveImage={saveImage}
|
||||
onSaveSign={saveSign}
|
||||
defaultSign={defaultSignImg}
|
||||
myInitial={myInitial}
|
||||
setDefaultSignImg={setDefaultSignImg}
|
||||
setMyInitial={setMyInitial}
|
||||
isInitial={isInitial}
|
||||
setIsInitial={setIsInitial}
|
||||
setIsStamp={setIsStamp}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
setIsAutoSign={setIsAutoSign}
|
||||
isAutoSign={isAutoSign}
|
||||
/>
|
||||
)}
|
||||
{/* pdf header which contain funish back button */}
|
||||
<Header
|
||||
isPdfRequestFiles={
|
||||
@@ -2286,9 +2122,6 @@ function PdfRequestFiles(
|
||||
pageNumber={pageNumber}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
pdfNewWidth={pdfNewWidth}
|
||||
setIsSignPad={setIsSignPad}
|
||||
setIsStamp={setIsStamp}
|
||||
setSignKey={setSignKey}
|
||||
pdfDetails={pdfDetails}
|
||||
signerPos={signerPos}
|
||||
successEmail={false}
|
||||
@@ -2302,11 +2135,7 @@ function PdfRequestFiles(
|
||||
pdfLoad={pdfLoad}
|
||||
setSignerPos={setSignerPos}
|
||||
containerWH={containerWH}
|
||||
setIsInitial={setIsInitial}
|
||||
setValidateAlert={setValidateAlert}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
divRef={divRef}
|
||||
setIsResize={setIsResize}
|
||||
@@ -2324,9 +2153,10 @@ function PdfRequestFiles(
|
||||
setUniqueId={setUniqueId}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
handleTextSettingModal={handleTextSettingModal}
|
||||
setWidgetType={setWidgetType}
|
||||
assignedWidgetId={assignedWidgetId}
|
||||
setRequestSignTour={setRequestSignTour}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
setTempSignerId={setTempSignerId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -2389,8 +2219,6 @@ function PdfRequestFiles(
|
||||
!alreadySign &&
|
||||
currentSigner && (
|
||||
<DefaultSignature
|
||||
defaultSignImg={defaultSignImg}
|
||||
myInitial={myInitial}
|
||||
userObjectId={signerObjectId}
|
||||
setIsLoading={setIsLoading}
|
||||
xyPosition={signerPos}
|
||||
@@ -2424,23 +2252,23 @@ function PdfRequestFiles(
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={validateAlert}
|
||||
title={t("validation-alert")}
|
||||
handleClose={() => setValidateAlert(false)}
|
||||
>
|
||||
<div className="h-[100%] p-[20px]">
|
||||
<p>{t("validation-alert-1")}</p>
|
||||
<div className="h-[1px] bg-[#9f9f9f] w-full my-[15px]"></div>
|
||||
<button
|
||||
onClick={() => setValidateAlert(false)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost"
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
{currentSigner && isShowModal[currWidgetsDetails?.key] && (
|
||||
<WidgetsValueModal
|
||||
key={currWidgetsDetails?.key}
|
||||
xyPosition={signerPos}
|
||||
pageNumber={pageNumber}
|
||||
setXyPosition={setSignerPos}
|
||||
uniqueId={uniqueId}
|
||||
setPageNumber={setPageNumber}
|
||||
finishDocument={handleSignPdf}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
index={pageNumber}
|
||||
setUniqueId={setUniqueId}
|
||||
tempSignerId={tempSignerId}
|
||||
signatureTypes={signatureType}
|
||||
/>
|
||||
)}
|
||||
<DownloadPdfZip
|
||||
setIsDownloadModal={setIsDownloadModal}
|
||||
isDownloadModal={isDownloadModal}
|
||||
|
||||
@@ -11,11 +11,11 @@ import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { useDrop } from "react-dnd";
|
||||
import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import WidgetComponent from "../components/pdf/WidgetComponent";
|
||||
import Tour from "reactour";
|
||||
import Tour from "../primitives/Tour";
|
||||
import { useLocation, useParams } from "react-router";
|
||||
import SignerListPlace from "../components/pdf/SignerListPlace";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import { RWebShare } from "react-web-share";
|
||||
import ShareButton from "../primitives/ShareButton";
|
||||
import {
|
||||
replaceMailVaribles,
|
||||
pdfNewWidthFun,
|
||||
@@ -44,7 +44,8 @@ import {
|
||||
handleSignatureType,
|
||||
getBase64FromUrl,
|
||||
generatePdfName,
|
||||
mailTemplate
|
||||
mailTemplate,
|
||||
getOriginalWH
|
||||
} from "../constant/Utils";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import { useNavigate } from "react-router";
|
||||
@@ -68,10 +69,12 @@ import LottieWithLoader from "../primitives/DotLottieReact";
|
||||
import Alert from "../primitives/Alert";
|
||||
import AsyncSelect from "react-select/async";
|
||||
import AddContact from "../primitives/AddContact";
|
||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal.jsx";
|
||||
|
||||
function PlaceHolderSign() {
|
||||
const { t } = useTranslation();
|
||||
const copyUrlRef = useRef(null);
|
||||
const isShowModal = useSelector((state) => state.widget.isShowModal);
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const editorRef = useRef();
|
||||
@@ -121,7 +124,8 @@ function PlaceHolderSign() {
|
||||
const [selectedEmail, setSelectedEmail] = useState(false);
|
||||
const [isResize, setIsResize] = useState(false);
|
||||
const [zIndex, setZIndex] = useState(1);
|
||||
const [signKey, setSignKey] = useState();
|
||||
// tempSignerId is used to temporarily store the currently selected signer's unique ID, When editing a text widget, it automatically attaches a prefill user, and since prefill users are not shown in the signer list, the selected signer from before editing would be lost. To handle this, we store the currently selected signer's unique ID in tempSignerId before entering the text widget edit mode. Once the text widget settings are completed,
|
||||
// we restore the original selected signer by setting tempSignerId back to uniqueId.This ensures that the correct signer remains selected and visible in the UI even after interacting with a prefill-only widget like the text widget.
|
||||
const [tempSignerId, setTempSignerId] = useState("");
|
||||
const [blockColor, setBlockColor] = useState("");
|
||||
const [isTextSetting, setIsTextSetting] = useState(false);
|
||||
@@ -134,14 +138,11 @@ function PlaceHolderSign() {
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const [widgetType, setWidgetType] = useState("");
|
||||
const [isUiLoading, setIsUiLoading] = useState(false);
|
||||
const [isRadio, setIsRadio] = useState(false);
|
||||
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
|
||||
const [selectWidgetId, setSelectWidgetId] = useState("");
|
||||
const [isCheckbox, setIsCheckbox] = useState(false);
|
||||
const [isNameModal, setIsNameModal] = useState(false);
|
||||
const [widgetName, setWidgetName] = useState(false);
|
||||
const [mailStatus, setMailStatus] = useState("");
|
||||
const [isCurrUser, setIsCurrUser] = useState(false);
|
||||
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
|
||||
@@ -513,7 +514,8 @@ function PlaceHolderSign() {
|
||||
pageNumber,
|
||||
containerWH
|
||||
);
|
||||
let dropData = [];
|
||||
let dropData = [],
|
||||
dropObj;
|
||||
let placeHolder;
|
||||
const dragTypeValue = item?.text ? item.text : monitor.type;
|
||||
const widgetWidth =
|
||||
@@ -522,11 +524,15 @@ function PlaceHolderSign() {
|
||||
defaultWidthHeight(dragTypeValue).height * containerScale;
|
||||
//adding and updating drop position in array when user drop signature button in div
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
|
||||
const divWidth = divRef.current.getBoundingClientRect().width;
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
const dropObj = {
|
||||
// Compute the pixel‐space center within the PDF viewport:
|
||||
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
|
||||
const xPosition_Final = centerX_Pixels / (containerScale * scale);
|
||||
dropObj = {
|
||||
//onclick put placeholder center on pdf
|
||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
||||
xPosition: xPosition_Final,
|
||||
yPosition: widgetHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
@@ -556,7 +562,7 @@ function PlaceHolderSign() {
|
||||
const getYPosition = signBtnPosition[0]
|
||||
? y - signBtnPosition[0].yPos
|
||||
: y;
|
||||
const dropObj = {
|
||||
dropObj = {
|
||||
xPosition: getXPosition / (containerScale * scale),
|
||||
yPosition: getYPosition / (containerScale * scale),
|
||||
isStamp:
|
||||
@@ -572,7 +578,6 @@ function PlaceHolderSign() {
|
||||
dropData.push(dropObj);
|
||||
placeHolder = { pageNumber: pageNumber, pos: dropData };
|
||||
}
|
||||
setSelectWidgetId(key);
|
||||
if (signer) {
|
||||
let filterSignerPos, currentPagePosition;
|
||||
if (dragTypeValue === textWidget) {
|
||||
@@ -664,24 +669,14 @@ function PlaceHolderSign() {
|
||||
} else if (dragTypeValue === radioButtonWidget) {
|
||||
setIsRadio(true);
|
||||
}
|
||||
setWidgetType(dragTypeValue);
|
||||
setSignKey(key);
|
||||
setCurrWidgetsDetails({});
|
||||
setWidgetName(dragTypeValue);
|
||||
setCurrWidgetsDetails(dropObj);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//function for get pdf page details
|
||||
const pageDetails = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
const totalPages = pdf?.numPages;
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const scale = 1;
|
||||
const { width, height } = getPage.getViewport({ scale });
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
}
|
||||
const pdfWHObj = await getOriginalWH(pdf);
|
||||
setPdfOriginalWH(pdfWHObj);
|
||||
setPdfLoad(true);
|
||||
};
|
||||
@@ -865,7 +860,9 @@ function PlaceHolderSign() {
|
||||
});
|
||||
const isSignYourSelfFlow = false;
|
||||
try {
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
const pdfBase64 = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
placeholder,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -966,7 +963,7 @@ function PlaceHolderSign() {
|
||||
const IsSignerNotExist = filterPrefill?.filter((x) => !x.signerObjId);
|
||||
if (IsSignerNotExist && IsSignerNotExist?.length > 0) {
|
||||
setSignerExistModal(true);
|
||||
setSelectWidgetId(IsSignerNotExist[0]?.placeHolder?.[0]?.pos?.[0]?.key);
|
||||
setCurrWidgetsDetails(IsSignerNotExist[0]?.placeHolder?.[0]?.pos);
|
||||
} else {
|
||||
saveDocumentDetails();
|
||||
}
|
||||
@@ -1144,9 +1141,13 @@ function PlaceHolderSign() {
|
||||
<i className="fa-light fa-copy" />
|
||||
<span className=" hidden md:block ml-1 ">{t("copy-link")}</span>
|
||||
</button>
|
||||
<RWebShare data={{ url: data.url, title: t("sign-url") }}>
|
||||
<ShareButton
|
||||
title={t("sign-url")}
|
||||
text={t("sign-url")}
|
||||
url={data.url}
|
||||
>
|
||||
<i className="fa-light fa-share-from-square op-link op-link-secondary no-underline"></i>
|
||||
</RWebShare>
|
||||
</ShareButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -1219,7 +1220,7 @@ function PlaceHolderSign() {
|
||||
receiver_phone: signerMail[i]?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
replaceVar = replaceMailVaribles(
|
||||
requestSubject,
|
||||
@@ -1248,7 +1249,7 @@ function PlaceHolderSign() {
|
||||
receiver_phone: signerMail[i]?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
|
||||
}
|
||||
@@ -1259,7 +1260,7 @@ function PlaceHolderSign() {
|
||||
title: documentName,
|
||||
organization: orgName,
|
||||
localExpireDate: localExpireDate,
|
||||
sigingUrl: signPdf
|
||||
signingUrl: signPdf
|
||||
};
|
||||
let params = {
|
||||
extUserId: owner?.objectId,
|
||||
@@ -1428,21 +1429,21 @@ function PlaceHolderSign() {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === signKey) {
|
||||
if (widgetType === radioButtonWidget) {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (currWidgetsDetails?.type === radioButtonWidget) {
|
||||
if (addOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height + 15
|
||||
: defaultWidthHeight(widgetType).height + 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height + 15
|
||||
};
|
||||
} else if (deleteOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height - 15
|
||||
: defaultWidthHeight(widgetType).height - 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height - 15
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -1463,20 +1464,20 @@ function PlaceHolderSign() {
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (widgetType === "checkbox") {
|
||||
} else if (currWidgetsDetails?.type === "checkbox") {
|
||||
if (addOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height + 15
|
||||
: defaultWidthHeight(widgetType).height + 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height + 15
|
||||
};
|
||||
} else if (deleteOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height - 15
|
||||
: defaultWidthHeight(widgetType).height - 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height - 15
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -1567,7 +1568,7 @@ function PlaceHolderSign() {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === signKey) {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === textInputWidget) {
|
||||
return {
|
||||
...position,
|
||||
@@ -2367,9 +2368,9 @@ function PlaceHolderSign() {
|
||||
setXyPosition={setSignerPos}
|
||||
allPages={allPages}
|
||||
pageNumber={pageNumber}
|
||||
signKey={signKey}
|
||||
signKey={currWidgetsDetails?.key}
|
||||
Id={uniqueId}
|
||||
widgetType={widgetType}
|
||||
widgetType={currWidgetsDetails?.type}
|
||||
setUniqueId={setUniqueId}
|
||||
tempSignerId={tempSignerId}
|
||||
setTempSignerId={setTempSignerId}
|
||||
@@ -2474,17 +2475,13 @@ function PlaceHolderSign() {
|
||||
setZIndex={setZIndex}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
signersdata={signersdata}
|
||||
setSignKey={setSignKey}
|
||||
handleLinkUser={handleLinkUser}
|
||||
setUniqueId={setUniqueId}
|
||||
isDragging={isDragging}
|
||||
setShowDropdown={setShowDropdown}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
handleNameModal={setIsNameModal}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
@@ -2499,6 +2496,7 @@ function PlaceHolderSign() {
|
||||
setFontColor={setFontColor}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
divRef={divRef}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -2536,7 +2534,6 @@ function PlaceHolderSign() {
|
||||
handleDeleteUser={handleDeleteUser}
|
||||
uniqueId={uniqueId}
|
||||
setSignerPos={setSignerPos}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -2582,6 +2579,23 @@ function PlaceHolderSign() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isShowModal[currWidgetsDetails?.key] && (
|
||||
<WidgetsValueModal
|
||||
key={currWidgetsDetails?.key}
|
||||
xyPosition={signerPos}
|
||||
pageNumber={pageNumber}
|
||||
setXyPosition={setSignerPos}
|
||||
uniqueId={uniqueId}
|
||||
setPageNumber={setPageNumber}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
index={pageNumber}
|
||||
isSave={true}
|
||||
tempSignerId={tempSignerId}
|
||||
setUniqueId={setUniqueId}
|
||||
signatureTypes={signatureType}
|
||||
/>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={isAlreadyPlace.status}
|
||||
title={t("document-alert")}
|
||||
@@ -2610,7 +2624,7 @@ function PlaceHolderSign() {
|
||||
)}
|
||||
<WidgetNameModal
|
||||
signatureType={signatureType}
|
||||
widgetName={widgetName}
|
||||
widgetName={currWidgetsDetails?.options?.name}
|
||||
defaultdata={currWidgetsDetails}
|
||||
isOpen={isNameModal}
|
||||
handleClose={handleNameModal}
|
||||
|
||||
@@ -296,6 +296,15 @@ const Preferences = () => {
|
||||
const updateRes = JSON.parse(JSON.stringify(updateTenant));
|
||||
setRequestBody(updateRes?.RequestBody);
|
||||
setRequestSubject(updateRes?.RequestSubject);
|
||||
let extUser =
|
||||
localStorage.getItem("Extand_Class") &&
|
||||
JSON.parse(localStorage.getItem("Extand_Class"))?.[0];
|
||||
if (extUser && extUser?.objectId) {
|
||||
extUser.TenantId.RequestBody = updateRes?.RequestBody;
|
||||
extUser.TenantId.RequestBody = updateRes?.RequestSubject;
|
||||
const _extUser = JSON.parse(JSON.stringify(extUser));
|
||||
localStorage.setItem("Extand_Class", JSON.stringify([_extUser]));
|
||||
}
|
||||
setIsAlert({ type: "success", msg: t("saved-successfully") });
|
||||
setTimeout(() => setIsAlert({ type: "", msg: "" }), 1500);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import "../styles/signature.css";
|
||||
import Parse from "parse";
|
||||
@@ -8,7 +8,6 @@ import RenderAllPdfPage from "../components/pdf/RenderAllPdfPage";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { useDrop } from "react-dnd";
|
||||
import SignPad from "../components/pdf/SignPad";
|
||||
import EmailComponent from "../components/pdf/EmailComponent";
|
||||
import WidgetComponent from "../components/pdf/WidgetComponent";
|
||||
import {
|
||||
@@ -18,8 +17,6 @@ import {
|
||||
multiSignEmbed,
|
||||
calculateInitialWidthHeight,
|
||||
defaultWidthHeight,
|
||||
onSaveImage,
|
||||
onSaveSign,
|
||||
contractUsers,
|
||||
contactBook,
|
||||
randomId,
|
||||
@@ -35,23 +32,23 @@ import {
|
||||
onClickZoomIn,
|
||||
onClickZoomOut,
|
||||
rotatePdfPage,
|
||||
signatureTypes,
|
||||
getBase64FromUrl,
|
||||
convertBase64ToFile,
|
||||
generatePdfName,
|
||||
handleRemoveWidgets,
|
||||
compressedFileSize,
|
||||
addWidgetSelfsignOptions
|
||||
addWidgetSelfsignOptions,
|
||||
getOriginalWH,
|
||||
signatureTypes
|
||||
} from "../constant/Utils";
|
||||
import { useParams } from "react-router";
|
||||
import Tour from "reactour";
|
||||
import Tour from "../primitives/Tour";
|
||||
import Signedby from "../components/pdf/Signedby";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import PlaceholderCopy from "../components/pdf/PlaceholderCopy";
|
||||
import Title from "../components/Title";
|
||||
import DropdownWidgetOption from "../components/pdf/DropdownWidgetOption";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import TextFontSetting from "../components/pdf/TextFontSetting";
|
||||
import VerifyEmail from "../components/pdf/VerifyEmail";
|
||||
import PdfZoom from "../components/pdf/PdfZoom";
|
||||
@@ -63,43 +60,44 @@ import ModalUi from "../primitives/ModalUi";
|
||||
import TourContentWithBtn from "../primitives/TourContentWithBtn";
|
||||
import HandleError from "../primitives/HandleError";
|
||||
import LoaderWithMsg from "../primitives/LoaderWithMsg";
|
||||
import {
|
||||
setSaveSignCheckbox,
|
||||
setMyInitial,
|
||||
setDefaultSignImg,
|
||||
setIsShowModal,
|
||||
resetWidgetState
|
||||
} from "../redux/reducers/widgetSlice.js";
|
||||
import WidgetsValueModal from "../components/pdf/WidgetsValueModal";
|
||||
//For signYourself inProgress section signer can add sign and complete doc sign.
|
||||
function SignYourSelf() {
|
||||
const { t } = useTranslation();
|
||||
const { docId } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
const isShowModal = useSelector((state) => state.widget.isShowModal);
|
||||
const saveSignCheckbox = useSelector(
|
||||
(state) => state.widget.saveSignCheckbox
|
||||
);
|
||||
const appName =
|
||||
"OpenSign™";
|
||||
const divRef = useRef(null);
|
||||
const nodeRef = useRef(null);
|
||||
const imageRef = useRef(null);
|
||||
const pdfRef = useRef();
|
||||
const numPages = 1;
|
||||
const [pdfDetails, setPdfDetails] = useState([]);
|
||||
const [isSignPad, setIsSignPad] = useState(false);
|
||||
const [allPages, setAllPages] = useState(null);
|
||||
const [pdfUrl, setPdfUrl] = useState();
|
||||
const [xyPosition, setXyPosition] = useState([]);
|
||||
const [defaultSignImg, setDefaultSignImg] = useState();
|
||||
const [pageNumber, setPageNumber] = useState(1);
|
||||
const [image, setImage] = useState(null);
|
||||
const [isImageSelect, setIsImageSelect] = useState(false);
|
||||
const [signature, setSignature] = useState();
|
||||
const [isStamp, setIsStamp] = useState(false);
|
||||
const [isEmail, setIsEmail] = useState(false);
|
||||
const [signBtnPosition, setSignBtnPosition] = useState([]);
|
||||
const [xySignature, setXYSignature] = useState({});
|
||||
const [dragKey, setDragKey] = useState();
|
||||
const [fontSize, setFontSize] = useState();
|
||||
const [fontColor, setFontColor] = useState();
|
||||
const [signKey, setSignKey] = useState();
|
||||
const [imgWH, setImgWH] = useState({});
|
||||
const [pdfNewWidth, setPdfNewWidth] = useState();
|
||||
const [pdfOriginalWH, setPdfOriginalWH] = useState([]);
|
||||
const [successEmail, setSuccessEmail] = useState(false);
|
||||
const [myInitial, setMyInitial] = useState("");
|
||||
const [isInitial, setIsInitial] = useState(false);
|
||||
const [isUiLoading, setIsUiLoading] = useState(false);
|
||||
const [validateAlert, setValidateAlert] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState({
|
||||
isLoad: true,
|
||||
message: t("loading-mssg")
|
||||
@@ -113,7 +111,6 @@ function SignYourSelf() {
|
||||
const [contractName, setContractName] = useState("");
|
||||
const [containerWH, setContainerWH] = useState({});
|
||||
const [isPageCopy, setIsPageCopy] = useState(false);
|
||||
const [selectWidgetId, setSelectWidgetId] = useState("");
|
||||
const [otpLoader, setOtpLoader] = useState(false);
|
||||
const [showAlreadySignDoc, setShowAlreadySignDoc] = useState({
|
||||
status: false
|
||||
@@ -121,7 +118,6 @@ function SignYourSelf() {
|
||||
const [isTextSetting, setIsTextSetting] = useState(false);
|
||||
const [currWidgetsDetails, setCurrWidgetsDetails] = useState({});
|
||||
const [isCheckbox, setIsCheckbox] = useState(false);
|
||||
const [widgetType, setWidgetType] = useState("");
|
||||
const [pdfLoad, setPdfLoad] = useState(false);
|
||||
const [isAlert, setIsAlert] = useState({ isShow: false, alertMessage: "" });
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
@@ -142,10 +138,7 @@ function SignYourSelf() {
|
||||
const [isDownloadModal, setIsDownloadModal] = useState(false);
|
||||
const [isResize, setIsResize] = useState(false);
|
||||
const [isUploadPdf, setIsUploadPdf] = useState(false);
|
||||
const [saveSignCheckbox, setSaveSignCheckbox] = useState({
|
||||
isVisible: false,
|
||||
signId: ""
|
||||
});
|
||||
|
||||
const [owner, setOwner] = useState({});
|
||||
const [, drop] = useDrop({
|
||||
accept: "BOX",
|
||||
@@ -173,6 +166,7 @@ function SignYourSelf() {
|
||||
const jsonSender = JSON.parse(senderUser);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(resetWidgetState([]));
|
||||
if (documentId) {
|
||||
getDocumentDetails(true);
|
||||
}
|
||||
@@ -243,7 +237,6 @@ function SignYourSelf() {
|
||||
});
|
||||
} else {
|
||||
setIsUiLoading(false);
|
||||
setIsSignPad(false);
|
||||
setIsEmail(true);
|
||||
setXyPosition([]);
|
||||
setSignBtnPosition([]);
|
||||
@@ -266,13 +259,15 @@ function SignYourSelf() {
|
||||
//function to get default signatur eof current user from `contracts_Signature` class
|
||||
const defaultSignRes = await getDefaultSignature(jsonSender.objectId);
|
||||
if (defaultSignRes?.status === "success") {
|
||||
setSaveSignCheckbox((prev) => ({
|
||||
...prev,
|
||||
isVisible: true,
|
||||
signId: defaultSignRes?.res?.id
|
||||
}));
|
||||
setDefaultSignImg(defaultSignRes?.res?.defaultSignature);
|
||||
setMyInitial(defaultSignRes?.res?.defaultInitial);
|
||||
dispatch(
|
||||
setSaveSignCheckbox({
|
||||
...saveSignCheckbox,
|
||||
isVisible: true,
|
||||
signId: defaultSignRes?.res?.id
|
||||
})
|
||||
);
|
||||
dispatch(setDefaultSignImg(defaultSignRes?.res?.defaultSignature));
|
||||
dispatch(setMyInitial(defaultSignRes?.res?.defaultInitial));
|
||||
}
|
||||
const contractUsersRes = await contractUsers();
|
||||
if (contractUsersRes === "Error: Something went wrong!") {
|
||||
@@ -281,7 +276,8 @@ function SignYourSelf() {
|
||||
} else if (contractUsersRes[0] && contractUsersRes.length > 0) {
|
||||
setContractName("_Users");
|
||||
setSignerUserId(contractUsersRes[0].objectId);
|
||||
setSaveSignCheckbox((prev) => ({ ...prev, isVisible: true }));
|
||||
dispatch(setSaveSignCheckbox({ ...saveSignCheckbox, isVisible: true }));
|
||||
|
||||
const tourstatuss =
|
||||
contractUsersRes[0].TourStatus && contractUsersRes[0].TourStatus;
|
||||
if (tourstatuss && tourstatuss.length > 0 && !isCompleted) {
|
||||
@@ -293,12 +289,9 @@ function SignYourSelf() {
|
||||
setCheckTourStatus(checkTourRecipients[0].signyourself);
|
||||
}
|
||||
} else {
|
||||
setCheckTourStatus(true);
|
||||
setCheckTourStatus(false);
|
||||
}
|
||||
const loadObj = {
|
||||
isLoad: false
|
||||
};
|
||||
setIsLoading(loadObj);
|
||||
setIsLoading({ isLoad: false });
|
||||
} else if (contractUsersRes.length === 0) {
|
||||
const contractContactBook = await contactBook(jsonSender.objectId);
|
||||
if (contractContactBook && contractContactBook.length > 0) {
|
||||
@@ -373,15 +366,19 @@ function SignYourSelf() {
|
||||
);
|
||||
//adding and updating drop position in array when user drop signature button in div
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
const divWidth = divRef.current.getBoundingClientRect().width;
|
||||
const getWidth = widgetTypeExist
|
||||
? calculateInitialWidthHeight(widgetValue).getWidth
|
||||
: defaultWidthHeight(dragTypeValue).width;
|
||||
const getHeight = defaultWidthHeight(dragTypeValue).height;
|
||||
|
||||
// Compute the pixel‐space center within the PDF viewport:
|
||||
const centerX_Pixels = divWidth / 2 - getWidth / 2;
|
||||
const xPosition_Final = centerX_Pixels / (containerScale * scale);
|
||||
dropObj = {
|
||||
xPosition: getWidth / 2 + containerWH.width / 2,
|
||||
xPosition: xPosition_Final,
|
||||
yPosition: getHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
@@ -436,20 +433,7 @@ function SignYourSelf() {
|
||||
const xyPos = { pageNumber: pageNumber, pos: dropData };
|
||||
setXyPosition((prev) => [...prev, xyPos]);
|
||||
}
|
||||
|
||||
if (
|
||||
dragTypeValue === "signature" ||
|
||||
dragTypeValue === "stamp" ||
|
||||
dragTypeValue === "image" ||
|
||||
dragTypeValue === "initials"
|
||||
) {
|
||||
setIsSignPad(true);
|
||||
}
|
||||
if (dragTypeValue === "stamp" || dragTypeValue === "image") {
|
||||
setIsStamp(true);
|
||||
} else if (dragTypeValue === "initials") {
|
||||
setIsInitial(true);
|
||||
} else if (dragTypeValue === "checkbox") {
|
||||
if (dragTypeValue === "checkbox") {
|
||||
setIsCheckbox(true);
|
||||
} else if (
|
||||
[
|
||||
@@ -464,9 +448,10 @@ function SignYourSelf() {
|
||||
setFontSize(12);
|
||||
setFontColor("black");
|
||||
}
|
||||
setWidgetType(dragTypeValue);
|
||||
setSelectWidgetId(key);
|
||||
setSignKey(key);
|
||||
dispatch(setIsShowModal({ [key]: true }));
|
||||
// if (dragTypeValue !== "checkbox") {
|
||||
setCurrWidgetsDetails(dropObj);
|
||||
// }
|
||||
};
|
||||
|
||||
//`handleResend` function is used to resend otp for email verification
|
||||
@@ -646,16 +631,17 @@ function SignYourSelf() {
|
||||
const HeaderDocId = extUserPtr?.HeaderDocId;
|
||||
//embed document's object id to all pages in pdf document
|
||||
if (!HeaderDocId) {
|
||||
await embedDocId(pdfDoc, documentId, allPages);
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
await embedDocId(pdfOriginalWH, pdfDoc, documentId);
|
||||
}
|
||||
//embed multi signature in pdf
|
||||
//embed all widgets in document
|
||||
const pdfBytes = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
xyPosition,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
scale
|
||||
);
|
||||
// console.log("pdf", pdfBytes);
|
||||
//function for call to embed signature in pdf and get digital signature pdf
|
||||
if (!pdfBytes?.error) {
|
||||
await signPdfFun(pdfBytes, documentId);
|
||||
@@ -759,7 +745,6 @@ function SignYourSelf() {
|
||||
getDocumentDetails(false);
|
||||
}
|
||||
};
|
||||
|
||||
//function for save x and y position and show signature tab on that position
|
||||
const handleTabDrag = (key) => {
|
||||
setDragKey(key);
|
||||
@@ -810,14 +795,7 @@ function SignYourSelf() {
|
||||
};
|
||||
//function for get pdf page details
|
||||
const pageDetails = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
const totalPages = pdf?.numPages;
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const scale = 1;
|
||||
const { width, height } = getPage.getViewport({ scale });
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
}
|
||||
const pdfWHObj = await getOriginalWH(pdf);
|
||||
setPdfOriginalWH(pdfWHObj);
|
||||
setPdfLoad(true);
|
||||
};
|
||||
@@ -826,53 +804,7 @@ function SignYourSelf() {
|
||||
setSignBtnPosition([]);
|
||||
setPageNumber((prevPageNumber) => prevPageNumber + offset);
|
||||
}
|
||||
//function for image upload or update
|
||||
const onImageChange = (event) => {
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const file = event.target.files[0];
|
||||
compressedFileSize(file, setImgWH, setImage);
|
||||
}
|
||||
};
|
||||
|
||||
//function for upload stamp or image
|
||||
const saveImage = () => {
|
||||
const getImage = onSaveImage(xyPosition, index, signKey, imgWH, image);
|
||||
setXyPosition(getImage);
|
||||
};
|
||||
|
||||
//function for save button to save signature or image url
|
||||
const saveSign = (type, isDefaultSign, width, height, typedSignature) => {
|
||||
const isTypeText = width && height ? true : false;
|
||||
const signatureImg = isDefaultSign
|
||||
? isDefaultSign === "initials"
|
||||
? myInitial
|
||||
: defaultSignImg
|
||||
: signature;
|
||||
let imgWH = { width: width ? width : "", height: height ? height : "" };
|
||||
setIsSignPad(false);
|
||||
setIsImageSelect(false);
|
||||
setImage();
|
||||
if (isDefaultSign) {
|
||||
const img = new Image();
|
||||
img.src = defaultSignImg;
|
||||
if (img.complete) {
|
||||
imgWH = { width: img.width, height: img.height };
|
||||
}
|
||||
}
|
||||
const getUpdatePosition = onSaveSign(
|
||||
type,
|
||||
xyPosition,
|
||||
index,
|
||||
signKey,
|
||||
signatureImg,
|
||||
imgWH,
|
||||
isDefaultSign,
|
||||
isTypeText,
|
||||
typedSignature
|
||||
);
|
||||
|
||||
setXyPosition(getUpdatePosition);
|
||||
};
|
||||
//function for capture position on hover or touch widgets button
|
||||
const handleDivClick = (e) => {
|
||||
const isTouchEvent = e.type.startsWith("touch");
|
||||
@@ -932,7 +864,7 @@ function SignYourSelf() {
|
||||
selector: '[data-tut="addWidgets"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={t("tour-mssg.signyour-self-2")}
|
||||
message={t("tour-mssg.signyour-self-1")}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
@@ -970,14 +902,10 @@ function SignYourSelf() {
|
||||
} else {
|
||||
updatedTourStatus = [{ signyourself: true }];
|
||||
}
|
||||
await axios
|
||||
.put(
|
||||
`${localStorage.getItem(
|
||||
"baseUrl"
|
||||
)}classes/contracts${contractName}/${signerUserId}`,
|
||||
{
|
||||
TourStatus: updatedTourStatus
|
||||
},
|
||||
try {
|
||||
await axios.put(
|
||||
`${localStorage.getItem("baseUrl")}classes/contracts${contractName}/${signerUserId}`,
|
||||
{ TourStatus: updatedTourStatus },
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -985,14 +913,11 @@ function SignYourSelf() {
|
||||
sessionToken: localStorage.getItem("accesstoken")
|
||||
}
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
// const json = Listdata.data;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("axois err ", err);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
});
|
||||
);
|
||||
} catch (err) {
|
||||
console.log("axois err ", err);
|
||||
alert(t("something-went-wrong-mssg"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1015,20 +940,20 @@ function SignYourSelf() {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === signKey) {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (addOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height + 15
|
||||
: defaultWidthHeight(widgetType).height + 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height + 15
|
||||
};
|
||||
} else if (deleteOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height - 15
|
||||
: defaultWidthHeight(widgetType).height - 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height - 15
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -1078,7 +1003,7 @@ function SignYourSelf() {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === signKey) {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
return {
|
||||
...position,
|
||||
options: {
|
||||
@@ -1195,7 +1120,7 @@ function SignYourSelf() {
|
||||
/>
|
||||
)}
|
||||
{/* this component used for UI interaction and show their functionality */}
|
||||
{pdfLoad && !checkTourStatus && (
|
||||
{pdfLoad && !checkTourStatus && !isCompleted && (
|
||||
<Tour
|
||||
onRequestClose={closeTour}
|
||||
steps={tourConfig}
|
||||
@@ -1292,39 +1217,9 @@ function SignYourSelf() {
|
||||
setXyPosition={setXyPosition}
|
||||
allPages={allPages}
|
||||
pageNumber={pageNumber}
|
||||
signKey={signKey}
|
||||
widgetType={widgetType}
|
||||
signKey={currWidgetsDetails?.key}
|
||||
widgetType={currWidgetsDetails?.type}
|
||||
/>
|
||||
{/* this is modal of signature pad */}
|
||||
{isSignPad && (
|
||||
<SignPad
|
||||
saveSignCheckbox={saveSignCheckbox}
|
||||
setSaveSignCheckbox={setSaveSignCheckbox}
|
||||
signatureTypes={signatureTypes}
|
||||
isSignPad={isSignPad}
|
||||
isStamp={isStamp}
|
||||
setIsImageSelect={setIsImageSelect}
|
||||
setIsSignPad={setIsSignPad}
|
||||
setImage={setImage}
|
||||
isImageSelect={isImageSelect}
|
||||
imageRef={imageRef}
|
||||
onImageChange={onImageChange}
|
||||
setSignature={setSignature}
|
||||
image={image}
|
||||
onSaveImage={saveImage}
|
||||
onSaveSign={saveSign}
|
||||
defaultSign={defaultSignImg}
|
||||
myInitial={myInitial}
|
||||
setDefaultSignImg={setDefaultSignImg}
|
||||
setMyInitial={setMyInitial}
|
||||
isInitial={isInitial}
|
||||
setIsInitial={setIsInitial}
|
||||
setIsStamp={setIsStamp}
|
||||
widgetType={widgetType}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
/>
|
||||
)}
|
||||
{/*render email component to send email after finish signature on document */}
|
||||
<EmailComponent
|
||||
isEmail={isEmail}
|
||||
@@ -1372,10 +1267,7 @@ function SignYourSelf() {
|
||||
handleTabDrag={handleTabDrag}
|
||||
handleStop={handleStop}
|
||||
isDragging={isDragging}
|
||||
setIsSignPad={setIsSignPad}
|
||||
setIsStamp={setIsStamp}
|
||||
handleDeleteSign={handleDeleteSign}
|
||||
setSignKey={setSignKey}
|
||||
pdfDetails={pdfDetails}
|
||||
setIsDragging={setIsDragging}
|
||||
xyPosition={xyPosition}
|
||||
@@ -1389,13 +1281,8 @@ function SignYourSelf() {
|
||||
index={index}
|
||||
containerWH={containerWH}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setIsInitial={setIsInitial}
|
||||
setWidgetType={setWidgetType}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
setValidateAlert={setValidateAlert}
|
||||
handleTextSettingModal={handleTextSettingModal}
|
||||
setScale={setScale}
|
||||
scale={scale}
|
||||
@@ -1407,6 +1294,7 @@ function SignYourSelf() {
|
||||
isResize={isResize}
|
||||
setIsResize={setIsResize}
|
||||
divRef={divRef}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1424,7 +1312,6 @@ function SignYourSelf() {
|
||||
isSignYourself={true}
|
||||
addPositionOfSignature={addPositionOfSignature}
|
||||
isMailSend={false}
|
||||
// setSelectWidgetId={setSelectWidgetId}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -1437,24 +1324,20 @@ function SignYourSelf() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<ModalUi
|
||||
isOpen={validateAlert}
|
||||
title={t("validation-alert")}
|
||||
handleClose={() => setValidateAlert(false)}
|
||||
>
|
||||
<div className="p-[20px] h-full">
|
||||
<p>{t("validate-alert-mssg")}</p>
|
||||
|
||||
<div className="h-[1px] w-full my-[15px] bg-[#9f9f9f]"></div>
|
||||
<button
|
||||
onClick={() => setValidateAlert(false)}
|
||||
type="button"
|
||||
className="op-btn op-btn-ghost shadow-md"
|
||||
>
|
||||
{t("close")}
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
{!isCheckbox && isShowModal[currWidgetsDetails?.key] && (
|
||||
<WidgetsValueModal
|
||||
key={currWidgetsDetails?.key}
|
||||
xyPosition={xyPosition} //placeholder details
|
||||
pageNumber={pageNumber} //current page number
|
||||
setXyPosition={setXyPosition} //placeholder details state
|
||||
setPageNumber={setPageNumber}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
index={index}
|
||||
isSave={true}
|
||||
signatureTypes={signatureTypes}
|
||||
/>
|
||||
)}
|
||||
<RotateAlert
|
||||
showRotateAlert={showRotateAlert.status}
|
||||
setShowRotateAlert={setShowRotateAlert}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
import { useDrop } from "react-dnd";
|
||||
import WidgetComponent from "../components/pdf/WidgetComponent";
|
||||
import Tour from "reactour";
|
||||
import Tour from "../primitives/Tour";
|
||||
import SignerListPlace from "../components/pdf/SignerListPlace";
|
||||
import Header from "../components/pdf/PdfHeader";
|
||||
import WidgetNameModal from "../components/pdf/WidgetNameModal";
|
||||
@@ -38,7 +38,8 @@ import {
|
||||
convertPdfArrayBuffer,
|
||||
generatePdfName,
|
||||
textWidget,
|
||||
multiSignEmbed
|
||||
multiSignEmbed,
|
||||
getOriginalWH
|
||||
} from "../constant/Utils";
|
||||
import RenderPdf from "../components/pdf/RenderPdf";
|
||||
import "../styles/AddUser.css";
|
||||
@@ -100,10 +101,8 @@ const TemplatePlaceholder = () => {
|
||||
const [isSigners, setIsSigners] = useState(false);
|
||||
const [zIndex, setZIndex] = useState(1);
|
||||
const [showDropdown, setShowDropdown] = useState(false);
|
||||
const [widgetType, setWidgetType] = useState("");
|
||||
const [isRadio, setIsRadio] = useState(false);
|
||||
const [blockColor, setBlockColor] = useState("");
|
||||
const [selectWidgetId, setSelectWidgetId] = useState("");
|
||||
const [isNameModal, setIsNameModal] = useState(false);
|
||||
const [isTextSetting, setIsTextSetting] = useState(false);
|
||||
const [pdfLoad, setPdfLoad] = useState(false);
|
||||
@@ -122,13 +121,11 @@ const TemplatePlaceholder = () => {
|
||||
const [isCreateDoc, setIsCreateDoc] = useState(false);
|
||||
const [isEditTemplate, setIsEditTemplate] = useState(false);
|
||||
const [isPageCopy, setIsPageCopy] = useState(false);
|
||||
const [signKey, setSignKey] = useState();
|
||||
const [IsReceipent, setIsReceipent] = useState(true);
|
||||
const [isDontShow, setIsDontShow] = useState(false);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [currWidgetsDetails, setCurrWidgetsDetails] = useState([]);
|
||||
const [isCheckbox, setIsCheckbox] = useState(false);
|
||||
const [widgetName, setWidgetName] = useState(false);
|
||||
const [isAddRole, setIsAddRole] = useState(false);
|
||||
const [fontSize, setFontSize] = useState();
|
||||
const [fontColor, setFontColor] = useState();
|
||||
@@ -137,7 +134,6 @@ const TemplatePlaceholder = () => {
|
||||
const [signatureType, setSignatureType] = useState([]);
|
||||
const [pdfArrayBuffer, setPdfArrayBuffer] = useState("");
|
||||
const [updatedPdfUrl, setUpdatedPdfUrl] = useState("");
|
||||
const [tempSignerId, setTempSignerId] = useState("");
|
||||
const [unSignedWidgetId, setUnSignedWidgetId] = useState("");
|
||||
const [owner, setOwner] = useState({});
|
||||
useEffect(() => {
|
||||
@@ -353,9 +349,6 @@ const TemplatePlaceholder = () => {
|
||||
|
||||
//function for setting position after drop signature button over pdf
|
||||
const addPositionOfSignature = (item, monitor) => {
|
||||
if (item && item.text) {
|
||||
setWidgetName(item.text);
|
||||
}
|
||||
getSignerPos(item, monitor);
|
||||
};
|
||||
|
||||
@@ -378,15 +371,20 @@ const TemplatePlaceholder = () => {
|
||||
const widgetHeight =
|
||||
defaultWidthHeight(dragTypeValue).height * containerScale;
|
||||
let dropData = [],
|
||||
dropObj,
|
||||
currentPagePosition,
|
||||
filterSignerPos;
|
||||
let placeHolder;
|
||||
if (item === "onclick") {
|
||||
// `getBoundingClientRect()` is used to get accurate measurement height of the div
|
||||
// `getBoundingClientRect()` is used to get accurate measurement width, height of the Pdf div
|
||||
const divWidth = divRef.current.getBoundingClientRect().width;
|
||||
const divHeight = divRef.current.getBoundingClientRect().height;
|
||||
const dropObj = {
|
||||
// Compute the pixel‐space center within the PDF viewport:
|
||||
const centerX_Pixels = divWidth / 2 - widgetWidth / 2;
|
||||
const xPosition_Final = centerX_Pixels / (containerScale * scale);
|
||||
dropObj = {
|
||||
//onclick put placeholder center on pdf
|
||||
xPosition: widgetWidth / 4 + containerWH.width / 2,
|
||||
xPosition: xPosition_Final,
|
||||
yPosition: widgetHeight + divHeight / 2,
|
||||
isStamp:
|
||||
(dragTypeValue === "stamp" || dragTypeValue === "image") && true,
|
||||
@@ -414,7 +412,7 @@ const TemplatePlaceholder = () => {
|
||||
const getYPosition = signBtnPosition[0]
|
||||
? y - signBtnPosition[0].yPos
|
||||
: y;
|
||||
const dropObj = {
|
||||
dropObj = {
|
||||
xPosition: getXPosition / (containerScale * scale),
|
||||
yPosition: getYPosition / (containerScale * scale),
|
||||
isStamp:
|
||||
@@ -521,10 +519,7 @@ const TemplatePlaceholder = () => {
|
||||
setFontSize(12);
|
||||
setFontColor("black");
|
||||
}
|
||||
setCurrWidgetsDetails({});
|
||||
setWidgetType(dragTypeValue);
|
||||
setSignKey(key);
|
||||
setSelectWidgetId(key);
|
||||
setCurrWidgetsDetails(dropObj);
|
||||
} else {
|
||||
setIsReceipent(false);
|
||||
}
|
||||
@@ -544,14 +539,7 @@ const TemplatePlaceholder = () => {
|
||||
|
||||
//function for get pdf page details
|
||||
const pageDetails = async (pdf) => {
|
||||
let pdfWHObj = [];
|
||||
const totalPages = pdf?.numPages;
|
||||
for (let index = 0; index < totalPages; index++) {
|
||||
const getPage = await pdf.getPage(index + 1);
|
||||
const scale = 1;
|
||||
const { width, height } = getPage.getViewport({ scale });
|
||||
pdfWHObj.push({ pageNumber: index + 1, width, height });
|
||||
}
|
||||
const pdfWHObj = await getOriginalWH(pdf);
|
||||
setPdfOriginalWH(pdfWHObj);
|
||||
setPdfLoad(true);
|
||||
};
|
||||
@@ -842,7 +830,9 @@ const TemplatePlaceholder = () => {
|
||||
});
|
||||
const isSignYourSelfFlow = false;
|
||||
try {
|
||||
//pdfOriginalWH contained all pdf's pages width,height & pagenumber in array format
|
||||
const pdfBase64 = await multiSignEmbed(
|
||||
pdfOriginalWH,
|
||||
placeholder,
|
||||
pdfDoc,
|
||||
isSignYourSelfFlow,
|
||||
@@ -988,14 +978,14 @@ const TemplatePlaceholder = () => {
|
||||
style: { fontSize: "13px" }
|
||||
},
|
||||
{
|
||||
selector: '[data-tut="reactourFirst"]',
|
||||
selector: '[data-tut="nonpresentmask"]',
|
||||
content: () => (
|
||||
<TourContentWithBtn
|
||||
message={t("tour-mssg.template-placeholder-2")}
|
||||
isChecked={handleDontShow}
|
||||
/>
|
||||
),
|
||||
position: "top",
|
||||
position: "center",
|
||||
style: { fontSize: "13px" },
|
||||
action: () => handleCloseRoleModal()
|
||||
},
|
||||
@@ -1159,25 +1149,6 @@ const TemplatePlaceholder = () => {
|
||||
const handleLinkUser = (id) => {
|
||||
setIsAddUser({ [id]: true });
|
||||
};
|
||||
//function to use unlink signer from widgets
|
||||
const handleUnlinkSigner = () => {
|
||||
//remove existing signer's details from 'signerPos' array
|
||||
const updatePlaceHolder = signerPos.map((x) => {
|
||||
if (x.Id === uniqueId) {
|
||||
return { ...x, signerPtr: {}, signerObjId: "" };
|
||||
}
|
||||
return { ...x };
|
||||
});
|
||||
setSignerPos(updatePlaceHolder);
|
||||
//remove existing signer's details from 'signersdata' array and keep role and id
|
||||
const updateSigner = signersdata.map((item) => {
|
||||
if (item.Id == uniqueId) {
|
||||
return { Role: item.Role, Id: item.Id, blockColor: item.blockColor };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
setSignersData(updateSigner);
|
||||
};
|
||||
// `handleAddUser` is used to adduser
|
||||
const handleAddUser = (data) => {
|
||||
const signerPtr = {
|
||||
@@ -1318,21 +1289,21 @@ const TemplatePlaceholder = () => {
|
||||
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === signKey) {
|
||||
if (widgetType === radioButtonWidget) {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (currWidgetsDetails?.type === radioButtonWidget) {
|
||||
if (addOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height + 15
|
||||
: defaultWidthHeight(widgetType).height + 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height + 15
|
||||
};
|
||||
} else if (deleteOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height - 15
|
||||
: defaultWidthHeight(widgetType).height - 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height - 15
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -1354,20 +1325,20 @@ const TemplatePlaceholder = () => {
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (widgetType === "checkbox") {
|
||||
} else if (currWidgetsDetails?.type === "checkbox") {
|
||||
if (addOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height + 15
|
||||
: defaultWidthHeight(widgetType).height + 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height + 15
|
||||
};
|
||||
} else if (deleteOption) {
|
||||
return {
|
||||
...position,
|
||||
Height: position.Height
|
||||
? position.Height - 15
|
||||
: defaultWidthHeight(widgetType).height - 15
|
||||
: defaultWidthHeight(currWidgetsDetails?.type).height - 15
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -1459,7 +1430,7 @@ const TemplatePlaceholder = () => {
|
||||
const getXYdata = getPageNumer[0].pos;
|
||||
const getPosData = getXYdata;
|
||||
const addSignPos = getPosData.map((position) => {
|
||||
if (position.key === signKey) {
|
||||
if (position.key === currWidgetsDetails?.key) {
|
||||
if (position.type === textInputWidget) {
|
||||
return {
|
||||
...position,
|
||||
@@ -1538,13 +1509,6 @@ const TemplatePlaceholder = () => {
|
||||
setShowDropdown(false);
|
||||
setIsRadio(false);
|
||||
setIsCheckbox(false);
|
||||
//condition for text widget type after set all values for text widget
|
||||
//change setUniqueId which is set in tempsignerId
|
||||
//because textwidget do not have signer user so for selected signers we have to do
|
||||
if (currWidgetsDetails.type === textWidget) {
|
||||
setUniqueId(tempSignerId);
|
||||
setTempSignerId("");
|
||||
}
|
||||
};
|
||||
|
||||
const clickOnZoomIn = () => {
|
||||
@@ -1791,12 +1755,10 @@ const TemplatePlaceholder = () => {
|
||||
setXyPosition={setSignerPos}
|
||||
allPages={allPages}
|
||||
pageNumber={pageNumber}
|
||||
signKey={signKey}
|
||||
signKey={currWidgetsDetails?.key}
|
||||
Id={uniqueId}
|
||||
widgetType={widgetType}
|
||||
widgetType={currWidgetsDetails?.type}
|
||||
setUniqueId={setUniqueId}
|
||||
tempSignerId={tempSignerId}
|
||||
setTempSignerId={setTempSignerId}
|
||||
/>
|
||||
{/* pdf header which contain funish back button */}
|
||||
<Header
|
||||
@@ -1853,14 +1815,10 @@ const TemplatePlaceholder = () => {
|
||||
setUniqueId={setUniqueId}
|
||||
signersdata={signersdata}
|
||||
setIsPageCopy={setIsPageCopy}
|
||||
setSignKey={setSignKey}
|
||||
isDragging={isDragging}
|
||||
setShowDropdown={setShowDropdown}
|
||||
setCurrWidgetsDetails={setCurrWidgetsDetails}
|
||||
setWidgetType={setWidgetType}
|
||||
setIsRadio={setIsRadio}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
selectWidgetId={selectWidgetId}
|
||||
setIsCheckbox={setIsCheckbox}
|
||||
handleNameModal={setIsNameModal}
|
||||
pdfOriginalWH={pdfOriginalWH}
|
||||
@@ -1874,9 +1832,9 @@ const TemplatePlaceholder = () => {
|
||||
setFontColor={setFontColor}
|
||||
isResize={isResize}
|
||||
divRef={divRef}
|
||||
setTempSignerId={setTempSignerId}
|
||||
uniqueId={uniqueId}
|
||||
unSignedWidgetId={unSignedWidgetId}
|
||||
currWidgetsDetails={currWidgetsDetails}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1913,7 +1871,6 @@ const TemplatePlaceholder = () => {
|
||||
setBlockColor={setBlockColor}
|
||||
setSignerPos={setSignerPos}
|
||||
uniqueId={uniqueId}
|
||||
setSelectWidgetId={setSelectWidgetId}
|
||||
isTemplateFlow={true}
|
||||
/>
|
||||
</div>
|
||||
@@ -1974,7 +1931,9 @@ const TemplatePlaceholder = () => {
|
||||
closePopup={closePopup}
|
||||
signersData={signersdata}
|
||||
signerPos={signerPos}
|
||||
handleUnlinkSigner={handleUnlinkSigner}
|
||||
setSignerPos={setSignerPos}
|
||||
setSignersData={setSignersData}
|
||||
isRemove={true}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1991,7 +1950,7 @@ const TemplatePlaceholder = () => {
|
||||
)}
|
||||
<WidgetNameModal
|
||||
signatureType={signatureType}
|
||||
widgetName={widgetName}
|
||||
widgetName={currWidgetsDetails?.options?.name}
|
||||
defaultdata={currWidgetsDetails}
|
||||
isOpen={isNameModal}
|
||||
handleClose={handleNameModal}
|
||||
|
||||
@@ -423,9 +423,9 @@ function UserProfile() {
|
||||
onClick={() =>
|
||||
editmode ? handleCancel() : navigate("/changepassword")
|
||||
}
|
||||
className={`op-btn ${
|
||||
editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"
|
||||
}`}
|
||||
className={
|
||||
`op-btn ${editmode ? "op-btn-ghost w-[100px]" : "op-btn-secondary"}`
|
||||
}
|
||||
>
|
||||
{editmode ? t("cancel") : t("change-password")}
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,864 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PDFDocument, PDFName, PDFSignature, PDFRef, PDFDict } from 'pdf-lib'; // Updated import
|
||||
import * as asn1js from 'asn1js';
|
||||
import { Certificate, ContentInfo, SignedData, IssuerAndSerialNumber } from 'pkijs';
|
||||
// import * as jsrsasign from 'jsrsasign'; // Removed for dynamic loading
|
||||
|
||||
const VerifyDocument = () => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedFile, setSelectedFile] = useState(null);
|
||||
const [fileBuffer, setFileBuffer] = useState(null);
|
||||
const [verificationResult, setVerificationResult] = useState('');
|
||||
const [detailedResults, setDetailedResults] = useState([]);
|
||||
const [collapsedSections, setCollapsedSections] = useState({});
|
||||
// const [jsrsasignStatus, setJsrsasignStatus] = useState('loading'); // Removed
|
||||
|
||||
// OID to human-readable label mapping
|
||||
const oidMapping = {
|
||||
'2.5.4.6': 'Country',
|
||||
'2.5.4.10': 'Organization',
|
||||
'2.5.4.11': 'Organizational Unit',
|
||||
'2.5.4.17': 'Postal Code',
|
||||
'2.5.4.8': 'State',
|
||||
'2.5.4.7': 'Locality', // Alternative for City
|
||||
'2.5.4.9': 'City',
|
||||
'2.5.4.51': 'Address',
|
||||
'2.5.4.3': 'Common Name',
|
||||
'2.5.4.4': 'Surname',
|
||||
'2.5.4.5': 'Serial Number',
|
||||
'2.5.4.12': 'Title',
|
||||
'2.5.4.13': 'Description',
|
||||
'2.5.4.16': 'Postal Address',
|
||||
'2.5.4.18': 'Post Office Box',
|
||||
'2.5.4.20': 'Telephone Number',
|
||||
'1.2.840.113549.1.9.1': 'Email Address',
|
||||
// Common alternative OIDs
|
||||
'C': 'Country',
|
||||
'O': 'Organization',
|
||||
'OU': 'Organizational Unit',
|
||||
'CN': 'Common Name',
|
||||
'ST': 'State',
|
||||
'L': 'Locality',
|
||||
'STREET': 'Address',
|
||||
'emailAddress': 'Email Address',
|
||||
'serialNumber': 'Serial Number'
|
||||
};
|
||||
|
||||
// Function to parse certificate subject/issuer into structured data
|
||||
const parseCertificateInfo = (certString) => {
|
||||
if (!certString) return {};
|
||||
|
||||
const parsed = {};
|
||||
|
||||
// Find all OID patterns and their positions
|
||||
const oidPattern = /(\d+\.\d+\.\d+\.\d+|\w+)=/g;
|
||||
const matches = [];
|
||||
let match;
|
||||
|
||||
while ((match = oidPattern.exec(certString)) !== null) {
|
||||
matches.push({
|
||||
oid: match[1],
|
||||
startIndex: match.index,
|
||||
equalIndex: match.index + match[1].length
|
||||
});
|
||||
}
|
||||
|
||||
// Extract value for each OID
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
const currentMatch = matches[i];
|
||||
const nextMatch = matches[i + 1];
|
||||
|
||||
const valueStart = currentMatch.equalIndex + 1; // Skip the "=" character
|
||||
const valueEnd = nextMatch ? nextMatch.startIndex - 2 : certString.length; // -2 to remove ", " before next OID
|
||||
|
||||
const value = certString.substring(valueStart, valueEnd).trim();
|
||||
const label = oidMapping[currentMatch.oid] || currentMatch.oid;
|
||||
|
||||
parsed[label] = value;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
};
|
||||
|
||||
// Function to determine if status should show success icon
|
||||
const isSuccessStatus = (status) => {
|
||||
const successTerms = ['valid', 'success', 'parsed', 'verified'];
|
||||
const errorTerms = ['error', 'invalid', 'failed', 'expired'];
|
||||
|
||||
const statusLower = status.toLowerCase();
|
||||
|
||||
// Check for explicit error terms first
|
||||
if (errorTerms.some(term => statusLower.includes(term))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for success terms
|
||||
return successTerms.some(term => statusLower.includes(term));
|
||||
};
|
||||
|
||||
// Function to determine if certificate validity should show success icon
|
||||
const isCertificateValid = (validityText) => {
|
||||
const validityLower = validityText.toLowerCase();
|
||||
|
||||
// If it contains "valid" and doesn't contain negative terms
|
||||
return validityLower.includes('valid') &&
|
||||
!validityLower.includes('expired') &&
|
||||
!validityLower.includes('not yet valid') &&
|
||||
!validityLower.includes('invalid');
|
||||
};
|
||||
|
||||
// Toggle collapsible sections
|
||||
const toggleSection = (signatureIndex, section) => {
|
||||
const key = `${signatureIndex}-${section}`;
|
||||
setCollapsedSections(prev => ({
|
||||
...prev,
|
||||
[key]: !prev[key]
|
||||
}));
|
||||
};
|
||||
|
||||
// useEffect for jsrsasign loading removed
|
||||
|
||||
const handleFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file && file.type === 'application/pdf') {
|
||||
setSelectedFile(file);
|
||||
setVerificationResult('');
|
||||
setDetailedResults([]);
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
setFileBuffer(e.target.result);
|
||||
};
|
||||
reader.readAsArrayBuffer(file);
|
||||
} else {
|
||||
setSelectedFile(null);
|
||||
setFileBuffer(null);
|
||||
setDetailedResults([]);
|
||||
setVerificationResult(t('please-select-pdf'));
|
||||
}
|
||||
};
|
||||
|
||||
const parseSignature = async (pdfDoc) => {
|
||||
const signatureFields = pdfDoc.getForm().getFields().filter(field => field instanceof PDFSignature); // Updated filter logic
|
||||
if (!signatureFields.length) {
|
||||
return { error: t('no-signature-found') };
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const field of signatureFields) {
|
||||
try {
|
||||
if (!field.acroField || !field.acroField.dict) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-acrofield-dict'),
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// New logic to determine the actual signature dictionary
|
||||
const fieldDict = field.acroField.dict;
|
||||
const vEntry = fieldDict.get(PDFName.of('V'));
|
||||
let actualSignatureDict = null;
|
||||
|
||||
if (vEntry) {
|
||||
if (vEntry instanceof PDFRef) {
|
||||
const lookedUp = pdfDoc.context.lookup(vEntry);
|
||||
if (lookedUp instanceof PDFDict) {
|
||||
actualSignatureDict = lookedUp;
|
||||
}
|
||||
} else if (vEntry instanceof PDFDict) {
|
||||
actualSignatureDict = vEntry;
|
||||
}
|
||||
}
|
||||
|
||||
// Use actualSignatureDict if found, otherwise behavior might be problematic (as per existing logic)
|
||||
// If actualSignatureDict is null, subsequent checks for byteRangeObject etc. will fail,
|
||||
// leading to an error message for this signature, which is acceptable.
|
||||
const signatureDict = actualSignatureDict;
|
||||
|
||||
// Check if signatureDict is null (meaning actualSignatureDict was not resolved)
|
||||
// and push an error if it is, before trying to get ByteRange or Contents.
|
||||
if (!signatureDict) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('signature-dictionary-not-found-or-invalid'), // New error message
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const byteRangeObject = signatureDict.get(PDFName.of('ByteRange'));
|
||||
let byteRange; // Will be assigned after validation
|
||||
|
||||
// Comprehensive validation for byteRangeObject and its contents
|
||||
if (!byteRangeObject ||
|
||||
!byteRangeObject.array ||
|
||||
!Array.isArray(byteRangeObject.array) ||
|
||||
byteRangeObject.array.length === 0 ||
|
||||
byteRangeObject.array.length % 2 !== 0) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-byterange'),
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const byteRangeNumbers = [];
|
||||
let byteRangeIsValid = true;
|
||||
for (const pdfObject of byteRangeObject.array) {
|
||||
if (!pdfObject || typeof pdfObject.asNumber !== 'function') {
|
||||
byteRangeIsValid = false;
|
||||
break;
|
||||
}
|
||||
const num = pdfObject.asNumber();
|
||||
if (!Number.isFinite(num)) { // Checks for NaN, Infinity, -Infinity
|
||||
byteRangeIsValid = false;
|
||||
break;
|
||||
}
|
||||
byteRangeNumbers.push(num);
|
||||
}
|
||||
|
||||
if (!byteRangeIsValid) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-byterange'), // Or a more specific error like "ByteRange contains non-numeric values"
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
byteRange = byteRangeNumbers; // Assign the validated numbers to byteRange
|
||||
|
||||
const contentsObject = signatureDict.get(PDFName.of('Contents'));
|
||||
if (!contentsObject || typeof contentsObject.asString !== 'function') {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-contents'),
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const contents = contentsObject.asString();
|
||||
|
||||
// The old basic check can be removed now as the more specific checks above cover these cases.
|
||||
// if (!byteRange || !contents) { ... }
|
||||
|
||||
// Calculate totalSignedLength for accurate buffer initialization
|
||||
let totalSignedLength = 0;
|
||||
for (let i = 1; i < byteRange.length; i += 2) {
|
||||
totalSignedLength += byteRange[i];
|
||||
}
|
||||
|
||||
if (totalSignedLength <= 0) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-byterange'), // totalSignedLength being non-positive implies invalid ByteRange
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const pdfSignedDataBytes = new Uint8Array(totalSignedLength);
|
||||
let offset = 0;
|
||||
let reconstructionFailed = false;
|
||||
|
||||
for (let i = 0; i < byteRange.length; i += 2) {
|
||||
const start = byteRange[i];
|
||||
const length = byteRange[i+1];
|
||||
|
||||
if (start < 0 || length <= 0 || start + length > fileBuffer.byteLength) {
|
||||
reconstructionFailed = true;
|
||||
break;
|
||||
}
|
||||
pdfSignedDataBytes.set(new Uint8Array(fileBuffer.slice(start, start + length)), offset);
|
||||
offset += length;
|
||||
}
|
||||
|
||||
if (reconstructionFailed) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: t('missing-or-invalid-byterange'), // Error during reconstruction due to invalid segment
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
isCertificateDateValid: false,
|
||||
calculatedDocumentHash: t('not-available'),
|
||||
messageDigestInSignature: t('not-available'),
|
||||
hashComparisonResult: t('not-performed'),
|
||||
authenticatedAttributesSignatureResult: t('not-performed'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove leading/trailing null bytes from hex if present from PDF content
|
||||
const pkcs7Hex = contents.trim();
|
||||
|
||||
if (!pkcs7Hex) {
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('signature-invalid-basic'),
|
||||
errorDetails: t('missing-signature-contents'), // New i18n key
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert hex string to ArrayBuffer
|
||||
let cmsContentBuffer;
|
||||
try {
|
||||
cmsContentBuffer = new Uint8Array(pkcs7Hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))).buffer;
|
||||
} catch (hexError) {
|
||||
// console.error('Error converting hex string to ArrayBuffer:', hexError); // Removed
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('signature-invalid-basic'),
|
||||
errorDetails: t('invalid-signature-hex-format'), // New i18n key
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Parse the CMS ContentInfo
|
||||
const asn1 = asn1js.fromBER(cmsContentBuffer);
|
||||
if (asn1.offset === -1) {
|
||||
// console.error('Error parsing ASN.1 from signature data'); // Removed
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('signature-invalid-basic'),
|
||||
errorDetails: 'ASN.1 parsing error from signature data.',
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const cmsContentInfo = new ContentInfo({ schema: asn1.result });
|
||||
if (String(cmsContentInfo.contentType).trim() !== String(ContentInfo.SIGNED_DATA).trim()) {
|
||||
// console.error('Not a SignedData content type. Actual type:', cmsContentInfo.contentType, 'Expected:', ContentInfo.SIGNED_DATA); // Removed
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('signature-invalid-basic'),
|
||||
errorDetails: t('unsupported-signature-format-not-signeddata'), // New i18n key
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const signedData = new SignedData({ schema: cmsContentInfo.content });
|
||||
|
||||
let signerInfoText = t('signer-info-not-available');
|
||||
let certSubject = '';
|
||||
let certIssuer = '';
|
||||
let certValidity = t('cert-validity-not-checked');
|
||||
let isValid = false;
|
||||
|
||||
if (signedData.signerInfos && signedData.signerInfos.length > 0) {
|
||||
const signerInfo = signedData.signerInfos[0];
|
||||
|
||||
if (signedData.certificates && signedData.certificates.length > 0) {
|
||||
let signerCertificate = null;
|
||||
for (const cert of signedData.certificates) {
|
||||
if (cert instanceof Certificate) {
|
||||
const issuerAndSerialNumber = signerInfo.sid;
|
||||
if (issuerAndSerialNumber instanceof IssuerAndSerialNumber) {
|
||||
let certMatch = true;
|
||||
if (cert.issuer.typesAndValues.length === issuerAndSerialNumber.issuer.typesAndValues.length) {
|
||||
for (let i = 0; i < cert.issuer.typesAndValues.length; i++) {
|
||||
if (cert.issuer.typesAndValues[i].type !== issuerAndSerialNumber.issuer.typesAndValues[i].type ||
|
||||
cert.issuer.typesAndValues[i].value.valueBlock.value !== issuerAndSerialNumber.issuer.typesAndValues[i].value.valueBlock.value) {
|
||||
certMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
certMatch = false;
|
||||
}
|
||||
|
||||
if (certMatch && cert.serialNumber.valueBlock.valueHexView.join('') === issuerAndSerialNumber.serialNumber.valueBlock.valueHexView.join('')) {
|
||||
signerCertificate = cert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (signerCertificate) {
|
||||
certSubject = signerCertificate.subject.typesAndValues.map(tv => `${tv.type}=${tv.value.valueBlock.value}`).join(', ');
|
||||
certIssuer = signerCertificate.issuer.typesAndValues.map(tv => `${tv.type}=${tv.value.valueBlock.value}`).join(', ');
|
||||
signerInfoText = `${t('signer')}: ${certSubject}, ${t('issuer')}: ${certIssuer}`;
|
||||
|
||||
const notBefore = signerCertificate.notBefore.value;
|
||||
const notAfter = signerCertificate.notAfter.value;
|
||||
const currentDate = new Date();
|
||||
certValidity = `${t('valid-from')} ${notBefore.toLocaleDateString()} ${t('to')} ${notAfter.toLocaleDateString()}`;
|
||||
if (currentDate < notBefore || currentDate > notAfter) {
|
||||
certValidity += ` (${t('expired-or-not-yet-valid')})`;
|
||||
isValid = false; // Explicitly false if expired
|
||||
} else {
|
||||
certValidity += ` (${t('valid')})`;
|
||||
isValid = true;
|
||||
}
|
||||
} else {
|
||||
signerInfoText = t('signer-certificate-not-found'); // New i18n key
|
||||
}
|
||||
} else {
|
||||
signerInfoText = t('no-certificates-in-signature'); // New i18n key
|
||||
}
|
||||
} else {
|
||||
signerInfoText = t('no-signer-info-in-pkcs7'); // Re-use existing key, or make new one
|
||||
}
|
||||
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: isValid ? t('signature-valid-basic') : t('signature-invalid-basic'),
|
||||
signerInfo: signerInfoText,
|
||||
certificateSubject: certSubject,
|
||||
certificateIssuer: certIssuer,
|
||||
certificateValidity: certValidity,
|
||||
errorDetails: !isValid && signerInfoText === t('signer-info-not-available') ? t('could-not-parse-signer-info') : undefined, // New i18n key
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
console.error('Error processing signature field with pkijs:', field.getName(), e);
|
||||
results.push({
|
||||
name: field.getName() || t('unnamed-signature-field'),
|
||||
status: t('error-processing-signature'),
|
||||
errorDetails: e.message,
|
||||
signerInfo: t('signer-info-not-available'),
|
||||
certificateSubject: '',
|
||||
certificateIssuer: '',
|
||||
certificateValidity: t('cert-validity-not-checked'),
|
||||
});
|
||||
}
|
||||
}
|
||||
return { results };
|
||||
};
|
||||
|
||||
|
||||
const handleVerifyDocument = async () => {
|
||||
// Removed jsrsasignStatus check
|
||||
|
||||
if (!fileBuffer) {
|
||||
setVerificationResult(t('please-select-file-to-verify'));
|
||||
setDetailedResults([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setVerificationResult(t('verification-in-progress'));
|
||||
setDetailedResults([]);
|
||||
|
||||
try {
|
||||
// Removed window.KJUR and window.X509 check
|
||||
|
||||
const pdfDoc = await PDFDocument.load(fileBuffer, { ignoreEncryption: true });
|
||||
const signatureInfo = await parseSignature(pdfDoc);
|
||||
|
||||
if (signatureInfo.error) {
|
||||
setVerificationResult(signatureInfo.error);
|
||||
} else if (signatureInfo.results && signatureInfo.results.length > 0) {
|
||||
setDetailedResults(signatureInfo.results);
|
||||
// Overall status can be determined by checking if all signatures are valid
|
||||
const allValid = signatureInfo.results.every(res => res.status === t('signature-valid-basic'));
|
||||
setVerificationResult(allValid ? t('all-signatures-verified-convincing') : t('some-signatures-invalid-basic'));
|
||||
} else {
|
||||
setVerificationResult(t('no-signatures-processed')); // Should be caught by no-signature-found earlier
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error during PDF processing or signature verification:', e);
|
||||
setVerificationResult(`${t('error-verifying-pdf')}: ${e.message}`);
|
||||
setDetailedResults([]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6 bg-base-100 shadow-xl rounded-lg mt-10">
|
||||
<style>{`
|
||||
.checkmark__circle {
|
||||
stroke-dasharray: 166;
|
||||
stroke-dashoffset: 166;
|
||||
stroke-width: 2;
|
||||
stroke-miterlimit: 10;
|
||||
stroke: #7ac142; /* Green color */
|
||||
fill: none;
|
||||
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
||||
}
|
||||
|
||||
.checkmark {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
stroke-width: 2;
|
||||
stroke: #fff; /* White check path */
|
||||
stroke-miterlimit: 10;
|
||||
margin: 10px auto; /* Example margin */
|
||||
box-shadow: inset 0px 0px 0px #7ac142;
|
||||
animation: fill .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
|
||||
}
|
||||
|
||||
.checkmark__check {
|
||||
transform-origin: 50% 50%;
|
||||
stroke-dasharray: 48;
|
||||
stroke-dashoffset: 48;
|
||||
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
|
||||
}
|
||||
|
||||
@keyframes stroke {
|
||||
100% {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scale {
|
||||
0%, 100% {
|
||||
transform: none;
|
||||
}
|
||||
50% {
|
||||
transform: scale3d(1.1, 1.1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fill {
|
||||
100% {
|
||||
box-shadow: inset 0px 0px 0px 30px #7ac142;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
<h1 className="text-3xl font-bold mb-6 text-center text-base-content">
|
||||
{t('verify-document-signature')}
|
||||
</h1>
|
||||
|
||||
<div className="mb-6 p-6 border border-base-300 rounded-lg bg-base-200/30 shadow-sm">
|
||||
<label
|
||||
htmlFor="document-upload"
|
||||
className="block text-lg font-medium text-base-content mb-2"
|
||||
>
|
||||
{t('select-pdf-document')}
|
||||
</label>
|
||||
<input
|
||||
type="file"
|
||||
id="document-upload"
|
||||
accept=".pdf"
|
||||
onChange={handleFileChange}
|
||||
className="file-input file-input-bordered file-input-primary w-full max-w-xs"
|
||||
/>
|
||||
{selectedFile && (
|
||||
<p className="mt-2 text-sm text-base-content w-full truncate">
|
||||
{t('selected-file')}: {selectedFile.name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-center mb-6">
|
||||
<button
|
||||
onClick={handleVerifyDocument}
|
||||
className="op-btn op-btn-primary op-btn-md"
|
||||
disabled={!selectedFile || verificationResult === t('verification-in-progress')}
|
||||
>
|
||||
{/* Removed jsrsasignStatus === 'loading' condition for spinner */}
|
||||
{verificationResult === t('verification-in-progress') ? (
|
||||
<span className="loading loading-spinner"></span>
|
||||
) : (
|
||||
t('verify-signature')
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{verificationResult && verificationResult !== t('verification-in-progress') && (
|
||||
<div className="mt-8 p-6 border border-base-300 rounded-lg bg-base-200 shadow-md min-h-[120px] flex flex-col items-center justify-center">
|
||||
<h2 className="text-2xl font-bold mb-4 text-base-content text-center">
|
||||
{t('verification-status')}
|
||||
</h2>
|
||||
{verificationResult === "Document Verified: All signatures have been successfully validated." && (
|
||||
<div className="flex flex-col items-center my-4">
|
||||
<svg className="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
||||
<circle className="checkmark__circle" cx="26" cy="26" r="25" fill="none"/>
|
||||
<path className="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-lg text-base-content mb-4 text-center">{verificationResult}</p>
|
||||
{detailedResults.length > 0 && (
|
||||
<div className="w-full space-y-6">
|
||||
{detailedResults.map((res, index) => {
|
||||
const signerInfo = parseCertificateInfo(res.certificateSubject);
|
||||
const issuerInfo = parseCertificateInfo(res.certificateIssuer);
|
||||
|
||||
return (
|
||||
<div key={index} className="bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden">
|
||||
{/* Header Section */}
|
||||
<div className="bg-gradient-to-r from-blue-600 to-indigo-600 text-white p-6">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-2xl">🔏</span>
|
||||
<div>
|
||||
<h4 className="text-xl font-bold">Signature Details</h4>
|
||||
<p className="text-blue-100 text-sm">Digital Certificate Information</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Basic Info Section */}
|
||||
<div className="p-6 border-b border-gray-100">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-500 uppercase tracking-wide">Field Name</span>
|
||||
<p className="mt-1 text-lg font-semibold text-gray-900 font-mono">{res.name}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-500 uppercase tracking-wide">Overall Status</span>
|
||||
<div className="mt-1 flex items-center space-x-2">
|
||||
<span className={`text-lg ${isSuccessStatus(res.status) ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{isSuccessStatus(res.status) ? '✅' : '❌'}
|
||||
</span>
|
||||
<span className="text-lg font-semibold text-gray-900">{res.status}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Signer Information Section */}
|
||||
{Object.keys(signerInfo).length > 0 && (
|
||||
<div className="border-b border-gray-100">
|
||||
<button
|
||||
onClick={() => toggleSection(index, 'signer')}
|
||||
className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-xl">📇</span>
|
||||
<h5 className="text-lg font-semibold text-gray-900">Signer Information</h5>
|
||||
</div>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-400 transition-transform duration-200 ${
|
||||
collapsedSections[`${index}-signer`] ? 'transform rotate-180' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
{!collapsedSections[`${index}-signer`] && (
|
||||
<div className="px-6 pb-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(signerInfo).map(([label, value]) => (
|
||||
<div key={label} className="bg-gray-50 rounded-lg p-4">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">{label}</span>
|
||||
<p className="mt-1 text-sm font-mono text-gray-900 break-all">{value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Issuer Information Section */}
|
||||
{Object.keys(issuerInfo).length > 0 && (
|
||||
<div className="border-b border-gray-100">
|
||||
<button
|
||||
onClick={() => toggleSection(index, 'issuer')}
|
||||
className="w-full px-6 py-4 text-left hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:bg-gray-50"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-xl">🏢</span>
|
||||
<h5 className="text-lg font-semibold text-gray-900">Issuer Details</h5>
|
||||
</div>
|
||||
<svg
|
||||
className={`w-5 h-5 text-gray-400 transition-transform duration-200 ${
|
||||
collapsedSections[`${index}-issuer`] ? 'transform rotate-180' : ''
|
||||
}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
{!collapsedSections[`${index}-issuer`] && (
|
||||
<div className="px-6 pb-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{Object.entries(issuerInfo).map(([label, value]) => (
|
||||
<div key={label} className="bg-gray-50 rounded-lg p-4">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide">{label}</span>
|
||||
<p className="mt-1 text-sm font-mono text-gray-900 break-all">{value}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Certificate Validity Section */}
|
||||
{res.certificateValidity && (
|
||||
<div className="p-6 bg-gray-50">
|
||||
<div className="flex items-center space-x-3 mb-4">
|
||||
<span className="text-xl">🕒</span>
|
||||
<h5 className="text-lg font-semibold text-gray-900">Certificate Validity</h5>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg p-4 border">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className={`text-lg ${isCertificateValid(res.certificateValidity) ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{isCertificateValid(res.certificateValidity) ? '✅' : '❌'}
|
||||
</span>
|
||||
<span className="text-sm font-mono text-gray-900">{res.certificateValidity}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Technical Details Section (if any) */}
|
||||
{(res.calculatedDocumentHash || res.messageDigestInSignature || res.hashComparisonResult || res.authenticatedAttributesSignatureResult || res.errorDetails || res.certificateSubject || res.certificateIssuer) && (
|
||||
<div className="p-6 bg-gray-50 border-t">
|
||||
<details className="group">
|
||||
<summary className="flex items-center justify-between cursor-pointer text-sm font-medium text-gray-700 hover:text-gray-900">
|
||||
<span>🔧 Technical Details</span>
|
||||
<svg className="w-4 h-4 transition-transform group-open:rotate-180" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</summary>
|
||||
<div className="mt-4 space-y-3">
|
||||
{/* Raw Certificate Data */}
|
||||
{res.certificateSubject && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Raw Certificate Subject</span>
|
||||
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.certificateSubject}</code>
|
||||
</div>
|
||||
)}
|
||||
{res.certificateIssuer && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Raw Certificate Issuer</span>
|
||||
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.certificateIssuer}</code>
|
||||
</div>
|
||||
)}
|
||||
{res.calculatedDocumentHash && res.calculatedDocumentHash !== t('not-available') && res.calculatedDocumentHash !== t('not-calculated') && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Calculated Document Hash</span>
|
||||
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.calculatedDocumentHash}</code>
|
||||
</div>
|
||||
)}
|
||||
{res.messageDigestInSignature && res.messageDigestInSignature !== t('not-available') && res.messageDigestInSignature !== t('not-found-in-signature') && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Message Digest in Signature</span>
|
||||
<code className="text-xs text-gray-800 break-all bg-gray-100 p-2 rounded block">{res.messageDigestInSignature}</code>
|
||||
</div>
|
||||
)}
|
||||
{res.hashComparisonResult && res.hashComparisonResult !== t('not-performed') && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Hash Comparison</span>
|
||||
<span className="text-sm text-gray-800">{res.hashComparisonResult}</span>
|
||||
</div>
|
||||
)}
|
||||
{res.authenticatedAttributesSignatureResult && res.authenticatedAttributesSignatureResult !== t('not-performed') && (
|
||||
<div className="bg-white rounded p-3 border">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wide block mb-1">Attributes Signature Verification</span>
|
||||
<span className="text-sm text-gray-800">{res.authenticatedAttributesSignatureResult}</span>
|
||||
</div>
|
||||
)}
|
||||
{res.errorDetails && (
|
||||
<div className="bg-red-50 border border-red-200 rounded p-3">
|
||||
<span className="text-xs font-medium text-red-600 uppercase tracking-wide block mb-1">Error Details</span>
|
||||
<span className="text-sm text-red-800">{res.errorDetails}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{verificationResult === t('verification-in-progress') && (
|
||||
<div className="mt-8 p-4 border border-base-300 rounded-lg bg-base-200 min-h-[100px] flex justify-center items-center">
|
||||
<span className="loading loading-lg loading-dots"></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!verificationResult && !selectedFile && (
|
||||
<div className="mt-8 p-4 border border-base-300 rounded-lg bg-base-200 min-h-[100px] flex justify-center items-center"> {/* Added flex for centering */}
|
||||
<p className="text-base-content/60 italic text-center">{t('verification-results-will-appear-here')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerifyDocument;
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
} from "../constant/const";
|
||||
import Alert from "./Alert";
|
||||
import Tooltip from "./Tooltip";
|
||||
import { RWebShare } from "react-web-share";
|
||||
import Tour from "reactour";
|
||||
import ShareButton from "./ShareButton";
|
||||
import Tour from "../primitives/Tour";
|
||||
import Parse from "parse";
|
||||
import {
|
||||
copytoData,
|
||||
@@ -398,7 +398,7 @@ const ReportTable = (props) => {
|
||||
setIsResendMail({ [item.objectId]: true });
|
||||
} else if (act.action === "bulksend") {
|
||||
handleBulkSend(item);
|
||||
} else if (act.action === "sharewith") {
|
||||
} else if (act.action === "sharewithteam") {
|
||||
if (item?.SharedWith && item?.SharedWith.length > 0) {
|
||||
// below code is used to get existing sharewith teams and formated them as per react-select
|
||||
const formatedList = item?.SharedWith.map((x) => ({
|
||||
@@ -679,7 +679,7 @@ const ReportTable = (props) => {
|
||||
receiver_phone: userDetails?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc.ExtUserPtr.Company,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
const res = replaceMailVaribles(subject, "", variables);
|
||||
setMail((prev) => ({ ...prev, subject: res.subject }));
|
||||
@@ -710,7 +710,7 @@ const ReportTable = (props) => {
|
||||
receiver_phone: userDetails?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc.ExtUserPtr.Company,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
const res = replaceMailVaribles("", body, variables);
|
||||
|
||||
@@ -754,7 +754,7 @@ const ReportTable = (props) => {
|
||||
receiver_phone: user?.signerPtr?.Phone || "",
|
||||
expiry_date: localExpireDate,
|
||||
company_name: doc?.ExtUserPtr?.Company || "",
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`
|
||||
signing_url: signPdf
|
||||
};
|
||||
const subject =
|
||||
doc?.RequestSubject ||
|
||||
@@ -1411,7 +1411,7 @@ const ReportTable = (props) => {
|
||||
onRequestClose={closeTour}
|
||||
steps={props.tourData}
|
||||
isOpen={isTour}
|
||||
// rounded={5}
|
||||
rounded={5}
|
||||
closeWithMask={false}
|
||||
/>
|
||||
</>
|
||||
@@ -2183,17 +2183,15 @@ const ReportTable = (props) => {
|
||||
{share.email}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<RWebShare
|
||||
data={{
|
||||
url: share.url,
|
||||
title: "Sign url"
|
||||
}}
|
||||
<ShareButton
|
||||
title={t("sign-url")}
|
||||
text={t("sign-url")}
|
||||
url={share.url}
|
||||
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm "
|
||||
>
|
||||
<button className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm ">
|
||||
<i className="fa-light fa-share-from-square"></i>{" "}
|
||||
{t("btnLabel.Share")}
|
||||
</button>
|
||||
</RWebShare>
|
||||
<i className="fa-light fa-share-from-square"></i>
|
||||
{t("btnLabel.Share")}
|
||||
</ShareButton>
|
||||
<button
|
||||
className="op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm"
|
||||
onClick={() => copytoclipboard(share)}
|
||||
|
||||
@@ -25,12 +25,10 @@ const LinkUserModal = (props) => {
|
||||
handleClose={props.closePopup}
|
||||
>
|
||||
<SelectSigners
|
||||
details={props.handleAddUser}
|
||||
{...props}
|
||||
closePopup={props.closePopup}
|
||||
signersData={props?.signersData}
|
||||
isContact={isContact}
|
||||
setIsContact={setIsContact}
|
||||
handleUnlinkSigner={props.handleUnlinkSigner}
|
||||
isExistSigner={isExistSigner}
|
||||
/>
|
||||
{isContact && (
|
||||
|
||||
@@ -8,13 +8,18 @@ const ModalUi = ({
|
||||
handleClose,
|
||||
showHeader = true,
|
||||
showClose = true,
|
||||
reduceWidth
|
||||
reduceWidth,
|
||||
position
|
||||
}) => {
|
||||
const width = reduceWidth;
|
||||
const isBottom = position === "bottom" ? "items-end pb-2" : "";
|
||||
return (
|
||||
<>
|
||||
{isOpen && (
|
||||
<dialog id="selectSignerModal" className="op-modal op-modal-open">
|
||||
<dialog
|
||||
id="selectSignerModal"
|
||||
className={`${isBottom} op-modal op-modal-open`}
|
||||
>
|
||||
<div
|
||||
className={`${
|
||||
width || "md:min-w-[500px]"
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import ModalUi from "./ModalUi";
|
||||
|
||||
function useShare({ title, text, url }) {
|
||||
const [error, setError] = useState(null);
|
||||
const isSupported = typeof navigator !== "undefined" && !!navigator.share;
|
||||
|
||||
const share = useCallback(async () => {
|
||||
if (!isSupported) {
|
||||
setError(new Error("Web Share API not supported"));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await navigator.share({ title, text, url });
|
||||
} catch (err) {
|
||||
// User may have cancelled, or another error occurred
|
||||
setError(err);
|
||||
}
|
||||
}, [isSupported, title, text, url]);
|
||||
|
||||
return { share, isSupported, error };
|
||||
}
|
||||
|
||||
/**
|
||||
* A customizable ShareButton component.
|
||||
* If `children` are provided, they are used as the trigger element;
|
||||
* otherwise, a default share button is rendered.
|
||||
*
|
||||
* @param {object} props
|
||||
* @param {string} props.title - Title for sharing
|
||||
* @param {string} props.text - Text for sharing
|
||||
* @param {string} props.url - URL to share
|
||||
* @param {string} [props.className] - Optional styling class
|
||||
* @param {React.ReactNode} [props.children] - Custom trigger element
|
||||
*/
|
||||
export default function ShareButton({ title, text, url, className, children }) {
|
||||
const { share, isSupported, error } = useShare({ title, text, url });
|
||||
const [isPopupOpen, setPopupOpen] = useState(false);
|
||||
|
||||
// Native Web Share API supported
|
||||
if (isSupported) {
|
||||
return (
|
||||
<button
|
||||
onClick={share}
|
||||
className={className}
|
||||
aria-label="Share this page"
|
||||
>
|
||||
{children || "🔗 Share"}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback: trigger opens popup
|
||||
const subject = encodeURIComponent(title || text);
|
||||
const body = encodeURIComponent(`${text}\n\n${url}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* React Fragment */}
|
||||
<button
|
||||
onClick={() => setPopupOpen(true)}
|
||||
className={className}
|
||||
aria-label="Open share options"
|
||||
>
|
||||
{children || "🔗 Share"}
|
||||
</button>
|
||||
{isPopupOpen && (
|
||||
<ModalUi
|
||||
isOpen
|
||||
title={
|
||||
<>
|
||||
<i class="fa-solid fa-share-from-square"></i> Share
|
||||
</>
|
||||
}
|
||||
handleClose={() => setPopupOpen(false)}
|
||||
>
|
||||
{error && <p style={{ color: "red" }}>Error: {error.message}</p>}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 justify-items-start text-lg p-[20px]">
|
||||
{/* Copy Link */}
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(url)}
|
||||
className="m-2 op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm w-[190px]"
|
||||
>
|
||||
<i className="fa-solid fa-clipboard fa-lg"></i> Copy to clipboard
|
||||
</button>
|
||||
{/* Twitter */}
|
||||
<button
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(url)}`,
|
||||
"_blank",
|
||||
"noopener"
|
||||
)
|
||||
}
|
||||
className="m-2 op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm w-[190px]"
|
||||
>
|
||||
<i className="fa-brands fa-square-x-twitter fa-lg"></i> Share on
|
||||
Twitter
|
||||
</button>
|
||||
|
||||
{/* Facebook */}
|
||||
<button
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`,
|
||||
"_blank",
|
||||
"noopener"
|
||||
)
|
||||
}
|
||||
className="m-2 op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm w-[190px]"
|
||||
>
|
||||
<i className="fa-brands fa-square-facebook fa-lg"></i> Share on
|
||||
Facebook
|
||||
</button>
|
||||
|
||||
{/* WhatsApp */}
|
||||
<button
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`https://wa.me/?text=${encodeURIComponent(text + " " + url)}`,
|
||||
"_blank",
|
||||
"noopener"
|
||||
)
|
||||
}
|
||||
className="m-2 op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm w-[190px]"
|
||||
>
|
||||
<i className="fa-brands fa-square-whatsapp fa-lg"></i> Share on
|
||||
WhatsApp
|
||||
</button>
|
||||
|
||||
{/* Gmail (Web) */}
|
||||
<button
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`https://mail.google.com/mail/?view=cm&fs=1&su=${subject}&body=${body}`,
|
||||
"_blank",
|
||||
"noopener"
|
||||
)
|
||||
}
|
||||
className="m-2 op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm w-[190px]"
|
||||
>
|
||||
<i className="fa-solid fa-envelope fa-lg"></i> Share via Gmail
|
||||
</button>
|
||||
|
||||
{/* Microsoft Teams */}
|
||||
<button
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`https://teams.microsoft.com/l/share?url=${encodeURIComponent(url)}&title=${encodeURIComponent(title)}`,
|
||||
"_blank",
|
||||
"noopener"
|
||||
)
|
||||
}
|
||||
className="m-2 op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm w-[190px]"
|
||||
>
|
||||
<i className="fa-brands fa-microsoft fa-lg"></i> Share on Teams
|
||||
</button>
|
||||
|
||||
{/* Outlook Web */}
|
||||
<button
|
||||
onClick={() =>
|
||||
window.open(
|
||||
`https://outlook.live.com/owa/?path=/mail/action/compose&subject=${subject}&body=${body}`,
|
||||
"_blank",
|
||||
"noopener"
|
||||
)
|
||||
}
|
||||
className="m-2 op-btn op-btn-primary op-btn-outline op-btn-xs md:op-btn-sm w-[190px]"
|
||||
>
|
||||
<i className="fa-solid fa-envelope-open-text fa-lg"></i> Share via
|
||||
Outlook
|
||||
</button>
|
||||
</div>
|
||||
</ModalUi>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import ReactTour from "reactour";
|
||||
|
||||
export default function Tour({
|
||||
steps,
|
||||
isOpen,
|
||||
rounded,
|
||||
className,
|
||||
showNumber,
|
||||
closeWithMask,
|
||||
onRequestClose,
|
||||
showNavigation,
|
||||
showCloseButton,
|
||||
showNavigationNumber,
|
||||
disableKeyboardNavigation
|
||||
}) {
|
||||
const radius = rounded || 5;
|
||||
return (
|
||||
<ReactTour
|
||||
className={className}
|
||||
steps={steps}
|
||||
isOpen={isOpen}
|
||||
rounded={radius}
|
||||
scrollOffset={-100}
|
||||
closeWithMask={closeWithMask}
|
||||
disableKeyboardNavigation={disableKeyboardNavigation}
|
||||
showCloseButton={showCloseButton}
|
||||
onRequestClose={onRequestClose}
|
||||
showNumber={showNumber}
|
||||
showNavigation={showNavigation}
|
||||
showNavigationNumber={showNavigationNumber}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// reducers/widgetSlice.js
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
const initialState = {
|
||||
isShowModal: false,
|
||||
saveSignCheckbox: {
|
||||
isVisible: false,
|
||||
signId: ""
|
||||
},
|
||||
signatureTypes: null,
|
||||
defaultSignImg: null,
|
||||
myInitial: null,
|
||||
lastIndex: ""
|
||||
};
|
||||
|
||||
const widgetSlice = createSlice({
|
||||
name: "widget",
|
||||
initialState,
|
||||
reducers: {
|
||||
setIsShowModal: (state, action) => {
|
||||
state.isShowModal = action.payload;
|
||||
},
|
||||
setSaveSignCheckbox: (state, action) => {
|
||||
state.saveSignCheckbox = action.payload;
|
||||
},
|
||||
setSignatureTypes: (state, action) => {
|
||||
state.signatureTypes = action.payload;
|
||||
},
|
||||
setDefaultSignImg: (state, action) => {
|
||||
state.defaultSignImg = action.payload;
|
||||
},
|
||||
setMyInitial: (state, action) => {
|
||||
state.myInitial = action.payload;
|
||||
},
|
||||
setLastIndex: (state, action) => {
|
||||
state.lastIndex = action.payload;
|
||||
},
|
||||
resetWidgetState: () => initialState
|
||||
}
|
||||
});
|
||||
|
||||
export const {
|
||||
setIsShowModal,
|
||||
setSaveSignCheckbox,
|
||||
setSignatureTypes,
|
||||
setMyInitial,
|
||||
resetWidgetState,
|
||||
setDefaultSignImg,
|
||||
setLastIndex
|
||||
} = widgetSlice.actions;
|
||||
|
||||
export default widgetSlice.reducer;
|
||||
@@ -1,18 +1,16 @@
|
||||
// import { createStore, applyMiddleware } from "redux";
|
||||
// import thunk from "redux-thunk";
|
||||
// import reducers from "./reducers";
|
||||
|
||||
// export const store = createStore(reducers, applyMiddleware(thunk));
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import infoReducer from "./reducers/infoReducer";
|
||||
import ShowTenant from "./reducers/ShowTenant";
|
||||
import TourStepsReducer from "./reducers/TourStepsReducer";
|
||||
import showHeader from "./reducers/showHeader";
|
||||
import widgetReducer from "./reducers/widgetSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
appInfo: infoReducer,
|
||||
TourSteps: TourStepsReducer,
|
||||
ShowTenant,
|
||||
showHeader
|
||||
showHeader,
|
||||
widget: widgetReducer
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.radioButton {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -31,14 +30,28 @@
|
||||
}
|
||||
|
||||
.signatureCanvas {
|
||||
width: 460px;
|
||||
height: 184px;
|
||||
width: 440px;
|
||||
height: 167px;
|
||||
}
|
||||
.tabWidth{
|
||||
width: 440px;
|
||||
}
|
||||
|
||||
.intialSignatureCanvas {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
.checked-radio::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 25%;
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
border-radius: 9999px;
|
||||
background-color: #111111; /* blue-500 */
|
||||
}
|
||||
|
||||
|
||||
.intialSignature {
|
||||
border: 2px solid #888;
|
||||
@@ -388,6 +401,9 @@ option {
|
||||
width: 300px;
|
||||
height: 120px;
|
||||
}
|
||||
.tabWidth{
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.penContainerDefault {
|
||||
width: 300px;
|
||||
@@ -411,6 +427,9 @@ option {
|
||||
width: 280px;
|
||||
height: 112px;
|
||||
}
|
||||
.tabWidth{
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.penContainerDefault {
|
||||
width: 280px;
|
||||
@@ -432,6 +451,9 @@ option {
|
||||
width: 230px;
|
||||
height: 92px;
|
||||
}
|
||||
.tabWidth{
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
.penContainerDefault {
|
||||
width: 230px;
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import path from "path";
|
||||
import rollupNodePolyFill from "rollup-plugin-node-polyfills";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify("production"),
|
||||
process: JSON.stringify({
|
||||
env: {
|
||||
NODE_ENV: "production"
|
||||
}
|
||||
})
|
||||
},
|
||||
build: {
|
||||
lib: {
|
||||
entry: path.resolve(__dirname, "src/ee/script/PublicTemplate.jsx"),
|
||||
name: "PublicTemplate",
|
||||
fileName: () => `public-template.bundle.js`,
|
||||
formats: ["iife"]
|
||||
},
|
||||
outDir: "public/static/js",
|
||||
emptyOutDir: false,
|
||||
assetsInlineLimit: 0,
|
||||
copyPublicDir: false,
|
||||
rollupOptions: {
|
||||
plugins: [rollupNodePolyFill()],
|
||||
output: {
|
||||
// 👇 Controls how asset files (like CSS) are named
|
||||
assetFileNames: (assetInfo) => {
|
||||
if (assetInfo.name && assetInfo.name.endsWith(".css")) {
|
||||
return "public-template.bundle.css"; // Custom CSS filename
|
||||
}
|
||||
return "[name].[ext]";
|
||||
},
|
||||
globals: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,27 +0,0 @@
|
||||
# Use an official Node runtime as the base image
|
||||
FROM node:20
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package.json and package-lock.json first to leverage Docker cache
|
||||
COPY ./package*.json ./
|
||||
|
||||
# Install application dependencies
|
||||
RUN npm install
|
||||
|
||||
# If you have native dependencies, you'll need extra tools. Uncomment the following line if needed.
|
||||
# RUN apk add --no-cache make gcc g++ python3
|
||||
|
||||
# Copy the current directory contents into the container
|
||||
COPY ./ .
|
||||
|
||||
# Make port 8080 available to the world outside this container
|
||||
EXPOSE 8080
|
||||
|
||||
# Define environment variables if needed
|
||||
# ENV NODE_ENV production
|
||||
# ENV DATABASE_URL mongodb://db:27017
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT npm start
|
||||
@@ -252,7 +252,7 @@ export const mailTemplate = param => {
|
||||
"</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 +
|
||||
param.signingUrl +
|
||||
"><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 +
|
||||
'. For any queries regarding this email, please contact the sender ' +
|
||||
|
||||
@@ -36,17 +36,24 @@ export default async function GetTemplate(request) {
|
||||
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);
|
||||
const sharedWithTeamQuery = new Parse.Query('contracts_Template');
|
||||
sharedWithTeamQuery.containedIn('SharedWith', teamsArr);
|
||||
|
||||
// Create the second query
|
||||
const sharedWithJsersQuery = new Parse.Query('contracts_Template');
|
||||
sharedWithJsersQuery.equalTo('SharedWithUsers', {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
});
|
||||
// Create the third 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 = Parse.Query.or(sharedWithTeamQuery, sharedWithJsersQuery, createdByQuery);
|
||||
template.equalTo('objectId', templateId);
|
||||
template.notEqualTo('IsArchive', true);
|
||||
template.include('ExtUserPtr');
|
||||
|
||||
@@ -4,9 +4,9 @@ export default async function Newsletter(request) {
|
||||
const email = request.params?.email?.toLowerCase()?.replace(/\s/g, '');
|
||||
const domain = request.params.domain;
|
||||
try {
|
||||
const envAppId = process.env.REACT_APP_APPID || 'opensign';
|
||||
const envAppId = '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 envProdServer = 'https://app.opensignlabs.com/api/app';
|
||||
const newsletter = await axios.post(
|
||||
`${envProdServer}/classes/Newsletter`,
|
||||
{ Name: name, Email: email, Domain: domain },
|
||||
|
||||
@@ -14,7 +14,7 @@ async function deductcount(docsCount, extUserId) {
|
||||
}
|
||||
async function sendMail(document, publicUrl) {
|
||||
//sessionToken
|
||||
const baseUrl = new URL(publicUrl); //process.env.PUBLIC_URL
|
||||
const baseUrl = new URL(publicUrl);
|
||||
|
||||
// console.log("pdfDetails", pdfDetails);
|
||||
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
|
||||
@@ -71,7 +71,7 @@ async function sendMail(document, publicUrl) {
|
||||
receiver_phone: existSigner?.Phone || '',
|
||||
expiry_date: localExpireDate,
|
||||
company_name: orgName,
|
||||
signing_url: `<a href=${signPdf} target=_blank>Sign here</a>`,
|
||||
signing_url: signPdf,
|
||||
};
|
||||
replaceVar = replaceMailVaribles(mailSubject, htmlReqBody, variables);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ async function sendMail(document, publicUrl) {
|
||||
title: document.Name,
|
||||
organization: orgName,
|
||||
localExpireDate: localExpireDate,
|
||||
sigingUrl: signPdf,
|
||||
signingUrl: signPdf,
|
||||
};
|
||||
let params = {
|
||||
extUserId: document.ExtUserPtr.objectId,
|
||||
|
||||
@@ -47,6 +47,13 @@ export default async function getReport(request) {
|
||||
objectId: extUser.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
SharedWithUsers: {
|
||||
__type: 'Pointer',
|
||||
className: 'contracts_Users',
|
||||
objectId: extUser.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -116,7 +116,7 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
|
||||
const creatorEmail = doc.ExtUserPtr.Email;
|
||||
const signerName = signUser.Name;
|
||||
const signerEmail = signUser.Email;
|
||||
const viewDocUrl = `${publicUrl}/recipientSignPdf/${doc.objectId}`; // ` ${process.env.PUBLIC_URL}/recipientSignPdf/${doc.objectId}`;
|
||||
const viewDocUrl = `${publicUrl}/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'>" +
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
export default function reportJson(id, userId) {
|
||||
const currentUserId = userId;
|
||||
|
||||
const commanKeys = [
|
||||
'URL',
|
||||
'Name',
|
||||
'ExtUserPtr.Name',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'TemplateId',
|
||||
];
|
||||
switch (id) {
|
||||
// draft documents report
|
||||
case 'ByHuevtCFY':
|
||||
@@ -14,19 +23,7 @@ export default function reportJson(id, userId) {
|
||||
SignedUrl: { $exists: false },
|
||||
CreatedBy: { __type: 'Pointer', className: '_User', objectId: currentUserId },
|
||||
},
|
||||
keys: [
|
||||
'Name',
|
||||
'Note',
|
||||
'Folder.Name',
|
||||
'URL',
|
||||
'ExtUserPtr.Name',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'IsSignyourself',
|
||||
'TemplateId',
|
||||
],
|
||||
keys: [...commanKeys, 'Note', 'Folder.Name', 'IsSignyourself'],
|
||||
};
|
||||
// Need your sign report
|
||||
case '4Hhwbp482K':
|
||||
@@ -48,20 +45,13 @@ export default function reportJson(id, userId) {
|
||||
},
|
||||
},
|
||||
keys: [
|
||||
'Name',
|
||||
...commanKeys,
|
||||
'Note',
|
||||
'Folder.Name',
|
||||
'URL',
|
||||
'ExtUserPtr.Name',
|
||||
'ExtUserPtr.Email',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'Signers.UserId',
|
||||
'AuditTrail',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
'ExpiryDate',
|
||||
],
|
||||
};
|
||||
@@ -80,22 +70,15 @@ export default function reportJson(id, userId) {
|
||||
ExpiryDate: { $gt: { __type: 'Date', iso: new Date().toISOString() } },
|
||||
},
|
||||
keys: [
|
||||
'Name',
|
||||
...commanKeys,
|
||||
'Note',
|
||||
'Folder.Name',
|
||||
'URL',
|
||||
'ExtUserPtr.Name',
|
||||
'ExtUserPtr.Email',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'AuditTrail',
|
||||
'AuditTrail.UserPtr',
|
||||
'ExpiryDate',
|
||||
'SendMail',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
'RequestBody',
|
||||
'RequestSubject',
|
||||
'ExtUserPtr.TenantId.RequestBody',
|
||||
@@ -133,20 +116,13 @@ export default function reportJson(id, userId) {
|
||||
],
|
||||
},
|
||||
keys: [
|
||||
'Name',
|
||||
...commanKeys,
|
||||
'Note',
|
||||
'Folder.Name',
|
||||
'URL',
|
||||
'SignedUrl',
|
||||
'ExtUserPtr.Name',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'TimeToCompleteDays',
|
||||
'Placeholders',
|
||||
'IsSignyourself',
|
||||
'IsCompleted',
|
||||
'TemplateId',
|
||||
],
|
||||
};
|
||||
// declined documents report
|
||||
@@ -159,20 +135,7 @@ export default function reportJson(id, userId) {
|
||||
IsDeclined: true,
|
||||
CreatedBy: { __type: 'Pointer', className: '_User', objectId: currentUserId },
|
||||
},
|
||||
keys: [
|
||||
'Name',
|
||||
'Note',
|
||||
'Folder.Name',
|
||||
'URL',
|
||||
'ExtUserPtr.Name',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'DeclineReason',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
],
|
||||
keys: [...commanKeys, 'Note', 'Folder.Name', 'DeclineReason', 'SignedUrl'],
|
||||
};
|
||||
// Expired Documents report
|
||||
case 'zNqBHXHsYH':
|
||||
@@ -187,20 +150,7 @@ export default function reportJson(id, userId) {
|
||||
ExpiryDate: { $lt: { __type: 'Date', iso: new Date().toISOString() } },
|
||||
CreatedBy: { __type: 'Pointer', className: '_User', objectId: currentUserId },
|
||||
},
|
||||
keys: [
|
||||
'Name',
|
||||
'Note',
|
||||
'Folder.Name',
|
||||
'URL',
|
||||
'ExtUserPtr.Name',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
'ExpiryDate',
|
||||
],
|
||||
keys: [...commanKeys, 'Note', 'Folder.Name', 'SignedUrl', 'ExpiryDate'],
|
||||
};
|
||||
// Recently sent for signatures report show on dashboard
|
||||
case 'd9k3UfYHBc':
|
||||
@@ -217,20 +167,13 @@ export default function reportJson(id, userId) {
|
||||
ExpiryDate: { $gt: { __type: 'Date', iso: new Date().toISOString() } },
|
||||
},
|
||||
keys: [
|
||||
'Name',
|
||||
...commanKeys,
|
||||
'Folder.Name',
|
||||
'URL',
|
||||
'ExtUserPtr.Name',
|
||||
'ExtUserPtr.Email',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'AuditTrail',
|
||||
'AuditTrail.UserPtr',
|
||||
'ExpiryDate',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
'RequestBody',
|
||||
'RequestSubject',
|
||||
'ExtUserPtr.TenantId.RequestBody',
|
||||
@@ -257,18 +200,11 @@ export default function reportJson(id, userId) {
|
||||
},
|
||||
},
|
||||
keys: [
|
||||
'Name',
|
||||
'URL',
|
||||
'ExtUserPtr.Name',
|
||||
...commanKeys,
|
||||
'ExtUserPtr.Email',
|
||||
'Signers.Name',
|
||||
'Signers.UserId',
|
||||
'AuditTrail',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'SignedUrl',
|
||||
'TemplateId',
|
||||
'ExpiryDate',
|
||||
],
|
||||
};
|
||||
@@ -284,18 +220,7 @@ export default function reportJson(id, userId) {
|
||||
SignedUrl: { $exists: false },
|
||||
CreatedBy: { __type: 'Pointer', className: '_User', objectId: currentUserId },
|
||||
},
|
||||
keys: [
|
||||
'Name',
|
||||
'Note',
|
||||
'Folder.Name',
|
||||
'URL',
|
||||
'ExtUserPtr.Name',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'TemplateId',
|
||||
],
|
||||
keys: [...commanKeys, 'Note', 'Folder.Name'],
|
||||
};
|
||||
// contact book report
|
||||
case 'contacts':
|
||||
@@ -315,15 +240,9 @@ export default function reportJson(id, userId) {
|
||||
reportClass: 'contracts_Template',
|
||||
params: { Type: { $ne: 'Folder' }, IsArchive: { $ne: true } },
|
||||
keys: [
|
||||
'Name',
|
||||
...commanKeys,
|
||||
'Note',
|
||||
'Folder.Name',
|
||||
'URL',
|
||||
'ExtUserPtr.Name',
|
||||
'Signers.Name',
|
||||
'Signers.Email',
|
||||
'Signers.Phone',
|
||||
'Placeholders',
|
||||
'IsPublic',
|
||||
'SharedWith.Name',
|
||||
'SendinOrder',
|
||||
|
||||
@@ -161,7 +161,7 @@ app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
app.use(function (req, res, next) {
|
||||
req.headers['x-real-ip'] = getUserIP(req);
|
||||
const publicUrl = 'https://' + req?.get('host');
|
||||
req.headers['public_url'] = publicUrl; // process.env.PUBLIC_URL
|
||||
req.headers['public_url'] = publicUrl;
|
||||
next();
|
||||
});
|
||||
function getUserIP(request) {
|
||||
|
||||
Generated
+1517
-1494
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "open_sign_server",
|
||||
"version": "1.4.0",
|
||||
"version": "2.21.1",
|
||||
"description": "An example Parse API server using the parse-server module",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -18,8 +18,8 @@
|
||||
"watch": "nodemon index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.802.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.802.0",
|
||||
"@aws-sdk/client-s3": "^3.812.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.812.0",
|
||||
"@parse/fs-files-adapter": "^3.0.0",
|
||||
"@parse/s3-files-adapter": "^4.1.0",
|
||||
"@pdf-lib/fontkit": "^1.1.1",
|
||||
@@ -35,12 +35,12 @@
|
||||
"form-data": "^4.0.2",
|
||||
"generate-api-key": "^1.0.2",
|
||||
"googleapis": "^148.0.0",
|
||||
"mailgun.js": "^11.1.0",
|
||||
"mailgun.js": "^12.0.1",
|
||||
"mongodb": "^6.16.0",
|
||||
"multer": "^1.4.5-lts.2",
|
||||
"multer": "^2.0.0",
|
||||
"multer-s3": "^3.0.1",
|
||||
"node-forge": "^1.3.1",
|
||||
"nodemailer": "^6.10.1",
|
||||
"nodemailer": "^7.0.3",
|
||||
"parse": "^6.1.1",
|
||||
"parse-dbtool": "^1.2.0",
|
||||
"parse-server": "^8.2.0",
|
||||
@@ -48,13 +48,14 @@
|
||||
"pdf-lib": "^1.17.1",
|
||||
"posthog-node": "^4.17.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"rate-limiter-flexible": "^7.1.1",
|
||||
"speakeasy": "^2.0.0",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@babel/eslint-parser": "^7.27.1",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint": "^9.27.0",
|
||||
"jasmine": "^5.7.1",
|
||||
"mongodb-runner": "^5.8.3",
|
||||
"nodemon": "^3.1.10",
|
||||
|
||||
Reference in New Issue
Block a user